TokenController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <?php
  2. namespace OAuth2\Controller;
  3. use OAuth2\ResponseType\AccessTokenInterface;
  4. use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
  5. use OAuth2\GrantType\GrantTypeInterface;
  6. use OAuth2\ScopeInterface;
  7. use OAuth2\Scope;
  8. use OAuth2\Storage\ClientInterface;
  9. use OAuth2\RequestInterface;
  10. use OAuth2\ResponseInterface;
  11. /**
  12. * @see OAuth2\Controller\TokenControllerInterface
  13. */
  14. class TokenController implements TokenControllerInterface
  15. {
  16. protected $accessToken;
  17. protected $grantTypes;
  18. protected $clientAssertionType;
  19. protected $scopeUtil;
  20. protected $clientStorage;
  21. public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null)
  22. {
  23. if (is_null($clientAssertionType)) {
  24. foreach ($grantTypes as $grantType) {
  25. if (!$grantType instanceof ClientAssertionTypeInterface) {
  26. throw new \InvalidArgumentException('You must supply an instance of OAuth2\ClientAssertionType\ClientAssertionTypeInterface or only use grant types which implement OAuth2\ClientAssertionType\ClientAssertionTypeInterface');
  27. }
  28. }
  29. }
  30. $this->clientAssertionType = $clientAssertionType;
  31. $this->accessToken = $accessToken;
  32. $this->clientStorage = $clientStorage;
  33. foreach ($grantTypes as $grantType) {
  34. $this->addGrantType($grantType);
  35. }
  36. if (is_null($scopeUtil)) {
  37. $scopeUtil = new Scope();
  38. }
  39. $this->scopeUtil = $scopeUtil;
  40. }
  41. public function handleTokenRequest(RequestInterface $request, ResponseInterface $response)
  42. {
  43. if ($token = $this->grantAccessToken($request, $response)) {
  44. // @see http://tools.ietf.org/html/rfc6749#section-5.1
  45. // server MUST disable caching in headers when tokens are involved
  46. $response->setStatusCode(200);
  47. $response->addParameters($token);
  48. $response->addHttpHeaders(array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache'));
  49. }
  50. }
  51. /**
  52. * Grant or deny a requested access token.
  53. * This would be called from the "/token" endpoint as defined in the spec.
  54. * You can call your endpoint whatever you want.
  55. *
  56. * @param $request - RequestInterface
  57. * Request object to grant access token
  58. *
  59. * @throws InvalidArgumentException
  60. * @throws LogicException
  61. *
  62. * @see http://tools.ietf.org/html/rfc6749#section-4
  63. * @see http://tools.ietf.org/html/rfc6749#section-10.6
  64. * @see http://tools.ietf.org/html/rfc6749#section-4.1.3
  65. *
  66. * @ingroup oauth2_section_4
  67. */
  68. public function grantAccessToken(RequestInterface $request, ResponseInterface $response)
  69. {
  70. if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
  71. $response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2');
  72. $response->addHttpHeaders(array('Allow' => 'POST'));
  73. return null;
  74. }
  75. /**
  76. * Determine grant type from request
  77. * and validate the request for that grant type
  78. */
  79. if (!$grantTypeIdentifier = $request->request('grant_type')) {
  80. $response->setError(400, 'invalid_request', 'The grant type was not specified in the request');
  81. return null;
  82. }
  83. if (!isset($this->grantTypes[$grantTypeIdentifier])) {
  84. /* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */
  85. $response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier));
  86. return null;
  87. }
  88. $grantType = $this->grantTypes[$grantTypeIdentifier];
  89. /**
  90. * Retrieve the client information from the request
  91. * ClientAssertionTypes allow for grant types which also assert the client data
  92. * in which case ClientAssertion is handled in the validateRequest method
  93. *
  94. * @see OAuth2\GrantType\JWTBearer
  95. * @see OAuth2\GrantType\ClientCredentials
  96. */
  97. if (!$grantType instanceof ClientAssertionTypeInterface) {
  98. if (!$this->clientAssertionType->validateRequest($request, $response)) {
  99. return null;
  100. }
  101. $clientId = $this->clientAssertionType->getClientId();
  102. }
  103. /**
  104. * Retrieve the grant type information from the request
  105. * The GrantTypeInterface object handles all validation
  106. * If the object is an instance of ClientAssertionTypeInterface,
  107. * That logic is handled here as well
  108. */
  109. if (!$grantType->validateRequest($request, $response)) {
  110. return null;
  111. }
  112. if ($grantType instanceof ClientAssertionTypeInterface) {
  113. $clientId = $grantType->getClientId();
  114. } else {
  115. // validate the Client ID (if applicable)
  116. if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) {
  117. $response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier));
  118. return null;
  119. }
  120. }
  121. /**
  122. * Validate the client can use the requested grant type
  123. */
  124. if (!$this->clientStorage->checkRestrictedGrantType($clientId, $grantTypeIdentifier)) {
  125. $response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id');
  126. return false;
  127. }
  128. /**
  129. * Validate the scope of the token
  130. *
  131. * requestedScope - the scope specified in the token request
  132. * availableScope - the scope associated with the grant type
  133. * ex: in the case of the "Authorization Code" grant type,
  134. * the scope is specified in the authorize request
  135. *
  136. * @see http://tools.ietf.org/html/rfc6749#section-3.3
  137. */
  138. $requestedScope = $this->scopeUtil->getScopeFromRequest($request);
  139. $availableScope = $grantType->getScope();
  140. if ($requestedScope) {
  141. // validate the requested scope
  142. if ($availableScope) {
  143. if (!$this->scopeUtil->checkScope($requestedScope, $availableScope)) {
  144. $response->setError(400, 'invalid_scope', 'The scope requested is invalid for this request');
  145. return null;
  146. }
  147. } else {
  148. // validate the client has access to this scope
  149. if ($clientScope = $this->clientStorage->getClientScope($clientId)) {
  150. if (!$this->scopeUtil->checkScope($requestedScope, $clientScope)) {
  151. $response->setError(400, 'invalid_scope', 'The scope requested is invalid for this client');
  152. return false;
  153. }
  154. } elseif (!$this->scopeUtil->scopeExists($requestedScope)) {
  155. $response->setError(400, 'invalid_scope', 'An unsupported scope was requested');
  156. return null;
  157. }
  158. }
  159. } elseif ($availableScope) {
  160. // use the scope associated with this grant type
  161. $requestedScope = $availableScope;
  162. } else {
  163. // use a globally-defined default scope
  164. $defaultScope = $this->scopeUtil->getDefaultScope($clientId);
  165. // "false" means default scopes are not allowed
  166. if (false === $defaultScope) {
  167. $response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter');
  168. return null;
  169. }
  170. $requestedScope = $defaultScope;
  171. }
  172. return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope);
  173. }
  174. /**
  175. * addGrantType
  176. *
  177. * @param grantType - OAuth2\GrantTypeInterface
  178. * the grant type to add for the specified identifier
  179. * @param identifier - string
  180. * a string passed in as "grant_type" in the response that will call this grantType
  181. */
  182. public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
  183. {
  184. if (is_null($identifier) || is_numeric($identifier)) {
  185. $identifier = $grantType->getQuerystringIdentifier();
  186. }
  187. $this->grantTypes[$identifier] = $grantType;
  188. }
  189. public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response)
  190. {
  191. if ($this->revokeToken($request, $response)) {
  192. $response->setStatusCode(200);
  193. $response->addParameters(array('revoked' => true));
  194. }
  195. }
  196. /**
  197. * Revoke a refresh or access token. Returns true on success and when tokens are invalid
  198. *
  199. * Note: invalid tokens do not cause an error response since the client
  200. * cannot handle such an error in a reasonable way. Moreover, the
  201. * purpose of the revocation request, invalidating the particular token,
  202. * is already achieved.
  203. *
  204. * @param RequestInterface $request
  205. * @param ResponseInterface $response
  206. * @return bool|null
  207. */
  208. public function revokeToken(RequestInterface $request, ResponseInterface $response)
  209. {
  210. if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
  211. $response->setError(405, 'invalid_request', 'The request method must be POST when revoking an access token', '#section-3.2');
  212. $response->addHttpHeaders(array('Allow' => 'POST'));
  213. return null;
  214. }
  215. $token_type_hint = $request->request('token_type_hint');
  216. if (!in_array($token_type_hint, array(null, 'access_token', 'refresh_token'), true)) {
  217. $response->setError(400, 'invalid_request', 'Token type hint must be either \'access_token\' or \'refresh_token\'');
  218. return null;
  219. }
  220. $token = $request->request('token');
  221. if ($token === null) {
  222. $response->setError(400, 'invalid_request', 'Missing token parameter to revoke');
  223. return null;
  224. }
  225. // @todo remove this check for v2.0
  226. if (!method_exists($this->accessToken, 'revokeToken')) {
  227. $class = get_class($this->accessToken);
  228. throw new \RuntimeException("AccessToken {$class} does not implement required revokeToken method");
  229. }
  230. $this->accessToken->revokeToken($token, $token_type_hint);
  231. return true;
  232. }
  233. }