diff options
author | Jeremy <jeremy.benoist@gmail.com> | 2015-02-08 21:47:36 +0100 |
---|---|---|
committer | Jeremy <jeremy.benoist@gmail.com> | 2015-02-08 23:13:40 +0100 |
commit | d91691573f108422cc2080462af35ebd62dc93fb (patch) | |
tree | dfef307e3c79e681cbc066c7b18e91f97a294268 /src/Wallabag/CoreBundle | |
parent | 7812f508bcb68d9d0e1868fa568d7a435e7975b7 (diff) | |
download | wallabag-d91691573f108422cc2080462af35ebd62dc93fb.tar.gz wallabag-d91691573f108422cc2080462af35ebd62dc93fb.tar.zst wallabag-d91691573f108422cc2080462af35ebd62dc93fb.zip |
Add custom auth encoder & provider
These custom classes allow Wallabag v2 to be compatible with Wallabag v1 salted password
Diffstat (limited to 'src/Wallabag/CoreBundle')
3 files changed, 182 insertions, 1 deletions
diff --git a/src/Wallabag/CoreBundle/Entity/User.php b/src/Wallabag/CoreBundle/Entity/User.php index cfbd57f8..c83250c3 100644 --- a/src/Wallabag/CoreBundle/Entity/User.php +++ b/src/Wallabag/CoreBundle/Entity/User.php | |||
@@ -161,7 +161,11 @@ class User implements AdvancedUserInterface, \Serializable | |||
161 | */ | 161 | */ |
162 | public function setPassword($password) | 162 | public function setPassword($password) |
163 | { | 163 | { |
164 | $this->password = $password; | 164 | if (!$password && 0 === strlen($password)) { |
165 | return; | ||
166 | } | ||
167 | |||
168 | $this->password = sha1($password.$this->getUsername().$this->getSalt()); | ||
165 | 169 | ||
166 | return $this; | 170 | return $this; |
167 | } | 171 | } |
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php new file mode 100644 index 00000000..56f1affe --- /dev/null +++ b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php | |||
@@ -0,0 +1,88 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Security\Authentication\Encoder; | ||
4 | |||
5 | use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder; | ||
6 | use Symfony\Component\Security\Core\Exception\BadCredentialsException; | ||
7 | |||
8 | /** | ||
9 | * This override just add en extra variable (username) to be able to salt the password | ||
10 | * the way Wallabag v1 does. It will avoid to break compatibility with Wallabag v1 | ||
11 | * | ||
12 | */ | ||
13 | class WallabagPasswordEncoder extends BasePasswordEncoder | ||
14 | { | ||
15 | private $algorithm; | ||
16 | private $encodeHashAsBase64; | ||
17 | private $iterations; | ||
18 | private $username = null; | ||
19 | |||
20 | /** | ||
21 | * Constructor. | ||
22 | * | ||
23 | * @param string $algorithm The digest algorithm to use | ||
24 | * @param bool $encodeHashAsBase64 Whether to base64 encode the password hash | ||
25 | * @param int $iterations The number of iterations to use to stretch the password hash | ||
26 | */ | ||
27 | public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000) | ||
28 | { | ||
29 | $this->algorithm = $algorithm; | ||
30 | $this->encodeHashAsBase64 = $encodeHashAsBase64; | ||
31 | $this->iterations = $iterations; | ||
32 | } | ||
33 | |||
34 | public function setUsername($username) | ||
35 | { | ||
36 | $this->username = $username; | ||
37 | } | ||
38 | |||
39 | /** | ||
40 | * {@inheritdoc} | ||
41 | */ | ||
42 | public function encodePassword($raw, $salt) | ||
43 | { | ||
44 | if (null === $this->username) { | ||
45 | throw new \LogicException('We can not check the password without a username.'); | ||
46 | } | ||
47 | |||
48 | if ($this->isPasswordTooLong($raw)) { | ||
49 | throw new BadCredentialsException('Invalid password.'); | ||
50 | } | ||
51 | |||
52 | if (!in_array($this->algorithm, hash_algos(), true)) { | ||
53 | throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); | ||
54 | } | ||
55 | |||
56 | $salted = $this->mergePasswordAndSalt($raw, $salt); | ||
57 | $digest = hash($this->algorithm, $salted, true); | ||
58 | |||
59 | // "stretch" hash | ||
60 | for ($i = 1; $i < $this->iterations; $i++) { | ||
61 | $digest = hash($this->algorithm, $digest.$salted, true); | ||
62 | } | ||
63 | |||
64 | return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * {@inheritdoc} | ||
69 | * | ||
70 | * We inject the username inside the salted password | ||
71 | */ | ||
72 | protected function mergePasswordAndSalt($password, $salt) | ||
73 | { | ||
74 | if (empty($salt)) { | ||
75 | return $password; | ||
76 | } | ||
77 | |||
78 | return $password.$this->username.$salt; | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * {@inheritdoc} | ||
83 | */ | ||
84 | public function isPasswordValid($encoded, $raw, $salt) | ||
85 | { | ||
86 | return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); | ||
87 | } | ||
88 | } | ||
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php new file mode 100644 index 00000000..1c7c5fae --- /dev/null +++ b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php | |||
@@ -0,0 +1,89 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\CoreBundle\Security\Authentication\Provider; | ||
4 | |||
5 | use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; | ||
6 | use Symfony\Component\Security\Core\User\UserProviderInterface; | ||
7 | use Symfony\Component\Security\Core\User\UserCheckerInterface; | ||
8 | use Symfony\Component\Security\Core\User\UserInterface; | ||
9 | use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; | ||
10 | use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; | ||
11 | use Symfony\Component\Security\Core\Exception\BadCredentialsException; | ||
12 | use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; | ||
13 | use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider; | ||
14 | |||
15 | class WallabagAuthenticationProvider extends UserAuthenticationProvider | ||
16 | { | ||
17 | private $encoderFactory; | ||
18 | private $userProvider; | ||
19 | |||
20 | /** | ||
21 | * Constructor. | ||
22 | * | ||
23 | * @param UserProviderInterface $userProvider An UserProviderInterface instance | ||
24 | * @param UserCheckerInterface $userChecker An UserCheckerInterface instance | ||
25 | * @param string $providerKey The provider key | ||
26 | * @param EncoderFactoryInterface $encoderFactory An EncoderFactoryInterface instance | ||
27 | * @param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not | ||
28 | */ | ||
29 | public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true) | ||
30 | { | ||
31 | parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); | ||
32 | |||
33 | $this->encoderFactory = $encoderFactory; | ||
34 | $this->userProvider = $userProvider; | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * {@inheritdoc} | ||
39 | */ | ||
40 | protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) | ||
41 | { | ||
42 | $currentUser = $token->getUser(); | ||
43 | if ($currentUser instanceof UserInterface) { | ||
44 | if ($currentUser->getPassword() !== $user->getPassword()) { | ||
45 | throw new BadCredentialsException('The credentials were changed from another session.'); | ||
46 | } | ||
47 | } else { | ||
48 | if ("" === ($presentedPassword = $token->getCredentials())) { | ||
49 | throw new BadCredentialsException('The presented password cannot be empty.'); | ||
50 | } | ||
51 | |||
52 | // give username, it's used to hash the password | ||
53 | $encoder = $this->encoderFactory->getEncoder($user); | ||
54 | $encoder->setUsername($user->getUsername()); | ||
55 | |||
56 | if (!$encoder->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { | ||
57 | throw new BadCredentialsException('The presented password is invalid.'); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * {@inheritdoc} | ||
64 | */ | ||
65 | protected function retrieveUser($username, UsernamePasswordToken $token) | ||
66 | { | ||
67 | $user = $token->getUser(); | ||
68 | if ($user instanceof UserInterface) { | ||
69 | return $user; | ||
70 | } | ||
71 | |||
72 | try { | ||
73 | $user = $this->userProvider->loadUserByUsername($username); | ||
74 | |||
75 | if (!$user instanceof UserInterface) { | ||
76 | throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); | ||
77 | } | ||
78 | |||
79 | return $user; | ||
80 | } catch (UsernameNotFoundException $notFound) { | ||
81 | $notFound->setUsername($username); | ||
82 | throw $notFound; | ||
83 | } catch (\Exception $repositoryProblem) { | ||
84 | $ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem); | ||
85 | $ex->setToken($token); | ||
86 | throw $ex; | ||
87 | } | ||
88 | } | ||
89 | } | ||