From 301421ea159c5d163a8ae34d46b646e95fe46a20 Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Tue, 8 Mar 2022 22:32:18 +0000 Subject: [PATCH] [SECURITY][EVENT] Remove deprecated uses of Symfony Guard. Add LoginSucess and LoginFailure events --- config/packages/security.yaml | 22 ++-- src/Controller/Security.php | 15 +-- src/Core/GNUsocial.php | 2 - src/Core/Security.php | 26 ++++- src/Security/Authenticator.php | 201 --------------------------------- src/Security/EmailVerifier.php | 61 ---------- 6 files changed, 38 insertions(+), 289 deletions(-) delete mode 100644 src/Security/Authenticator.php delete mode 100644 src/Security/EmailVerifier.php diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 9566d2688c..e9a4dbb7a9 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -24,28 +24,26 @@ security: pattern: ^/oauth security: false main: - entry_point: App\Security\Authenticator - guard: - authenticators: - - App\Security\Authenticator + lazy: true provider: local_user form_login: login_path: security_login check_path: security_login - enable_csrf: true logout: path: security_logout # where to redirect after logout target: root - remember_me: - secret: '%kernel.secret%' - secure: true - httponly: '%remember_me_httponly%' - samesite: '%remember_me_samesite%' - token_provider: 'Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider' + # remember_me: + # secret: '%kernel.secret%' + # secure: true + # httponly: '%remember_me_httponly%' + # samesite: '%remember_me_samesite%' + # token_provider: 'Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider' - # activate different ways to authenticate + # custom_authenticator: 'App\Core\Security' + + # activate different ways to authenticate # https://symfony.com/doc/current/security.html#firewalls-authentication # https://symfony.com/doc/current/security/impersonating_user.html diff --git a/src/Controller/Security.php b/src/Controller/Security.php index 2b4866f7f0..2bd35b8190 100644 --- a/src/Controller/Security.php +++ b/src/Controller/Security.php @@ -14,8 +14,6 @@ use App\Core\Log; use App\Entity\Actor; use App\Entity\Feed; use App\Entity\LocalUser; -use App\Security\Authenticator; -use App\Security\EmailVerifier; use App\Util\Common; use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\EmailException; @@ -40,8 +38,8 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -95,8 +93,8 @@ class Security extends Controller public function register( Request $request, UserPasswordHasherInterface $user_password_hasher, - Authenticator $authenticator, - GuardAuthenticatorHandler $guard, + // \App\Core\Security $authenticator, + // UserAuthenticatorInterface $user_authenticator, ): array|Response { $form = Form::create([ ['nickname', TextType::class, [ @@ -207,12 +205,7 @@ class Security extends Controller $user->setIsEmailVerified(true); } - return $guard->authenticateUserAndHandleSuccess( - $user, - $request, - $authenticator, - 'main', - ); + // return $user_authenticator->authenticateUser($user, $authenticator, $request); } return [ diff --git a/src/Core/GNUsocial.php b/src/Core/GNUsocial.php index a14b091479..ac5182b932 100644 --- a/src/Core/GNUsocial.php +++ b/src/Core/GNUsocial.php @@ -49,7 +49,6 @@ use App\Core\I18n\I18n; use App\Core\Queue\Queue; use App\Core\Router\Router; use App\Kernel; -use App\Security\EmailVerifier; use App\Util\Common; use App\Util\Exception\ConfigurationException; use App\Util\Formatting; @@ -176,7 +175,6 @@ class GNUsocial implements EventSubscriberInterface Router::setRouter($this->router); HTTPClient::setClient($this->client); Formatting::setTwig($this->twig); - EmailVerifier::setHelpers($this->email_verify_helper, $this->mailer_helper); Cache::setupCache(); DB::initTableMap(); diff --git a/src/Core/Security.php b/src/Core/Security.php index 1fc0b2af29..d05da28beb 100644 --- a/src/Core/Security.php +++ b/src/Core/Security.php @@ -34,7 +34,10 @@ namespace App\Core; use App\Entity\LocalUser; use BadMethodCallException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Security as SymfonySecurity; +use Symfony\Component\Security\Http\Event\LoginFailureEvent; +use Symfony\Component\Security\Http\Event\LoginSuccessEvent; /** * Forwards method calls to either Symfony\Component\Security\Core\Security or @@ -45,15 +48,34 @@ use Symfony\Component\Security\Core\Security as SymfonySecurity; * * @method static LocalUser getUser() */ -abstract class Security +class Security implements EventSubscriberInterface //implements AuthenticatorInterface { private static ?SymfonySecurity $security; - public static function setHelper($sec): void { self::$security = $sec; } + public function loginSucess(LoginSuccessEvent $event): LoginSuccessEvent + { + Event::handle('LoginSuccess', [$event]); + return $event; + } + + public function loginFailure(LoginFailureEvent $event): LoginFailureEvent + { + Event::handle('LoginFailure', [$event]); + return $event; + } + + public static function getSubscribedEvents(): array + { + return [ + LoginSuccessEvent::class => 'loginSucess', + LoginFailureEvent::class => 'loginFailure', + ]; + } + public static function __callStatic(string $name, array $args) { if (method_exists(self::$security, $name)) { diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php deleted file mode 100644 index faff3cc8ab..0000000000 --- a/src/Security/Authenticator.php +++ /dev/null @@ -1,201 +0,0 @@ -. -// }}} - -namespace App\Security; - -use function App\Core\I18n\_m; -use App\Core\Log; -use App\Core\Router\Router; -use App\Entity\LocalUser; -use App\Util\Common; -use App\Util\Exception\ClientException; -use App\Util\Exception\NoSuchActorException; -use App\Util\Exception\NotFoundException; -use App\Util\Exception\ServerException; -use App\Util\Nickname; -use Stringable; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; -use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; -use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; -use Symfony\Component\Security\Guard\AuthenticatorInterface; -use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; -use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; -use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; -use Symfony\Component\Security\Http\Authenticator\Passport\Passport; -use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; -use Symfony\Component\Security\Http\Util\TargetPathTrait; - -/** - * User authenticator - * - * @category Authentication - * @package GNUsocial - * - * @author Hugo Sales - * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ -class Authenticator extends AbstractFormLoginAuthenticator implements AuthenticatorInterface -{ - use TargetPathTrait; - - public const LOGIN_ROUTE = 'security_login'; - - private CsrfTokenManagerInterface $csrfTokenManager; - - public function __construct(CsrfTokenManagerInterface $csrfTokenManager) - { - $this->csrfTokenManager = $csrfTokenManager; - } - - public function supports(Request $request): bool - { - return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST'); - } - - /** - * @return array - */ - public function getCredentials(Request $request): array - { - return [ - 'nickname_or_email' => $request->request->get('_username'), - 'password' => $request->request->get('_password'), - 'csrf_token' => $request->request->get('_csrf_token'), - ]; - } - - /** - * Get a user given credentials and a CSRF token - * - * @param array $credentials result of self::getCredentials - * - * @throws NoSuchActorException - * @throws ServerException - * - * @return ?LocalUser - */ - public function getUser($credentials, UserProviderInterface $userProvider): ?LocalUser - { - $token = new CsrfToken('authenticate', $credentials['csrf_token']); - if (!$this->csrfTokenManager->isTokenValid($token)) { - throw new InvalidCsrfTokenException(); - } - $user = null; - try { - if (Common::isValidEmail($credentials['nickname_or_email'])) { - $user = LocalUser::getByEmail($credentials['nickname_or_email']); - } elseif (Nickname::isValid($credentials['nickname_or_email'])) { - $user = LocalUser::getByNickname($credentials['nickname_or_email']); - } - if (\is_null($user)) { - throw new NoSuchActorException('No such local user.'); - } - $credentials['nickname'] = $user->getNickname(); - } catch (NoSuchActorException|NotFoundException) { - throw new CustomUserMessageAuthenticationException( - _m('Invalid login credentials.'), - ); - } - return $user; - } - - /** - * @param array $credentials result of self::getCredentials - * @param LocalUser $user - * - * @throws ServerException - */ - public function checkCredentials($credentials, $user): bool - { - if (!$user->checkPassword($credentials['password'])) { - throw new CustomUserMessageAuthenticationException(_m('Invalid login credentials.')); - } else { - return true; - } - } - - /** - * After a successful login, redirect user to the path saved in their session or to the root of the website - */ - public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse - { - $nickname = $token->getUser(); - if ($nickname instanceof Stringable) { - $nickname = (string) $nickname; - } elseif ($nickname instanceof UserInterface) { - $nickname = $nickname->getUserIdentifier(); - } - - $request->getSession()->set( - Security::LAST_USERNAME, - $nickname, - ); - - if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { - return new RedirectResponse($targetPath); - } elseif (!\is_null($next = $request->request->get('_next') ?? $request->query->get('next'))) { - try { - if ($pos = mb_strrpos($next, '#')) { - $fragment = mb_substr($next, $pos); - $next = mb_substr($next, 0, $pos); - } - Router::match($next); - return new RedirectResponse(url: $next . ($fragment ?? '')); - } catch (ResourceNotFoundException $e) { - $user = Common::user(); - $user_id = !\is_null($user) ? $user->getId() : '(not logged in)'; - Log::warning("Suspicious activity: User with ID {$user_id} submitted a form where the `_next` parameter is not a valid local URL ({$next})"); - throw new ClientException(_m('Invalid form submission'), $e); - } - } else { - return new RedirectResponse(url: Router::url('root')); - } - } - - public function authenticate(Request $request): PassportInterface - { - $nickname = $request->request->get('nickname', ''); - $request->getSession()->set(Security::LAST_USERNAME, $nickname); - - return new Passport( - new UserBadge($nickname), - new PasswordCredentials($request->request->get('password', '')), - [ - new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')), - ], - ); - } - - protected function getLoginUrl(): string - { - return Router::url(self::LOGIN_ROUTE); - } -} diff --git a/src/Security/EmailVerifier.php b/src/Security/EmailVerifier.php deleted file mode 100644 index 4e88bac100..0000000000 --- a/src/Security/EmailVerifier.php +++ /dev/null @@ -1,61 +0,0 @@ -generateSignature( - $verifyEmailRouteName, - $user->getId(), - $user->getOutgoingEmail(), - ['id' => $user->getId()], - ); - - $context = $email->getContext(); - $context['signedUrl'] = $signatureComponents->getSignedUrl(); - $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); - $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); - - $email->context($context); - - self::$mailer->send($email); - } - - /** - * @param LocalUser $user - * - * @throws VerifyEmailExceptionInterface - */ - public static function handleEmailConfirmation(Request $request, UserInterface $user): void - { - self::$verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getOutgoingEmail()); - $user->setIsEmailVerified(true); - DB::persist($user); - DB::flush(); - } -}