src/Module/Cms/Subscriber/PreviewSubscriber.php line 55

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Module\Cms\Subscriber;
  4. use Exception;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpKernel\Event\RequestEvent;
  8. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  9. use Symfony\Component\HttpKernel\KernelEvents;
  10. /**
  11. * Class PreviewSubscriber
  12. *
  13. * @package App\Module\Cms\Subscriber
  14. */
  15. class PreviewSubscriber implements EventSubscriberInterface
  16. {
  17. /**
  18. * VALIDATION_TOKEN_LIFETIME
  19. */
  20. private const VALIDATION_TOKEN_LIFETIME = 9000;
  21. private string $previewToken;
  22. /**
  23. * PreviewSubscriber constructor.
  24. *
  25. * @param string $previewToken
  26. */
  27. public function __construct(string $previewToken)
  28. {
  29. $this->previewToken = $previewToken;
  30. }
  31. /**
  32. * @return array[][]
  33. */
  34. public static function getSubscribedEvents(): array
  35. {
  36. return [
  37. KernelEvents::REQUEST => [
  38. ['onKernelRequest', 32],
  39. ],
  40. ];
  41. }
  42. /**
  43. * @param RequestEvent $event
  44. * @throws Exception
  45. * @return void
  46. */
  47. public function onKernelRequest(RequestEvent $event): void
  48. {
  49. if (!$event->isMasterRequest()) {
  50. return;
  51. }
  52. $request = $event->getRequest();
  53. if ($request->getPathInfo() !== '/preview') {
  54. return;
  55. }
  56. if (!$this->hasValidToken($request)) {
  57. throw new AccessDeniedHttpException('Token missing or invalid!');
  58. }
  59. }
  60. /**
  61. * @param Request $request
  62. * @return string
  63. */
  64. private function generateValidationToken(Request $request): string
  65. {
  66. $tokenData = $request->query->get('_storyblok_tk');
  67. $rawToken = sprintf('%s:%s:%s', $tokenData['space_id'], $this->previewToken, $tokenData['timestamp']);
  68. return sha1($rawToken);
  69. }
  70. /**
  71. * @param Request $request
  72. * @return bool
  73. */
  74. private function hasValidToken(Request $request): bool
  75. {
  76. $tokenData = $request->query->get('_storyblok_tk');
  77. $tokenTimestamp = (int) ($tokenData['timestamp'] ?? 0);
  78. if ($tokenTimestamp === 0 || $tokenTimestamp < (strtotime('now') - self::VALIDATION_TOKEN_LIFETIME)) {
  79. throw new AccessDeniedHttpException('Expired - tokenTimestamp: ' . $tokenTimestamp . ' expirationDate: ' . (strtotime('now') - self::VALIDATION_TOKEN_LIFETIME));
  80. }
  81. $token = $tokenData['token'] ?? null;
  82. if ($token === null || $token !== $this->generateValidationToken($request)) {
  83. throw new AccessDeniedHttpException('Invalid token - delivered: ' . $token . ' expected: ' . $this->generateValidationToken($request));
  84. }
  85. return true;
  86. }
  87. }