Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add introspection implementation - updated #1255

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9446f0e
Add introspection implementation
fetzi Mar 2, 2018
1ad5514
Apply styleci fixes
fetzi Mar 2, 2018
651ee9b
Apply styleci fixes
fetzi Mar 2, 2018
225553f
Fix phpstan errors
fetzi Mar 2, 2018
880b4bd
Apply styleci fixes
fetzi Mar 2, 2018
487241b
Refactor introspection response to not use exceptions to control the …
Jul 20, 2018
eba79d7
refactor response to be more inline with other package reponses
Jul 20, 2018
fece711
Merge branch 'master' of github.com:steveporter92/oauth2-server into …
Jul 20, 2018
caf15b9
update code style
Jul 20, 2018
d143c46
update code style
Jul 20, 2018
f00b07e
fix type hints for tests
Jul 22, 2018
595cace
fix code style and unit tests and rename introspection params function
Jul 22, 2018
33eef79
add validate request method
Jul 22, 2018
e4b49c6
add test for extra params
Jul 22, 2018
baa74fb
code style fixes
Jul 22, 2018
8bf9c36
code style for test
Jul 22, 2018
4af0d2a
add more introspection tests
Jul 22, 2018
b00e6fa
add introspect example
Jul 22, 2018
4805243
remove blank linbe
Jul 22, 2018
5eeb624
add missing brackets to new class
Sep 25, 2018
b0e6eff
update phpdoc to reflect the function
Sep 25, 2018
7def7a8
add missing doc
Sep 25, 2018
99cb04a
fix return type
Sep 25, 2018
9175628
add missing full stop
Sep 25, 2018
d088a3f
create bearer token validator
Nov 11, 2018
1737752
add bearer token introspection response
Nov 11, 2018
af2cde2
add introspection validator interface
Nov 11, 2018
2a1a8b9
refactor introspection response
Nov 11, 2018
4611bed
update introspector to use introspection validator interface
Nov 11, 2018
2745059
fix code style
Nov 11, 2018
66f9843
update phpdoc
Nov 11, 2018
46dded2
Merge remote-tracking branch 'other/feature/add-introspection-impleme…
Dec 5, 2021
f283fa8
Upgrade introspection codebase, fix deprecations, fix tests
Dec 8, 2021
97a4ddc
Refactor Introspection Solution
Dec 11, 2021
69aca7d
Small method declaration changes
Dec 11, 2021
759eea8
Apply StyleCI suggestions
Dec 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The following RFCs are implemented:
* [RFC6750 " The OAuth 2.0 Authorization Framework: Bearer Token Usage"](https://tools.ietf.org/html/rfc6750)
* [RFC7519 "JSON Web Token (JWT)"](https://tools.ietf.org/html/rfc7519)
* [RFC7636 "Proof Key for Code Exchange by OAuth Public Clients"](https://tools.ietf.org/html/rfc7636)
* [RFC7662 "OAuth 2.0 Token Introspection"](https://tools.ietf.org/html/rfc7662)

This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](https://twitter.com/alexbilbie).

Expand Down
51 changes: 51 additions & 0 deletions examples/public/introspect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

use Laminas\Diactoros\Response;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\IntrospectionServer;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

include __DIR__ . '/../vendor/autoload.php';

$app = new App([
// Add the authorization server to the DI container
IntrospectionServer::class => function () {

// Setup the authorization server
$server = new IntrospectionServer(
new AccessTokenRepository(),
'file://' . __DIR__ . '/../public.key'
);

return $server;
},
]);

$app->post(
'/introspect',
function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
/* @var IntrospectionServer $server */
$server = $app->getContainer()->get(IntrospectionServer::class);

try {
// Try to respond to the introspection request
return $server->respondToIntrospectionRequest($request, new Response());
} catch (OAuthServerException $exception) {

// All instances of OAuthServerException can be converted to a PSR-7 response
return $exception->generateHttpResponse($response);
} catch (Exception $exception) {

// Catch unexpected exceptions
$body = $response->getBody();
$body->write($exception->getMessage());

return $response->withStatus(500)->withBody($body);
}
}
);

$app->run();
122 changes: 122 additions & 0 deletions src/IntrospectionServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace League\OAuth2\Server;

use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface;
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator as AuthorizationBearerTokenValidator;
use League\OAuth2\Server\IntrospectionValidators\BearerTokenValidator as IntrospectionBearerTokenValidator;
use League\OAuth2\Server\IntrospectionValidators\IntrospectionValidatorInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\ResponseTypes\Introspection\AbstractResponseType;
use League\OAuth2\Server\ResponseTypes\Introspection\BearerTokenResponse;
use League\OAuth2\Server\ResponseTypes\Introspection\ResponseTypeInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class IntrospectionServer
{
/**
* @var AccessTokenRepositoryInterface
*/
protected $accessTokenRepository;

/**
* @var CryptKey
*/
protected $publicKey;

/**
* @var AbstractResponseType
*/
protected $responseType;

/**
* @var AuthorizationValidatorInterface|null
*/
protected $authorizationValidator;

/**
* @var IntrospectionValidatorInterface|null
*/
protected $introspectionValidator;

public function __construct(
AccessTokenRepositoryInterface $accessTokenRepository,
$publicKey,
IntrospectionValidatorInterface $introspectionValidator = null,
AuthorizationValidatorInterface $authorizationValidator = null,
ResponseTypeInterface $responseType = null
) {
$this->accessTokenRepository = $accessTokenRepository;

if ($publicKey instanceof CryptKey === false) {
$publicKey = new CryptKey($publicKey);
}

$this->publicKey = $publicKey;
$this->introspectionValidator = $introspectionValidator;
$this->authorizationValidator = $authorizationValidator;

if ($responseType === null) {
$this->responseType = new BearerTokenResponse();
} else {
$this->responseType = clone $responseType;
}
}

/**
* Get the introspection validator
*
* @return IntrospectionValidatorInterface
*/
protected function getIntrospectionValidator(): IntrospectionValidatorInterface
{
if ($this->introspectionValidator instanceof IntrospectionValidatorInterface === false) {
$this->introspectionValidator = new IntrospectionBearerTokenValidator($this->accessTokenRepository);

$this->introspectionValidator->setPublicKey($this->publicKey);
}

return $this->introspectionValidator;
}

/**
* Get the authorization validator
*
* @return AuthorizationValidatorInterface
*/
protected function getAuthorizationValidator(): AuthorizationValidatorInterface
{
if ($this->authorizationValidator instanceof AuthorizationValidatorInterface === false) {
$this->authorizationValidator = new AuthorizationBearerTokenValidator($this->accessTokenRepository);

$this->authorizationValidator->setPublicKey($this->publicKey);
}

return $this->authorizationValidator;
}

/**
* Return an introspection response.
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
*
* @return ResponseInterface
*
* @throws Exception\OAuthServerException
*/
public function respondToIntrospectionRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$this->getAuthorizationValidator()->validateAuthorization($request);

$this->responseType->setRequest($request);
$this->responseType->setValidity(
$this->getIntrospectionValidator()->validateIntrospection($request)
);

return $this->responseType->generateHttpResponse($response);
}
}
152 changes: 152 additions & 0 deletions src/IntrospectionValidators/BearerTokenValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace League\OAuth2\Server\IntrospectionValidators;

use DateTimeZone;
use InvalidArgumentException;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface;

class BearerTokenValidator implements IntrospectionValidatorInterface
{
/**
* @var AccessTokenRepositoryInterface
*/
private $accessTokenRepository;

/**
* @var CryptKey
*/
protected $publicKey;

/**
* @var Configuration
*/
protected $jwtConfiguration;

/**
* @param AccessTokenRepositoryInterface $accessTokenRepository
*/
public function __construct(AccessTokenRepositoryInterface $accessTokenRepository)
{
$this->accessTokenRepository = $accessTokenRepository;
}

/**
* Set the private key.
*
* @param CryptKey $key
*/
public function setPublicKey(CryptKey $key): void
{
$this->publicKey = $key;

$this->initJwtConfiguration();
}

/**
* Initialise the JWT configuration.
*/
private function initJwtConfiguration(): void
{
$this->jwtConfiguration = Configuration::forSymmetricSigner(new Sha256(), InMemory::empty());

$this->jwtConfiguration->setValidationConstraints(
\class_exists(StrictValidAt::class)
? new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get())))
: new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))),
new SignedWith(
new Sha256(),
InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '')
)
);
}

