diff --git a/com_oauthserver/administrator/src/AuthorizationValidators/JwtInQueryTokenValidator.php b/com_oauthserver/administrator/src/AuthorizationValidators/JwtInQueryTokenValidator.php new file mode 100644 index 0000000..03232a3 --- /dev/null +++ b/com_oauthserver/administrator/src/AuthorizationValidators/JwtInQueryTokenValidator.php @@ -0,0 +1,146 @@ +accessTokenRepository = $accessTokenRepository; + $this->jwtValidAtDateLeeway = $jwtValidAtDateLeeway; + $this->queryParamName = $queryParamName; + } + + /** + * Set the public key + * + * @param CryptKey $key + */ + public function setPublicKey(CryptKey $key) + { + $this->publicKey = $key; + + $this->initJwtConfiguration(); + } + + /** + * Initialise the JWT configuration. + */ + private function initJwtConfiguration() + { + $this->jwtConfiguration = Configuration::forSymmetricSigner( + new Sha256(), + InMemory::plainText('empty', 'empty') + ); + + $clock = new SystemClock(new \DateTimeZone(\date_default_timezone_get())); + $this->jwtConfiguration->setValidationConstraints( + new LooseValidAt($clock, $this->jwtValidAtDateLeeway), + new SignedWith( + new Sha256(), + InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') + ) + ); + } + + /** + * {@inheritdoc} + */ + public function validateAuthorization(ServerRequestInterface $request) + { + $query = $request->getQueryParams(); + if (!key_exists($this->queryParamName, $query) + || empty($query[$this->queryParamName])) { + throw OAuthServerException::accessDenied(sprintf('Missing parameter "%s" in query', $this->queryParamName)); + } + + $jwt = \trim((string)$query[$this->queryParamName]); + + try { + // Attempt to parse the JWT + $token = $this->jwtConfiguration->parser()->parse($jwt); + } catch (\Lcobucci\JWT\Exception $exception) { + throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); + } + + try { + // Attempt to validate the JWT + $constraints = $this->jwtConfiguration->validationConstraints(); + $this->jwtConfiguration->validator()->assert($token, ...$constraints); + } catch (RequiredConstraintsViolated $exception) { + throw OAuthServerException::accessDenied('Access token could not be verified', null, $exception); + } + + $claims = $token->claims(); + + // Check if token has been revoked + if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) { + throw OAuthServerException::accessDenied('Access token has been revoked'); + } + + // Return the request with additional attributes + return $request + ->withAttribute('oauth_access_token_id', $claims->get('jti')) + ->withAttribute('oauth_client_id', $this->convertSingleRecordAudToString($claims->get('aud'))) + ->withAttribute('oauth_user_id', $claims->get('sub')) + ->withAttribute('oauth_scopes', $claims->get('scopes')); + } + + /** + * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt + * + * @param mixed $aud + * + * @return array|string + */ + private function convertSingleRecordAudToString($aud) + { + return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; + } +}