Merge branch 'V5-WIP' into secure_body_params_access

This commit is contained in:
Julián Gutiérrez 2016-02-12 17:10:52 +01:00
commit 95e3c1d1a2
23 changed files with 812 additions and 328 deletions

View File

@ -7,7 +7,6 @@
"php": ">=5.5.9",
"league/event": "~2.1",
"zendframework/zend-diactoros": "~1.1",
"namshi/jose": "^6.0",
"lcobucci/jwt": "^3.1",
"paragonie/random_compat": "^1.1"
},
@ -65,5 +64,8 @@
"branch-alias": {
"dev-V5-WIP": "5.0-dev"
}
},
"suggest": {
"league/plates": "Required for parsing authorization code templates"
}
}

View File

@ -7,7 +7,8 @@
],
"require": {
"slim/slim": "3.0.*",
"league/oauth2-server": "dev-V5-WIP"
"league/oauth2-server": "dev-V5-WIP",
"league/plates": "^3.1"
},
"autoload": {
"psr-4": {

63
examples/composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ff6f832d21c141662627622e68079ca5",
"content-hash": "f08d5c7c3ede910150d75ad3c56d1b46",
"hash": "143453cc35e7f499b130b6460222dc5a",
"content-hash": "1ea46581fb6db25f323a37a45ef74f95",
"packages": [
{
"name": "container-interop/container-interop",
@ -145,9 +145,14 @@
{
"name": "league/oauth2-server",
"version": "dev-V5-WIP",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-server.git",
"reference": "95919a688e29c911d1e4e83112cacd18f719700f"
},
"dist": {
"type": "path",
"url": "../",
"url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/95919a688e29c911d1e4e83112cacd18f719700f",
"reference": "168e7640c6e8217b7e961151de522810b3edce6e",
"shasum": null
},
@ -214,6 +219,58 @@
"server"
]
},
{
"name": "league/plates",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/plates.git",
"reference": "2d8569e9f140a70d6a05db38006926f7547cb802"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/plates/zipball/2d8569e9f140a70d6a05db38006926f7547cb802",
"reference": "2d8569e9f140a70d6a05db38006926f7547cb802",
"shasum": ""
},
"require-dev": {
"mikey179/vfsstream": "~1.4.0",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~1.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Plates\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Reinink",
"email": "jonathan@reinink.ca",
"role": "Developer"
}
],
"description": "Plates, the native PHP template system that's fast, easy to use and easy to extend.",
"homepage": "http://platesphp.com",
"keywords": [
"league",
"package",
"templates",
"templating",
"views"
],
"time": "2015-07-09 02:14:40"
},
{
"name": "namshi/jose",
"version": "6.1.0",

View File

@ -0,0 +1,83 @@
<?php
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use League\OAuth2\Server\Server;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\AuthCodeRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use OAuth2ServerExamples\Repositories\UserRepository;
use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;
include(__DIR__ . '/../vendor/autoload.php');
// App
$app = new App([
Server::class => function () {
// Init our repositories
$clientRepository = new ClientRepository();
$scopeRepository = new ScopeRepository();
$accessTokenRepository = new AccessTokenRepository();
$userRepository = new UserRepository();
$refreshTokenRepository = new RefreshTokenRepository();
$authCodeRepository = new AuthCodeRepository();
$privateKeyPath = 'file://' . __DIR__ . '/../private.key';
$publicKeyPath = 'file://' . __DIR__ . '/../public.key';
// Setup the authorization server
$server = new Server(
$clientRepository,
$accessTokenRepository,
$scopeRepository,
$privateKeyPath,
$publicKeyPath
);
// Enable the password grant on the server with a token TTL of 1 hour
$server->enableGrantType(
new AuthCodeGrant(
$authCodeRepository,
$refreshTokenRepository,
$userRepository,
new \DateInterval('PT10M')
),
new \DateInterval('PT1H')
);
return $server;
},
]);
$app->any('/authorize', function (Request $request, Response $response) {
/** @var Server $server */
$server = $this->get(Server::class);
try {
return $server->respondToRequest($request, $response);
} catch (OAuthServerException $e) {
return $e->generateHttpResponse($response);
} catch (\Exception $e) {
return $response->withStatus(500)->write($e->getMessage());
}
});
$app->post('/access_token', function (Request $request, Response $response) {
/** @var Server $server */
$server = $this->get(Server::class);
try {
return $server->respondToRequest($request, $response);
} catch (OAuthServerException $e) {
return $e->generateHttpResponse($response);
} catch (\Exception $e) {
return $response->withStatus(500)->write($e->getMessage());
}
});
$app->run();

View File

@ -1,5 +1,7 @@
<?php
use League\OAuth2\Server\Grant\PasswordGrant;
use League\OAuth2\Server\Grant\RefreshTokenGrant;
use League\OAuth2\Server\Middleware\AuthenticationServerMiddleware;
use League\OAuth2\Server\Server;
@ -10,8 +12,6 @@ use OAuth2ServerExamples\Repositories\ScopeRepository;
use OAuth2ServerExamples\Repositories\UserRepository;
use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;
include(__DIR__ . '/../vendor/autoload.php');

View File

@ -0,0 +1,42 @@
<?php
namespace OAuth2ServerExamples\Repositories;
use League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
class AuthCodeRepository implements AuthCodeRepositoryInterface
{
/**
* Persists a new auth code to permanent storage
*
* @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $authCodeEntity
*/
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity)
{
// TODO: Implement persistNewAuthCode() method.
}
/**
* Revoke an auth code
*
* @param string $codeId
*/
public function revokeAuthCode($codeId)
{
// TODO: Implement revokeAuthCode() method.
}
/**
* Check if the auth code has been revoked
*
* @param string $codeId
*
* @return bool Return true if this code has been revoked
*/
public function isAuthCodeRevoked($codeId)
{
// TODO: Implement isAuthCodeRevoked() method.
}
}

