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 all 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();
116 changes: 116 additions & 0 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\GrantTypeInterface;
use League\OAuth2\Server\IntrospectionValidators\BearerTokenValidator;
use League\OAuth2\Server\IntrospectionValidators\IntrospectionValidatorInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\AbstractResponseType;
use League\OAuth2\Server\ResponseTypes\BearerTokenIntrospectionResponse;
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 +57,21 @@ class AuthorizationServer implements EmitterAwareInterface
*/
protected $responseType;

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

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

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

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

/**
* Set the introspection response type.
*
* @param IntrospectionResponse $reponseType
*/
public function setIntrospectionReponseType(IntrospectionResponse $reponseType)
{
$this->introspectionResponseType = $reponseType;
}

/**
* Set the validator used for introspection requests.
*
* @param IntrospectionValidatorInterface $introspectionValidator
*/
public function setIntrospectionValidator(IntrospectionValidatorInterface $introspectionValidator)
{
$this->introspectionValidator = $introspectionValidator;
}

/**
* Get the introspection response.
*
* @return IntrospectionResponse
*/
protected function getIntrospectionResponseType()
{
if ($this->introspectionResponseType instanceof IntrospectionResponse === false) {
$this->introspectionResponseType = new BearerTokenIntrospectionResponse();
}

return $this->introspectionResponseType;
}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BearerTokenValidator has no validation-key set, unless you also call here

$this->introspectionValidator->setPrivateKey($this->privateKey);

But that also requires that the public-key is also added to the private-key file, as Lcobucci\JWT\Signer\Rsa::doVerify($expected, $payload, Key $key) calls open_ssl_publickey($key->getContent())!

With that change I was able to verify/introspect access_keys generated by my server.

I'm currently implementing an OpenID Connect / OAuth2 server for EGroupware


return $this->introspectionValidator;
}

/**
* 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);
}

/**
* Validate an introspection request.
*
* @param ServerRequestInterface $request
*/
public function validateIntrospectionRequest(ServerRequestInterface $request)
{
$introspector = $this->getIntrospector();
$introspector->validateIntrospectionRequest($request);
}

/**
* Returns the introspector.
*
* @return Introspector
*/
private function getIntrospector()
{
if (!isset($this->introspector)) {
$this->introspector = new Introspector(
$this->accessTokenRepository,
$this->privateKey,
$this->getIntrospectionValidator()
);
}

return $this->introspector;
}

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

namespace League\OAuth2\Server\IntrospectionValidators;

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\CryptKey;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface;

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

/**
* @var \League\OAuth2\Server\CryptKey
*/
protected $privateKey;

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

/**
* Set the private key.
*
* @param \League\OAuth2\Server\CryptKey $key
*/
public function setPrivateKey(CryptKey $key)
{
$this->privateKey = $key;
}

/**
* {@inheritdoc}
*/
public function validateIntrospection(ServerRequestInterface $request)
{
try {
$token = $this->getTokenFromRequest($request);
} catch (InvalidArgumentException $e) {
return false;
}

if (
$this->isTokenRevoked($token) ||
$this->isTokenExpired($token) ||
$this->isTokenUnverified($token)
) {
return false;
}

return true;
}

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

return (new Parser())
->parse($jwt);
}

/**
* Checks whether the token is unverified.
*
* @param Token $token
*
* @return bool
*/
private function isTokenUnverified(Token $token)
{
$keychain = new Keychain();

$key = $keychain->getPrivateKey(
$this->privateKey->getKeyPath(),
$this->privateKey->getPassPhrase()
);

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

/**
* 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 token is revoked.
*
* @param Token $token
*
* @return bool
*/
private function isTokenRevoked(Token $token)
{
return $this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'));
}
}
17 changes: 17 additions & 0 deletions src/IntrospectionValidators/IntrospectionValidatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace League\OAuth2\Server\IntrospectionValidators;

use Psr\Http\Message\ServerRequestInterface;

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