-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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 #869
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
<?php | ||
|
||
namespace League\OAuth2\Server; | ||
|
||
use Exception; | ||
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; | ||
} | ||
|
||
/** | ||
* Return an introspection response. | ||
* | ||
* @param ServerRequestInterface $request | ||
* | ||
* @return IntrospectionResponse | ||
*/ | ||
public function respondToIntrospectionRequest(ServerRequestInterface $request) | ||
{ | ||
$jwt = $request->getParsedBody()['token'] ?? null; | ||
|
||
try { | ||
$token = $this->parser->parse($jwt); | ||
|
||
$this->verifyToken($token); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should use exceptions to control the flow of the program here. It is expected that we sometimes will fail these validation checks so I think this would be better handled with boolean checks |
||
$this->checkIfTokenIsExpired($token); | ||
$this->checkIfTokenIsRevoked($token); | ||
|
||
return $this->createActiveResponse($token); | ||
} catch (Exception $ex) { | ||
return $this->createInactiveResponse(); | ||
} | ||
} | ||
|
||
/** | ||
* Validate the JWT token. | ||
* | ||
* @param Token $token | ||
* | ||
* @throws OAuthServerException | ||
*/ | ||
private function verifyToken(Token $token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use existing verifiers for this within the package? |
||
{ | ||
$keychain = new Keychain(); | ||
$key = $keychain->getPrivateKey($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase()); | ||
|
||
if (!$token->verify(new Sha256, $key->getContent())) { | ||
throw OAuthServerException::accessDenied('Access token could not be verified'); | ||
} | ||
} | ||
|
||
/** | ||
* Ensure access token hasn't expired | ||
* | ||
* @param Token $token | ||
* | ||
* @throws OAuthServerException | ||
*/ | ||
private function checkIfTokenIsExpired(Token $token) | ||
{ | ||
$data = new ValidationData(time()); | ||
|
||
if (!$token->validate($data)) { | ||
throw OAuthServerException::accessDenied('Access token is invalid'); | ||
} | ||
} | ||
|
||
/** | ||
* Check if the given access token is revoked. | ||
* | ||
* @param Token $token | ||
* | ||
* @throws OAuthServerException | ||
*/ | ||
private function checkIfTokenIsRevoked(Token $token) | ||
{ | ||
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) { | ||
throw OAuthServerException::accessDenied('Access token has been revoked'); | ||
} | ||
} | ||
|
||
/** | ||
* Create active introspection response. | ||
* | ||
* @param Token $token | ||
* | ||
* @return IntrospectionResponse | ||
*/ | ||
private function createActiveResponse(Token $token) | ||
{ | ||
$response = new IntrospectionResponse(); | ||
|
||
$response->setIntrospectionData( | ||
[ | ||
'active' => true, | ||
'token_type' => 'access_token', | ||
'scope' => $token->getClaim('scopes', ''), | ||
'client_id' => $token->getClaim('aud'), | ||
'exp' => $token->getClaim('exp'), | ||
'iat' => $token->getClaim('iat'), | ||
'sub' => $token->getClaim('sub'), | ||
'jti' => $token->getClaim('jti'), | ||
] | ||
); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Create inactive introspection response | ||
* | ||
* @return IntrospectionResponse | ||
*/ | ||
private function createInactiveResponse() | ||
{ | ||
$response = new IntrospectionResponse(); | ||
|
||
$response->setIntrospectionData( | ||
[ | ||
'active' => false, | ||
] | ||
); | ||
|
||
return $response; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace League\OAuth2\Server\ResponseTypes; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class IntrospectionResponse extends AbstractResponseType | ||
{ | ||
/** | ||
* @var array | ||
*/ | ||
private $introspectionData; | ||
|
||
/** | ||
* @param array $introspectionData | ||
*/ | ||
public function setIntrospectionData(array $introspectionData) | ||
{ | ||
$this->introspectionData = $introspectionData; | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function getIntrospectionData() | ||
{ | ||
return $this->introspectionData; | ||
} | ||
|
||
/** | ||
* @param ResponseInterface $response | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function generateHttpResponse(ResponseInterface $response) | ||
{ | ||
$response->getBody()->write(json_encode($this->introspectionData)); | ||
|
||
return $response; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check that the request is a POST request as per the RFC, section 2.1