View File

@ -15,7 +15,7 @@ class ClientRepository implements ClientRepositoryInterface
'myawesomeapp' => [
'secret' => password_hash('abc123', PASSWORD_BCRYPT),
'name' => 'My Awesome App',
'redirect_uri' => ''
'redirect_uri' => 'http://foo/bar'
]
];
@ -30,7 +30,7 @@ class ClientRepository implements ClientRepositoryInterface
}
// Check if redirect URI is valid
if ($redirectUri !== null && $redirectUri !== $clients[$clientIdentifier]['redirectUri']) {
if ($redirectUri !== null && $redirectUri !== $clients[$clientIdentifier]['redirect_uri']) {
return null;
}

View File

@ -0,0 +1,79 @@
<?php
namespace League\OAuth2\Server\Entities;
class AuthorizationCodeRequestEntity
{
/**
* @var string
*/
private $clientId;
/**
* @var null|string
*/
private $redirectUri;
/**
* @var null|string
*/
private $scope;
/**
* @var null|string
*/
private $state;
/**
* @return string
*/
public function getClientId()
{
return $this->clientId;
}
/**
* @return null|string
*/
public function getRedirectUri()
{
return $this->redirectUri;
}
/**
* @return null|string
*/
public function getScope()
{
return $this->scope;
}
/**
* @return null|string
*/
public function getState()
{
return $this->state;
}
/**
* AuthorizationCodeRequestEntity constructor.
*
* @param string $clientId
* @param string|null $redirectUri
* @param string|null $scope
* @param string|null $state
*/
public function __construct($clientId, $redirectUri = null, $scope = null, $state = null)
{
$this->clientId = $clientId;
$this->redirectUri = $redirectUri;
$this->scope = $scope;
$this->state = $state;
}
public function __sleep()
{
return ['clientId', 'redirectUri', 'scope', 'state'];
}
}

View File

@ -197,13 +197,20 @@ class OAuthServerException extends \Exception
/**
* Access denied
*
* @param null|string $hint
* @param string|null $hint
* @param string|null $redirectUri
*
* @return static
*/
public static function accessDenied($hint = null)
public static function accessDenied($hint = null, $redirectUri = null)
{
return new static('The server denied the request.', 'access_denied', 401, $hint);
return new static(
'The resource owner or authorization server denied the request.',
'access_denied',
401,
$hint,
$redirectUri
);
}
/**

View File

@ -15,6 +15,7 @@ use League\Event\EmitterAwareTrait;
use League\Event\EmitterInterface;
use League\Event\Event;
use League\OAuth2\Server\Entities\AccessTokenEntity;
use League\OAuth2\Server\Entities\AuthCodeEntity;
use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntity;
use League\OAuth2\Server\Entities\ScopeEntity;
@ -34,13 +35,6 @@ abstract class AbstractGrant implements GrantTypeInterface
const SCOPE_DELIMITER_STRING = ' ';
/**
* Grant identifier
*
* @var string
*/
protected $identifier = '';
/**
* Grant responds with
*
@ -139,14 +133,6 @@ abstract class AbstractGrant implements GrantTypeInterface
$this->refreshTokenTTL = $refreshTokenTTL;
}
/**
* {@inheritdoc}
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* {@inheritdoc}
*/
@ -156,14 +142,20 @@ abstract class AbstractGrant implements GrantTypeInterface
}
/**
* Validate the client
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param bool $validateSecret
* @param bool $validateRedirectUri
*
* @return \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface
*
* @throws \League\OAuth2\Server\Exception\OAuthServerException
*/
protected function validateClient(ServerRequestInterface $request)
{
protected function validateClient(
ServerRequestInterface $request,
$validateSecret = true,
$validateRedirectUri = false
) {
$clientId = $this->getRequestParameter(
'client_id',
$request,
@ -178,15 +170,20 @@ abstract class AbstractGrant implements GrantTypeInterface
$request,
$this->getServerParameter('PHP_AUTH_PW', $request)
);
if (is_null($clientSecret)) {
if (is_null($clientSecret) && $validateSecret === true) {
throw OAuthServerException::invalidRequest('client_secret', null, '`%s` parameter is missing');
}
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if (is_null($redirectUri) && $validateRedirectUri === true) {
throw OAuthServerException::invalidRequest('redirect_uri', null, '`%s` parameter is missing');
}
$client = $this->clientRepository->getClientEntity(
$this->getIdentifier(),
$clientId,
$clientSecret,
null
$redirectUri,
$this->getIdentifier()
);
if (!$client instanceof ClientEntityInterface) {
@ -199,6 +196,8 @@ abstract class AbstractGrant implements GrantTypeInterface
}
/**
* Validate scopes in the request
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client
* @param string $redirectUri
@ -249,9 +248,36 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null)
{
return (is_array($request->getParsedBody()) && isset($request->getParsedBody()[$parameter]))
? $request->getParsedBody()[$parameter]
: $default;
$requestParameters = (array) $request->getParsedBody();
return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
}
/**
* Retrieve query string parameter.
*
* @param string $parameter
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param mixed $default
*
* @return null|string
*/
protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null)
{
return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default;
}
/**
* Retrieve cookie parameter.
*
* @param string $parameter
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param mixed $default
*
* @return null|string
*/
protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null)
{
return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default;
}
/**
@ -265,10 +291,12 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null)
{
return (isset($request->getServerParams()[$parameter])) ? $request->getServerParams()[$parameter] : $default;
return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default;
}
/**
* Issue an access token
*
* @param \DateInterval $tokenTTL
* @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client
* @param string $userIdentifier
@ -295,6 +323,39 @@ abstract class AbstractGrant implements GrantTypeInterface
return $accessToken;
}
/**
* Issue an auth code
*
* @param \DateInterval $tokenTTL
* @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client
* @param string $userIdentifier
* @param string $redirectUri
* @param array $scopes
*
* @return \League\OAuth2\Server\Entities\AuthCodeEntity
* @throws \League\OAuth2\Server\Exception\OAuthServerException
*/
protected function issueAuthCode(
\DateInterval $tokenTTL,
ClientEntityInterface $client,
$userIdentifier,
$redirectUri,
array $scopes = []
) {
$authCode = new AuthCodeEntity();
$authCode->setIdentifier(SecureKey::generate());
$authCode->setExpiryDateTime((new \DateTime())->add($tokenTTL));
$authCode->setClient($client);
$authCode->setUserIdentifier($userIdentifier);
$authCode->setRedirectUri($redirectUri);
foreach ($scopes as $scope) {
$authCode->addScope($scope);
}
return $authCode;
}
/**
* @param \League\OAuth2\Server\Entities\AccessTokenEntity $accessToken
*
@ -315,10 +376,11 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function canRespondToRequest(ServerRequestInterface $request)
{
$requestParameters = (array) $request->getParsedBody();
return (
is_array($request->getParsedBody())
&& isset($request->getParsedBody()['grant_type'])
&& $request->getParsedBody()['grant_type'] === $this->identifier
array_key_exists('grant_type', $requestParameters)
&& $requestParameters['grant_type'] === $this->getIdentifier()
);
}
}

View File

@ -1,303 +1,357 @@
<?php
/**
* OAuth 2.0 Auth code grant
*
* @package league/oauth2-server
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
* @license http://mit-license.org/
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\Grant;
use League\Event\Emitter;
use League\Event\Event;
use League\OAuth2\Server\Entities\AccessTokenEntity;
use League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface;
use League\OAuth2\Server\Exception\InvalidClientException;
use League\OAuth2\Server\Exception\InvalidRequestException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\TokenTypes\TokenTypeInterface;
use League\OAuth2\Server\Utils\SecureKey;
use Symfony\Component\HttpFoundation\Request;
use DateInterval;
use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface;
use League\OAuth2\Server\Entities\Interfaces\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use League\OAuth2\Server\Utils\KeyCrypt;
use League\Plates\Engine;
use League\Event\Event;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Uri;
/**
* Auth code grant class
*/
class AuthCodeGrant extends AbstractGrant
{
/**
* @var \DateInterval
*/
private $authCodeTTL;
/**
* @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface
*/
protected $authCodeRepository;
private $authCodeRepository;
/**
* @var \League\OAuth2\Server\Repositories\UserRepositoryInterface
*/
private $userRepository;
/**
* @var null|string
*/
private $pathToLoginTemplate;
/**
* @var null|string
*/
private $pathToAuthorizeTemplate;
/**
* @var \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface
*/
private $refreshTokenRepository;
/**
* @param \League\Event\Emitter $emitter
* @param \League\OAuth2\Server\Repositories\ClientRepositoryInterface $clientRepository
* @param \League\OAuth2\Server\Repositories\ScopeRepositoryInterface $scopeRepository
* @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository
* @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository
* @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
* @param \League\OAuth2\Server\Repositories\UserRepositoryInterface $userRepository
* @param \DateInterval $authCodeTTL
* @param string|null $pathToLoginTemplate
* @param string|null $pathToAuthorizeTemplate
*/
public function __construct(
Emitter $emitter,
ClientRepositoryInterface $clientRepository,
ScopeRepositoryInterface $scopeRepository,
AccessTokenRepositoryInterface $accessTokenRepository,
AuthCodeRepositoryInterface $authCodeRepository,
RefreshTokenRepositoryInterface $refreshTokenRepository = null
RefreshTokenRepositoryInterface $refreshTokenRepository,
UserRepositoryInterface $userRepository,
\DateInterval $authCodeTTL,
$pathToLoginTemplate = null,
$pathToAuthorizeTemplate = null
) {
$this->authCodeRepository = $authCodeRepository;
$this->refreshTokenRepository = $refreshTokenRepository;
parent::__construct($emitter, $clientRepository, $scopeRepository, $accessTokenRepository);
$this->userRepository = $userRepository;
$this->authCodeTTL = $authCodeTTL;
$this->pathToLoginTemplate = ($pathToLoginTemplate === null)
? __DIR__ . '/../ResponseTypes/DefaultTemplates/login_user.php'
: $this->pathToLoginTemplate;
$this->pathToAuthorizeTemplate = ($pathToLoginTemplate === null)
? __DIR__ . '/../ResponseTypes/DefaultTemplates/authorize_client.php'
: $this->pathToAuthorizeTemplate;
$this->refreshTokenTTL = new \DateInterval('P1M');
}
/**
* Grant identifier
*
* @var string
*/
protected $identifier = 'authorization_code';
/**
* Response type
* Respond to an authorization request
*
* @var string
* @param \Psr\Http\Message\ServerRequestInterface $request
*
* @return \Psr\Http\Message\ResponseInterface
* @throws \League\OAuth2\Server\Exception\OAuthServerException
*/
protected $responseType = 'code';
/**
* AuthServer instance
*
* @var \League\OAuth2\Server\AuthorizationServer
*/
protected $server = null;
/**
* Access token expires in override
*
* @var int
*/
protected $accessTokenTTL = null;
/**
* The TTL of the auth token
*
* @var integer
*/
protected $authTokenTTL = 600;
/**
* Override the default access token expire time
*
* @param int $authTokenTTL
*
* @return void
*/
public function setAuthTokenTTL($authTokenTTL)
{
$this->authTokenTTL = $authTokenTTL;
}
/**
* Check authorize parameters
*
* @return array Authorize request parameters
*
* @throws
*/
/*public function checkAuthorizeParams()
{
// Get required params
$clientId = $request->query->get('client_id', null);
protected function respondToAuthorizationRequest(
ServerRequestInterface $request
) {
$clientId = $this->getQueryStringParameter(
'client_id',
$request,
$this->getServerParameter('PHP_AUTH_USER', $request)
);
if (is_null($clientId)) {
throw new InvalidRequestException('client_id');
throw OAuthServerException::invalidRequest('client_id', null, '`%s` parameter is missing');
}
$redirectUri = $request->query->get('redirect_uri', null);
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request, null);
if (is_null($redirectUri)) {
throw new InvalidRequestException('redirect_uri');
throw OAuthServerException::invalidRequest('redirect_uri', null, '`%s` parameter is missing');
}
// Validate client ID and redirect URI
$client = $this->server->getClientStorage()->get(
$client = $this->clientRepository->getClientEntity(
$clientId,
null,
$redirectUri,
$this->getIdentifier()
);
if (($client instanceof ClientEntity) === false) {
$this->server->getEventEmitter()->emit(new Event\ClientAuthenticationFailedEvent($request));
throw new Exception\InvalidClientException();
if ($client instanceof ClientEntityInterface === false) {
$this->emitter->emit(new Event('client.authentication.failed', $request));
throw OAuthServerException::invalidClient();
}
$state = $request->query->get('state', null);
if ($this->server->stateParamRequired() === true && is_null($state)) {
throw new InvalidRequestException('state', $redirectUri);
}
$responseType = $request->query->get('response_type', null);
if (is_null($responseType)) {
throw new InvalidRequestException('response_type', $redirectUri);
}
// Ensure response type is one that is recognised
if (!in_array($responseType, $this->server->getResponseTypes())) {
throw new Exception\UnsupportedResponseTypeException($responseType, $redirectUri);
}
// Validate any scopes that are in the request
$scopeParam = $request->query->get('scope', '');
$scopes = $this->validateScopes($scopeParam, $client, $redirectUri);
return [
'client' => $client,
'redirect_uri' => $redirectUri,
'state' => $state,
'response_type' => $responseType,
'scopes' => $scopes
];
}*/
/**
* Parse a new authorize request
*
* @param string $type The session owner's type
* @param string $typeId The session owner's ID
* @param array $authParams The authorize request $_GET parameters
*
* @return string An authorisation code
*/
/*public function newAuthorizeRequest($type, $typeId, $authParams = [])
{
// Create a new session
$session = new SessionEntity($this->server);
$session->setOwner($type, $typeId);
$session->associateClient($authParams['client']);
$session->save();
// Create a new auth code
$authCode = new AuthCodeEntity($this->server);
$authCode->setId(SecureKey::generate());
$authCode->setRedirectUri($authParams['redirect_uri']);
$authCode->setExpireTime(time() + $this->authTokenTTL);
foreach ($authParams['scopes'] as $scope) {
$authCode->associateScope($scope);
}
$authCode->setSession($session);
$authCode->save();
return $authCode->generateRedirectUri($authParams['state']);
}*/
/**
* Return an access token
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \League\OAuth2\Server\TokenTypes\TokenTypeInterface $tokenType
* @param \DateInterval $accessTokenTTL
* @param string $scopeDelimiter
*
* @return \League\OAuth2\Server\TokenTypes\TokenTypeInterface
* @throws \League\OAuth2\Server\Exception\InvalidClientException
* @throws \League\OAuth2\Server\Exception\InvalidGrantException
* @throws \League\OAuth2\Server\Exception\InvalidRequestException
*/
public function getAccessTokenAsType(
Request $request,
TokenTypeInterface $tokenType,
DateInterval $accessTokenTTL,
$scopeDelimiter = ' '
) {
// Get the required params
$clientId = $request->request->get('client_id', $request->getUser());
if (is_null($clientId)) {
throw new InvalidRequestException('client_id', '');
}
$clientSecret = $request->request->get('client_secret',
$request->getPassword());
if (is_null($clientSecret)) {
throw new InvalidRequestException('client_secret');
}
$redirectUri = $request->request->get('redirect_uri', null);
if (is_null($redirectUri)) {
throw new InvalidRequestException('redirect_uri');
}
// Validate client ID and client secret
$client = $this->clientRepository->get(
$clientId,
$clientSecret,
$redirectUri,
$this->getIdentifier()
$scopes = $this->validateScopes($request, $client, $redirectUri);
$queryString = http_build_query($request->getQueryParams());
$postbackUri = new Uri(
sprintf(
'//%s%s',
$request->getServerParams()['HTTP_HOST'],
$request->getServerParams()['REQUEST_URI']
)
);
if (($client instanceof ClientEntityInterface) === false) {
$this->emitter->emit(new Event('client.authentication.failed', $request));
throw new InvalidClientException();
$userId = null;
$userHasApprovedClient = null;
if ($this->getRequestParameter('action', $request, null) !== null) {
$userHasApprovedClient = ($this->getRequestParameter('action', $request) === 'approve');
}
// Validate the auth code
$authCode = $request->request->get('code', null);
if (is_null($authCode)) {
throw new InvalidRequestException('code');
// Check if the user has been authenticated
$oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null);
if ($oauthCookie !== null) {
try {
$oauthCookiePayload = json_decode(KeyCrypt::decrypt($oauthCookie, $this->pathToPublicKey));
if (is_object($oauthCookiePayload)) {
$userId = $oauthCookiePayload->user_id;
}
} catch (\LogicException $e) {
throw OAuthServerException::serverError($e->getMessage());
}
}
$code = $this->authCodeRepository->get($authCode);
if (($code instanceof AuthCodeEntityInterface) === false) {
throw new InvalidRequestException('code');
// The username + password might be available in $_POST
$usernameParameter = $this->getRequestParameter('username', $request, null);
$passwordParameter = $this->getRequestParameter('password', $request, null);
$loginError = null;
// Assert if the user has logged in already
if ($userId === null && $usernameParameter !== null && $passwordParameter !== null) {
$userEntity = $this->userRepository->getUserEntityByUserCredentials(
$usernameParameter,
$passwordParameter
);
if ($userEntity instanceof UserEntityInterface) {
$userId = $userEntity->getIdentifier();
} else {
$loginError = 'Incorrect username or password';
}
}
// Ensure the auth code hasn't expired
if ($code->isExpired() === true) {
throw new InvalidRequestException('code');
// The user hasn't logged in yet so show a login form
if ($userId === null) {
$engine = new Engine(dirname($this->pathToLoginTemplate));
$html = $engine->render(
'login_user',
[
'error' => $loginError,
'postback_uri' => (string) $postbackUri->withQuery($queryString),
]
);
return new Response\HtmlResponse($html);
}
// Check redirect URI presented matches redirect URI originally used in authorize request
if ($code->getRedirectUri() !== $redirectUri) {
throw new InvalidRequestException('redirect_uri');
// The user hasn't approved the client yet so show an authorize form
if ($userId !== null && $userHasApprovedClient === null) {
$engine = new Engine(dirname($this->pathToAuthorizeTemplate));
$html = $engine->render(
'authorize_client',
[
'client' => $client,
'scopes' => $scopes,
'postback_uri' => (string) $postbackUri->withQuery($queryString),
]
);
return new Response\HtmlResponse(
$html,
200,
[
'Set-Cookie' => sprintf(
'oauth_authorize_request=%s; Expires=%s',
urlencode(KeyCrypt::encrypt(
json_encode([
'user_id' => $userId,
]),
$this->pathToPrivateKey
)),
(new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e')
),
]
);
}
// Generate the access token
$accessToken = new AccessTokenEntity($this->server);
$accessToken->setIdentifier(SecureKey::generate());
$expirationDateTime = (new \DateTime())->add($accessTokenTTL);
$accessToken->setExpiryDateTime($expirationDateTime);
$accessToken->setClient($client);
$stateParameter = $this->getQueryStringParameter('state', $request);
foreach ($code->getScopes() as $scope) {
$accessToken->addScope($scope);
$redirectUri = new Uri($redirectUri);
parse_str($redirectUri->getQuery(), $redirectPayload);
if ($stateParameter !== null) {
$redirectPayload['state'] = $stateParameter;
}
$tokenType->setAccessToken($accessToken);
if ($userHasApprovedClient === true) {
$authCode = $this->issueAuthCode(
$this->authCodeTTL,
$client,
$userId,
$redirectUri,
$scopes
);
$this->authCodeRepository->persistNewAuthCode($authCode);
// Associate a refresh token if set
if ($this->refreshTokenRepository instanceof RefreshTokenRepositoryInterface) {
// $refreshToken = new RefreshTokenEntity($this->server);
// $refreshToken->setId(SecureKey::generate());
// $refreshToken->setExpireTime($this->server->getGrantType('refresh_token')->getRefreshTokenTTL() + time());
// $tokenType->setParam('refresh_token', $refreshToken->getId());
// $refreshToken->setAccessToken($accessToken);
$redirectPayload['code'] = KeyCrypt::encrypt(
json_encode(
[
'client_id' => $authCode->getClient()->getIdentifier(),
'auth_code_id' => $authCode->getIdentifier(),
'scopes' => $authCode->getScopes(),
'user_id' => $authCode->getUserIdentifier(),
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
]
),
$this->pathToPrivateKey
);
return new Response\RedirectResponse($redirectUri->withQuery(http_build_query($redirectPayload)));
}
// Expire the auth code
$this->authCodeRepository->delete($code);
$exception = OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri);
return $exception->generateHttpResponse();
}
// Save the access token
$this->accessTokenRepository->create($accessToken);
/**
* Respond to an access token request
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL
*
* @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface
* @throws \League\OAuth2\Server\Exception\OAuthServerException
*/
protected function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
$encryptedAuthCode = $this->getRequestParameter('code', $request, null);
return $tokenType;
if ($encryptedAuthCode === null) {
throw OAuthServerException::invalidRequest('code');
}
// Validate the authorization code
try {
$authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthCode, $this->pathToPublicKey));
if (time() > $authCodePayload->expire_time) {
throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
}
if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
}
if ($authCodePayload->client_id !== $client->getIdentifier()) {
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
}
} catch (\LogicException $e) {
throw OAuthServerException::invalidRequest('code', null, 'Cannot decrypt the authorization code');
}
// Issue and persist access + refresh tokens
$accessToken = $this->issueAccessToken(
$accessTokenTTL,
$client,
$authCodePayload->user_id,
$authCodePayload->scopes
);
$refreshToken = $this->issueRefreshToken($accessToken);
$this->accessTokenRepository->persistNewAccessToken($accessToken);
$this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
// Inject tokens into response type
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
return $responseType;
}
/**
* @inheritdoc
*/
public function canRespondToRequest(ServerRequestInterface $request)
{
return (
(
isset($request->getQueryParams()['response_type'])
&& $request->getQueryParams()['response_type'] === 'code'
&& isset($request->getQueryParams()['client_id'])
) || (parent::canRespondToRequest($request))
);
}
/**
* Return the grant identifier that can be used in matching up requests
*
* @return string
*/
public function getIdentifier()
{
return 'authorization_code';
}
/**
* @inheritdoc
*/
public function respondToRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
) {
if (
isset($request->getQueryParams()['response_type'])
&& $request->getQueryParams()['response_type'] === 'code'
&& isset($request->getQueryParams()['client_id'])
) {
return $this->respondToAuthorizationRequest($request);
} elseif (
isset($request->getParsedBody()['grant_type'])
&& $request->getParsedBody()['grant_type'] === 'authorization_code'
) {
return $this->respondToAccessTokenRequest($request, $responseType, $accessTokenTTL);
} else {
throw OAuthServerException::serverError('respondToRequest() should not have been called');
}
}
}

View File

@ -19,13 +19,6 @@ use Psr\Http\Message\ServerRequestInterface;
*/
class ClientCredentialsGrant extends AbstractGrant
{
/**
* Grant identifier
*
* @var string
*/
protected $identifier = 'client_credentials';
/**
* @inheritdoc
*/
@ -47,4 +40,12 @@ class ClientCredentialsGrant extends AbstractGrant
return $responseType;
}
/**
* @inheritdoc
*/
public function getIdentifier()
{
return 'client_credentials';
}
}

View File

@ -31,7 +31,7 @@ interface GrantTypeInterface extends EmitterAwareInterface
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL);
/**
* Return the identifier
* Return the grant identifier that can be used in matching up requests
*
* @return string
*/

View File

@ -24,13 +24,6 @@ use Psr\Http\Message\ServerRequestInterface;
*/
class PasswordGrant extends AbstractGrant
{
/**
* Grant identifier
*
* @var string
*/
protected $identifier = 'password';
/**
* @var \League\OAuth2\Server\Repositories\UserRepositoryInterface
*/
@ -109,4 +102,12 @@ class PasswordGrant extends AbstractGrant
return $user;
}
/**
* @inheritdoc
*/
public function getIdentifier()
{
return 'password';
}
}

