]> git.immae.eu Git - github/wallabag/wallabag.git/commitdiff
Add custom auth encoder & provider
authorJeremy <jeremy.benoist@gmail.com>
Sun, 8 Feb 2015 20:47:36 +0000 (21:47 +0100)
committerJeremy <jeremy.benoist@gmail.com>
Sun, 8 Feb 2015 22:13:40 +0000 (23:13 +0100)
These custom classes allow Wallabag v2 to be compatible with Wallabag v1 salted password

app/config/security.yml
app/config/services.yml
src/Wallabag/CoreBundle/Entity/User.php
src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php [new file with mode: 0644]
src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php [new file with mode: 0644]

index c1b0fb7782df748a907b5209fc885cb96a733574..e161c3b53b911f70f77443f51c30609fa52263ff 100644 (file)
@@ -1,6 +1,6 @@
 security:
     encoders:
-        Wallabag\CoreBundle\Entity\Users:
+        Wallabag\CoreBundle\Entity\User:
             algorithm:        sha1
             encode_as_base64: false
             iterations:       1
@@ -11,7 +11,7 @@ security:
 
     providers:
         administrators:
-            entity: { class: WallabagCoreBundle:Users, property: username }
+            entity: { class: WallabagCoreBundle:User, property: username }
 
     # the main part of the security, where you can set up firewalls
     # for specific sections of your app
@@ -23,35 +23,35 @@ security:
             pattern:    ^/login$
             anonymous:  ~
 
-#        secured_area:
-#            pattern:    ^/
-#            anonymous: ~
-#            form_login:
-#                login_path:                     /login
-#
-#                use_forward:                    false
-#
-#                check_path:                     /login_check
-#
-#                post_only:                      true
-#
-#                always_use_default_target_path: true
-#                default_target_path:            /
-#                target_path_parameter:          redirect_url
-#                use_referer:                    true
-#
-#                failure_path:                   null
-#                failure_forward:                false
-#
-#                username_parameter:             _username
-#                password_parameter:             _password
-#
-#                csrf_parameter:                 _csrf_token
-#                intention:                      authenticate
-#
-#            logout:
-#                path:   /logout
-#                target: /
+        secured_area:
+            pattern:    ^/
+            anonymous: ~
+            form_login:
+                login_path:                     /login
+
+                use_forward:                    false
+
+                check_path:                     /login_check
+
+                post_only:                      true
+
+                always_use_default_target_path: true
+                default_target_path:            /
+                target_path_parameter:          redirect_url
+                use_referer:                    true
+
+                failure_path:                   null
+                failure_forward:                false
+
+                username_parameter:             _username
+                password_parameter:             _password
+
+                csrf_parameter:                 _csrf_token
+                intention:                      authenticate
+
+            logout:
+                path:   /logout
+                target: /
 
     access_control:
         - { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
index 5c76fc5988534367e579dfddfb4a611e377c03f9..d4485e4290b20c672afdae4e53477ade0ea67508 100644 (file)
@@ -1,7 +1,8 @@
 # Learn more about services, parameters and containers at
 # http://symfony.com/doc/current/book/service_container.html
 parameters:
-#    parameter_name: value
+    security.authentication.provider.dao.class: Wallabag\CoreBundle\Security\Authentication\Provider\WallabagAuthenticationProvider
+    security.encoder.digest.class: Wallabag\CoreBundle\Security\Authentication\Encoder\WallabagPasswordEncoder
 
 services:
 #    service_name:
index cfbd57f8835049f19b52d5497906b92e0e37a28b..c83250c37e6d2a2bec25530c1a92667bc72904d1 100644 (file)
@@ -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;
     }
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php b/src/Wallabag/CoreBundle/Security/Authentication/Encoder/WallabagPasswordEncoder.php
new file mode 100644 (file)
index 0000000..56f1aff
--- /dev/null
@@ -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));
+    }
+}
diff --git a/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php b/src/Wallabag/CoreBundle/Security/Authentication/Provider/WallabagAuthenticationProvider.php
new file mode 100644 (file)
index 0000000..1c7c5fa
--- /dev/null
@@ -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;
+        }
+    }
+}