forked from wallabag/wallabag
Add custom auth encoder & provider
These custom classes allow Wallabag v2 to be compatible with Wallabag v1 salted password
This commit is contained in:
@ -161,7 +161,11 @@ class User implements AdvancedUserInterface, \Serializable
|
||||
*/
|
||||
public function setPassword($password)
|
||||
{
|
||||
$this->password = $password;
|
||||
if (!$password && 0 === strlen($password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->password = sha1($password.$this->getUsername().$this->getSalt());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Security\Authentication\Encoder;
|
||||
|
||||
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
|
||||
/**
|
||||
* This override just add en extra variable (username) to be able to salt the password
|
||||
* the way Wallabag v1 does. It will avoid to break compatibility with Wallabag v1
|
||||
*
|
||||
*/
|
||||
class WallabagPasswordEncoder extends BasePasswordEncoder
|
||||
{
|
||||
private $algorithm;
|
||||
private $encodeHashAsBase64;
|
||||
private $iterations;
|
||||
private $username = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $algorithm The digest algorithm to use
|
||||
* @param bool $encodeHashAsBase64 Whether to base64 encode the password hash
|
||||
* @param int $iterations The number of iterations to use to stretch the password hash
|
||||
*/
|
||||
public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
|
||||
{
|
||||
$this->algorithm = $algorithm;
|
||||
$this->encodeHashAsBase64 = $encodeHashAsBase64;
|
||||
$this->iterations = $iterations;
|
||||
}
|
||||
|
||||
public function setUsername($username)
|
||||
{
|
||||
$this->username = $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encodePassword($raw, $salt)
|
||||
{
|
||||
if (null === $this->username) {
|
||||
throw new \LogicException('We can not check the password without a username.');
|
||||
}
|
||||
|
||||
if ($this->isPasswordTooLong($raw)) {
|
||||
throw new BadCredentialsException('Invalid password.');
|
||||
}
|
||||
|
||||
if (!in_array($this->algorithm, hash_algos(), true)) {
|
||||
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
|
||||
}
|
||||
|
||||
$salted = $this->mergePasswordAndSalt($raw, $salt);
|
||||
$digest = hash($this->algorithm, $salted, true);
|
||||
|
||||
// "stretch" hash
|
||||
for ($i = 1; $i < $this->iterations; $i++) {
|
||||
$digest = hash($this->algorithm, $digest.$salted, true);
|
||||
}
|
||||
|
||||
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* We inject the username inside the salted password
|
||||
*/
|
||||
protected function mergePasswordAndSalt($password, $salt)
|
||||
{
|
||||
if (empty($salt)) {
|
||||
return $password;
|
||||
}
|
||||
|
||||
return $password.$this->username.$salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPasswordValid($encoded, $raw, $salt)
|
||||
{
|
||||
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\CoreBundle\Security\Authentication\Provider;
|
||||
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
|
||||
|
||||
class WallabagAuthenticationProvider extends UserAuthenticationProvider
|
||||
{
|
||||
private $encoderFactory;
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param UserProviderInterface $userProvider An UserProviderInterface instance
|
||||
* @param UserCheckerInterface $userChecker An UserCheckerInterface instance
|
||||
* @param string $providerKey The provider key
|
||||
* @param EncoderFactoryInterface $encoderFactory An EncoderFactoryInterface instance
|
||||
* @param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not
|
||||
*/
|
||||
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
|
||||
{
|
||||
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
|
||||
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
$this->userProvider = $userProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
|
||||
{
|
||||
$currentUser = $token->getUser();
|
||||
if ($currentUser instanceof UserInterface) {
|
||||
if ($currentUser->getPassword() !== $user->getPassword()) {
|
||||
throw new BadCredentialsException('The credentials were changed from another session.');
|
||||
}
|
||||
} else {
|
||||
if ("" === ($presentedPassword = $token->getCredentials())) {
|
||||
throw new BadCredentialsException('The presented password cannot be empty.');
|
||||
}
|
||||
|
||||
// give username, it's used to hash the password
|
||||
$encoder = $this->encoderFactory->getEncoder($user);
|
||||
$encoder->setUsername($user->getUsername());
|
||||
|
||||
if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
|
||||
throw new BadCredentialsException('The presented password is invalid.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function retrieveUser($username, UsernamePasswordToken $token)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
if ($user instanceof UserInterface) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->userProvider->loadUserByUsername($username);
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
|
||||
}
|
||||
|
||||
return $user;
|
||||
} catch (UsernameNotFoundException $notFound) {
|
||||
$notFound->setUsername($username);
|
||||
throw $notFound;
|
||||
} catch (\Exception $repositoryProblem) {
|
||||
$ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
|
||||
$ex->setToken($token);
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user