View File

@ -23,13 +23,6 @@ use Psr\Http\Message\ServerRequestInterface;
*/
class RefreshTokenGrant extends AbstractGrant
{
/**
* Grant identifier
*
* @var string
*/
protected $identifier = 'refresh_token';
/**
* @var \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface
*/
@ -133,4 +126,12 @@ class RefreshTokenGrant extends AbstractGrant
return $refreshTokenData;
}
/**
* @inheritdoc
*/
public function getIdentifier()
{
return 'refresh_token';
}
}

View File

@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Stream;
class AuthenticationServerMiddleware
{
@ -38,9 +39,10 @@ class AuthenticationServerMiddleware
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
$response->getBody()->write($exception->getMessage());
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500);
return $response->withStatus(500)->withBody($body);
}
if (in_array($response->getStatusCode(), [400, 401, 500])) {

View File

@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Stream;
class ResourceServerMiddleware
{
@ -34,13 +35,14 @@ class ResourceServerMiddleware
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
try {
$request = $this->server->getResponseType()->determineAccessTokenInHeader($request);
$request = $this->server->validateRequest($request);
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
} catch (\Exception $exception) {
$response->getBody()->write($exception->getMessage());
$body = new Stream('php://temp', 'r+');
$body->write($exception->getMessage());
return $response->withStatus(500);
return $response->withStatus(500)->withBody($body);
}
// Pass the request and response on to the next responder in the chain

View File

@ -19,27 +19,25 @@ use League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface;
interface AuthCodeRepositoryInterface extends RepositoryInterface
{
/**
* Get the auth code
* Persists a new auth code to permanent storage
*
* @param string $code
*
* @return \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface
* @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $authCodeEntity
*/
public function getAuthCodeEntityByCodeString($code);
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity);
/**
* Persist a new authorization code
* Revoke an auth code
*
* @param string $code The authorization code string
* @param integer $expireTime Token expire time
* @param string $redirectUri Client redirect uri
* @param string $codeId
*/
public function persistNewAuthCode($code, $expireTime, $redirectUri);
public function revokeAuthCode($codeId);
/**
* Delete an access token
* Check if the auth code has been revoked
*
* @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $token The access token to delete
* @param string $codeId
*
* @return bool Return true if this code has been revoked
*/
public function deleteAuthCodeEntity(AuthCodeEntityInterface $token);
public function isAuthCodeRevoked($codeId);
}

View File

@ -65,12 +65,13 @@ class BearerTokenResponse extends AbstractResponseType
$responseParams['refresh_token'] = $refreshToken;
}
$response
$response = $response
->withStatus(200)
->withHeader('pragma', 'no-cache')
->withHeader('cache-control', 'no-store')
->withHeader('content-type', 'application/json;charset=UTF-8')
->getBody()->write(json_encode($responseParams));
->withHeader('content-type', 'application/json; charset=UTF-8');
$response->getBody()->write(json_encode($responseParams));
return $response;
}

