[LOGIN] Implement password checking and related systems
This commit is contained in:
parent
f3ccdf8017
commit
0eba267a73
|
@ -1,7 +1,10 @@
|
||||||
security:
|
security:
|
||||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||||
providers:
|
providers:
|
||||||
users_in_memory: { memory: null }
|
users:
|
||||||
|
entity:
|
||||||
|
class: 'App\Entity\LocalUser'
|
||||||
|
property: 'nickname'
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
@ -9,7 +12,7 @@ security:
|
||||||
main:
|
main:
|
||||||
anonymous: true
|
anonymous: true
|
||||||
lazy: true
|
lazy: true
|
||||||
provider: users_in_memory
|
provider: users
|
||||||
guard:
|
guard:
|
||||||
authenticators:
|
authenticators:
|
||||||
- App\Security\Authenticator
|
- App\Security\Authenticator
|
||||||
|
|
|
@ -51,14 +51,18 @@ use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\FormFactoryInterface;
|
use Symfony\Component\Form\FormFactoryInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Routing\RouterInterface;
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
|
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
class GNUsocial implements EventSubscriberInterface
|
class GNUsocial implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
|
use TargetPathTrait;
|
||||||
|
|
||||||
protected LoggerInterface $logger;
|
protected LoggerInterface $logger;
|
||||||
protected TranslatorInterface $translator;
|
protected TranslatorInterface $translator;
|
||||||
protected EntityManagerInterface $entity_manager;
|
protected EntityManagerInterface $entity_manager;
|
||||||
|
@ -66,6 +70,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||||
protected FormFactoryInterface $form_factory;
|
protected FormFactoryInterface $form_factory;
|
||||||
protected MessageBusInterface $message_bus;
|
protected MessageBusInterface $message_bus;
|
||||||
protected EventDispatcherInterface $event_dispatcher;
|
protected EventDispatcherInterface $event_dispatcher;
|
||||||
|
protected SessionInterface $session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symfony dependency injection gives us access to these services
|
* Symfony dependency injection gives us access to these services
|
||||||
|
@ -76,7 +81,8 @@ class GNUsocial implements EventSubscriberInterface
|
||||||
RouterInterface $router,
|
RouterInterface $router,
|
||||||
FormFactoryInterface $ff,
|
FormFactoryInterface $ff,
|
||||||
MessageBusInterface $mb,
|
MessageBusInterface $mb,
|
||||||
EventDispatcherInterface $ed)
|
EventDispatcherInterface $ed,
|
||||||
|
SessionInterface $s)
|
||||||
{
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->translator = $trans;
|
$this->translator = $trans;
|
||||||
|
@ -85,6 +91,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||||
$this->form_factory = $ff;
|
$this->form_factory = $ff;
|
||||||
$this->message_bus = $mb;
|
$this->message_bus = $mb;
|
||||||
$this->event_dispatcher = $ed;
|
$this->event_dispatcher = $ed;
|
||||||
|
$this->session = $s;
|
||||||
|
|
||||||
$this->register();
|
$this->register();
|
||||||
}
|
}
|
||||||
|
@ -122,6 +129,12 @@ class GNUsocial implements EventSubscriberInterface
|
||||||
public function onKernelRequest(RequestEvent $event,
|
public function onKernelRequest(RequestEvent $event,
|
||||||
string $event_name): RequestEvent
|
string $event_name): RequestEvent
|
||||||
{
|
{
|
||||||
|
$request = $event->getRequest();
|
||||||
|
if (!(!$event->isMasterRequest() || $request->isXmlHttpRequest()
|
||||||
|
|| 'login' === $request->attributes->get('_route'))) {
|
||||||
|
$this->saveTargetPath($this->session, 'main', $request->getUri());
|
||||||
|
}
|
||||||
|
|
||||||
$this->register();
|
$this->register();
|
||||||
return $event;
|
return $event;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Core\DB\DB;
|
||||||
use App\Core\UserRoles;
|
use App\Core\UserRoles;
|
||||||
|
use App\Util\Common;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
@ -307,4 +309,47 @@ class LocalUser implements UserInterface
|
||||||
public function eraseCredentials()
|
public function eraseCredentials()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkPassword(string $new_password): bool
|
||||||
|
{
|
||||||
|
// Timing safe password verification on supported PHP versions
|
||||||
|
if (password_verify($new_password, $this->getPassword())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old format
|
||||||
|
// crypt understands what the salt part of $this->getPassword() is
|
||||||
|
if ($this->getPassword() === crypt($new_password, $this->getPassword())) {
|
||||||
|
$this->changePassword($new_password, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changePassword(string $new_password, bool $override = false): void
|
||||||
|
{
|
||||||
|
if ($override || $this->checkPassword($new_password)) {
|
||||||
|
$this->setPassword($this->hashPassword($new_password));
|
||||||
|
DB::flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hashPassword(string $password)
|
||||||
|
{
|
||||||
|
switch (Common::config('security', 'algorithm')) {
|
||||||
|
case 'bcrypt':
|
||||||
|
$algorithm = PASSWORD_BCRYPT;
|
||||||
|
break;
|
||||||
|
case 'argon2i':
|
||||||
|
$algorithm = PASSWORD_ARGON2I;
|
||||||
|
break;
|
||||||
|
case 'argon2id':
|
||||||
|
$algorithm = PASSWORD_ARGON2ID;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$options = Common::config('security', 'options');
|
||||||
|
|
||||||
|
return password_hash($password, $algorithm, $options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ namespace App\Security;
|
||||||
|
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use function App\Core\I18n\_m;
|
use function App\Core\I18n\_m;
|
||||||
use App\Core\Log;
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Util\Nickname;
|
use App\Util\Nickname;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
@ -95,10 +94,9 @@ class Authenticator extends AbstractFormLoginAuthenticator
|
||||||
|
|
||||||
$nick = Nickname::normalize($credentials['nickname']);
|
$nick = Nickname::normalize($credentials['nickname']);
|
||||||
$user = DB::findOneBy('local_user', ['or' => ['nickname' => $nick, 'outgoing_email' => $nick]]);
|
$user = DB::findOneBy('local_user', ['or' => ['nickname' => $nick, 'outgoing_email' => $nick]]);
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
throw new CustomUserMessageAuthenticationException(
|
throw new CustomUserMessageAuthenticationException(
|
||||||
_m('Either \'{nickname}\' doesn\'t match any registered nickname or email, or the supplied password is incorrect.', ['{nickname}' => $credentials['nickname']]));
|
_m('\'{nickname}\' doesn\'t match any registered nickname or email.', ['{nickname}' => $credentials['nickname']]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
@ -106,29 +104,11 @@ class Authenticator extends AbstractFormLoginAuthenticator
|
||||||
|
|
||||||
public function checkCredentials($credentials, UserInterface $user)
|
public function checkCredentials($credentials, UserInterface $user)
|
||||||
{
|
{
|
||||||
$password = $user->getPassword();
|
if (!$user->checkPassword($credentials['password'])) {
|
||||||
Log::error(print_r($user, true));
|
throw new CustomUserMessageAuthenticationException(_m('Invalid login credentials.'));
|
||||||
// crypt understands what the salt part of $user->password is
|
} else {
|
||||||
if ($password === crypt($credentials['password'], $user->password)) {
|
return true;
|
||||||
$this->changePassword($user->nickname, null, $password);
|
|
||||||
return $user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we check StatusNet hash, for backwards compatibility and migration
|
|
||||||
if ($this->statusnet && $user->password === md5($password . $user->id)) {
|
|
||||||
// and update password hash entry to crypt() compatible
|
|
||||||
if ($this->overwrite) {
|
|
||||||
$this->changePassword($user->nickname, null, $password);
|
|
||||||
}
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timing safe password verification on supported PHP versions
|
|
||||||
if (password_verify($password, $user->password)) {
|
|
||||||
return $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||||
|
@ -137,8 +117,7 @@ class Authenticator extends AbstractFormLoginAuthenticator
|
||||||
return new RedirectResponse($targetPath);
|
return new RedirectResponse($targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
|
return new RedirectResponse($this->urlGenerator->generate('main_all'));
|
||||||
throw new \Exception('TODO: provide a valid redirect inside ' . __FILE__);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getLoginUrl()
|
protected function getLoginUrl()
|
||||||
|
|
|
@ -42,7 +42,7 @@ abstract class Common
|
||||||
{
|
{
|
||||||
$c = DB::find('config', ['section' => $section, 'setting' => $setting]);
|
$c = DB::find('config', ['section' => $section, 'setting' => $setting]);
|
||||||
if ($c === null) {
|
if ($c === null) {
|
||||||
throw new Exception("The field section = {$section} and setting = {$setting} doesn't exist");
|
throw new \Exception("The field section = {$section} and setting = {$setting} doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
return unserialize($c->getValue());
|
return unserialize($c->getValue());
|
||||||
|
|
Loading…
Reference in New Issue
Block a user