归档
This commit is contained in:
456
includes/OAuth2/Controller/AuthorizeController.php
Normal file
456
includes/OAuth2/Controller/AuthorizeController.php
Normal file
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
namespace OAuth2\Controller;
|
||||
|
||||
use OAuth2\Storage\ClientInterface;
|
||||
use OAuth2\ScopeInterface;
|
||||
use OAuth2\RequestInterface;
|
||||
use OAuth2\ResponseInterface;
|
||||
use OAuth2\Scope;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @see AuthorizeControllerInterface
|
||||
*/
|
||||
class AuthorizeController implements AuthorizeControllerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $scope;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $client_id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $redirect_uri;
|
||||
|
||||
/**
|
||||
* The response type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $response_type;
|
||||
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
protected $clientStorage;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $responseTypes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var ScopeInterface
|
||||
*/
|
||||
protected $scopeUtil;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param 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:
|
||||
* @param ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
|
||||
* @code
|
||||
* $config = array(
|
||||
* 'allow_implicit' => false, // 如果控制器应允许“隐式”授权类型
|
||||
* 'enforce_state' => true // 控制器是否需要“状态”参数
|
||||
* 'require_exact_redirect_uri' => true, // 如果控制器需要“redirect\u uri”参数的精确匹配
|
||||
* 'redirect_status_code' => 302, // 用于重定向响应的HTTP状态代码
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
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' => false,
|
||||
'require_exact_redirect_uri' => false,
|
||||
'redirect_status_code' => 302,
|
||||
), $config);
|
||||
|
||||
if (is_null($scopeUtil)) {
|
||||
$scopeUtil = new Scope();
|
||||
}
|
||||
$this->scopeUtil = $scopeUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization request
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param boolean $is_authorized
|
||||
* @param mixed $user_id
|
||||
* @return mixed|void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
|
||||
{
|
||||
if (!is_bool($is_authorized)) {
|
||||
throw new InvalidArgumentException('参数“is_authorized”必须是布尔值。此方法必须知道用户是否已授予对客户端的访问权限。');
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set not authorized response
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param string $redirect_uri
|
||||
* @param mixed $user_id
|
||||
*/
|
||||
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
|
||||
{
|
||||
$error = 'access_denied';
|
||||
$error_message = '用户拒绝访问您的应用程序';
|
||||
$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
|
||||
*
|
||||
* @TODO: add dependency injection for the parameters in this method
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param mixed $user_id
|
||||
* @return array
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the OAuth request
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
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', "未提供客户端id");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get client details
|
||||
if (!$clientData = $this->clientStorage->getClientDetails($client_id)) {
|
||||
$response->setError(400, 'invalid_client', '提供的客户端id无效');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$registered_redirect_uri = isset($clientData['redirect_uri']) ? $clientData['redirect_uri'] : '';
|
||||
|
||||
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', '重定向URI不能包含片段');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($parts['host']!=$registered_redirect_uri){
|
||||
$response->setError(400, 'redirect_uri_error', '重定向URI不匹配', '#section-3.1.2');
|
||||
return false;
|
||||
}
|
||||
$redirect_uri = $supplied_redirect_uri;
|
||||
} else {
|
||||
$response->setError(400, 'redirect_uri_miss', '重定向URI缺失', '#section-3.1.2');
|
||||
return false;
|
||||
}
|
||||
|
||||
$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', '无效或缺少响应类型', 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', '不支持授权代码授予类型', null);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) {
|
||||
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', '此客户端id的授权类型未经授权', null);
|
||||
|
||||
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', '请求了不受支持的作用域', 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', '随机字符串是必须要有的');
|
||||
|
||||
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 string $uri An absolute URI.
|
||||
* @param array $params Parameters to be append as GET.
|
||||
*
|
||||
* @return string
|
||||
* 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 the uri 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
|
||||
* @return bool
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||
*/
|
||||
protected function validateRedirectUri($inputUri, $registeredUriString)
|
||||
{
|
||||
if (!$inputUri || !$registeredUriString) {
|
||||
return false; // 如果缺少其中一个,则假定无效
|
||||
}
|
||||
|
||||
$registered_uris = preg_split('/\s+/', $registeredUriString);
|
||||
foreach ($registered_uris as $registered_uri) {
|
||||
if ($this->config['require_exact_redirect_uri']) {
|
||||
// 使用精确匹配根据注册的uri验证输入uri
|
||||
if (strcmp($inputUri, $registered_uri) === 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$registered_uri_length = strlen($registered_uri);
|
||||
if ($registered_uri_length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用不区分大小写的初始字符串匹配,根据注册的uri验证输入uri
|
||||
// i、 e.可以应用其他查询参数
|
||||
if (strcasecmp(substr($inputUri, 0, $registered_uri_length), $registered_uri) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to access the scope
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to access the state
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to access the client id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getClientId()
|
||||
{
|
||||
return $this->client_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to access the redirect url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRedirectUri()
|
||||
{
|
||||
return $this->redirect_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to access the response type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResponseType()
|
||||
{
|
||||
return $this->response_type;
|
||||
}
|
||||
}
|
||||
58
includes/OAuth2/Controller/AuthorizeControllerInterface.php
Normal file
58
includes/OAuth2/Controller/AuthorizeControllerInterface.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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
|
||||
*
|
||||
* @code
|
||||
* $user_id = $this->somehowDetermineUserId();
|
||||
* $is_authorized = $this->somehowDetermineUserAuthorization();
|
||||
* $response = new OAuth2\Response();
|
||||
* $authorizeController->handleAuthorizeRequest(
|
||||
* OAuth2\Request::createFromGlobals(),
|
||||
* $response,
|
||||
* $is_authorized,
|
||||
* $user_id
|
||||
* );
|
||||
* $response->send();
|
||||
* @endcode
|
||||
*/
|
||||
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';
|
||||
|
||||
/**
|
||||
* Handle the OAuth request
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param $is_authorized
|
||||
* @param null $user_id
|
||||
* @return mixed
|
||||
*/
|
||||
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return bool
|
||||
*/
|
||||
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
156
includes/OAuth2/Controller/ResourceController.php
Normal file
156
includes/OAuth2/Controller/ResourceController.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?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 ResourceControllerInterface
|
||||
*/
|
||||
class ResourceController implements ResourceControllerInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $token;
|
||||
|
||||
/**
|
||||
* @var TokenTypeInterface
|
||||
*/
|
||||
protected $tokenType;
|
||||
|
||||
/**
|
||||
* @var AccessTokenInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var ScopeInterface
|
||||
*/
|
||||
protected $scopeUtil;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param TokenTypeInterface $tokenType
|
||||
* @param AccessTokenInterface $tokenStorage
|
||||
* @param array $config
|
||||
* @param ScopeInterface $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the resource request
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @param null $scope
|
||||
* @return bool
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token data.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
* @return array|null
|
||||
*/
|
||||
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, 'invalid_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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
41
includes/OAuth2/Controller/ResourceControllerInterface.php
Normal file
41
includes/OAuth2/Controller/ResourceControllerInterface.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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.
|
||||
*
|
||||
* @code
|
||||
* if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
|
||||
* $response->send(); // authorization failed
|
||||
* die();
|
||||
* }
|
||||
* return json_encode($resource); // valid token! Send the stuff!
|
||||
* @endcode
|
||||
*/
|
||||
interface ResourceControllerInterface
|
||||
{
|
||||
/**
|
||||
* Verify the resource request
|
||||
*
|
||||
* @param RequestInterface $request - Request object
|
||||
* @param ResponseInterface $response - Response object
|
||||
* @param string $scope
|
||||
* @return mixed
|
||||
*/
|
||||
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
|
||||
|
||||
/**
|
||||
* Get access token data.
|
||||
*
|
||||
* @param RequestInterface $request - Request object
|
||||
* @param ResponseInterface $response - Response object
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
333
includes/OAuth2/Controller/TokenController.php
Normal file
333
includes/OAuth2/Controller/TokenController.php
Normal file
@@ -0,0 +1,333 @@
|
||||
<?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;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @see TokenControllerInterface
|
||||
*/
|
||||
class TokenController implements TokenControllerInterface
|
||||
{
|
||||
/**
|
||||
* @var AccessTokenInterface
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/**
|
||||
* @var array<GrantTypeInterface>
|
||||
*/
|
||||
protected $grantTypes;
|
||||
|
||||
/**
|
||||
* @var ClientAssertionTypeInterface
|
||||
*/
|
||||
protected $clientAssertionType;
|
||||
|
||||
/**
|
||||
* @var ScopeInterface
|
||||
*/
|
||||
protected $scopeUtil;
|
||||
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
protected $clientStorage;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param AccessTokenInterface $accessToken
|
||||
* @param ClientInterface $clientStorage
|
||||
* @param array $grantTypes
|
||||
* @param ClientAssertionTypeInterface $clientAssertionType
|
||||
* @param ScopeInterface $scopeUtil
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the token request.
|
||||
*
|
||||
* @param RequestInterface $request - Request object to grant access token
|
||||
* @param ResponseInterface $response - Response object
|
||||
*/
|
||||
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 RequestInterface $request - Request object to grant access token
|
||||
* @param ResponseInterface $response - Response object
|
||||
*
|
||||
* @return bool|null|array
|
||||
*
|
||||
* @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')) === 'options') {
|
||||
$response->addHttpHeaders(array('Allow' => 'POST, OPTIONS'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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, OPTIONS'));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** @var GrantTypeInterface $grantType */
|
||||
$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', '请求的作用域对此客户端无效');
|
||||
|
||||
return false;
|
||||
}
|
||||
} elseif (!$this->scopeUtil->scopeExists($requestedScope)) {
|
||||
$response->setError(400, 'invalid_scope', '请求了不受支持的作用域');
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add grant type
|
||||
*
|
||||
* @param GrantTypeInterface $grantType - the grant type to add for the specified identifier
|
||||
* @param string|null $identifier - 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
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
|
||||
* @throws RuntimeException
|
||||
* @return bool|null
|
||||
*/
|
||||
public function revokeToken(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if (strtolower($request->server('REQUEST_METHOD')) === 'options') {
|
||||
$response->addHttpHeaders(array('Allow' => 'POST, OPTIONS'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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, OPTIONS'));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
39
includes/OAuth2/Controller/TokenControllerInterface.php
Normal file
39
includes/OAuth2/Controller/TokenControllerInterface.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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
|
||||
*
|
||||
* @code
|
||||
* $tokenController->handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response());
|
||||
* $response->send();
|
||||
* @endcode
|
||||
*/
|
||||
interface TokenControllerInterface
|
||||
{
|
||||
/**
|
||||
* Handle the token request
|
||||
*
|
||||
* @param RequestInterface $request - The current http request
|
||||
* @param ResponseInterface $response - An instance of OAuth2\ResponseInterface to contain the response data
|
||||
*/
|
||||
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response);
|
||||
|
||||
/**
|
||||
* 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 RequestInterface $request - Request object to grant access token
|
||||
* @param ResponseInterface $response - Response object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function grantAccessToken(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
Reference in New Issue
Block a user