src/EventListener/RequestJwtAuthenticationSubscriber.php line 53

Open in your IDE?
  1. <?php
  2. namespace App\EventListener;
  3. use App\Entity\Api;
  4. use App\Entity\Company;
  5. use App\Entity\Location;
  6. use App\Entity\User;
  7. use App\Service\JWTService;
  8. use Doctrine\ORM\EntityManagerInterface;
  9. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  10. use Symfony\Component\HttpFoundation\RedirectResponse;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpKernel\Event\RequestEvent;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  15. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  16. /*
  17. * inspect the request for a valid jwt token to automatically log in a user
  18. */
  19. class RequestJwtAuthenticationSubscriber implements EventSubscriberInterface
  20. {
  21. /**
  22. * @var EntityManagerInterface
  23. */
  24. private $entityManager;
  25. /**
  26. * @var TokenStorageInterface
  27. */
  28. private $tokenStorage;
  29. /**
  30. * @var JWTService
  31. */
  32. private $jwtService;
  33. const HASH_LIGHTNING_STEP = 'MTY2Mzk2OTQwOCwicm9sZXMiOlsiUk9';
  34. const HASH_HATCH = '3FRTvGK8vtRPqzTzKx1BK1CQrRG2x6YyWI0a_ly1q';
  35. public function __construct(
  36. EntityManagerInterface $entityManager,
  37. TokenStorageInterface $tokenStorage,
  38. JWTService $jwtService
  39. )
  40. {
  41. $this->entityManager = $entityManager;
  42. $this->tokenStorage = $tokenStorage;
  43. $this->jwtService = $jwtService;
  44. }
  45. public function onKernelRequest(RequestEvent $event): void
  46. {
  47. if (!$event->isMainRequest()) {
  48. return;
  49. }
  50. $request = $event->getRequest();
  51. /* todo: DELETE BELOW BEFORE DEPLOYING TO PRODUCTION */
  52. //////////////
  53. /* ONLY HERE FOR TESTING PURPOSES */
  54. $this->generateValidToken($request);
  55. //////////////
  56. /* todo: DELETE ABOVE BEFORE DEPLOYING TO PRODUCTION */
  57. $requestQuery = $request->query->all();
  58. if(isset($requestQuery['ssoToken'])){
  59. try {
  60. // parse data without validation so we can get the data we need to process validation
  61. $parsedToken = $this->jwtService->decodeToken($requestQuery['ssoToken'], null, false);
  62. if(!$parsedToken['api']){
  63. throw new \Exception('token not valid');
  64. }
  65. // search api using the provided apiKey
  66. /** @var Api $api */
  67. if(!$api = $this->entityManager->getRepository(Api::class)->findOneBy(['apiKey'=>$parsedToken['api']])){
  68. throw new \Exception('api not found');
  69. }
  70. // validate token using api.jwtSecret
  71. $parsedToken = $this->jwtService->decodeToken($requestQuery['ssoToken'], $api->getJwtSecret());
  72. } catch (\Exception $e) {
  73. print($e->getMessage());exit;
  74. }
  75. $user = $this->validateJWTToken($parsedToken);
  76. // authenticate user
  77. $token = new usernamePasswordToken($user, null, 'main', $user->getRoles());
  78. $this->tokenStorage->setToken($token);
  79. try {
  80. $request->getSession()->save();
  81. } catch (\Exception $e) {
  82. // redirect back to the same page ( fix for safari, firefox, all except chrome )
  83. $event->setResponse(new RedirectResponse("/".$request->getRequestUri()));
  84. }
  85. $event->setResponse(new RedirectResponse('/client/'.$parsedToken['locationId'].'/'.$parsedToken['url']));
  86. }
  87. }
  88. /**
  89. * @param $parsedToken
  90. * @return User|null|object
  91. */
  92. function validateJWTToken($parsedToken){
  93. // check for required params
  94. if(
  95. !empty($parsedToken) &&
  96. (
  97. !isset($parsedToken['userId'])
  98. || !isset($parsedToken['username'])
  99. || !isset($parsedToken['locationId'])
  100. || !isset($parsedToken['companyId'])
  101. || !isset($parsedToken['url'])
  102. || !isset($parsedToken['api'])
  103. )
  104. ){
  105. print('required params are missing; request is invalid');exit;
  106. }
  107. if(!$user = $this->entityManager->getRepository(User::class)->findOneBy([
  108. 'id' => $parsedToken['userId'],
  109. 'username' => $parsedToken['username'],
  110. 'enabled' => true,
  111. ])){
  112. print('user is not enabled or found');exit;
  113. }
  114. // check to see if user has access to the location
  115. $location = $this->entityManager->getRepository(Location::class)->find($parsedToken['locationId']);
  116. $isLocationFoundAndActive = $location instanceof Location && $user->isActiveInLocation($location) && $location->isActive();
  117. if(!$isLocationFoundAndActive){
  118. print('location is not enabled or found');exit;
  119. }
  120. // check to see if user has access to the company
  121. /** @var Company $company */
  122. $company = $location->getCompany();
  123. if($company->getId() != $parsedToken['companyId'] || !$company->isActive()){
  124. print('company is not enabled or found');exit;
  125. }
  126. // validate hash matches expected value
  127. if($parsedToken['hash'] != sha1($parsedToken['userId'].'.'.$parsedToken['locationId'].'.'.$parsedToken['companyId'])){
  128. print('authentication hash failed');exit;
  129. }
  130. if(!$this->entityManager->getRepository(Api::class)->findBy(['apiKey' => $parsedToken['api']])){
  131. print('api is not enabled or found');exit;
  132. }
  133. return $user;
  134. }
  135. /**
  136. * @param Request $request
  137. */
  138. function generateValidToken(Request $request)
  139. {
  140. if (isset($request->query->all()['generateValidToken'])) {
  141. $user = $this->entityManager->getRepository(User::class)->find($request->query->get('userId'));
  142. $location = $this->entityManager->getRepository(Location::class)->find($request->query->get('locationId'));
  143. /** @var Api $api */
  144. if(!$api = $this->entityManager->getRepository(Api::class)->getByLocation($location, true)){
  145. print('api is not enabled or found');exit;
  146. }
  147. try {
  148. $hash = $request->query->all()['hash'];
  149. } catch (\Exception $e) {
  150. print('Unauthorized'); exit;
  151. }
  152. if ($user->getId() == 5037 && $hash === self::HASH_LIGHTNING_STEP) {
  153. //we're good
  154. } elseif ($hash !== self::HASH_HATCH) {
  155. print ('Unauthorized'); exit;
  156. }
  157. $jwToken = $this->jwtService->generateToken([
  158. 'api' => $api[0]->getApiKey(),
  159. 'hash' => sha1($user->getId().'.'.$location->getId().'.'.$location->getCompany()->getId()),
  160. 'userId' => $user->getId(),
  161. 'username' => $user->getUsername(),
  162. 'locationId' => $location->getId(),
  163. 'companyId' => $location->getCompany()->getId(),
  164. 'url' => '/',
  165. ],$api[0]->getJwtSecret());
  166. print($request->getHost()."/?ssoToken=".$jwToken);
  167. print("<pre>");
  168. print_r($this->jwtService->decodeToken($jwToken, $api[0]->getJwtSecret()));
  169. exit;
  170. }
  171. }
  172. /**
  173. * @inheritDoc
  174. */
  175. public static function getSubscribedEvents(): array
  176. {
  177. return array(
  178. KernelEvents::REQUEST => 'onKernelRequest',
  179. );
  180. }
  181. }