These custom classes allow Wallabag v2 to be compatible with Wallabag v1 salted password
security:
encoders:
- Wallabag\CoreBundle\Entity\Users:
+ Wallabag\CoreBundle\Entity\User:
algorithm: sha1
encode_as_base64: false
iterations: 1
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
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 }
# 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:
*/
public function setPassword($password)
{
- $this->password = $password;
+ if (!$password && 0 === strlen($password)) {
+ return;
+ }
+
+ $this->password = sha1($password.$this->getUsername().$this->getSalt());
return $this;
}
--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?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;
+ }
+ }
+}