fjffk简单的咖啡店科开发2方法2 环滁皆山也。其西南诸峰,林壑尤美,望之蔚然而深秀者,琅琊也。山行六七里,渐闻水声潺潺,而泻出于两峰之间者,酿泉也 ??????????????????????????????? ??????????????/????????????????? ............/................... .........................../... ??????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dsdfLICENSE000064400000002046152665616300005565 0ustar00MIT License Copyright (C) Alex Bilbie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.src/RequestTypes/AuthorizationRequest.php000064400000010101152665616300014735 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\RequestTypes; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; class AuthorizationRequest { /** * The grant type identifier * * @var string */ protected $grantTypeId; /** * The client identifier * * @var ClientEntityInterface */ protected $client; /** * The user identifier * * @var UserEntityInterface */ protected $user; /** * An array of scope identifiers * * @var ScopeEntityInterface[] */ protected $scopes = []; /** * Has the user authorized the authorization request * * @var bool */ protected $authorizationApproved = false; /** * The redirect URI used in the request * * @var string|null */ protected $redirectUri; /** * The state parameter on the authorization request * * @var string|null */ protected $state; /** * The code challenge (if provided) * * @var string */ protected $codeChallenge; /** * The code challenge method (if provided) * * @var string */ protected $codeChallengeMethod; /** * @return string */ public function getGrantTypeId() { return $this->grantTypeId; } /** * @param string $grantTypeId */ public function setGrantTypeId($grantTypeId) { $this->grantTypeId = $grantTypeId; } /** * @return ClientEntityInterface */ public function getClient() { return $this->client; } /** * @param ClientEntityInterface $client */ public function setClient(ClientEntityInterface $client) { $this->client = $client; } /** * @return UserEntityInterface|null */ public function getUser() { return $this->user; } /** * @param UserEntityInterface $user */ public function setUser(UserEntityInterface $user) { $this->user = $user; } /** * @return ScopeEntityInterface[] */ public function getScopes() { return $this->scopes; } /** * @param ScopeEntityInterface[] $scopes */ public function setScopes(array $scopes) { $this->scopes = $scopes; } /** * @return bool */ public function isAuthorizationApproved() { return $this->authorizationApproved; } /** * @param bool $authorizationApproved */ public function setAuthorizationApproved($authorizationApproved) { $this->authorizationApproved = $authorizationApproved; } /** * @return string|null */ public function getRedirectUri() { return $this->redirectUri; } /** * @param string|null $redirectUri */ public function setRedirectUri($redirectUri) { $this->redirectUri = $redirectUri; } /** * @return string|null */ public function getState() { return $this->state; } /** * @param string $state */ public function setState($state) { $this->state = $state; } /** * @return string */ public function getCodeChallenge() { return $this->codeChallenge; } /** * @param string $codeChallenge */ public function setCodeChallenge($codeChallenge) { $this->codeChallenge = $codeChallenge; } /** * @return string */ public function getCodeChallengeMethod() { return $this->codeChallengeMethod; } /** * @param string $codeChallengeMethod */ public function setCodeChallengeMethod($codeChallengeMethod) { $this->codeChallengeMethod = $codeChallengeMethod; } } src/ResourceServer.php000064400000004756152665616300011050 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Psr\Http\Message\ServerRequestInterface; class ResourceServer { /** * @var AccessTokenRepositoryInterface */ private $accessTokenRepository; /** * @var CryptKey */ private $publicKey; /** * @var null|AuthorizationValidatorInterface */ private $authorizationValidator; /** * New server instance. * * @param AccessTokenRepositoryInterface $accessTokenRepository * @param CryptKey|string $publicKey * @param null|AuthorizationValidatorInterface $authorizationValidator */ public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, $publicKey, ?AuthorizationValidatorInterface $authorizationValidator = null ) { $this->accessTokenRepository = $accessTokenRepository; if ($publicKey instanceof CryptKey === false) { $publicKey = new CryptKey($publicKey); } $this->publicKey = $publicKey; $this->authorizationValidator = $authorizationValidator; } /** * @return AuthorizationValidatorInterface */ protected function getAuthorizationValidator() { if ($this->authorizationValidator instanceof AuthorizationValidatorInterface === false) { $this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository); } if ($this->authorizationValidator instanceof BearerTokenValidator === true) { $this->authorizationValidator->setPublicKey($this->publicKey); } return $this->authorizationValidator; } /** * Determine the access token validity. * * @param ServerRequestInterface $request * * @throws OAuthServerException * * @return ServerRequestInterface */ public function validateAuthenticatedRequest(ServerRequestInterface $request) { return $this->getAuthorizationValidator()->validateAuthorization($request); } } src/Exception/OAuthServerException.php000064400000026750152665616300014114 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Exception; use Exception; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Throwable; class OAuthServerException extends Exception { /** * @var int */ private $httpStatusCode; /** * @var string */ private $errorType; /** * @var null|string */ private $hint; /** * @var null|string */ private $redirectUri; /** * @var array */ private $payload; /** * @var ServerRequestInterface */ private $serverRequest; /** * Throw a new exception. * * @param string $message Error message * @param int $code Error code * @param string $errorType Error type * @param int $httpStatusCode HTTP status code to send (default = 400) * @param null|string $hint A helper hint * @param null|string $redirectUri A HTTP URI to redirect the user back to * @param Throwable $previous Previous exception */ public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; $this->errorType = $errorType; $this->hint = $hint; $this->redirectUri = $redirectUri; $this->payload = [ 'error' => $errorType, 'error_description' => $message, ]; if ($hint !== null) { $this->payload['hint'] = $hint; } } /** * Returns the current payload. * * @return array */ public function getPayload() { $payload = $this->payload; // The "message" property is deprecated and replaced by "error_description" // TODO: remove "message" property if (isset($payload['error_description']) && !isset($payload['message'])) { $payload['message'] = $payload['error_description']; } return $payload; } /** * Updates the current payload. * * @param array $payload */ public function setPayload(array $payload) { $this->payload = $payload; } /** * Set the server request that is responsible for generating the exception * * @param ServerRequestInterface $serverRequest */ public function setServerRequest(ServerRequestInterface $serverRequest) { $this->serverRequest = $serverRequest; } /** * Unsupported grant type error. * * @return static */ public static function unsupportedGrantType() { $errorMessage = 'The authorization grant type is not supported by the authorization server.'; $hint = 'Check that all required parameters have been provided'; return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint); } /** * Invalid request error. * * @param string $parameter The invalid parameter * @param null|string $hint * @param Throwable $previous Previous exception * * @return static */ public static function invalidRequest($parameter, $hint = null, ?Throwable $previous = null) { $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' . 'includes a parameter more than once, or is otherwise malformed.'; $hint = ($hint === null) ? \sprintf('Check the `%s` parameter', $parameter) : $hint; return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous); } /** * Invalid client error. * * @param ServerRequestInterface $serverRequest * * @return static */ public static function invalidClient(ServerRequestInterface $serverRequest) { $exception = new static('Client authentication failed', 4, 'invalid_client', 401); $exception->setServerRequest($serverRequest); return $exception; } /** * Invalid scope error. * * @param string $scope The bad scope * @param null|string $redirectUri A HTTP URI to redirect the user back to * * @return static */ public static function invalidScope($scope, $redirectUri = null) { $errorMessage = 'The requested scope is invalid, unknown, or malformed'; if (empty($scope)) { $hint = 'Specify a scope in the request or set a default scope'; } else { $hint = \sprintf( 'Check the `%s` scope', \htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false) ); } return new static($errorMessage, 5, 'invalid_scope', 400, $hint, $redirectUri); } /** * Invalid credentials error. * * @return static */ public static function invalidCredentials() { return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400); } /** * Server error. * * @param string $hint * @param Throwable $previous * * @return static * * @codeCoverageIgnore */ public static function serverError($hint, ?Throwable $previous = null) { return new static( 'The authorization server encountered an unexpected condition which prevented it from fulfilling' . ' the request: ' . $hint, 7, 'server_error', 500, null, null, $previous ); } /** * Invalid refresh token. * * @param null|string $hint * @param Throwable $previous * * @return static */ public static function invalidRefreshToken($hint = null, ?Throwable $previous = null) { return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous); } /** * Access denied. * * @param null|string $hint * @param null|string $redirectUri * @param Throwable $previous * * @return static */ public static function accessDenied($hint = null, $redirectUri = null, ?Throwable $previous = null) { return new static( 'The resource owner or authorization server denied the request.', 9, 'access_denied', 401, $hint, $redirectUri, $previous ); } /** * Invalid grant. * * @param string $hint * * @return static */ public static function invalidGrant($hint = '') { return new static( 'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token ' . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, ' . 'or was issued to another client.', 10, 'invalid_grant', 400, $hint ); } /** * @return string */ public function getErrorType() { return $this->errorType; } /** * Generate a HTTP response. * * @param ResponseInterface $response * @param bool $useFragment True if errors should be in the URI fragment instead of query string * @param int $jsonOptions options passed to json_encode * * @return ResponseInterface */ public function generateHttpResponse(ResponseInterface $response, $useFragment = false, $jsonOptions = 0) { $headers = $this->getHttpHeaders(); $payload = $this->getPayload(); if ($this->redirectUri !== null) { if ($useFragment === true) { $this->redirectUri .= (\strstr($this->redirectUri, '#') === false) ? '#' : '&'; } else { $this->redirectUri .= (\strstr($this->redirectUri, '?') === false) ? '?' : '&'; } return $response->withStatus(302)->withHeader('Location', $this->redirectUri . \http_build_query($payload)); } foreach ($headers as $header => $content) { $response = $response->withHeader($header, $content); } $responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed'; $response->getBody()->write($responseBody); return $response->withStatus($this->getHttpStatusCode()); } /** * Get all headers that have to be send with the error response. * * @return array Array with header values */ public function getHttpHeaders() { $headers = [ 'Content-type' => 'application/json', ]; // Add "WWW-Authenticate" header // // RFC 6749, section 5.2.: // "If the client attempted to authenticate via the 'Authorization' // request header field, the authorization server MUST // respond with an HTTP 401 (Unauthorized) status code and // include the "WWW-Authenticate" response header field // matching the authentication scheme used by the client. if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) { $authScheme = \strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic'; $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"'; } return $headers; } /** * Check if the exception has an associated redirect URI. * * Returns whether the exception includes a redirect, since * getHttpStatusCode() doesn't return a 302 when there's a * redirect enabled. This helps when you want to override local * error pages but want to let redirects through. * * @return bool */ public function hasRedirect() { return $this->redirectUri !== null; } /** * Returns the Redirect URI used for redirecting. * * @return string|null */ public function getRedirectUri() { return $this->redirectUri; } /** * Returns the HTTP status code to send when the exceptions is output. * * @return int */ public function getHttpStatusCode() { return $this->httpStatusCode; } /** * @return null|string */ public function getHint() { return $this->hint; } /** * Check if the request has a non-empty 'Authorization' header value. * * Returns true if the header is present and not an empty string, false * otherwise. * * @return bool */ private function requestHasAuthorizationHeader() { if (!$this->serverRequest->hasHeader('Authorization')) { return false; } $authorizationHeader = $this->serverRequest->getHeader('Authorization'); // Common .htaccess configurations yield an empty string for the // 'Authorization' header when one is not provided by the client. // For practical purposes that case should be treated as though the // header isn't present. // See https://github.com/thephpleague/oauth2-server/issues/1162 if (empty($authorizationHeader) || empty($authorizationHeader[0])) { return false; } return true; } } src/Exception/UniqueTokenIdentifierConstraintViolationException.php000064400000001163152665616300022100 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Exception; class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException { /** * @return UniqueTokenIdentifierConstraintViolationException */ public static function create() { $errorMessage = 'Could not create unique access token identifier'; return new static($errorMessage, 100, 'access_token_duplicate', 500); } } src/Entities/RefreshTokenEntityInterface.php000064400000002441152665616300015260 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; use DateTimeImmutable; interface RefreshTokenEntityInterface { /** * Get the token's identifier. * * @return string */ public function getIdentifier(); /** * Set the token's identifier. * * @param mixed $identifier */ public function setIdentifier($identifier); /** * Get the token's expiry date time. * * @return DateTimeImmutable */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * * @param DateTimeImmutable $dateTime */ public function setExpiryDateTime(DateTimeImmutable $dateTime); /** * Set the access token that the refresh token was associated with. * * @param AccessTokenEntityInterface $accessToken */ public function setAccessToken(AccessTokenEntityInterface $accessToken); /** * Get the access token that the refresh token was originally associated with. * * @return AccessTokenEntityInterface */ public function getAccessToken(); } src/Entities/ClientEntityInterface.php000064400000001546152665616300014104 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; interface ClientEntityInterface { /** * Get the client's identifier. * * @return string */ public function getIdentifier(); /** * Get the client's name. * * @return string */ public function getName(); /** * Returns the registered redirect URI (as a string). * * Alternatively return an indexed array of redirect URIs. * * @return string|string[] */ public function getRedirectUri(); /** * Returns true if the client is confidential. * * @return bool */ public function isConfidential(); } src/Entities/ScopeEntityInterface.php000064400000000707152665616300013735 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; use JsonSerializable; interface ScopeEntityInterface extends JsonSerializable { /** * Get the scope's identifier. * * @return string */ public function getIdentifier(); } src/Entities/Traits/TokenEntityTrait.php000064400000004643152665616300014400 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; trait TokenEntityTrait { /** * @var ScopeEntityInterface[] */ protected $scopes = []; /** * @var DateTimeImmutable */ protected $expiryDateTime; /** * @var string|int|null */ protected $userIdentifier; /** * @var ClientEntityInterface */ protected $client; /** * Associate a scope with the token. * * @param ScopeEntityInterface $scope */ public function addScope(ScopeEntityInterface $scope) { $this->scopes[$scope->getIdentifier()] = $scope; } /** * Return an array of scopes associated with the token. * * @return ScopeEntityInterface[] */ public function getScopes() { return \array_values($this->scopes); } /** * Get the token's expiry date time. * * @return DateTimeImmutable */ public function getExpiryDateTime() { return $this->expiryDateTime; } /** * Set the date time when the token expires. * * @param DateTimeImmutable $dateTime */ public function setExpiryDateTime(DateTimeImmutable $dateTime) { $this->expiryDateTime = $dateTime; } /** * Set the identifier of the user associated with the token. * * @param string|int|null $identifier The identifier of the user */ public function setUserIdentifier($identifier) { $this->userIdentifier = $identifier; } /** * Get the token user's identifier. * * @return string|int|null */ public function getUserIdentifier() { return $this->userIdentifier; } /** * Get the client that the token was issued to. * * @return ClientEntityInterface */ public function getClient() { return $this->client; } /** * Set the client that the token was issued to. * * @param ClientEntityInterface $client */ public function setClient(ClientEntityInterface $client) { $this->client = $client; } } src/Entities/Traits/RefreshTokenTrait.php000064400000002345152665616300014517 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; trait RefreshTokenTrait { /** * @var AccessTokenEntityInterface */ protected $accessToken; /** * @var DateTimeImmutable */ protected $expiryDateTime; /** * {@inheritdoc} */ public function setAccessToken(AccessTokenEntityInterface $accessToken) { $this->accessToken = $accessToken; } /** * {@inheritdoc} */ public function getAccessToken() { return $this->accessToken; } /** * Get the token's expiry date time. * * @return DateTimeImmutable */ public function getExpiryDateTime() { return $this->expiryDateTime; } /** * Set the date time when the token expires. * * @param DateTimeImmutable $dateTime */ public function setExpiryDateTime(DateTimeImmutable $dateTime) { $this->expiryDateTime = $dateTime; } } src/Entities/Traits/EntityTrait.php000064400000001147152665616300013373 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; trait EntityTrait { /** * @var string */ protected $identifier; /** * @return mixed */ public function getIdentifier() { return $this->identifier; } /** * @param mixed $identifier */ public function setIdentifier($identifier) { $this->identifier = $identifier; } } src/Entities/Traits/ScopeTrait.php000064400000001154152665616300013166 0ustar00 * @copyright Copyright (c) Andrew Millington * @license http://mit-license.org * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; trait ScopeTrait { /** * Serialize the object to the scopes string identifier when using json_encode(). * * @return string */ #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->getIdentifier(); } /** * @return string */ abstract public function getIdentifier(); } src/Entities/Traits/ClientTrait.php000064400000002133152665616300013331 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; trait ClientTrait { /** * @var string */ protected $name; /** * @var string|string[] */ protected $redirectUri; /** * @var bool */ protected $isConfidential = false; /** * Get the client's name. * * @return string * * @codeCoverageIgnore */ public function getName() { return $this->name; } /** * Returns the registered redirect URI (as a string). * * Alternatively return an indexed array of redirect URIs. * * @return string|string[] */ public function getRedirectUri() { return $this->redirectUri; } /** * Returns true if the client is confidential. * * @return bool */ public function isConfidential() { return $this->isConfidential; } } src/Entities/Traits/AuthCodeTrait.php000064400000001145152665616300013611 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; trait AuthCodeTrait { /** * @var null|string */ protected $redirectUri; /** * @return string|null */ public function getRedirectUri() { return $this->redirectUri; } /** * @param string $uri */ public function setRedirectUri($uri) { $this->redirectUri = $uri; } } src/Entities/Traits/AccessTokenTrait.php000064400000005137152665616300014324 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; trait AccessTokenTrait { /** * @var CryptKey */ private $privateKey; /** * @var Configuration */ private $jwtConfiguration; /** * Set the private key used to encrypt this access token. */ public function setPrivateKey(CryptKey $privateKey) { $this->privateKey = $privateKey; } /** * Initialise the JWT Configuration. */ public function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), InMemory::plainText($this->privateKey->getKeyContents(), $this->privateKey->getPassPhrase() ?? ''), InMemory::plainText('empty', 'empty') ); } /** * Generate a JWT from the access token * * @return Token */ private function convertToJWT() { $this->initJwtConfiguration(); return $this->jwtConfiguration->builder() ->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy($this->getIdentifier()) ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) ->relatedTo((string) $this->getUserIdentifier()) ->withClaim('scopes', $this->getScopes()) ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } /** * Generate a string representation from the access token */ public function __toString() { return $this->convertToJWT()->toString(); } /** * @return ClientEntityInterface */ abstract public function getClient(); /** * @return DateTimeImmutable */ abstract public function getExpiryDateTime(); /** * @return string|int */ abstract public function getUserIdentifier(); /** * @return ScopeEntityInterface[] */ abstract public function getScopes(); /** * @return string */ abstract public function getIdentifier(); } src/Entities/UserEntityInterface.php000064400000000627152665616300013603 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; interface UserEntityInterface { /** * Return the user's identifier. * * @return mixed */ public function getIdentifier(); } src/Entities/AuthCodeEntityInterface.php000064400000000742152665616300014357 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; interface AuthCodeEntityInterface extends TokenInterface { /** * @return string|null */ public function getRedirectUri(); /** * @param string $uri */ public function setRedirectUri($uri); } src/Entities/AccessTokenEntityInterface.php000064400000001127152665616300015063 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; use League\OAuth2\Server\CryptKey; interface AccessTokenEntityInterface extends TokenInterface { /** * Set a private key used to encrypt the access token. */ public function setPrivateKey(CryptKey $privateKey); /** * Generate a string representation of the access token. */ public function __toString(); } src/Entities/TokenInterface.php000064400000003531152665616300012545 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Entities; use DateTimeImmutable; interface TokenInterface { /** * Get the token's identifier. * * @return string */ public function getIdentifier(); /** * Set the token's identifier. * * @param mixed $identifier */ public function setIdentifier($identifier); /** * Get the token's expiry date time. * * @return DateTimeImmutable */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * * @param DateTimeImmutable $dateTime */ public function setExpiryDateTime(DateTimeImmutable $dateTime); /** * Set the identifier of the user associated with the token. * * @param string|int|null $identifier The identifier of the user */ public function setUserIdentifier($identifier); /** * Get the token user's identifier. * * @return string|int|null */ public function getUserIdentifier(); /** * Get the client that the token was issued to. * * @return ClientEntityInterface */ public function getClient(); /** * Set the client that the token was issued to. * * @param ClientEntityInterface $client */ public function setClient(ClientEntityInterface $client); /** * Associate a scope with the token. * * @param ScopeEntityInterface $scope */ public function addScope(ScopeEntityInterface $scope); /** * Return an array of scopes associated with the token. * * @return ScopeEntityInterface[] */ public function getScopes(); } src/CodeChallengeVerifiers/S256Verifier.php000064400000001572152665616300014552 0ustar00 * @copyright Copyright (c) Lukáš Unger * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\CodeChallengeVerifiers; class S256Verifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. * * @return string */ public function getMethod() { return 'S256'; } /** * Verify the code challenge. * * @param string $codeVerifier * @param string $codeChallenge * * @return bool */ public function verifyCodeChallenge($codeVerifier, $codeChallenge) { return \hash_equals( \strtr(\rtrim(\base64_encode(\hash('sha256', $codeVerifier, true)), '='), '+/', '-_'), $codeChallenge ); } } src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php000064400000001223152665616300020402 0ustar00 * @copyright Copyright (c) Lukáš Unger * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\CodeChallengeVerifiers; interface CodeChallengeVerifierInterface { /** * Return code challenge method. * * @return string */ public function getMethod(); /** * Verify the code challenge. * * @param string $codeVerifier * @param string $codeChallenge * * @return bool */ public function verifyCodeChallenge($codeVerifier, $codeChallenge); } src/CodeChallengeVerifiers/PlainVerifier.php000064400000001422152665616300015150 0ustar00 * @copyright Copyright (c) Lukáš Unger * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\CodeChallengeVerifiers; class PlainVerifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. * * @return string */ public function getMethod() { return 'plain'; } /** * Verify the code challenge. * * @param string $codeVerifier * @param string $codeChallenge * * @return bool */ public function verifyCodeChallenge($codeVerifier, $codeChallenge) { return \hash_equals($codeVerifier, $codeChallenge); } } src/AuthorizationServer.php000064400000016312152665616300012110 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use DateInterval; use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; 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\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class AuthorizationServer implements EmitterAwareInterface { use EmitterAwareTrait; /** * @var GrantTypeInterface[] */ protected $enabledGrantTypes = []; /** * @var DateInterval[] */ protected $grantTypeAccessTokenTTL = []; /** * @var CryptKey */ protected $privateKey; /** * @var CryptKey */ protected $publicKey; /** * @var ResponseTypeInterface */ protected $responseType; /** * @var ClientRepositoryInterface */ private $clientRepository; /** * @var AccessTokenRepositoryInterface */ private $accessTokenRepository; /** * @var ScopeRepositoryInterface */ private $scopeRepository; /** * @var string|Key */ private $encryptionKey; /** * @var string */ private $defaultScope = ''; /** * @var bool */ private $revokeRefreshTokens = true; /** * New server instance. * * @param ClientRepositoryInterface $clientRepository * @param AccessTokenRepositoryInterface $accessTokenRepository * @param ScopeRepositoryInterface $scopeRepository * @param CryptKey|string $privateKey * @param string|Key $encryptionKey * @param null|ResponseTypeInterface $responseType */ public function __construct( ClientRepositoryInterface $clientRepository, AccessTokenRepositoryInterface $accessTokenRepository, ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, ?ResponseTypeInterface $responseType = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; $this->scopeRepository = $scopeRepository; if ($privateKey instanceof CryptKey === false) { $privateKey = new CryptKey($privateKey); } $this->privateKey = $privateKey; $this->encryptionKey = $encryptionKey; if ($responseType === null) { $responseType = new BearerTokenResponse(); } else { $responseType = clone $responseType; } $this->responseType = $responseType; } /** * Enable a grant type on the server. * * @param GrantTypeInterface $grantType * @param null|DateInterval $accessTokenTTL */ public function enableGrantType(GrantTypeInterface $grantType, ?DateInterval $accessTokenTTL = null) { if ($accessTokenTTL === null) { $accessTokenTTL = new DateInterval('PT1H'); } $grantType->setAccessTokenRepository($this->accessTokenRepository); $grantType->setClientRepository($this->clientRepository); $grantType->setScopeRepository($this->scopeRepository); $grantType->setDefaultScope($this->defaultScope); $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); $grantType->setEncryptionKey($this->encryptionKey); $grantType->revokeRefreshTokens($this->revokeRefreshTokens); $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL; } /** * Validate an authorization request * * @param ServerRequestInterface $request * * @throws OAuthServerException * * @return AuthorizationRequest */ public function validateAuthorizationRequest(ServerRequestInterface $request) { foreach ($this->enabledGrantTypes as $grantType) { if ($grantType->canRespondToAuthorizationRequest($request)) { return $grantType->validateAuthorizationRequest($request); } } throw OAuthServerException::unsupportedGrantType(); } /** * Complete an authorization request * * @param AuthorizationRequest $authRequest * @param ResponseInterface $response * * @return ResponseInterface */ public function completeAuthorizationRequest(AuthorizationRequest $authRequest, ResponseInterface $response) { return $this->enabledGrantTypes[$authRequest->getGrantTypeId()] ->completeAuthorizationRequest($authRequest) ->generateHttpResponse($response); } /** * Return an access token response. * * @param ServerRequestInterface $request * @param ResponseInterface $response * * @throws OAuthServerException * * @return ResponseInterface */ public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseInterface $response) { foreach ($this->enabledGrantTypes as $grantType) { if (!$grantType->canRespondToAccessTokenRequest($request)) { continue; } $tokenResponse = $grantType->respondToAccessTokenRequest( $request, $this->getResponseType(), $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] ); if ($tokenResponse instanceof ResponseTypeInterface) { return $tokenResponse->generateHttpResponse($response); } } throw OAuthServerException::unsupportedGrantType(); } /** * Get the token type that grants will return in the HTTP response. * * @return ResponseTypeInterface */ protected function getResponseType() { $responseType = clone $this->responseType; if ($responseType instanceof AbstractResponseType) { $responseType->setPrivateKey($this->privateKey); } $responseType->setEncryptionKey($this->encryptionKey); return $responseType; } /** * Set the default scope for the authorization server. * * @param string $defaultScope */ public function setDefaultScope($defaultScope) { $this->defaultScope = $defaultScope; } /** * Sets whether to revoke refresh tokens or not (for all grant types). * * @param bool $revokeRefreshTokens */ public function revokeRefreshTokens(bool $revokeRefreshTokens): void { $this->revokeRefreshTokens = $revokeRefreshTokens; } } src/Repositories/RepositoryInterface.php000064400000000506152665616300014546 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; /** * Repository interface. */ interface RepositoryInterface { } src/Repositories/UserRepositoryInterface.php000064400000001613152665616300015405 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; interface UserRepositoryInterface extends RepositoryInterface { /** * Get a user entity. * * @param string $username * @param string $password * @param string $grantType The grant type used * @param ClientEntityInterface $clientEntity * * @return UserEntityInterface|null */ public function getUserEntityByUserCredentials( $username, $password, $grantType, ClientEntityInterface $clientEntity ); } src/Repositories/RefreshTokenRepositoryInterface.php000064400000002454152665616300017072 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Refresh token interface. */ interface RefreshTokenRepositoryInterface extends RepositoryInterface { /** * Creates a new refresh token * * @return RefreshTokenEntityInterface|null */ public function getNewRefreshToken(); /** * Create a new refresh token_name. * * @param RefreshTokenEntityInterface $refreshTokenEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity); /** * Revoke the refresh token. * * @param string $tokenId */ public function revokeRefreshToken($tokenId); /** * Check if the refresh token has been revoked. * * @param string $tokenId * * @return bool Return true if this token has been revoked */ public function isRefreshTokenRevoked($tokenId); } src/Repositories/ClientRepositoryInterface.php000064400000002014152665616300015701 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; /** * Client storage interface. */ interface ClientRepositoryInterface extends RepositoryInterface { /** * Get a client. * * @param string $clientIdentifier The client's identifier * * @return ClientEntityInterface|null */ public function getClientEntity($clientIdentifier); /** * Validate a client's secret. * * @param string $clientIdentifier The client's identifier * @param null|string $clientSecret The client's secret (if sent) * @param null|string $grantType The type of grant the client is using (if sent) * * @return bool */ public function validateClient($clientIdentifier, $clientSecret, $grantType); } src/Repositories/ScopeRepositoryInterface.php000064400000002434152665616300015542 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; /** * Scope interface. */ interface ScopeRepositoryInterface extends RepositoryInterface { /** * Return information about a scope. * * @param string $identifier The scope identifier * * @return ScopeEntityInterface|null */ public function getScopeEntityByIdentifier($identifier); /** * Given a client, grant type and optional user identifier validate the set of scopes requested are valid and optionally * append additional scopes or remove requested scopes. * * @param ScopeEntityInterface[] $scopes * @param string $grantType * @param ClientEntityInterface $clientEntity * @param null|string $userIdentifier * * @return ScopeEntityInterface[] */ public function finalizeScopes( array $scopes, $grantType, ClientEntityInterface $clientEntity, $userIdentifier = null ); } src/Repositories/AccessTokenRepositoryInterface.php000064400000003170152665616300016671 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Access token interface. */ interface AccessTokenRepositoryInterface extends RepositoryInterface { /** * Create a new access token * * @param ClientEntityInterface $clientEntity * @param ScopeEntityInterface[] $scopes * @param mixed $userIdentifier * * @return AccessTokenEntityInterface */ public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null); /** * Persists a new access token to permanent storage. * * @param AccessTokenEntityInterface $accessTokenEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity); /** * Revoke an access token. * * @param string $tokenId */ public function revokeAccessToken($tokenId); /** * Check if the access token has been revoked. * * @param string $tokenId * * @return bool Return true if this token has been revoked */ public function isAccessTokenRevoked($tokenId); } src/Repositories/AuthCodeRepositoryInterface.php000064400000002372152665616300016166 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** * Auth code storage interface. */ interface AuthCodeRepositoryInterface extends RepositoryInterface { /** * Creates a new AuthCode * * @return AuthCodeEntityInterface */ public function getNewAuthCode(); /** * Persists a new auth code to permanent storage. * * @param AuthCodeEntityInterface $authCodeEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity); /** * Revoke an auth code. * * @param string $codeId */ public function revokeAuthCode($codeId); /** * 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); } src/RequestAccessTokenEvent.php000064400000001733152665616300012637 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use Psr\Http\Message\ServerRequestInterface; class RequestAccessTokenEvent extends RequestEvent { /** * @var AccessTokenEntityInterface */ private $accessToken; /** * @param string $name * @param ServerRequestInterface $request */ public function __construct($name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) { parent::__construct($name, $request); $this->accessToken = $accessToken; } /** * @return AccessTokenEntityInterface * * @codeCoverageIgnore */ public function getAccessToken() { return $this->accessToken; } } src/Middleware/AuthorizationServerMiddleware.php000064400000003202152665616300016155 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Middleware; use Exception; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class AuthorizationServerMiddleware { /** * @var AuthorizationServer */ private $server; /** * @param AuthorizationServer $server */ public function __construct(AuthorizationServer $server) { $this->server = $server; } /** * @param ServerRequestInterface $request * @param ResponseInterface $response * @param callable $next * * @return ResponseInterface */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { try { $response = $this->server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); // @codeCoverageIgnoreStart } catch (Exception $exception) { return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) ->generateHttpResponse($response); // @codeCoverageIgnoreEnd } // Pass the request and response on to the next responder in the chain return $next($request, $response); } } src/Middleware/ResourceServerMiddleware.php000064400000003136152665616300015112 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Middleware; use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class ResourceServerMiddleware { /** * @var ResourceServer */ private $server; /** * @param ResourceServer $server */ public function __construct(ResourceServer $server) { $this->server = $server; } /** * @param ServerRequestInterface $request * @param ResponseInterface $response * @param callable $next * * @return ResponseInterface */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { try { $request = $this->server->validateAuthenticatedRequest($request); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); // @codeCoverageIgnoreStart } catch (Exception $exception) { return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) ->generateHttpResponse($response); // @codeCoverageIgnoreEnd } // Pass the request and response on to the next responder in the chain return $next($request, $response); } } src/RequestEvent.php000064400000002303152665616300010506 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use League\Event\Event; use Psr\Http\Message\ServerRequestInterface; class RequestEvent extends Event { const CLIENT_AUTHENTICATION_FAILED = 'client.authentication.failed'; const USER_AUTHENTICATION_FAILED = 'user.authentication.failed'; const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.failed'; const REFRESH_TOKEN_ISSUED = 'refresh_token.issued'; const ACCESS_TOKEN_ISSUED = 'access_token.issued'; /** * @var ServerRequestInterface */ private $request; /** * RequestEvent constructor. * * @param string $name * @param ServerRequestInterface $request */ public function __construct($name, ServerRequestInterface $request) { parent::__construct($name); $this->request = $request; } /** * @return ServerRequestInterface * * @codeCoverageIgnore */ public function getRequest() { return $this->request; } } src/RequestRefreshTokenEvent.php000064400000001746152665616300013040 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ServerRequestInterface; class RequestRefreshTokenEvent extends RequestEvent { /** * @var RefreshTokenEntityInterface */ private $refreshToken; /** * @param string $name * @param ServerRequestInterface $request */ public function __construct($name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) { parent::__construct($name, $request); $this->refreshToken = $refreshToken; } /** * @return RefreshTokenEntityInterface * * @codeCoverageIgnore */ public function getRefreshToken() { return $this->refreshToken; } } src/CryptKey.php000064400000007313152665616300007634 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use LogicException; class CryptKey { /** @deprecated left for backward compatibility check */ const RSA_KEY_PATTERN = '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s'; private const FILE_PREFIX = 'file://'; /** * @var string Key contents */ protected $keyContents; /** * @var string */ protected $keyPath; /** * @var null|string */ protected $passPhrase; /** * @param string $keyPath * @param null|string $passPhrase * @param bool $keyPermissionsCheck */ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true) { $this->passPhrase = $passPhrase; if (\strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { $this->keyContents = $keyPath; $this->keyPath = ''; // There's no file, so no need for permission check. $keyPermissionsCheck = false; } elseif (\is_file($keyPath)) { if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { $keyPath = self::FILE_PREFIX . $keyPath; } if (!\is_readable($keyPath)) { throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } $this->keyContents = \file_get_contents($keyPath); $this->keyPath = $keyPath; if (!$this->isValidKey($this->keyContents, $this->passPhrase ?? '')) { throw new LogicException('Unable to read key from file ' . $keyPath); } } else { throw new LogicException('Invalid key supplied'); } if ($keyPermissionsCheck === true) { // Verify the permissions of the key $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777); if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) { \trigger_error( \sprintf( 'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s', $this->keyPath, $keyPathPerms ), E_USER_NOTICE ); } } } /** * Get key contents * * @return string Key contents */ public function getKeyContents(): string { return $this->keyContents; } /** * Validate key contents. * * @param string $contents * @param string $passPhrase * * @return bool */ private function isValidKey($contents, $passPhrase) { $pkey = \openssl_pkey_get_private($contents, $passPhrase) ?: \openssl_pkey_get_public($contents); if ($pkey === false) { return false; } $details = \openssl_pkey_get_details($pkey); return $details !== false && \in_array( $details['type'] ?? -1, [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC], true ); } /** * Retrieve key path. * * @return string */ public function getKeyPath() { return $this->keyPath; } /** * Retrieve key pass phrase. * * @return null|string */ public function getPassPhrase() { return $this->passPhrase; } } src/Grant/AuthCodeGrant.php000064400000037452152665616300011634 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use DateTimeImmutable; use Exception; use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface; use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier; use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; use stdClass; class AuthCodeGrant extends AbstractAuthorizeGrant { /** * @var DateInterval */ private $authCodeTTL; /** * @var bool */ private $requireCodeChallengeForPublicClients = true; /** * @var CodeChallengeVerifierInterface[] */ private $codeChallengeVerifiers = []; /** * @param AuthCodeRepositoryInterface $authCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository * @param DateInterval $authCodeTTL * * @throws Exception */ public function __construct( AuthCodeRepositoryInterface $authCodeRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, DateInterval $authCodeTTL ) { $this->setAuthCodeRepository($authCodeRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->authCodeTTL = $authCodeTTL; $this->refreshTokenTTL = new DateInterval('P1M'); if (\in_array('sha256', \hash_algos(), true)) { $s256Verifier = new S256Verifier(); $this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier; } $plainVerifier = new PlainVerifier(); $this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier; } /** * Disable the requirement for a code challenge for public clients. */ public function disableRequireCodeChallengeForPublicClients() { $this->requireCodeChallengeForPublicClients = false; } /** * Respond to an access token request. * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType * @param DateInterval $accessTokenTTL * * @throws OAuthServerException * * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { list($clientId) = $this->getClientCredentials($request); $client = $this->getClientEntityOrFail($clientId, $request); // Only validate the client if it is confidential if ($client->isConfidential()) { $this->validateClient($request); } $encryptedAuthCode = $this->getRequestParameter('code', $request, null); if (!\is_string($encryptedAuthCode)) { throw OAuthServerException::invalidRequest('code'); } try { $authCodePayload = \json_decode($this->decrypt($encryptedAuthCode)); $this->validateAuthorizationCode($authCodePayload, $client, $request); $scopes = $this->scopeRepository->finalizeScopes( $this->validateScopes($authCodePayload->scopes), $this->getIdentifier(), $client, $authCodePayload->user_id ); } catch (LogicException $e) { throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); } $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); // If a code challenge isn't present but a code verifier is, reject the request to block PKCE downgrade attack if (empty($authCodePayload->code_challenge) && $codeVerifier !== null) { throw OAuthServerException::invalidRequest( 'code_challenge', 'code_verifier received when no code_challenge is present' ); } if (!empty($authCodePayload->code_challenge)) { $this->validateCodeChallenge($authCodePayload, $codeVerifier); } // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } // Revoke used auth code $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); return $responseType; } private function validateCodeChallenge($authCodePayload, $codeVerifier) { if ($codeVerifier === null) { throw OAuthServerException::invalidRequest('code_verifier'); } // Validate code_verifier according to RFC-7636 // @see: https://tools.ietf.org/html/rfc7636#section-4.1 if (\preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) { throw OAuthServerException::invalidRequest( 'code_verifier', 'Code Verifier must follow the specifications of RFC-7636.' ); } if (\property_exists($authCodePayload, 'code_challenge_method')) { if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) { $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method]; if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) { throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); } } else { throw OAuthServerException::serverError( \sprintf( 'Unsupported code challenge method `%s`', $authCodePayload->code_challenge_method ) ); } } } /** * Validate the authorization code. * * @param stdClass $authCodePayload * @param ClientEntityInterface $client * @param ServerRequestInterface $request */ private function validateAuthorizationCode( $authCodePayload, ClientEntityInterface $client, ServerRequestInterface $request ) { if (!\property_exists($authCodePayload, 'auth_code_id')) { throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); } 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'); } // The redirect URI is required in this request $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { throw OAuthServerException::invalidRequest('redirect_uri'); } if ($authCodePayload->redirect_uri !== $redirectUri) { throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); } } /** * Return the grant identifier that can be used in matching up requests. * * @return string */ public function getIdentifier() { return 'authorization_code'; } /** * {@inheritdoc} */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request) { return ( \array_key_exists('response_type', $request->getQueryParams()) && $request->getQueryParams()['response_type'] === 'code' && isset($request->getQueryParams()['client_id']) ); } /** * {@inheritdoc} */ public function validateAuthorizationRequest(ServerRequestInterface $request) { $clientId = $this->getQueryStringParameter( 'client_id', $request, $this->getServerParameter('PHP_AUTH_USER', $request) ); if ($clientId === null) { throw OAuthServerException::invalidRequest('client_id'); } $client = $this->getClientEntityOrFail($clientId, $request); $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { if (!\is_string($redirectUri)) { throw OAuthServerException::invalidRequest('redirect_uri'); } $this->validateRedirectUri($redirectUri, $client, $request); } elseif (empty($client->getRedirectUri()) || (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } $defaultClientRedirectUri = \is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] : $client->getRedirectUri(); $scopes = $this->validateScopes( $this->getQueryStringParameter('scope', $request, $this->defaultScope), $redirectUri ?? $defaultClientRedirectUri ); $stateParameter = $this->getQueryStringParameter('state', $request); $authorizationRequest = new AuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); $authorizationRequest->setRedirectUri($redirectUri); if ($stateParameter !== null) { $authorizationRequest->setState($stateParameter); } $authorizationRequest->setScopes($scopes); $codeChallenge = $this->getQueryStringParameter('code_challenge', $request); if ($codeChallenge !== null) { $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain'); if (\array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { throw OAuthServerException::invalidRequest( 'code_challenge_method', 'Code challenge method must be one of ' . \implode(', ', \array_map( function ($method) { return '`' . $method . '`'; }, \array_keys($this->codeChallengeVerifiers) )) ); } // Validate code_challenge according to RFC-7636 // @see: https://tools.ietf.org/html/rfc7636#section-4.2 if (\preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) { throw OAuthServerException::invalidRequest( 'code_challenge', 'Code challenge must follow the specifications of RFC-7636.' ); } $authorizationRequest->setCodeChallenge($codeChallenge); $authorizationRequest->setCodeChallengeMethod($codeChallengeMethod); } elseif ($this->requireCodeChallengeForPublicClients && !$client->isConfidential()) { throw OAuthServerException::invalidRequest('code_challenge', 'Code challenge must be provided for public clients'); } return $authorizationRequest; } /** * {@inheritdoc} */ public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } $finalRedirectUri = $authorizationRequest->getRedirectUri() ?? $this->getClientRedirectUri($authorizationRequest); // The user approved the client, redirect them back with an auth code if ($authorizationRequest->isAuthorizationApproved() === true) { $authCode = $this->issueAuthCode( $this->authCodeTTL, $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier(), $authorizationRequest->getRedirectUri(), $authorizationRequest->getScopes() ); $payload = [ 'client_id' => $authCode->getClient()->getIdentifier(), 'redirect_uri' => $authCode->getRedirectUri(), 'auth_code_id' => $authCode->getIdentifier(), 'scopes' => $authCode->getScopes(), 'user_id' => $authCode->getUserIdentifier(), 'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(), 'code_challenge' => $authorizationRequest->getCodeChallenge(), 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), ]; $jsonPayload = \json_encode($payload); if ($jsonPayload === false) { throw new LogicException('An error was encountered when JSON encoding the authorization request response'); } $response = new RedirectResponse(); $response->setRedirectUri( $this->makeRedirectUri( $finalRedirectUri, [ 'code' => $this->encrypt($jsonPayload), 'state' => $authorizationRequest->getState(), ] ) ); return $response; } // The user denied the client, redirect them back with an error throw OAuthServerException::accessDenied( 'The user denied the request', $this->makeRedirectUri( $finalRedirectUri, [ 'state' => $authorizationRequest->getState(), ] ) ); } /** * Get the client redirect URI if not set in the request. * * @param AuthorizationRequest $authorizationRequest * * @return string */ private function getClientRedirectUri(AuthorizationRequest $authorizationRequest) { return \is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] : $authorizationRequest->getClient()->getRedirectUri(); } } src/Grant/GrantTypeInterface.php000064400000010541152665616300012670 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKey; 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\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; /** * Grant type interface. */ interface GrantTypeInterface extends EmitterAwareInterface { /** * Set refresh token TTL. * * @param DateInterval $refreshTokenTTL */ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL); /** * Return the grant identifier that can be used in matching up requests. * * @return string */ public function getIdentifier(); /** * Respond to an incoming request. * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType * @param DateInterval $accessTokenTTL * * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ); /** * The grant type should return true if it is able to response to an authorization request * * @param ServerRequestInterface $request * * @return bool */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request); /** * If the grant can respond to an authorization request this method should be called to validate the parameters of * the request. * * If the validation is successful an AuthorizationRequest object will be returned. This object can be safely * serialized in a user's session, and can be used during user authentication and authorization. * * @param ServerRequestInterface $request * * @return AuthorizationRequest */ public function validateAuthorizationRequest(ServerRequestInterface $request); /** * Once a user has authenticated and authorized the client the grant can complete the authorization request. * The AuthorizationRequest object's $userId property must be set to the authenticated user and the * $authorizationApproved property must reflect their desire to authorize or deny the client. * * @param AuthorizationRequest $authorizationRequest * * @return ResponseTypeInterface */ public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest); /** * The grant type should return true if it is able to respond to this request. * * For example most grant types will check that the $_POST['grant_type'] property matches it's identifier property. * * @param ServerRequestInterface $request * * @return bool */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request); /** * Set the client repository. * * @param ClientRepositoryInterface $clientRepository */ public function setClientRepository(ClientRepositoryInterface $clientRepository); /** * Set the access token repository. * * @param AccessTokenRepositoryInterface $accessTokenRepository */ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository); /** * Set the scope repository. * * @param ScopeRepositoryInterface $scopeRepository */ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository); /** * Set the default scope. * * @param string $scope */ public function setDefaultScope($scope); /** * Set the path to the private key. * * @param CryptKey $privateKey */ public function setPrivateKey(CryptKey $privateKey); /** * Set the encryption key * * @param string|Key|null $key */ public function setEncryptionKey($key = null); } src/Grant/ClientCredentialsGrant.php000064400000004133152665616300013522 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; /** * Client credentials grant class. */ class ClientCredentialsGrant extends AbstractGrant { /** * {@inheritdoc} */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { list($clientId) = $this->getClientCredentials($request); $client = $this->getClientEntityOrFail($clientId, $request); if (!$client->isConfidential()) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } // Validate request $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client); // Issue and persist access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes); // Send event to emitter $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); // Inject access token into response type $responseType->setAccessToken($accessToken); return $responseType; } /** * {@inheritdoc} */ public function getIdentifier() { return 'client_credentials'; } } src/Grant/RefreshTokenGrant.php000064400000011343152665616300012526 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; /** * Refresh token grant. */ class RefreshTokenGrant extends AbstractGrant { /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository */ public function __construct(RefreshTokenRepositoryInterface $refreshTokenRepository) { $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); } /** * {@inheritdoc} */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier()); $scopes = $this->validateScopes( $this->getRequestParameter( 'scope', $request, \implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken['scopes']) ) ); // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure // the request doesn't include any new scopes foreach ($scopes as $scope) { if (\in_array($scope->getIdentifier(), $oldRefreshToken['scopes'], true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } // Expire old tokens $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); if ($this->revokeRefreshTokens) { $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); } // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given if ($this->revokeRefreshTokens) { $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } } return $responseType; } /** * @param ServerRequestInterface $request * @param string $clientId * * @throws OAuthServerException * * @return array */ protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId) { $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request); if (!\is_string($encryptedRefreshToken)) { throw OAuthServerException::invalidRequest('refresh_token'); } // Validate refresh token try { $refreshToken = $this->decrypt($encryptedRefreshToken); } catch (Exception $e) { throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } $refreshTokenData = \json_decode($refreshToken, true); if ($refreshTokenData['client_id'] !== $clientId) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request)); throw OAuthServerException::invalidRefreshToken('Token is not linked to client'); } if ($refreshTokenData['expire_time'] < \time()) { throw OAuthServerException::invalidRefreshToken('Token has expired'); } if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) { throw OAuthServerException::invalidRefreshToken('Token has been revoked'); } return $refreshTokenData; } /** * {@inheritdoc} */ public function getIdentifier() { return 'refresh_token'; } } src/Grant/AbstractGrant.php000064400000045000152665616300011667 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use DateTimeImmutable; use Error; use Exception; use League\Event\EmitterAwareTrait; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator; 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\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use LogicException; use Psr\Http\Message\ServerRequestInterface; use TypeError; /** * Abstract grant class. */ abstract class AbstractGrant implements GrantTypeInterface { use EmitterAwareTrait, CryptTrait; const SCOPE_DELIMITER_STRING = ' '; const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; /** * @var ClientRepositoryInterface */ protected $clientRepository; /** * @var AccessTokenRepositoryInterface */ protected $accessTokenRepository; /** * @var ScopeRepositoryInterface */ protected $scopeRepository; /** * @var AuthCodeRepositoryInterface */ protected $authCodeRepository; /** * @var RefreshTokenRepositoryInterface */ protected $refreshTokenRepository; /** * @var UserRepositoryInterface */ protected $userRepository; /** * @var DateInterval */ protected $refreshTokenTTL; /** * @var CryptKey */ protected $privateKey; /** * @var string */ protected $defaultScope; /** * @var bool */ protected $revokeRefreshTokens; /** * @param ClientRepositoryInterface $clientRepository */ public function setClientRepository(ClientRepositoryInterface $clientRepository) { $this->clientRepository = $clientRepository; } /** * @param AccessTokenRepositoryInterface $accessTokenRepository */ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository) { $this->accessTokenRepository = $accessTokenRepository; } /** * @param ScopeRepositoryInterface $scopeRepository */ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository) { $this->scopeRepository = $scopeRepository; } /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository */ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) { $this->refreshTokenRepository = $refreshTokenRepository; } /** * @param AuthCodeRepositoryInterface $authCodeRepository */ public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository) { $this->authCodeRepository = $authCodeRepository; } /** * @param UserRepositoryInterface $userRepository */ public function setUserRepository(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } /** * {@inheritdoc} */ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) { $this->refreshTokenTTL = $refreshTokenTTL; } /** * Set the private key * * @param CryptKey $key */ public function setPrivateKey(CryptKey $key) { $this->privateKey = $key; } /** * @param string $scope */ public function setDefaultScope($scope) { $this->defaultScope = $scope; } /** * @param bool $revokeRefreshTokens */ public function revokeRefreshTokens(bool $revokeRefreshTokens) { $this->revokeRefreshTokens = $revokeRefreshTokens; } /** * Validate the client. * * @param ServerRequestInterface $request * * @throws OAuthServerException * * @return ClientEntityInterface */ protected function validateClient(ServerRequestInterface $request) { [$clientId, $clientSecret] = $this->getClientCredentials($request); if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } $client = $this->getClientEntityOrFail($clientId, $request); // If a redirect URI is provided ensure it matches what is pre-registered $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); if ($redirectUri !== null) { if (!\is_string($redirectUri)) { throw OAuthServerException::invalidRequest('redirect_uri'); } $this->validateRedirectUri($redirectUri, $client, $request); } return $client; } /** * Wrapper around ClientRepository::getClientEntity() that ensures we emit * an event and throw an exception if the repo doesn't return a client * entity. * * This is a bit of defensive coding because the interface contract * doesn't actually enforce non-null returns/exception-on-no-client so * getClientEntity might return null. By contrast, this method will * always either return a ClientEntityInterface or throw. * * @param string $clientId * @param ServerRequestInterface $request * * @return ClientEntityInterface */ protected function getClientEntityOrFail($clientId, ServerRequestInterface $request) { $client = $this->clientRepository->getClientEntity($clientId); if ($client instanceof ClientEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } return $client; } /** * Gets the client credentials from the request from the request body or * the Http Basic Authorization header * * @param ServerRequestInterface $request * * @return array */ protected function getClientCredentials(ServerRequestInterface $request) { [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request); $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); if (\is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); if ($clientSecret !== null && !\is_string($clientSecret)) { throw OAuthServerException::invalidRequest('client_secret'); } return [$clientId, $clientSecret]; } /** * Validate redirectUri from the request. * If a redirect URI is provided ensure it matches what is pre-registered * * @param string $redirectUri * @param ClientEntityInterface $client * @param ServerRequestInterface $request * * @throws OAuthServerException */ protected function validateRedirectUri( string $redirectUri, ClientEntityInterface $client, ServerRequestInterface $request ) { $validator = new RedirectUriValidator($client->getRedirectUri()); if (!$validator->validateRedirectUri($redirectUri)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } } /** * Validate scopes in the request. * * @param string|array|null $scopes * @param string $redirectUri * * @throws OAuthServerException * * @return ScopeEntityInterface[] */ public function validateScopes($scopes, $redirectUri = null) { if ($scopes === null) { $scopes = []; } elseif (\is_string($scopes)) { $scopes = $this->convertScopesQueryStringToArray($scopes); } if (!\is_array($scopes)) { throw OAuthServerException::invalidRequest('scope'); } $validScopes = []; foreach ($scopes as $scopeItem) { $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem); if ($scope instanceof ScopeEntityInterface === false) { throw OAuthServerException::invalidScope($scopeItem, $redirectUri); } $validScopes[] = $scope; } return $validScopes; } /** * Converts a scopes query string to an array to easily iterate for validation. * * @param string $scopes * * @return array */ private function convertScopesQueryStringToArray(string $scopes) { return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) { return $scope !== ''; }); } /** * Retrieve request parameter. * * @param string $parameter * @param ServerRequestInterface $request * @param mixed $default * * @return null|string */ protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null) { $requestParameters = (array) $request->getParsedBody(); return $requestParameters[$parameter] ?? $default; } /** * Retrieve HTTP Basic Auth credentials with the Authorization header * of a request. First index of the returned array is the username, * second is the password (so list() will work). If the header does * not exist, or is otherwise an invalid HTTP Basic header, return * [null, null]. * * @param ServerRequestInterface $request * * @return string[]|null[] */ protected function getBasicAuthCredentials(ServerRequestInterface $request) { if (!$request->hasHeader('Authorization')) { return [null, null]; } $header = $request->getHeader('Authorization')[0]; if (\strpos($header, 'Basic ') !== 0) { return [null, null]; } if (!($decoded = \base64_decode(\substr($header, 6)))) { return [null, null]; } if (\strpos($decoded, ':') === false) { return [null, null]; // HTTP Basic header without colon isn't valid } return \explode(':', $decoded, 2); } /** * Retrieve query string parameter. * * @param string $parameter * @param 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 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; } /** * Retrieve server parameter. * * @param string $parameter * @param ServerRequestInterface $request * @param mixed $default * * @return null|string */ protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null) { return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default; } /** * Issue an access token. * * @param DateInterval $accessTokenTTL * @param ClientEntityInterface $client * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * * @return AccessTokenEntityInterface */ protected function issueAccessToken( DateInterval $accessTokenTTL, ClientEntityInterface $client, $userIdentifier, array $scopes = [] ) { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); $accessToken->setPrivateKey($this->privateKey); while ($maxGenerationAttempts-- > 0) { $accessToken->setIdentifier($this->generateUniqueIdentifier()); try { $this->accessTokenRepository->persistNewAccessToken($accessToken); return $accessToken; } catch (UniqueTokenIdentifierConstraintViolationException $e) { if ($maxGenerationAttempts === 0) { throw $e; } } } } /** * Issue an auth code. * * @param DateInterval $authCodeTTL * @param ClientEntityInterface $client * @param string $userIdentifier * @param string|null $redirectUri * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * * @return AuthCodeEntityInterface */ protected function issueAuthCode( DateInterval $authCodeTTL, ClientEntityInterface $client, $userIdentifier, $redirectUri, array $scopes = [] ) { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $authCode = $this->authCodeRepository->getNewAuthCode(); $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL)); $authCode->setClient($client); $authCode->setUserIdentifier($userIdentifier); if ($redirectUri !== null) { $authCode->setRedirectUri($redirectUri); } foreach ($scopes as $scope) { $authCode->addScope($scope); } while ($maxGenerationAttempts-- > 0) { $authCode->setIdentifier($this->generateUniqueIdentifier()); try { $this->authCodeRepository->persistNewAuthCode($authCode); return $authCode; } catch (UniqueTokenIdentifierConstraintViolationException $e) { if ($maxGenerationAttempts === 0) { throw $e; } } } } /** * @param AccessTokenEntityInterface $accessToken * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * * @return RefreshTokenEntityInterface|null */ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); if ($refreshToken === null) { return null; } $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL)); $refreshToken->setAccessToken($accessToken); $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; while ($maxGenerationAttempts-- > 0) { $refreshToken->setIdentifier($this->generateUniqueIdentifier()); try { $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); return $refreshToken; } catch (UniqueTokenIdentifierConstraintViolationException $e) { if ($maxGenerationAttempts === 0) { throw $e; } } } } /** * Generate a new unique identifier. * * @param int $length * * @throws OAuthServerException * * @return string */ protected function generateUniqueIdentifier($length = 40) { try { return \bin2hex(\random_bytes($length)); // @codeCoverageIgnoreStart } catch (TypeError $e) { throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (Error $e) { throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (Exception $e) { // If you get this message, the CSPRNG failed hard. throw OAuthServerException::serverError('Could not generate a random string', $e); } // @codeCoverageIgnoreEnd } /** * {@inheritdoc} */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request) { $requestParameters = (array) $request->getParsedBody(); return ( \array_key_exists('grant_type', $requestParameters) && $requestParameters['grant_type'] === $this->getIdentifier() ); } /** * {@inheritdoc} */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request) { return false; } /** * {@inheritdoc} */ public function validateAuthorizationRequest(ServerRequestInterface $request) { throw new LogicException('This grant cannot validate an authorization request'); } /** * {@inheritdoc} */ public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { throw new LogicException('This grant cannot complete an authorization request'); } } src/Grant/AbstractAuthorizeGrant.php000064400000001321152665616300013560 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; abstract class AbstractAuthorizeGrant extends AbstractGrant { /** * @param string $uri * @param array $params * @param string $queryDelimiter * * @return string */ public function makeRedirectUri($uri, $params = [], $queryDelimiter = '?') { $uri .= (\strstr($uri, $queryDelimiter) === false) ? $queryDelimiter : '&'; return $uri . \http_build_query($params); } } src/Grant/ImplicitGrant.php000064400000016757152665616300011717 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; class ImplicitGrant extends AbstractAuthorizeGrant { /** * @var DateInterval */ private $accessTokenTTL; /** * @var string */ private $queryDelimiter; /** * @param DateInterval $accessTokenTTL * @param string $queryDelimiter */ public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#') { $this->accessTokenTTL = $accessTokenTTL; $this->queryDelimiter = $queryDelimiter; } /** * @param DateInterval $refreshTokenTTL * * @throw LogicException */ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) { throw new LogicException('The Implicit Grant does not return refresh tokens'); } /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository * * @throw LogicException */ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) { throw new LogicException('The Implicit Grant does not return refresh tokens'); } /** * {@inheritdoc} */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request) { return false; } /** * Return the grant identifier that can be used in matching up requests. * * @return string */ public function getIdentifier() { return 'implicit'; } /** * Respond to an incoming request. * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType * @param DateInterval $accessTokenTTL * * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { throw new LogicException('This grant does not used this method'); } /** * {@inheritdoc} */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request) { return ( isset($request->getQueryParams()['response_type']) && $request->getQueryParams()['response_type'] === 'token' && isset($request->getQueryParams()['client_id']) ); } /** * {@inheritdoc} */ public function validateAuthorizationRequest(ServerRequestInterface $request) { $clientId = $this->getQueryStringParameter( 'client_id', $request, $this->getServerParameter('PHP_AUTH_USER', $request) ); if (\is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } $client = $this->getClientEntityOrFail($clientId, $request); $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { if (!\is_string($redirectUri)) { throw OAuthServerException::invalidRequest('redirect_uri'); } $this->validateRedirectUri($redirectUri, $client, $request); } elseif (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1 || empty($client->getRedirectUri())) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } else { $redirectUri = \is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] : $client->getRedirectUri(); } $scopes = $this->validateScopes( $this->getQueryStringParameter('scope', $request, $this->defaultScope), $redirectUri ); $stateParameter = $this->getQueryStringParameter('state', $request); if ($stateParameter !== null && !\is_string($stateParameter)) { throw OAuthServerException::invalidRequest('state'); } $authorizationRequest = new AuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); $authorizationRequest->setRedirectUri($redirectUri); if ($stateParameter !== null) { $authorizationRequest->setState($stateParameter); } $authorizationRequest->setScopes($scopes); return $authorizationRequest; } /** * {@inheritdoc} */ public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) ? \is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] : $authorizationRequest->getClient()->getRedirectUri() : $authorizationRequest->getRedirectUri(); // The user approved the client, redirect them back with an access token if ($authorizationRequest->isAuthorizationApproved() === true) { // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes( $authorizationRequest->getScopes(), $this->getIdentifier(), $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier() ); $accessToken = $this->issueAccessToken( $this->accessTokenTTL, $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier(), $finalizedScopes ); $response = new RedirectResponse(); $response->setRedirectUri( $this->makeRedirectUri( $finalRedirectUri, [ 'access_token' => (string) $accessToken, 'token_type' => 'Bearer', 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - \time(), 'state' => $authorizationRequest->getState(), ], $this->queryDelimiter ) ); return $response; } // The user denied the client, redirect them back with an error throw OAuthServerException::accessDenied( 'The user denied the request', $this->makeRedirectUri( $finalRedirectUri, [ 'state' => $authorizationRequest->getState(), ] ) ); } } src/Grant/PasswordGrant.php000064400000007703152665616300011736 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\Grant; use DateInterval; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; /** * Password grant class. */ class PasswordGrant extends AbstractGrant { /** * @param UserRepositoryInterface $userRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository */ public function __construct( UserRepositoryInterface $userRepository, RefreshTokenRepositoryInterface $refreshTokenRepository ) { $this->setUserRepository($userRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); } /** * {@inheritdoc} */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $user = $this->validateUser($request, $client); // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier()); // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } return $responseType; } /** * @param ServerRequestInterface $request * @param ClientEntityInterface $client * * @throws OAuthServerException * * @return UserEntityInterface */ protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client) { $username = $this->getRequestParameter('username', $request); if (!\is_string($username)) { throw OAuthServerException::invalidRequest('username'); } $password = $this->getRequestParameter('password', $request); if (!\is_string($password)) { throw OAuthServerException::invalidRequest('password'); } $user = $this->userRepository->getUserEntityByUserCredentials( $username, $password, $this->getIdentifier(), $client ); if ($user instanceof UserEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidCredentials(); } return $user; } /** * {@inheritdoc} */ public function getIdentifier() { return 'password'; } } src/AuthorizationValidators/AuthorizationValidatorInterface.php000064400000001250152665616300021274 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\AuthorizationValidators; use Psr\Http\Message\ServerRequestInterface; interface AuthorizationValidatorInterface { /** * Determine the access token in the authorization header and append OAUth properties to the request * as attributes. * * @param ServerRequestInterface $request * * @return ServerRequestInterface */ public function validateAuthorization(ServerRequestInterface $request); } src/AuthorizationValidators/BearerTokenValidator.php000064400000011066152665616300017022 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\AuthorizationValidators; use DateTimeZone; use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Validation\Constraint\LooseValidAt; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Psr\Http\Message\ServerRequestInterface; class BearerTokenValidator implements AuthorizationValidatorInterface { use CryptTrait; /** * @var AccessTokenRepositoryInterface */ private $accessTokenRepository; /** * @var CryptKey */ protected $publicKey; /** * @var Configuration */ private $jwtConfiguration; /** * @var \DateInterval|null */ private $jwtValidAtDateLeeway; /** * @param AccessTokenRepositoryInterface $accessTokenRepository * @param \DateInterval|null $jwtValidAtDateLeeway */ public function __construct(AccessTokenRepositoryInterface $accessTokenRepository, ?\DateInterval $jwtValidAtDateLeeway = null) { $this->accessTokenRepository = $accessTokenRepository; $this->jwtValidAtDateLeeway = $jwtValidAtDateLeeway; } /** * Set the public key * * @param CryptKey $key */ public function setPublicKey(CryptKey $key) { $this->publicKey = $key; $this->initJwtConfiguration(); } /** * Initialise the JWT configuration. */ private function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forSymmetricSigner( new Sha256(), InMemory::plainText('empty', 'empty') ); $clock = new SystemClock(new DateTimeZone(\date_default_timezone_get())); $this->jwtConfiguration->setValidationConstraints( new LooseValidAt($clock, $this->jwtValidAtDateLeeway), new SignedWith( new Sha256(), InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') ) ); } /** * {@inheritdoc} */ public function validateAuthorization(ServerRequestInterface $request) { if ($request->hasHeader('authorization') === false) { throw OAuthServerException::accessDenied('Missing "Authorization" header'); } $header = $request->getHeader('authorization'); $jwt = \trim((string) \preg_replace('/^\s*Bearer\s/', '', $header[0])); try { // Attempt to parse the JWT $token = $this->jwtConfiguration->parser()->parse($jwt); } catch (\Lcobucci\JWT\Exception $exception) { throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); } try { // Attempt to validate the JWT $constraints = $this->jwtConfiguration->validationConstraints(); $this->jwtConfiguration->validator()->assert($token, ...$constraints); } catch (RequiredConstraintsViolated $exception) { throw OAuthServerException::accessDenied('Access token could not be verified', null, $exception); } $claims = $token->claims(); // Check if token has been revoked if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) { throw OAuthServerException::accessDenied('Access token has been revoked'); } // Return the request with additional attributes return $request ->withAttribute('oauth_access_token_id', $claims->get('jti')) ->withAttribute('oauth_client_id', $this->convertSingleRecordAudToString($claims->get('aud'))) ->withAttribute('oauth_user_id', $claims->get('sub')) ->withAttribute('oauth_scopes', $claims->get('scopes')); } /** * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt * * @param mixed $aud * * @return array|string */ private function convertSingleRecordAudToString($aud) { return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; } } src/ResponseTypes/AbstractResponseType.php000064400000002477152665616300015037 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; abstract class AbstractResponseType implements ResponseTypeInterface { use CryptTrait; /** * @var AccessTokenEntityInterface */ protected $accessToken; /** * @var RefreshTokenEntityInterface */ protected $refreshToken; /** * @var CryptKey */ protected $privateKey; /** * {@inheritdoc} */ public function setAccessToken(AccessTokenEntityInterface $accessToken) { $this->accessToken = $accessToken; } /** * {@inheritdoc} */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) { $this->refreshToken = $refreshToken; } /** * Set the private key * * @param CryptKey $key */ public function setPrivateKey(CryptKey $key) { $this->privateKey = $key; } } src/ResponseTypes/BearerTokenResponse.php000064400000005340152665616300014623 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use LogicException; use Psr\Http\Message\ResponseInterface; class BearerTokenResponse extends AbstractResponseType { /** * {@inheritdoc} */ public function generateHttpResponse(ResponseInterface $response) { $expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp(); $responseParams = [ 'token_type' => 'Bearer', 'expires_in' => $expireDateTime - \time(), 'access_token' => (string) $this->accessToken, ]; if ($this->refreshToken instanceof RefreshTokenEntityInterface) { $refreshTokenPayload = \json_encode([ 'client_id' => $this->accessToken->getClient()->getIdentifier(), 'refresh_token_id' => $this->refreshToken->getIdentifier(), 'access_token_id' => $this->accessToken->getIdentifier(), 'scopes' => $this->accessToken->getScopes(), 'user_id' => $this->accessToken->getUserIdentifier(), 'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(), ]); if ($refreshTokenPayload === false) { throw new LogicException('Error encountered JSON encoding the refresh token payload'); } $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); } $responseParams = \json_encode(\array_merge($this->getExtraParams($this->accessToken), $responseParams)); if ($responseParams === false) { throw new LogicException('Error encountered JSON encoding response parameters'); } $response = $response ->withStatus(200) ->withHeader('pragma', 'no-cache') ->withHeader('cache-control', 'no-store') ->withHeader('content-type', 'application/json; charset=UTF-8'); $response->getBody()->write($responseParams); return $response; } /** * Add custom fields to your Bearer Token response here, then override * AuthorizationServer::getResponseType() to pull in your version of * this class rather than the default. * * @param AccessTokenEntityInterface $accessToken * * @return array */ protected function getExtraParams(AccessTokenEntityInterface $accessToken) { return []; } } src/ResponseTypes/RedirectResponse.php000064400000001554152665616300014166 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\ResponseTypes; use Psr\Http\Message\ResponseInterface; class RedirectResponse extends AbstractResponseType { /** * @var string */ private $redirectUri; /** * @param string $redirectUri */ public function setRedirectUri($redirectUri) { $this->redirectUri = $redirectUri; } /** * @param ResponseInterface $response * * @return ResponseInterface */ public function generateHttpResponse(ResponseInterface $response) { return $response->withStatus(302)->withHeader('Location', $this->redirectUri); } } src/ResponseTypes/ResponseTypeInterface.php000064400000002160152665616300015161 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\ResponseTypes; use Defuse\Crypto\Key; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; interface ResponseTypeInterface { /** * @param AccessTokenEntityInterface $accessToken */ public function setAccessToken(AccessTokenEntityInterface $accessToken); /** * @param RefreshTokenEntityInterface $refreshToken */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken); /** * @param ResponseInterface $response * * @return ResponseInterface */ public function generateHttpResponse(ResponseInterface $response); /** * Set the encryption key * * @param string|Key|null $key */ public function setEncryptionKey($key = null); } src/RedirectUriValidators/RedirectUriValidatorInterface.php000064400000001017152665616300020237 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\RedirectUriValidators; interface RedirectUriValidatorInterface { /** * Validates the redirect uri. * * @param string $redirectUri * * @return bool Return true if valid, false otherwise */ public function validateRedirectUri($redirectUri); } src/RedirectUriValidators/RedirectUriValidator.php000064400000006043152665616300016422 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server\RedirectUriValidators; use League\Uri\Exceptions\SyntaxError; use League\Uri\Uri; class RedirectUriValidator implements RedirectUriValidatorInterface { /** * @var array */ private $allowedRedirectUris; /** * New validator instance for the given uri * * @param string|array $allowedRedirectUris */ public function __construct($allowedRedirectUri) { if (\is_string($allowedRedirectUri)) { $this->allowedRedirectUris = [$allowedRedirectUri]; } elseif (\is_array($allowedRedirectUri)) { $this->allowedRedirectUris = $allowedRedirectUri; } else { $this->allowedRedirectUris = []; } } /** * Validates the redirect uri. * * @param string $redirectUri * * @return bool Return true if valid, false otherwise */ public function validateRedirectUri($redirectUri) { if ($this->isLoopbackUri($redirectUri)) { return $this->matchUriExcludingPort($redirectUri); } return $this->matchExactUri($redirectUri); } /** * According to section 7.3 of rfc8252, loopback uris are: * - "http://127.0.0.1:{port}/{path}" for IPv4 * - "http://[::1]:{port}/{path}" for IPv6 * * @param string $redirectUri * * @return bool */ private function isLoopbackUri($redirectUri) { try { $uri = Uri::createFromString($redirectUri); } catch (SyntaxError $e) { return false; } return $uri->getScheme() === 'http' && (\in_array($uri->getHost(), ['127.0.0.1', '[::1]'], true)); } /** * Find an exact match among allowed uris * * @param string $redirectUri * * @return bool Return true if an exact match is found, false otherwise */ private function matchExactUri($redirectUri) { return \in_array($redirectUri, $this->allowedRedirectUris, true); } /** * Find a match among allowed uris, allowing for different port numbers * * @param string $redirectUri * * @return bool Return true if a match is found, false otherwise */ private function matchUriExcludingPort($redirectUri) { $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); foreach ($this->allowedRedirectUris as $allowedRedirectUri) { if ($parsedUrl === $this->parseUrlAndRemovePort($allowedRedirectUri)) { return true; } } return false; } /** * Parse an url like \parse_url, excluding the port * * @param string $url * * @return array */ private function parseUrlAndRemovePort($url) { $uri = Uri::createFromString($url); return (string) $uri->withPort(null); } } src/CryptTrait.php000064400000004135152665616300010166 0ustar00 * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ namespace League\OAuth2\Server; use Defuse\Crypto\Crypto; use Defuse\Crypto\Key; use Exception; use LogicException; trait CryptTrait { /** * @var string|Key|null */ protected $encryptionKey; /** * Encrypt data with encryptionKey. * * @param string $unencryptedData * * @throws LogicException * * @return string */ protected function encrypt($unencryptedData) { try { if ($this->encryptionKey instanceof Key) { return Crypto::encrypt($unencryptedData, $this->encryptionKey); } if (\is_string($this->encryptionKey)) { return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); } throw new LogicException('Encryption key not set when attempting to encrypt'); } catch (Exception $e) { throw new LogicException($e->getMessage(), 0, $e); } } /** * Decrypt data with encryptionKey. * * @param string $encryptedData * * @throws LogicException * * @return string */ protected function decrypt($encryptedData) { try { if ($this->encryptionKey instanceof Key) { return Crypto::decrypt($encryptedData, $this->encryptionKey); } if (\is_string($this->encryptionKey)) { return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); } throw new LogicException('Encryption key not set when attempting to decrypt'); } catch (Exception $e) { throw new LogicException($e->getMessage(), 0, $e); } } /** * Set the encryption key * * @param string|Key $key */ public function setEncryptionKey($key = null) { $this->encryptionKey = $key; } } composer.json000064400000004100152665616300007273 0ustar00{ "name": "league/oauth2-server", "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { "php": "^8.0", "ext-openssl": "*", "league/event": "^2.2", "league/uri": "^6.7 || ^7.0", "lcobucci/jwt": "^4.3 || ^5.0", "psr/http-message": "^1.0.1 || ^2.0", "defuse/php-encryption": "^2.3", "lcobucci/clock": "^2.2 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^9.6.6", "laminas/laminas-diactoros": "^3.0.0", "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" }, "repositories": [ { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git" } ], "keywords": [ "oauth", "oauth2", "oauth 2", "oauth 2.0", "server", "auth", "authorization", "authorisation", "authentication", "resource", "api", "auth", "protect", "secure" ], "authors": [ { "name": "Alex Bilbie", "email": "hello@alexbilbie.com", "homepage": "http://www.alexbilbie.com", "role": "Developer" }, { "name": "Andy Millington", "email": "andrew@noexceptions.io", "homepage": "https://www.noexceptions.io", "role": "Developer" } ], "replace": { "lncd/oauth2": "*", "league/oauth2server": "*" }, "autoload": { "psr-4": { "League\\OAuth2\\Server\\": "src/" } }, "autoload-dev": { "psr-4": { "LeagueTests\\": "tests/" } } }