upstream V3 development https://www.gnusocial.rocks/v3
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

169 lines
5.8 KiB

  1. <?php
  2. declare(strict_types=1);
  3. // {{{ License
  4. // This file is part of GNU social - https://www.gnu.org/software/social
  5. //
  6. // GNU social is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // GNU social is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  18. // }}}
  19. namespace App\Security;
  20. use App\Core\Router\Router;
  21. use App\Entity\LocalUser;
  22. use App\Util\Common;
  23. use App\Util\Exception\NoSuchActorException;
  24. use App\Util\Exception\NotFoundException;
  25. use App\Util\Exception\ServerException;
  26. use App\Util\Nickname;
  27. use Stringable;
  28. use Symfony\Component\HttpFoundation\RedirectResponse;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  31. use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
  32. use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
  33. use Symfony\Component\Security\Core\Security;
  34. use Symfony\Component\Security\Core\User\UserInterface;
  35. use Symfony\Component\Security\Core\User\UserProviderInterface;
  36. use Symfony\Component\Security\Csrf\CsrfToken;
  37. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  38. use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
  39. use Symfony\Component\Security\Guard\AuthenticatorInterface;
  40. use Symfony\Component\Security\Http\Util\TargetPathTrait;
  41. use function App\Core\I18n\_m;
  42. /**
  43. * User authenticator
  44. *
  45. * @category Authentication
  46. * @package GNUsocial
  47. *
  48. * @author Hugo Sales <hugo@hsal.es>
  49. * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
  50. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  51. */
  52. class Authenticator extends AbstractFormLoginAuthenticator implements AuthenticatorInterface
  53. {
  54. use TargetPathTrait;
  55. public const LOGIN_ROUTE = 'security_login';
  56. private CsrfTokenManagerInterface $csrfTokenManager;
  57. public function __construct(CsrfTokenManagerInterface $csrfTokenManager)
  58. {
  59. $this->csrfTokenManager = $csrfTokenManager;
  60. }
  61. /**
  62. * @param Request $request
  63. * @return bool
  64. */
  65. public function supports(Request $request): bool
  66. {
  67. return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST');
  68. }
  69. /**
  70. * @return array<string, string>
  71. */
  72. public function getCredentials(Request $request): array
  73. {
  74. return [
  75. 'nickname_or_email' => $request->request->get('nickname_or_email'),
  76. 'password' => $request->request->get('password'),
  77. 'csrf_token' => $request->request->get('_csrf_token'),
  78. ];
  79. }
  80. /**
  81. * Get a user given credentials and a CSRF token
  82. *
  83. * @param array<string, string> $credentials result of self::getCredentials
  84. * @param UserProviderInterface $userProvider
  85. * @return ?LocalUser
  86. * @throws NoSuchActorException
  87. * @throws ServerException
  88. */
  89. public function getUser($credentials, UserProviderInterface $userProvider): ?LocalUser
  90. {
  91. $token = new CsrfToken('authenticate', $credentials['csrf_token']);
  92. if (!$this->csrfTokenManager->isTokenValid($token)) {
  93. throw new InvalidCsrfTokenException();
  94. }
  95. $user = null;
  96. try {
  97. if (Common::isValidEmail($credentials['nickname_or_email'])) {
  98. $user = LocalUser::getByEmail($credentials['nickname_or_email']);
  99. } elseif (Nickname::isValid($credentials['nickname_or_email'])) {
  100. $user = LocalUser::getByNickname($credentials['nickname_or_email']);
  101. }
  102. if (is_null($user)) {
  103. throw new NoSuchActorException('No such local user.');
  104. }
  105. $credentials['nickname'] = $user->getNickname();
  106. } catch (NoSuchActorException|NotFoundException) {
  107. throw new CustomUserMessageAuthenticationException(
  108. _m('Invalid login credentials.'),
  109. );
  110. }
  111. return $user;
  112. }
  113. /**
  114. * @param array<string, string> $credentials result of self::getCredentials
  115. * @param LocalUser $user
  116. * @return bool
  117. * @throws ServerException
  118. */
  119. public function checkCredentials($credentials, $user): bool
  120. {
  121. if (!$user->checkPassword($credentials['password'])) {
  122. throw new CustomUserMessageAuthenticationException(_m('Invalid login credentials.'));
  123. } else {
  124. return true;
  125. }
  126. }
  127. /**
  128. * After a successful login, redirect user to the path saved in their session or to the root of the website
  129. */
  130. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): RedirectResponse
  131. {
  132. $nickname = $token->getUser();
  133. if ($nickname instanceof Stringable) {
  134. $nickname = (string)$nickname;
  135. } elseif ($nickname instanceof UserInterface) {
  136. $nickname = $nickname->getUserIdentifier();
  137. }
  138. $request->getSession()->set(
  139. Security::LAST_USERNAME,
  140. $nickname,
  141. );
  142. if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
  143. return new RedirectResponse($targetPath);
  144. }
  145. return new RedirectResponse(Router::url('main_all'));
  146. }
  147. protected function getLoginUrl(): string
  148. {
  149. return Router::url(self::LOGIN_ROUTE);
  150. }
  151. }