/**
* {@inheritdoc}
*/
public function validateIntrospection(ServerRequestInterface $request): bool
{
$this->validateIntrospectionRequest($request);

try {
$token = $this->getTokenFromRequest($request);
} catch (InvalidArgumentException $e) {
return false;
}

if (
!$this->isTokenValid($token) ||
$this->isTokenRevoked($token)
) {
return false;
}

return true;
}

/**
* Gets the token from the request body.
*
* @param ServerRequestInterface $request
*
* @return Token
*/
public function getTokenFromRequest(ServerRequestInterface $request): Token
{
$jwt = $request->getParsedBody()['token'] ?? '';

return $this->jwtConfiguration->parser()
->parse($jwt);
}

/**
* Validate the introspection request.
*
* @param ServerRequestInterface $request
*
* @throws OAuthServerException
*/
private function validateIntrospectionRequest(ServerRequestInterface $request): void
{
if ($request->getMethod() !== 'POST') {
throw OAuthServerException::accessDenied('Invalid request method');
}
}

/**
* Check if the given token is revoked.
*
* @param Token $token
*
* @return bool
*/
private function isTokenRevoked(Token $token): bool
{
return $this->accessTokenRepository->isAccessTokenRevoked($token->claims()->get('jti'));
}

/**
* Check if the given token is valid
*
* @param Token $token
*
* @return bool
*/
private function isTokenValid(Token $token): bool
{
$constraints = $this->jwtConfiguration->validationConstraints();

return $this->jwtConfiguration->validator()->validate($token, ...$constraints);
}
}
20 changes: 20 additions & 0 deletions src/IntrospectionValidators/IntrospectionValidatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace League\OAuth2\Server\IntrospectionValidators;

use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ServerRequestInterface;

interface IntrospectionValidatorInterface
{
/**
* Determine whether the introspection request is valid.
*
* @param ServerRequestInterface $request
*
* @throws OAuthServerException
*
* @return bool
*/
public function validateIntrospection(ServerRequestInterface $request): bool;
}
Loading