Spaces:
No application file
No application file
| namespace Mautic\UserBundle\Security\Authenticator; | |
| use Mautic\PluginBundle\Helper\IntegrationHelper; | |
| use Mautic\UserBundle\Entity\User; | |
| use Mautic\UserBundle\Event\AuthenticationEvent; | |
| use Mautic\UserBundle\Security\Authentication\Token\PluginToken; | |
| use Mautic\UserBundle\UserEvents; | |
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
| use Symfony\Component\HttpFoundation\RedirectResponse; | |
| use Symfony\Component\HttpFoundation\Request; | |
| use Symfony\Component\HttpFoundation\RequestStack; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; | |
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
| use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
| use Symfony\Component\Security\Core\Exception\BadCredentialsException; | |
| use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; | |
| use Symfony\Component\Security\Core\Exception\UserNotFoundException; | |
| 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\PasswordAuthenticatedInterface; | |
| use Symfony\Component\Security\Http\Util\TargetPathTrait; | |
| class FormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface | |
| { | |
| use TargetPathTrait; | |
| public const LOGIN_ROUTE = 'login'; | |
| public const LOGIN_CHECK_ROUTE = 'mautic_user_logincheck'; | |
| /** | |
| * @var string|null After upgrade to Symfony 5.2 we should use Passport system to store the authenticatingService | |
| */ | |
| private ?string $authenticatingService = null; | |
| private ?Response $authEventResponse = null; | |
| public function __construct( | |
| private IntegrationHelper $integrationHelper, | |
| private UserPasswordHasher $hasher, | |
| private EventDispatcherInterface $dispatcher, | |
| private ?RequestStack $requestStack, | |
| private CsrfTokenManagerInterface $csrfTokenManager, | |
| private UrlGeneratorInterface $urlGenerator | |
| ) { | |
| } | |
| public function supports(Request $request): bool | |
| { | |
| return self::LOGIN_CHECK_ROUTE === $request->attributes->get('_route') | |
| && $request->isMethod(Request::METHOD_POST); | |
| } | |
| /** | |
| * @return array<string, mixed|null> | |
| */ | |
| public function getCredentials(Request $request): array | |
| { | |
| $credentials = [ | |
| 'username' => $request->request->get('_username'), | |
| 'password' => $request->request->get('_password'), | |
| 'csrf_token' => $request->request->get('_csrf_token'), | |
| 'integration' => $request->get('integration'), | |
| ]; | |
| $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']); | |
| return $credentials; | |
| } | |
| public function getUser($credentials, UserProviderInterface $userProvider): ?User | |
| { | |
| $csrfToken = new CsrfToken('authenticate', $credentials['csrf_token']); | |
| if (!$this->csrfTokenManager->isTokenValid($csrfToken)) { | |
| throw new InvalidCsrfTokenException(); | |
| } | |
| try { | |
| /** @var User $user */ | |
| $user = $userProvider->loadUserByIdentifier($credentials['username']); | |
| } catch (UserNotFoundException) { | |
| /** @var string $user */ | |
| $user = $credentials['username']; | |
| } | |
| $this->authenticatingService = $credentials['integration'] ?? null; | |
| // Try authenticating with a plugin first | |
| $integrations = $this->integrationHelper->getIntegrationObjects($this->authenticatingService, ['sso_form'], false, null, true); | |
| $token = new PluginToken( | |
| null, // In 4.4 there was a provider key. If the issue will be severe we need to override whole guard. Otherwise, wait for Symfony 5.2 and Passport. | |
| $this->authenticatingService, | |
| $user, | |
| ($user instanceof User) ? $this->getPassword($credentials) : '', | |
| ($user instanceof User) ? $user->getRoles() : [], | |
| $this->authEventResponse // though this will be null ? | |
| ); | |
| $authEvent = new AuthenticationEvent( | |
| $user, | |
| $token, | |
| $userProvider, | |
| $this->requestStack->getCurrentRequest(), | |
| false, | |
| $this->authenticatingService, | |
| $integrations | |
| ); | |
| if ($this->dispatcher->hasListeners(UserEvents::USER_FORM_AUTHENTICATION)) { | |
| $this->dispatcher->dispatch($authEvent, UserEvents::USER_FORM_AUTHENTICATION); | |
| } | |
| if ($authEvent->isAuthenticated()) { | |
| $user = $authEvent->getUser(); | |
| $this->authenticatingService = $authEvent->getAuthenticatingService(); | |
| } elseif ($authEvent->isFailed()) { | |
| throw new AuthenticationException($authEvent->getFailedAuthenticationMessage()); | |
| } | |
| $this->authEventResponse = $authEvent->getResponse(); | |
| if (!$user instanceof User) { | |
| throw new BadCredentialsException(); | |
| } | |
| if ($this->dispatcher->hasListeners(UserEvents::USER_FORM_POST_LOCAL_PASSWORD_AUTHENTICATION)) { | |
| $authEvent = new AuthenticationEvent($user, $token, $userProvider, $this->requestStack->getCurrentRequest()); | |
| $this->dispatcher->dispatch($authEvent, UserEvents::USER_FORM_POST_LOCAL_PASSWORD_AUTHENTICATION); | |
| } | |
| return $user; | |
| } | |
| public function checkCredentials($credentials, UserInterface $user): bool | |
| { | |
| // Temp solution to remap a UserInterface object to a PasswordAuthenticatedUserInterface object | |
| $newUser = new User(); | |
| $newUser->setUsername($user->getUserIdentifier()); | |
| $newUser->setPassword($user->getPassword()); | |
| return $this->hasher->isPasswordValid($newUser, $this->getPassword($credentials)); | |
| } | |
| public function getPassword($credentials): ?string | |
| { | |
| return $credentials['password'] ?? null; | |
| } | |
| public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?RedirectResponse | |
| { | |
| if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { | |
| return new RedirectResponse($targetPath); | |
| } | |
| // If integrations fail due to redirect to dashboard look into | |
| // how to detect if that's a proper form auth and return null if request must continue w/o redirect | |
| return new RedirectResponse($this->urlGenerator->generate('mautic_dashboard_index')); | |
| } | |
| protected function getLoginUrl(): string | |
| { | |
| return $this->urlGenerator->generate(self::LOGIN_ROUTE); | |
| } | |
| } | |