<?php
namespace AppBundle\Security;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener;
use Doctrine\ORM\EntityManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Core\User\UserProviderInterface;
/**
* CustomUsernamePasswordJsonAuthenticationListener
*/
class CustomUsernamePasswordJsonAuthenticationListener extends UsernamePasswordJsonAuthenticationListener
{
/** @var AuthenticationManagerInterface */
private $authenticationManager;
/** @var HttpUtils */
private $httpUtils;
// private $providerKey = null;
private $successHandler = null;
private $failureHandler = null;
private $options;
/** @var EventDispatcherInterface */
private $eventDispatcher;
private $sessionStrategy;
/** @var UsernamePasswordJsonAuthenticationListener */
private $innerService;
/** @var PropertyAccessorInterface */
private $propertyAccessor;
/** @var LoggerInterface */
private $logger;
/** @var TokenStorageInterface */
private $tokenStorage;
/** @var UserProviderInterface */
private $provider;
/** @var BeLAEntityManager */
private $belaEm;
/** @var int */
private $ldapLogLevel;
/** @var string */
private $providerKey;
public function __construct(
UsernamePasswordJsonAuthenticationListener $innerService,
TokenStorageInterface $tokenStorage,
AuthenticationManagerInterface $authenticationManager,
HttpUtils $httpUtils,
UserProviderInterface $provider,
EventDispatcherInterface $dispatcher = null,
EntityManager $belaEm = null,
LoggerInterface $logger = null,
int $ldapLogLevel = 0,
array $options = []
) {
parent::__construct(
$tokenStorage,
$authenticationManager,
$httpUtils,
null, // $providerKey,
null, // AuthenticationSuccessHandlerInterface $successHandler = null,
null, // AuthenticationFailureHandlerInterface $failureHandler = null,
$options,
$logger,
$dispatcher,
null // PropertyAccessorInterface $propertyAccessor = null
);
$this->innerService = $innerService;
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->httpUtils = $httpUtils;
$this->provider = $provider;
$this->eventDispatcher = $dispatcher;
$this->belaEm = $belaEm;
$this->logger = $logger;
$this->ldapLogLevel = $ldapLogLevel;
$this->options = $options;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
/** @todo how to get providerKey ("main" here) from configuration (security.yml)? */
$this->providerKey = 'main';
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === strpos($request->getRequestFormat(), 'json')
&& false === strpos($request->getContentType(), 'json')
) {
return;
}
/** @todo how to get $options[] from decorated class config (security.yml > firewalls > main > json_login)? we need check_path, username_path and password_path from there or their default values - see parent class and comments below */
// if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
if (!$this->httpUtils->checkRequestPath($request, '/api/login')) {
return;
}
$data = json_decode($request->getContent());
try {
if (!$data instanceof \stdClass) {
throw new BadRequestHttpException('Invalid JSON.');
}
try {
// $username = $this->propertyAccessor->getValue($data, $this->options['username_path']);
$username = $this->propertyAccessor->getValue($data, 'username');
} catch (AccessException $e) {
// throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['username_path']), $e);
throw new BadRequestHttpException('The key "username" must be provided.', $e);
}
try {
$password = $this->propertyAccessor->getValue($data, 'password');
} catch (AccessException $e) {
throw new BadRequestHttpException('The key "password" must be provided.', $e);
}
if (!\is_string($username)) {
throw new BadRequestHttpException('The key "username" must be a string.');
}
if (\strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
if (!\is_string($password)) {
throw new BadRequestHttpException('The key "password" must be a string.');
}
if (empty($password)) {
throw new BadCredentialsException('Password cannot be empty.');
}
$ldapAuth = LdapAuth::initialize($this->belaEm, $this->provider, $this->logger, $this->ldapLogLevel)
->setCallback([$this->innerService, 'handle'])
->setCallbackParams([ $event ]);
$user = $ldapAuth->handle($data);
if (empty($user)) {
return;
}
$authenticatedToken = new UsernamePasswordToken($user, $password, $this->providerKey, $user->getRoles());
$response = $this->onSuccess($request, $authenticatedToken);
} catch (AuthenticationException $e) {
$response = $this->onFailure($request, $e);
} catch (BadRequestHttpException $e) {
$request->setRequestFormat('json');
throw $e;
}
if (null === $response) {
return;
}
$event->setResponse($response);
}
private function onSuccess(Request $request, TokenInterface $token)
{
if (null !== $this->logger) {
$this->logger->info('User has been authenticated successfully.', ['username' => $token->getUsername()]);
}
$this->migrateSession($request, $token);
$this->tokenStorage->setToken($token);
if (null !== $this->eventDispatcher) {
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->eventDispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
}
if (!$this->successHandler) {
return; // let the original request succeeds
}
$response = $this->successHandler->onAuthenticationSuccess($request, $token);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Success Handler did not return a Response.');
}
return $response;
}
private function onFailure(Request $request, AuthenticationException $failed)
{
if (null !== $this->logger) {
$this->logger->info('Authentication request failed.', ['exception' => $failed]);
}
$token = $this->tokenStorage->getToken();
if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey()) {
$this->tokenStorage->setToken(null);
}
if (!$this->failureHandler) {
return new JsonResponse(['error' => $failed->getMessageKey()], 401);
}
$response = $this->failureHandler->onAuthenticationFailure($request, $failed);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
}
return $response;
}
private function migrateSession(Request $request, TokenInterface $token)
{
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {
return;
}
$this->sessionStrategy->onAuthentication($request, $token);
}
}