mirror of
https://github.com/wallabag/wallabag.git
synced 2024-09-27 05:50:02 +00:00
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
5 changed files with 215 additions and 33 deletions
|
@ -1,6 +1,6 @@
|
||||||
security:
|
security:
|
||||||
encoders:
|
encoders:
|
||||||
Wallabag\CoreBundle\Entity\Users:
|
Wallabag\CoreBundle\Entity\User:
|
||||||
algorithm: sha1
|
algorithm: sha1
|
||||||
encode_as_base64: false
|
encode_as_base64: false
|
||||||
iterations: 1
|
iterations: 1
|
||||||
|
@ -11,7 +11,7 @@ security:
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
administrators:
|
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
|
# the main part of the security, where you can set up firewalls
|
||||||
# for specific sections of your app
|
# for specific sections of your app
|
||||||
|
@ -23,35 +23,35 @@ security:
|
||||||
pattern: ^/login$
|
pattern: ^/login$
|
||||||
anonymous: ~
|
anonymous: ~
|
||||||
|
|
||||||
# secured_area:
|
secured_area:
|
||||||
# pattern: ^/
|
pattern: ^/
|
||||||
# anonymous: ~
|
anonymous: ~
|
||||||
# form_login:
|
form_login:
|
||||||
# login_path: /login
|
login_path: /login
|
||||||
#
|
|
||||||
# use_forward: false
|
use_forward: false
|
||||||
#
|
|
||||||
# check_path: /login_check
|
check_path: /login_check
|
||||||
#
|
|
||||||
# post_only: true
|
post_only: true
|
||||||
#
|
|
||||||
# always_use_default_target_path: true
|
always_use_default_target_path: true
|
||||||
# default_target_path: /
|
default_target_path: /
|
||||||
# target_path_parameter: redirect_url
|
target_path_parameter: redirect_url
|
||||||
# use_referer: true
|
use_referer: true
|
||||||
#
|
|
||||||
# failure_path: null
|
failure_path: null
|
||||||
# failure_forward: false
|
failure_forward: false
|
||||||
#
|
|
||||||
# username_parameter: _username
|
username_parameter: _username
|
||||||
# password_parameter: _password
|
password_parameter: _password
|
||||||
#
|
|
||||||
# csrf_parameter: _csrf_token
|
csrf_parameter: _csrf_token
|
||||||
# intention: authenticate
|
intention: authenticate
|
||||||
#
|
|
||||||
# logout:
|
logout:
|
||||||
# path: /logout
|
path: /logout
|
||||||
# target: /
|
target: /
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# Learn more about services, parameters and containers at
|
# Learn more about services, parameters and containers at
|
||||||
# http://symfony.com/doc/current/book/service_container.html
|
# http://symfony.com/doc/current/book/service_container.html
|
||||||
parameters:
|
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:
|
services:
|
||||||
# service_name:
|
# service_name:
|
||||||
|
|
|
@ -161,7 +161,11 @@ class User implements AdvancedUserInterface, \Serializable
|
||||||
*/
|
*/
|
||||||
public function setPassword($password)
|
public function setPassword($password)
|
||||||
{
|
{
|
||||||
$this->password = $password;
|
if (!$password && 0 === strlen($password)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->password = sha1($password.$this->getUsername().$this->getSalt());
|
||||||
|
|
||||||
return $this;
|
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 a new issue