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 #925

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 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
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 @@ -24,6 +24,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
59 changes: 59 additions & 0 deletions examples/public/introspect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
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
AuthorizationServer::class => function () {

// Setup the authorization server
$server = new AuthorizationServer(
new ClientRepository(), // instance of ClientRepositoryInterface
new AccessTokenRepository(), // instance of AccessTokenRepositoryInterface
new ScopeRepository(), // instance of ScopeRepositoryInterface
'file://' . __DIR__ . '/../private.key', // path to private key
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen' // encryption key
);

return $server;
},
]);

$app->post(
'/introspect',
function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {

/* @var \League\OAuth2\Server\AuthorizationServer $server */
$server = $app->getContainer()->get(AuthorizationServer::class);

try {
// Validate the given introspect request
$server->validateIntrospectionRequest($request);

// Try to respond to the introspection request
return $server->respondToIntrospectionRequest($request, $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();
79 changes: 79 additions & 0 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace League\OAuth2\Server;

use Defuse\Crypto\Key;
use Lcobucci\JWT\Parser;
use League\Event\EmitterAwareInterface;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
Expand All @@ -20,6 +21,7 @@
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\AbstractResponseType;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
use League\OAuth2\Server\ResponseTypes\IntrospectionResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -53,6 +55,16 @@ class AuthorizationServer implements EmitterAwareInterface
*/
protected $responseType;

/**
* @var null|IntrospectionResponse
*/
protected $introspectionResponseType;

/**
* @var null|Introspector
*/
protected $introspector;

/**
* @var ClientRepositoryInterface
*/
Expand Down Expand Up @@ -197,6 +209,73 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res
throw OAuthServerException::unsupportedGrantType();
}

/**
* @param IntrospectionResponse $reponseType
*/
public function setIntrospectionReponseType(IntrospectionResponse $reponseType)
{
$this->introspectionResponseType = $reponseType;
}

/**
* Get the introspection response
*
* @return IntrospectionResponse
*/
protected function getIntrospectionResponseType()
{
if ($this->introspectionResponseType instanceof IntrospectionResponse === false) {
$this->introspectionResponseType = new IntrospectionResponse;
StevePorter92 marked this conversation as resolved.
Show resolved Hide resolved
}

return $this->introspectionResponseType;
}

/**
* Return an introspection response.
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
*
* @return ResponseInterface
*/
public function respondToIntrospectionRequest(ServerRequestInterface $request, ResponseInterface $response)
{
$introspector = $this->getIntrospector();

$introspectionResponse = $introspector->respondToIntrospectionRequest(
$request,
$this->getIntrospectionResponseType()
);

return $introspectionResponse->generateHttpResponse($response);
}

/**
* Return an introspection response.
StevePorter92 marked this conversation as resolved.
Show resolved Hide resolved
*
* @param ServerRequestInterface $request
*/
public function validateIntrospectionRequest(ServerRequestInterface $request)
{
$introspector = $this->getIntrospector();
$introspector->validateIntrospectionRequest($request);
}

/**
* Returns the introspector
StevePorter92 marked this conversation as resolved.
Show resolved Hide resolved
*
* @return Introspector
*/
private function getIntrospector()
{
if (!isset($this->introspector)) {
$this->introspector = new Introspector($this->accessTokenRepository, $this->privateKey, new Parser);
}

return $this->introspector;
}

/**
* Get the token type that grants will return in the HTTP response.
*
Expand Down
153 changes: 153 additions & 0 deletions src/Introspector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace League\OAuth2\Server;

use InvalidArgumentException;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Keychain;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\ValidationData;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\ResponseTypes\IntrospectionResponse;
use Psr\Http\Message\ServerRequestInterface;

class Introspector
{
/**
* @var AccessTokenRepositoryInterface
*/
private $accessTokenRepository;

/**
* @var CryptKey
*/
private $privateKey;

/**
* @var Parser
*/
private $parser;

/**
* New Introspector instance.
*
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param CryptKey $privateKey
* @param Parser $parser
*/
public function __construct(
AccessTokenRepositoryInterface $accessTokenRepository,
CryptKey $privateKey,
Parser $parser
) {
$this->accessTokenRepository = $accessTokenRepository;
$this->privateKey = $privateKey;
$this->parser = $parser;
}

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

/**
* Return an introspection response.
*
* @param ServerRequestInterface $request
* @param IntrospectionResponse $responseType
*
* @return IntrospectionResponse
*/
public function respondToIntrospectionRequest(
ServerRequestInterface $request,
IntrospectionResponse $responseType
) {
$jwt = $request->getParsedBody()['token'] ?? null;

try {
$token = $this->parser->parse($jwt);
} catch (InvalidArgumentException $e) {
return $responseType;
}

return $this->isTokenValid($token) ?
$this->setTokenOnResponse($token, $responseType) :
$responseType;
}

/**
* Validate the JWT and make sure it has not expired or been revoked
*
* @return bool
*/
private function isTokenValid(Token $token)
{
return $this->verifyToken($token) && !$this->isTokenExpired($token) && !$this->isTokenRevoked($token);
}

/**
* Validate the JWT token.
*
* @param Token $token
*
* @return bool
*/
private function verifyToken(Token $token)
{
$keychain = new Keychain();
$key = $keychain->getPrivateKey($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase());

return $token->verify(new Sha256, $key->getContent());
}

/**
* Ensure access token hasn't expired
*
* @param Token $token
*
* @return bool
*/
private function isTokenExpired(Token $token)
{
$data = new ValidationData(time());

return !$token->validate($data);
}

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

/**
* Create active introspection response.
*
* @param Token $token
*
* @return IntrospectionResponse
*/
private function setTokenOnResponse(Token $token, IntrospectionResponse $responseType)
{
$responseType->setToken($token);

return $responseType;
}
}
Loading