<?php
namespace App\EventListener;
use App\Entity\Api;
use App\Entity\Company;
use App\Entity\Location;
use App\Entity\User;
use App\Service\JWTService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
/*
* inspect the request for a valid jwt token to automatically log in a user
*/
class RequestJwtAuthenticationSubscriber implements EventSubscriberInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var JWTService
*/
private $jwtService;
const HASH_LIGHTNING_STEP = 'MTY2Mzk2OTQwOCwicm9sZXMiOlsiUk9';
const HASH_HATCH = '3FRTvGK8vtRPqzTzKx1BK1CQrRG2x6YyWI0a_ly1q';
public function __construct(
EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage,
JWTService $jwtService
)
{
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
$this->jwtService = $jwtService;
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
/* todo: DELETE BELOW BEFORE DEPLOYING TO PRODUCTION */
//////////////
/* ONLY HERE FOR TESTING PURPOSES */
$this->generateValidToken($request);
//////////////
/* todo: DELETE ABOVE BEFORE DEPLOYING TO PRODUCTION */
$requestQuery = $request->query->all();
if(isset($requestQuery['ssoToken'])){
try {
// parse data without validation so we can get the data we need to process validation
$parsedToken = $this->jwtService->decodeToken($requestQuery['ssoToken'], null, false);
if(!$parsedToken['api']){
throw new \Exception('token not valid');
}
// search api using the provided apiKey
/** @var Api $api */
if(!$api = $this->entityManager->getRepository(Api::class)->findOneBy(['apiKey'=>$parsedToken['api']])){
throw new \Exception('api not found');
}
// validate token using api.jwtSecret
$parsedToken = $this->jwtService->decodeToken($requestQuery['ssoToken'], $api->getJwtSecret());
} catch (\Exception $e) {
print($e->getMessage());exit;
}
$user = $this->validateJWTToken($parsedToken);
// authenticate user
$token = new usernamePasswordToken($user, null, 'main', $user->getRoles());
$this->tokenStorage->setToken($token);
try {
$request->getSession()->save();
} catch (\Exception $e) {
// redirect back to the same page ( fix for safari, firefox, all except chrome )
$event->setResponse(new RedirectResponse("/".$request->getRequestUri()));
}
$event->setResponse(new RedirectResponse('/client/'.$parsedToken['locationId'].'/'.$parsedToken['url']));
}
}
/**
* @param $parsedToken
* @return User|null|object
*/
function validateJWTToken($parsedToken){
// check for required params
if(
!empty($parsedToken) &&
(
!isset($parsedToken['userId'])
|| !isset($parsedToken['username'])
|| !isset($parsedToken['locationId'])
|| !isset($parsedToken['companyId'])
|| !isset($parsedToken['url'])
|| !isset($parsedToken['api'])
)
){
print('required params are missing; request is invalid');exit;
}
if(!$user = $this->entityManager->getRepository(User::class)->findOneBy([
'id' => $parsedToken['userId'],
'username' => $parsedToken['username'],
'enabled' => true,
])){
print('user is not enabled or found');exit;
}
// check to see if user has access to the location
$location = $this->entityManager->getRepository(Location::class)->find($parsedToken['locationId']);
$isLocationFoundAndActive = $location instanceof Location && $user->isActiveInLocation($location) && $location->isActive();
if(!$isLocationFoundAndActive){
print('location is not enabled or found');exit;
}
// check to see if user has access to the company
/** @var Company $company */
$company = $location->getCompany();
if($company->getId() != $parsedToken['companyId'] || !$company->isActive()){
print('company is not enabled or found');exit;
}
// validate hash matches expected value
if($parsedToken['hash'] != sha1($parsedToken['userId'].'.'.$parsedToken['locationId'].'.'.$parsedToken['companyId'])){
print('authentication hash failed');exit;
}
if(!$this->entityManager->getRepository(Api::class)->findBy(['apiKey' => $parsedToken['api']])){
print('api is not enabled or found');exit;
}
return $user;
}
/**
* @param Request $request
*/
function generateValidToken(Request $request)
{
if (isset($request->query->all()['generateValidToken'])) {
$user = $this->entityManager->getRepository(User::class)->find($request->query->get('userId'));
$location = $this->entityManager->getRepository(Location::class)->find($request->query->get('locationId'));
/** @var Api $api */
if(!$api = $this->entityManager->getRepository(Api::class)->getByLocation($location, true)){
print('api is not enabled or found');exit;
}
try {
$hash = $request->query->all()['hash'];
} catch (\Exception $e) {
print('Unauthorized'); exit;
}
if ($user->getId() == 5037 && $hash === self::HASH_LIGHTNING_STEP) {
//we're good
} elseif ($hash !== self::HASH_HATCH) {
print ('Unauthorized'); exit;
}
$jwToken = $this->jwtService->generateToken([
'api' => $api[0]->getApiKey(),
'hash' => sha1($user->getId().'.'.$location->getId().'.'.$location->getCompany()->getId()),
'userId' => $user->getId(),
'username' => $user->getUsername(),
'locationId' => $location->getId(),
'companyId' => $location->getCompany()->getId(),
'url' => '/',
],$api[0]->getJwtSecret());
print($request->getHost()."/?ssoToken=".$jwToken);
print("<pre>");
print_r($this->jwtService->decodeToken($jwToken, $api[0]->getJwtSecret()));
exit;
}
}
/**
* @inheritDoc
*/
public static function getSubscribedEvents(): array
{
return array(
KernelEvents::REQUEST => 'onKernelRequest',
);
}
}