View File

@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Authorize <?=$this->e($client->getName())?></title>
</head>
<body>
<h1>
Authorize <?=$this->e($client->getName())?>
</h1>
<p>
Do you want to authorize <?=$this->e($client->getName())?> to access the following data?
</p>
<ul>
<?php foreach ($scopes as $scope): ?>
<li><?=$scope->getIdentifier()?></li>
<?php endforeach; ?>
</ul>
<form method="POST">
<input type="hidden" value="approve" name="action">
<button type="submit">Approve</button>
</form>
<form method="POST">
<input type="hidden" value="deny" name="action">
<button type="submit">Deny</button>
</form>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<?php if ($error !== null): ?>
<div style="border:solid 1px red; padding: 1rem; margin-bottom:1rem">
<?=$this->e($error)?>
</div>
<?php endif; ?>
<form method="POST">
<label for="username">Username</label>
<input type="text" id="username" name="username">
<br>
<label for="password">Password</label>
<input type="password" id="password" name="password">
<br>
<input type="submit" value="Login">
</form>
</body>
</html>

View File

@ -2,6 +2,7 @@
namespace League\OAuth2\Server;
use DateInterval;
use League\Event\EmitterAwareInterface;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
@ -26,7 +27,7 @@ class Server implements EmitterAwareInterface
protected $enabledGrantTypes = [];
/**
* @var DateInterval[]
* @var \DateInterval[]
*/
protected $grantTypeAccessTokenTTL = [];
@ -90,9 +91,9 @@ class Server implements EmitterAwareInterface
* Enable a grant type on the server
*
* @param \League\OAuth2\Server\Grant\GrantTypeInterface $grantType
* @param DateInterval $accessTokenTTL
* @param \DateInterval $accessTokenTTL
*/
public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL)
public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL)
{
$grantType->setAccessTokenRepository($this->accessTokenRepository);
$grantType->setClientRepository($this->clientRepository);
@ -136,19 +137,37 @@ class Server implements EmitterAwareInterface
}
}
if (!$tokenResponse instanceof ResponseTypeInterface) {
if ($tokenResponse instanceof ResponseInterface) {
return $tokenResponse;
}
if ($tokenResponse instanceof ResponseTypeInterface === false) {
return OAuthServerException::unsupportedGrantType()->generateHttpResponse($response);
}
return $tokenResponse->generateHttpResponse($response);
}
/**
* Determine the access token validity
*
* @param \Psr\Http\Message\ServerRequestInterface $request
*
* @return \Psr\Http\Message\ServerRequestInterface
*
* @throws \League\OAuth2\Server\Exception\OAuthServerException
*/
public function validateRequest(ServerRequestInterface $request)
{
return $this->getResponseType()->determineAccessTokenInHeader($request);
}
/**
* Get the token type that grants will return in the HTTP response
*
* @return ResponseTypeInterface
*/
public function getResponseType()
protected function getResponseType()
{
if (!$this->responseType instanceof ResponseTypeInterface) {
$this->responseType = new BearerTokenResponse(

View File

@ -51,6 +51,8 @@ class KeyCrypt
* @param string $encryptedData
* @param string $pathToPublicKey
*
* @throws \LogicException
*
* @return string
*/
public static function decrypt($encryptedData, $pathToPublicKey)