aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeremy <jeremy.benoist@gmail.com>2015-02-08 21:47:36 +0100
committerJeremy <jeremy.benoist@gmail.com>2015-02-08 23:13:40 +0100
commitd91691573f108422cc2080462af35ebd62dc93fb (patch)
treedfef307e3c79e681cbc066c7b18e91f97a294268
parent7812f508bcb68d9d0e1868fa568d7a435e7975b7 (diff)
downloadwallabag-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
-rw-r--r--app/config/security.yml62
-rw-r--r--app/config/services.yml3
-rw-r--r--src/Wallabag/CoreBundle/Entity/User.php6
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php88
-rw-r--r--src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php89
5 files changed, 215 insertions, 33 deletions
diff --git a/app/config/security.yml b/app/config/security.yml
index c1b0fb77..e161c3b5 100644
--- a/app/config/security.yml
+++ b/app/config/security.yml
@@ -1,6 +1,6 @@
1security: 1security:
2 encoders: 2 encoders:
3 Wallabag\CoreBundle\Entity\Users: 3 Wallabag\CoreBundle\Entity\User:
4 algorithm: sha1 4 algorithm: sha1
5 encode_as_base64: false 5 encode_as_base64: false
6 iterations: 1 6 iterations: 1
@@ -11,7 +11,7 @@ security:
11 11
12 providers: 12 providers:
13 administrators: 13 administrators:
14 entity: { class: WallabagCoreBundle:Users, property: username } 14 entity: { class: WallabagCoreBundle:User, property: username }
15 15
16 # the main part of the security, where you can set up firewalls 16 # the main part of the security, where you can set up firewalls
17 # for specific sections of your app 17 # for specific sections of your app
@@ -23,35 +23,35 @@ security:
23 pattern: ^/login$ 23 pattern: ^/login$
24 anonymous: ~ 24 anonymous: ~
25 25
26# secured_area: 26 secured_area:
27# pattern: ^/ 27 pattern: ^/
28# anonymous: ~ 28 anonymous: ~
29# form_login: 29 form_login:
30# login_path: /login 30 login_path: /login
31# 31
32# use_forward: false 32 use_forward: false
33# 33
34# check_path: /login_check 34 check_path: /login_check
35# 35
36# post_only: true 36 post_only: true
37# 37
38# always_use_default_target_path: true 38 always_use_default_target_path: true
39# default_target_path: / 39 default_target_path: /
40# target_path_parameter: redirect_url 40 target_path_parameter: redirect_url
41# use_referer: true 41 use_referer: true
42# 42
43# failure_path: null 43 failure_path: null
44# failure_forward: false 44 failure_forward: false
45# 45
46# username_parameter: _username 46 username_parameter: _username
47# password_parameter: _password 47 password_parameter: _password
48# 48
49# csrf_parameter: _csrf_token 49 csrf_parameter: _csrf_token
50# intention: authenticate 50 intention: authenticate
51# 51
52# logout: 52 logout:
53# path: /logout 53 path: /logout
54# target: / 54 target: /
55 55
56 access_control: 56 access_control:
57 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } 57 - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
diff --git a/app/config/services.yml b/app/config/services.yml
index 5c76fc59..d4485e42 100644
--- a/app/config/services.yml
+++ b/app/config/services.yml
@@ -1,7 +1,8 @@
1# Learn more about services, parameters and containers at 1# Learn more about services, parameters and containers at
2# http://symfony.com/doc/current/book/service_container.html 2# http://symfony.com/doc/current/book/service_container.html
3parameters: 3parameters:
4# parameter_name: value 4 security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
5 security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
5 6
6services: 7services:
7# service_name: 8# service_name:
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
3namespace Wallabag\CoreBundle\Security\Authentication\Encoder;
4
5use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
6use 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 */
13class 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
3namespace Wallabag\CoreBundle\Security\Authentication\Provider;
4
5use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
6use Symfony\Component\Security\Core\User\UserProviderInterface;
7use Symfony\Component\Security\Core\User\UserCheckerInterface;
8use Symfony\Component\Security\Core\User\UserInterface;
9use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
10use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
11use Symfony\Component\Security\Core\Exception\BadCredentialsException;
12use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
13use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
14
15class 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}