securityAspect = SecurityAspect::provideIn($context); $this->noncePool = $this->securityAspect->getNoncePool(); } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // @todo someā„¢ route handling mechanism might verify request-tokens (-> e.g. backend-routes, unsure for frontend) $this->noncePool->merge($this->resolveNoncePool($request))->purge(); try { $this->securityAspect->setReceivedRequestToken($this->resolveReceivedRequestToken($request)); } catch (RequestTokenException $exception) { // request token was given, but could not be verified $this->securityAspect->setReceivedRequestToken(false); $this->logger->debug('Could not resolve request token', ['exception' => $exception]); } $response = $handler->handle($request); return $this->enrichResponseWithCookie($request, $response); } protected function resolveNoncePool(ServerRequestInterface $request): NoncePool { $secure = $this->isHttps($request); // resolves cookie name dependent on whether TLS is used in request and uses `__Secure-` prefix, // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes $securePrefix = $secure ? self::SECURE_PREFIX : ''; $cookiePrefix = $securePrefix . self::COOKIE_PREFIX; $cookiePrefixLength = strlen($cookiePrefix); $cookies = array_filter( $request->getCookieParams(), static fn(mixed $name): bool => is_string($name) && str_starts_with($name, $cookiePrefix), ARRAY_FILTER_USE_KEY ); $items = []; foreach ($cookies as $name => $value) { $name = substr($name, $cookiePrefixLength); try { $items[$name] = Nonce::fromHashSignedJwt($value); } catch (NonceException $exception) { $this->logger->debug('Could not resolve received nonce', ['exception' => $exception]); $items[$name] = null; } } // @todo pool `$options` should be configurable via `$TYPO3_CONF_VARS` return GeneralUtility::makeInstance(NoncePool::class, $items); } /** * @throws RequestTokenException */ protected function resolveReceivedRequestToken(ServerRequestInterface $request): ?RequestToken { $headerValue = $request->getHeaderLine(RequestToken::HEADER_NAME); $paramValue = ''; if (isset($request->getParsedBody()[RequestToken::PARAM_NAME]) && is_scalar($request->getParsedBody()[RequestToken::PARAM_NAME])) { $paramValue = (string)($request->getParsedBody()[RequestToken::PARAM_NAME]); } if ($headerValue !== '') { $tokenValue = $headerValue; } elseif (in_array($request->getMethod(), self::ALLOWED_METHODS, true)) { $tokenValue = $paramValue; } else { $tokenValue = ''; } if ($tokenValue === '') { return null; } return RequestToken::fromHashSignedJwt($tokenValue, $this->securityAspect->getSigningSecretResolver()); } protected function enrichResponseWithCookie(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $secure = $this->isHttps($request); $normalizedParams = $request->getAttribute('normalizedParams'); $path = $normalizedParams->getSitePath(); $securePrefix = $secure ? self::SECURE_PREFIX : ''; $cookiePrefix = $securePrefix . self::COOKIE_PREFIX; $createCookie = static fn(string $name, string $value, int $expire): Cookie => new Cookie( $name, $value, $expire, $path, null, $secure, true, false, Cookie::SAMESITE_STRICT ); $cookies = []; // emit new nonce cookies foreach ($this->noncePool->getEmittableNonces() as $name => $nonce) { $cookies[] = $createCookie($cookiePrefix . $name, $nonce->toHashSignedJwt(), 0); } // revoke nonce cookies (exceeded pool size, expired or explicitly revoked) foreach ($this->noncePool->getRevocableNames() as $name) { $cookies[] = $createCookie($cookiePrefix . $name, '', -1); } // finally apply to response foreach ($cookies as $cookie) { $response = $response->withAddedHeader('Set-Cookie', (string)$cookie); } return $response; } protected function isHttps(ServerRequestInterface $request): bool { $normalizedParams = $request->getAttribute('normalizedParams'); return $normalizedParams instanceof NormalizedParams && $normalizedParams->isHttps(); } }