add oauth2/oidc lib
This commit is contained in:
388
library/oauth2/src/OAuth2/Controller/AuthorizeController.php
Normal file
388
library/oauth2/src/OAuth2/Controller/AuthorizeController.php
Normal file
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\Storage\ClientInterface;
|
||||
use OAuth2\ScopeInterface;
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
use OAuth2\Scope;
|
||||
|
||||
/**
|
||||
* @see OAuth2\Controller\AuthorizeControllerInterface
|
||||
*/
|
||||
class AuthorizeController implements AuthorizeControllerInterface
|
||||
{
|
||||
private $scope;
|
||||
private $state;
|
||||
private $client_id;
|
||||
private $redirect_uri;
|
||||
private $response_type;
|
||||
|
||||
protected $clientStorage;
|
||||
protected $responseTypes;
|
||||
protected $config;
|
||||
protected $scopeUtil;
|
||||
|
||||
/**
|
||||
* @param OAuth2\Storage\ClientInterface $clientStorage REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information
|
||||
* @param array $responseTypes OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array
|
||||
* keys are "code" and "token"
|
||||
* @param array $config OPTIONAL Configuration options for the server
|
||||
* <code>
|
||||
* $config = array(
|
||||
* 'allow_implicit' => false, // if the controller should allow the "implicit" grant type
|
||||
* 'enforce_state' => true // if the controller should require the "state" parameter
|
||||
* 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter
|
||||
* 'redirect_status_code' => 302, // HTTP status code to use for redirect responses
|
||||
* );
|
||||
* </code>
|
||||
* @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
|
||||
*/
|
||||
public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null)
|
||||
{
|
||||
$this->clientStorage = $clientStorage;
|
||||
$this->responseTypes = $responseTypes;
|
||||
$this->config = array_merge(array(
|
||||
'allow_implicit' => false,
|
||||
'enforce_state' => true,
|
||||
'require_exact_redirect_uri' => true,
|
||||
'redirect_status_code' => 302,
|
||||
), $config);
|
||||
|
||||
if (is_null($scopeUtil)) {
|
||||
$scopeUtil = new Scope();
|
||||
}
|
||||
$this->scopeUtil = $scopeUtil;
|
||||
}
|
||||
|
||||
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
|
||||
{
|
||||
if (!is_bool($is_authorized)) {
|
||||
throw new \InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.');
|
||||
}
|
||||
|
||||
// We repeat this, because we need to re-validate. The request could be POSTed
|
||||
// by a 3rd-party (because we are not internally enforcing NONCEs, etc)
|
||||
if (!$this->validateAuthorizeRequest($request, $response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no redirect_uri is passed in the request, use client's registered one
|
||||
if (empty($this->redirect_uri)) {
|
||||
$clientData = $this->clientStorage->getClientDetails($this->client_id);
|
||||
$registered_redirect_uri = $clientData['redirect_uri'];
|
||||
}
|
||||
|
||||
// the user declined access to the client's application
|
||||
if ($is_authorized === false) {
|
||||
$redirect_uri = $this->redirect_uri ?: $registered_redirect_uri;
|
||||
$this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// build the parameters to set in the redirect URI
|
||||
if (!$params = $this->buildAuthorizeParameters($request, $response, $user_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authResult = $this->responseTypes[$this->response_type]->getAuthorizeResponse($params, $user_id);
|
||||
|
||||
list($redirect_uri, $uri_params) = $authResult;
|
||||
|
||||
if (empty($redirect_uri) && !empty($registered_redirect_uri)) {
|
||||
$redirect_uri = $registered_redirect_uri;
|
||||
}
|
||||
|
||||
$uri = $this->buildUri($redirect_uri, $uri_params);
|
||||
|
||||
// return redirect response
|
||||
$response->setRedirect($this->config['redirect_status_code'], $uri);
|
||||
}
|
||||
|
||||
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
|
||||
{
|
||||
$error = 'access_denied';
|
||||
$error_message = 'The user denied access to your application';
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message);
|
||||
}
|
||||
|
||||
/*
|
||||
* We have made this protected so this class can be extended to add/modify
|
||||
* these parameters
|
||||
*/
|
||||
protected function buildAuthorizeParameters($request, $response, $user_id)
|
||||
{
|
||||
// @TODO: we should be explicit with this in the future
|
||||
$params = array(
|
||||
'scope' => $this->scope,
|
||||
'state' => $this->state,
|
||||
'client_id' => $this->client_id,
|
||||
'redirect_uri' => $this->redirect_uri,
|
||||
'response_type' => $this->response_type,
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
// Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI)
|
||||
if (!$client_id = $request->query('client_id', $request->request('client_id'))) {
|
||||
// We don't have a good URI to use
|
||||
$response->setError(400, 'invalid_client', "No client id supplied");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get client details
|
||||
if (!$clientData = $this->clientStorage->getClientDetails($client_id)) {
|
||||
$response->setError(400, 'invalid_client', 'The client id supplied is invalid');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$registered_redirect_uri = isset($clientData['redirect_uri']) ? $clientData['redirect_uri'] : '';
|
||||
|
||||
// Make sure a valid redirect_uri was supplied. If specified, it must match the clientData URI.
|
||||
// @see http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
// @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1
|
||||
// @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1
|
||||
if ($supplied_redirect_uri = $request->query('redirect_uri', $request->request('redirect_uri'))) {
|
||||
// validate there is no fragment supplied
|
||||
$parts = parse_url($supplied_redirect_uri);
|
||||
if (isset($parts['fragment']) && $parts['fragment']) {
|
||||
$response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate against the registered redirect uri(s) if available
|
||||
if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) {
|
||||
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2');
|
||||
|
||||
return false;
|
||||
}
|
||||
$redirect_uri = $supplied_redirect_uri;
|
||||
} else {
|
||||
// use the registered redirect_uri if none has been supplied, if possible
|
||||
if (!$registered_redirect_uri) {
|
||||
$response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count(explode(' ', $registered_redirect_uri)) > 1) {
|
||||
$response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3');
|
||||
|
||||
return false;
|
||||
}
|
||||
$redirect_uri = $registered_redirect_uri;
|
||||
}
|
||||
|
||||
// Select the redirect URI
|
||||
$response_type = $request->query('response_type', $request->request('response_type'));
|
||||
|
||||
// for multiple-valued response types - make them alphabetical
|
||||
if (false !== strpos($response_type, ' ')) {
|
||||
$types = explode(' ', $response_type);
|
||||
sort($types);
|
||||
$response_type = ltrim(implode(' ', $types));
|
||||
}
|
||||
|
||||
$state = $request->query('state', $request->request('state'));
|
||||
|
||||
// type and client_id are required
|
||||
if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
|
||||
if (!isset($this->responseTypes['code'])) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) {
|
||||
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied');
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!$this->config['allow_implicit']) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate requested scope if it exists
|
||||
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
|
||||
|
||||
if ($requestedScope) {
|
||||
// restrict scope by client specific scope if applicable,
|
||||
// otherwise verify the scope exists
|
||||
$clientScope = $this->clientStorage->getClientScope($client_id);
|
||||
if ((empty($clientScope) && !$this->scopeUtil->scopeExists($requestedScope))
|
||||
|| (!empty($clientScope) && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// use a globally-defined default scope
|
||||
$defaultScope = $this->scopeUtil->getDefaultScope($client_id);
|
||||
|
||||
if (false === $defaultScope) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$requestedScope = $defaultScope;
|
||||
}
|
||||
|
||||
// Validate state parameter exists (if configured to enforce this)
|
||||
if ($this->config['enforce_state'] && !$state) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// save the input data and return true
|
||||
$this->scope = $requestedScope;
|
||||
$this->state = $state;
|
||||
$this->client_id = $client_id;
|
||||
// Only save the SUPPLIED redirect URI (@see http://tools.ietf.org/html/rfc6749#section-4.1.3)
|
||||
$this->redirect_uri = $supplied_redirect_uri;
|
||||
$this->response_type = $response_type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the absolute URI based on supplied URI and parameters.
|
||||
*
|
||||
* @param $uri An absolute URI.
|
||||
* @param $params Parameters to be append as GET.
|
||||
*
|
||||
* @return
|
||||
* An absolute URI with supplied parameters.
|
||||
*
|
||||
* @ingroup oauth2_section_4
|
||||
*/
|
||||
private function buildUri($uri, $params)
|
||||
{
|
||||
$parse_url = parse_url($uri);
|
||||
|
||||
// Add our params to the parsed uri
|
||||
foreach ($params as $k => $v) {
|
||||
if (isset($parse_url[$k])) {
|
||||
$parse_url[$k] .= "&" . http_build_query($v, '', '&');
|
||||
} else {
|
||||
$parse_url[$k] = http_build_query($v, '', '&');
|
||||
}
|
||||
}
|
||||
|
||||
// Put humpty dumpty back together
|
||||
return
|
||||
((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
|
||||
. ((isset($parse_url["user"])) ? $parse_url["user"]
|
||||
. ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
|
||||
. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
|
||||
. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
|
||||
. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
|
||||
. ((isset($parse_url["query"]) && !empty($parse_url['query'])) ? "?" . $parse_url["query"] : "")
|
||||
. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "")
|
||||
;
|
||||
}
|
||||
|
||||
protected function getValidResponseTypes()
|
||||
{
|
||||
return array(
|
||||
self::RESPONSE_TYPE_ACCESS_TOKEN,
|
||||
self::RESPONSE_TYPE_AUTHORIZATION_CODE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method for validating redirect URI supplied
|
||||
*
|
||||
* @param string $inputUri The submitted URI to be validated
|
||||
* @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to
|
||||
* allow for multiple URIs
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
*/
|
||||
protected function validateRedirectUri($inputUri, $registeredUriString)
|
||||
{
|
||||
if (!$inputUri || !$registeredUriString) {
|
||||
return false; // if either one is missing, assume INVALID
|
||||
}
|
||||
|
||||
$registered_uris = explode(' ', $registeredUriString);
|
||||
foreach ($registered_uris as $registered_uri) {
|
||||
if ($this->config['require_exact_redirect_uri']) {
|
||||
// the input uri is validated against the registered uri using exact match
|
||||
if (strcmp($inputUri, $registered_uri) === 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$registered_uri_length = strlen($registered_uri);
|
||||
if ($registered_uri_length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the input uri is validated against the registered uri using case-insensitive match of the initial string
|
||||
// i.e. additional query parameters may be applied
|
||||
if (strcasecmp(substr($inputUri, 0, $registered_uri_length), $registered_uri) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods to access the parameters derived from the validated request
|
||||
*/
|
||||
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function getClientId()
|
||||
{
|
||||
return $this->client_id;
|
||||
}
|
||||
|
||||
public function getRedirectUri()
|
||||
{
|
||||
return $this->redirect_uri;
|
||||
}
|
||||
|
||||
public function getResponseType()
|
||||
{
|
||||
return $this->response_type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This controller is called when a user should be authorized
|
||||
* by an authorization server. As OAuth2 does not handle
|
||||
* authorization directly, this controller ensures the request is valid, but
|
||||
* requires the application to determine the value of $is_authorized
|
||||
*
|
||||
* ex:
|
||||
* > $user_id = $this->somehowDetermineUserId();
|
||||
* > $is_authorized = $this->somehowDetermineUserAuthorization();
|
||||
* > $response = new OAuth2\Response();
|
||||
* > $authorizeController->handleAuthorizeRequest(
|
||||
* > OAuth2\Request::createFromGlobals(),
|
||||
* > $response,
|
||||
* > $is_authorized,
|
||||
* > $user_id);
|
||||
* > $response->send();
|
||||
*
|
||||
*/
|
||||
interface AuthorizeControllerInterface
|
||||
{
|
||||
/**
|
||||
* List of possible authentication response types.
|
||||
* The "authorization_code" mechanism exclusively supports 'code'
|
||||
* and the "implicit" mechanism exclusively supports 'token'.
|
||||
*
|
||||
* @var string
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4.2.1
|
||||
*/
|
||||
const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code';
|
||||
const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
|
||||
|
||||
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
|
||||
|
||||
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
111
library/oauth2/src/OAuth2/Controller/ResourceController.php
Normal file
111
library/oauth2/src/OAuth2/Controller/ResourceController.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\TokenType\TokenTypeInterface;
|
||||
use OAuth2\Storage\AccessTokenInterface;
|
||||
use OAuth2\ScopeInterface;
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
use OAuth2\Scope;
|
||||
|
||||
/**
|
||||
* @see OAuth2\Controller\ResourceControllerInterface
|
||||
*/
|
||||
class ResourceController implements ResourceControllerInterface
|
||||
{
|
||||
private $token;
|
||||
|
||||
protected $tokenType;
|
||||
protected $tokenStorage;
|
||||
protected $config;
|
||||
protected $scopeUtil;
|
||||
|
||||
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ScopeInterface $scopeUtil = null)
|
||||
{
|
||||
$this->tokenType = $tokenType;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
|
||||
$this->config = array_merge(array(
|
||||
'www_realm' => 'Service',
|
||||
), $config);
|
||||
|
||||
if (is_null($scopeUtil)) {
|
||||
$scopeUtil = new Scope();
|
||||
}
|
||||
$this->scopeUtil = $scopeUtil;
|
||||
}
|
||||
|
||||
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
|
||||
{
|
||||
$token = $this->getAccessTokenData($request, $response);
|
||||
|
||||
// Check if we have token data
|
||||
if (is_null($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check scope, if provided
|
||||
* If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
|
||||
* @see http://tools.ietf.org/html/rfc6750#section-3.1
|
||||
*/
|
||||
if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
|
||||
$response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
|
||||
$response->addHttpHeaders(array(
|
||||
'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
|
||||
$this->tokenType->getTokenType(),
|
||||
$this->config['www_realm'],
|
||||
$scope,
|
||||
$response->getParameter('error'),
|
||||
$response->getParameter('error_description')
|
||||
)
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow retrieval of the token
|
||||
$this->token = $token;
|
||||
|
||||
return (bool) $token;
|
||||
}
|
||||
|
||||
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
// Get the token parameter
|
||||
if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) {
|
||||
// Get the stored token data (from the implementing subclass)
|
||||
// Check we have a well formed token
|
||||
// Check token expiration (expires is a mandatory paramter)
|
||||
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
|
||||
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
|
||||
} elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
|
||||
$response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
|
||||
} elseif (time() > $token["expires"]) {
|
||||
$response->setError(401, 'expired_token', 'The access token provided has expired');
|
||||
} else {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
$authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']);
|
||||
|
||||
if ($error = $response->getParameter('error')) {
|
||||
$authHeader = sprintf('%s, error="%s"', $authHeader, $error);
|
||||
if ($error_description = $response->getParameter('error_description')) {
|
||||
$authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description);
|
||||
}
|
||||
}
|
||||
|
||||
$response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// convenience method to allow retrieval of the token
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This controller is called when a "resource" is requested.
|
||||
* call verifyResourceRequest in order to determine if the request
|
||||
* contains a valid token.
|
||||
*
|
||||
* ex:
|
||||
* > if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
|
||||
* > $response->send(); // authorization failed
|
||||
* > die();
|
||||
* > }
|
||||
* > return json_encode($resource); // valid token! Send the stuff!
|
||||
*
|
||||
*/
|
||||
interface ResourceControllerInterface
|
||||
{
|
||||
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
|
||||
|
||||
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
278
library/oauth2/src/OAuth2/Controller/TokenController.php
Normal file
278
library/oauth2/src/OAuth2/Controller/TokenController.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\ResponseType\AccessTokenInterface;
|
||||
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
|
||||
use OAuth2\GrantType\GrantTypeInterface;
|
||||
use OAuth2\ScopeInterface;
|
||||
use OAuth2\Scope;
|
||||
use OAuth2\Storage\ClientInterface;
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @see OAuth2\Controller\TokenControllerInterface
|
||||
*/
|
||||
class TokenController implements TokenControllerInterface
|
||||
{
|
||||
protected $accessToken;
|
||||
protected $grantTypes;
|
||||
protected $clientAssertionType;
|
||||
protected $scopeUtil;
|
||||
protected $clientStorage;
|
||||
|
||||
public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null)
|
||||
{
|
||||
if (is_null($clientAssertionType)) {
|
||||
foreach ($grantTypes as $grantType) {
|
||||
if (!$grantType instanceof ClientAssertionTypeInterface) {
|
||||
throw new \InvalidArgumentException('You must supply an instance of OAuth2\ClientAssertionType\ClientAssertionTypeInterface or only use grant types which implement OAuth2\ClientAssertionType\ClientAssertionTypeInterface');
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->clientAssertionType = $clientAssertionType;
|
||||
$this->accessToken = $accessToken;
|
||||
$this->clientStorage = $clientStorage;
|
||||
foreach ($grantTypes as $grantType) {
|
||||
$this->addGrantType($grantType);
|
||||
}
|
||||
|
||||
if (is_null($scopeUtil)) {
|
||||
$scopeUtil = new Scope();
|
||||
}
|
||||
$this->scopeUtil = $scopeUtil;
|
||||
}
|
||||
|
||||
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if ($token = $this->grantAccessToken($request, $response)) {
|
||||
// @see http://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// server MUST disable caching in headers when tokens are involved
|
||||
$response->setStatusCode(200);
|
||||
$response->addParameters($token);
|
||||
$response->addHttpHeaders(array(
|
||||
'Cache-Control' => 'no-store',
|
||||
'Pragma' => 'no-cache',
|
||||
'Content-Type' => 'application/json'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant or deny a requested access token.
|
||||
* This would be called from the "/token" endpoint as defined in the spec.
|
||||
* You can call your endpoint whatever you want.
|
||||
*
|
||||
* @param $request - RequestInterface
|
||||
* Request object to grant access token
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws LogicException
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-10.6
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||
*
|
||||
* @ingroup oauth2_section_4
|
||||
*/
|
||||
public function grantAccessToken(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
|
||||
$response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2');
|
||||
$response->addHttpHeaders(array('Allow' => 'POST'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine grant type from request
|
||||
* and validate the request for that grant type
|
||||
*/
|
||||
if (!$grantTypeIdentifier = $request->request('grant_type')) {
|
||||
$response->setError(400, 'invalid_request', 'The grant type was not specified in the request');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($this->grantTypes[$grantTypeIdentifier])) {
|
||||
/* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */
|
||||
$response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$grantType = $this->grantTypes[$grantTypeIdentifier];
|
||||
|
||||
/**
|
||||
* Retrieve the client information from the request
|
||||
* ClientAssertionTypes allow for grant types which also assert the client data
|
||||
* in which case ClientAssertion is handled in the validateRequest method
|
||||
*
|
||||
* @see OAuth2\GrantType\JWTBearer
|
||||
* @see OAuth2\GrantType\ClientCredentials
|
||||
*/
|
||||
if (!$grantType instanceof ClientAssertionTypeInterface) {
|
||||
if (!$this->clientAssertionType->validateRequest($request, $response)) {
|
||||
return null;
|
||||
}
|
||||
$clientId = $this->clientAssertionType->getClientId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the grant type information from the request
|
||||
* The GrantTypeInterface object handles all validation
|
||||
* If the object is an instance of ClientAssertionTypeInterface,
|
||||
* That logic is handled here as well
|
||||
*/
|
||||
if (!$grantType->validateRequest($request, $response)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($grantType instanceof ClientAssertionTypeInterface) {
|
||||
$clientId = $grantType->getClientId();
|
||||
} else {
|
||||
// validate the Client ID (if applicable)
|
||||
if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) {
|
||||
$response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the client can use the requested grant type
|
||||
*/
|
||||
if (!$this->clientStorage->checkRestrictedGrantType($clientId, $grantTypeIdentifier)) {
|
||||
$response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the scope of the token
|
||||
*
|
||||
* requestedScope - the scope specified in the token request
|
||||
* availableScope - the scope associated with the grant type
|
||||
* ex: in the case of the "Authorization Code" grant type,
|
||||
* the scope is specified in the authorize request
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-3.3
|
||||
*/
|
||||
|
||||
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
|
||||
$availableScope = $grantType->getScope();
|
||||
|
||||
if ($requestedScope) {
|
||||
// validate the requested scope
|
||||
if ($availableScope) {
|
||||
if (!$this->scopeUtil->checkScope($requestedScope, $availableScope)) {
|
||||
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this request');
|
||||
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// validate the client has access to this scope
|
||||
if ($clientScope = $this->clientStorage->getClientScope($clientId)) {
|
||||
if (!$this->scopeUtil->checkScope($requestedScope, $clientScope)) {
|
||||
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this client');
|
||||
|
||||
return false;
|
||||
}
|
||||
} elseif (!$this->scopeUtil->scopeExists($requestedScope)) {
|
||||
$response->setError(400, 'invalid_scope', 'An unsupported scope was requested');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} elseif ($availableScope) {
|
||||
// use the scope associated with this grant type
|
||||
$requestedScope = $availableScope;
|
||||
} else {
|
||||
// use a globally-defined default scope
|
||||
$defaultScope = $this->scopeUtil->getDefaultScope($clientId);
|
||||
|
||||
// "false" means default scopes are not allowed
|
||||
if (false === $defaultScope) {
|
||||
$response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$requestedScope = $defaultScope;
|
||||
}
|
||||
|
||||
return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* addGrantType
|
||||
*
|
||||
* @param grantType - OAuth2\GrantTypeInterface
|
||||
* the grant type to add for the specified identifier
|
||||
* @param identifier - string
|
||||
* a string passed in as "grant_type" in the response that will call this grantType
|
||||
*/
|
||||
public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
|
||||
{
|
||||
if (is_null($identifier) || is_numeric($identifier)) {
|
||||
$identifier = $grantType->getQuerystringIdentifier();
|
||||
}
|
||||
|
||||
$this->grantTypes[$identifier] = $grantType;
|
||||
}
|
||||
|
||||
public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if ($this->revokeToken($request, $response)) {
|
||||
$response->setStatusCode(200);
|
||||
$response->addParameters(array('revoked' => true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a refresh or access token. Returns true on success and when tokens are invalid
|
||||
*
|
||||
* Note: invalid tokens do not cause an error response since the client
|
||||
* cannot handle such an error in a reasonable way. Moreover, the
|
||||
* purpose of the revocation request, invalidating the particular token,
|
||||
* is already achieved.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool|null
|
||||
*/
|
||||
public function revokeToken(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
|
||||
$response->setError(405, 'invalid_request', 'The request method must be POST when revoking an access token', '#section-3.2');
|
||||
$response->addHttpHeaders(array('Allow' => 'POST'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$token_type_hint = $request->request('token_type_hint');
|
||||
if (!in_array($token_type_hint, array(null, 'access_token', 'refresh_token'), true)) {
|
||||
$response->setError(400, 'invalid_request', 'Token type hint must be either \'access_token\' or \'refresh_token\'');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = $request->request('token');
|
||||
if ($token === null) {
|
||||
$response->setError(400, 'invalid_request', 'Missing token parameter to revoke');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// @todo remove this check for v2.0
|
||||
if (!method_exists($this->accessToken, 'revokeToken')) {
|
||||
$class = get_class($this->accessToken);
|
||||
throw new \RuntimeException("AccessToken {$class} does not implement required revokeToken method");
|
||||
}
|
||||
|
||||
$this->accessToken->revokeToken($token, $token_type_hint);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This controller is called when a token is being requested.
|
||||
* it is called to handle all grant types the application supports.
|
||||
* It also validates the client's credentials
|
||||
*
|
||||
* ex:
|
||||
* > $tokenController->handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response());
|
||||
* > $response->send();
|
||||
*
|
||||
*/
|
||||
interface TokenControllerInterface
|
||||
{
|
||||
/**
|
||||
* handleTokenRequest
|
||||
*
|
||||
* @param $request
|
||||
* OAuth2\RequestInterface - The current http request
|
||||
* @param $response
|
||||
* OAuth2\ResponseInterface - An instance of OAuth2\ResponseInterface to contain the response data
|
||||
*
|
||||
*/
|
||||
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response);
|
||||
|
||||
public function grantAccessToken(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
Reference in New Issue
Block a user