mirror of https://github.com/wallabag/wallabag.git
Add custom auth encoder & provider
These custom classes allow Wallabag v2 to be compatible with Wallabag v1 salted password
This commit is contained in:
parent
7812f508bc
commit
d91691573f
|
@ -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 }
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue