. // }}} namespace App\Security; use App\Core\Router\Router; use App\Entity\LocalUser; use App\Util\Common; 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\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\Util\TargetPathTrait; use function App\Core\I18n\_m; /** * 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; } /** * @param Request $request * @return bool */ 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 * @param UserProviderInterface $userProvider * @return ?LocalUser * @throws NoSuchActorException * @throws ServerException */ 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 * @return bool * @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); } return new RedirectResponse(Router::url('main_all')); } 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() { return Router::url(self::LOGIN_ROUTE); } }