PageRenderTime 60ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/src/OAuth2/Server.php

https://github.com/rich-choy/oauth2-server-php
PHP | 538 lines | 354 code | 60 blank | 124 comment | 42 complexity | 6d52febb9c006143f51f0b7b47a280c5 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. namespace OAuth2;
  3. use OAuth2\Controller\ResourceControllerInterface;
  4. use OAuth2\Controller\ResourceController;
  5. use OAuth2\Controller\AuthorizeControllerInterface;
  6. use OAuth2\Controller\AuthorizeController;
  7. use OAuth2\Controller\TokenControllerInterface;
  8. use OAuth2\Controller\TokenController;
  9. use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
  10. use OAuth2\ClientAssertionType\HttpBasic;
  11. use OAuth2\ResponseType\ResponseTypeInterface;
  12. use OAuth2\ResponseType\AuthorizationCode as AuthorizationCodeResponseType;
  13. use OAuth2\ResponseType\AccessToken;
  14. use OAuth2\TokenType\TokenTypeInterface;
  15. use OAuth2\TokenType\Bearer;
  16. use OAuth2\GrantType\GrantTypeInterface;
  17. use OAuth2\GrantType\UserCredentials;
  18. use OAuth2\GrantType\ClientCredentials;
  19. use OAuth2\GrantType\RefreshToken;
  20. use OAuth2\GrantType\AuthorizationCode;
  21. /**
  22. * Server class for OAuth2
  23. * This class serves as a convience class which wraps the other Controller classes
  24. *
  25. * @see OAuth2\Controller\ResourceController
  26. * @see OAuth2\Controller\AuthorizeController
  27. * @see OAuth2\Controller\TokenController
  28. */
  29. class Server implements ResourceControllerInterface,
  30. AuthorizeControllerInterface,
  31. TokenControllerInterface
  32. {
  33. // misc properties
  34. protected $response;
  35. protected $config;
  36. protected $storages;
  37. // servers
  38. protected $authorizeController;
  39. protected $tokenController;
  40. protected $resourceController;
  41. // config classes
  42. protected $grantTypes;
  43. protected $responseTypes;
  44. protected $tokenType;
  45. protected $scopeUtil;
  46. protected $clientAssertionType;
  47. protected $storageMap = array(
  48. 'access_token' => 'OAuth2\Storage\AccessTokenInterface',
  49. 'authorization_code' => 'OAuth2\Storage\AuthorizationCodeInterface',
  50. 'client_credentials' => 'OAuth2\Storage\ClientCredentialsInterface',
  51. 'client' => 'OAuth2\Storage\ClientInterface',
  52. 'refresh_token' => 'OAuth2\Storage\RefreshTokenInterface',
  53. 'user_credentials' => 'OAuth2\Storage\UserCredentialsInterface',
  54. 'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface',
  55. 'scope' => 'OAuth2\Storage\ScopeInterface',
  56. );
  57. protected $responseTypeMap = array(
  58. 'token' => 'OAuth2\ResponseType\AccessTokenInterface',
  59. 'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface',
  60. );
  61. /**
  62. * @param mixed $storage
  63. * array - array of Objects to implement storage
  64. * OAuth2\Storage object implementing all required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
  65. * @param array $config
  66. * specify a different token lifetime, token header name, etc
  67. * @param array $grantTypes
  68. * An array of OAuth2\GrantTypeInterface to use for granting access tokens
  69. * @param array $responseTypes
  70. * Response types to use. array keys should be "code" and and "token" for
  71. * Access Token and Authorization Code response types
  72. * @param OAuth2\TokenTypeInterface $tokenType
  73. * The token type object to use. Valid token types are "bearer" and "mac"
  74. * @param OAuth2\ScopeInterface $scopeUtil
  75. * The scope utility class to use to validate scope
  76. * @param OAuth2\ClientAssertionTypeInterface $clientAssertionType
  77. * The method in which to verify the client identity. Default is HttpBasic
  78. *
  79. * @ingroup oauth2_section_7
  80. */
  81. public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null)
  82. {
  83. $storage = is_array($storage) ? $storage : array($storage);
  84. $this->storages = array();
  85. foreach ($storage as $key => $service) {
  86. $this->addStorage($service, $key);
  87. }
  88. // merge all config values. These get passed to our controller objects
  89. $this->config = array_merge(array(
  90. 'access_lifetime' => 3600,
  91. 'www_realm' => 'Service',
  92. 'token_param_name' => 'access_token',
  93. 'token_bearer_header_name' => 'Bearer',
  94. 'enforce_state' => true,
  95. 'require_exact_redirect_uri' => true,
  96. 'allow_implicit' => false,
  97. 'allow_credentials_in_request_body' => true,
  98. ), $config);
  99. foreach ($grantTypes as $key => $grantType) {
  100. $this->addGrantType($grantType, $key);
  101. }
  102. foreach ($responseTypes as $key => $responseType) {
  103. $this->addResponseType($responseType, $key);
  104. }
  105. $this->tokenType = $tokenType;
  106. $this->scopeUtil = $scopeUtil;
  107. $this->clientAssertionType = $clientAssertionType;
  108. }
  109. public function getAuthorizeController()
  110. {
  111. if (is_null($this->authorizeController)) {
  112. $this->authorizeController = $this->createDefaultAuthorizeController();
  113. }
  114. return $this->authorizeController;
  115. }
  116. public function getTokenController()
  117. {
  118. if (is_null($this->tokenController)) {
  119. $this->tokenController = $this->createDefaultTokenController();
  120. }
  121. return $this->tokenController;
  122. }
  123. public function getResourceController()
  124. {
  125. if (is_null($this->resourceController)) {
  126. $this->resourceController = $this->createDefaultResourceController();
  127. }
  128. return $this->resourceController;
  129. }
  130. /**
  131. * every getter deserves a setter
  132. */
  133. public function setAuthorizeController(AuthorizeControllerInterface $authorizeController)
  134. {
  135. $this->authorizeController = $authorizeController;
  136. }
  137. /**
  138. * every getter deserves a setter
  139. */
  140. public function setTokenController(TokenControllerInterface $tokenController)
  141. {
  142. $this->tokenController = $tokenController;
  143. }
  144. /**
  145. * every getter deserves a setter
  146. */
  147. public function setResourceController(ResourceControllerInterface $resourceController)
  148. {
  149. $this->resourceController = $resourceController;
  150. }
  151. /**
  152. * Grant or deny a requested access token.
  153. * This would be called from the "/token" endpoint as defined in the spec.
  154. * Obviously, you can call your endpoint whatever you want.
  155. *
  156. * @param $request - OAuth2\RequestInterface
  157. * Request object to grant access token
  158. *
  159. * @param $response - OAuth2\ResponseInterface
  160. * Response object containing error messages (failure) or access token (success)
  161. *
  162. * @throws InvalidArgumentException
  163. * @throws LogicException
  164. *
  165. * @see http://tools.ietf.org/html/rfc6749#section-4
  166. * @see http://tools.ietf.org/html/rfc6749#section-10.6
  167. * @see http://tools.ietf.org/html/rfc6749#section-4.1.3
  168. *
  169. * @ingroup oauth2_section_4
  170. */
  171. public function handleTokenRequest(RequestInterface $request, ResponseInterface $response = null)
  172. {
  173. $this->response = is_null($response) ? new Response() : $response;
  174. $this->getTokenController()->handleTokenRequest($request, $this->response);
  175. return $this->response;
  176. }
  177. public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null)
  178. {
  179. $this->response = is_null($response) ? new Response() : $response;
  180. $value = $this->getTokenController()->grantAccessToken($request, $this->response);
  181. return $value;
  182. }
  183. /**
  184. * Redirect the user appropriately after approval.
  185. *
  186. * After the user has approved or denied the resource request the
  187. * authorization server should call this function to redirect the user
  188. * appropriately.
  189. *
  190. * @param $request
  191. * The request should have the follow parameters set in the querystring:
  192. * - response_type: The requested response: an access token, an
  193. * authorization code, or both.
  194. * - client_id: The client identifier as described in Section 2.
  195. * - redirect_uri: An absolute URI to which the authorization server
  196. * will redirect the user-agent to when the end-user authorization
  197. * step is completed.
  198. * - scope: (optional) The scope of the resource request expressed as a
  199. * list of space-delimited strings.
  200. * - state: (optional) An opaque value used by the client to maintain
  201. * state between the request and callback.
  202. * @param $is_authorized
  203. * TRUE or FALSE depending on whether the user authorized the access.
  204. * @param $user_id
  205. * Identifier of user who authorized the client
  206. *
  207. * @see http://tools.ietf.org/html/rfc6749#section-4
  208. *
  209. * @ingroup oauth2_section_4
  210. */
  211. public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
  212. {
  213. $this->response = $response;
  214. $this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id);
  215. return $this->response;
  216. }
  217. /**
  218. * Pull the authorization request data out of the HTTP request.
  219. * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
  220. * by setting $config['enforce_redirect'] to true.
  221. * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
  222. * CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true.
  223. *
  224. * The draft specifies that the parameters should be retrieved from GET, override the Response
  225. * object to change this
  226. *
  227. * @return
  228. * The authorization parameters so the authorization server can prompt
  229. * the user for approval if valid.
  230. *
  231. * @see http://tools.ietf.org/html/rfc6749#section-4.1.1
  232. * @see http://tools.ietf.org/html/rfc6749#section-10.12
  233. *
  234. * @ingroup oauth2_section_3
  235. */
  236. public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response = null)
  237. {
  238. $this->response = is_null($response) ? new Response() : $response;
  239. $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response);
  240. return $value;
  241. }
  242. public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null)
  243. {
  244. $this->response = is_null($response) ? new Response() : $response;
  245. $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope);
  246. return $value;
  247. }
  248. public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null)
  249. {
  250. $this->response = is_null($response) ? new Response() : $response;
  251. $value = $this->getResourceController()->getAccessTokenData($request, $this->response);
  252. return $value;
  253. }
  254. public function addGrantType(GrantTypeInterface $grantType, $key = null)
  255. {
  256. if (is_string($key)) {
  257. $this->grantTypes[$key] = $grantType;
  258. } else {
  259. $this->grantTypes[] = $grantType;
  260. }
  261. // persist added grant type down to TokenController
  262. if (!is_null($this->tokenController)) {
  263. $this->getTokenController()->addGrantType($grantType);
  264. }
  265. }
  266. /**
  267. * Set a storage object for the server
  268. *
  269. * @param $storage
  270. * An object implementing one of the Storage interfaces
  271. * @param $key
  272. * If null, the storage is set to the key of each storage interface it implements
  273. *
  274. * @see storageMap
  275. */
  276. public function addStorage($storage, $key = null)
  277. {
  278. // if explicitly set to a valid key, do not "magically" set below
  279. if (isset($this->storageMap[$key])) {
  280. if (!$storage instanceof $this->storageMap[$key]) {
  281. throw new \InvalidArgumentException(sprintf('storage of type "%s" must implement interface "%s"', $key, $this->storageMap[$key]));
  282. }
  283. $this->storages[$key] = $storage;
  284. } elseif (!is_null($key) && !is_numeric($key)) {
  285. throw new \InvalidArgumentException(sprintf('unknown storage key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->storageMap))));
  286. } else {
  287. $set = false;
  288. foreach ($this->storageMap as $type => $interface) {
  289. if ($storage instanceof $interface) {
  290. $this->storages[$type] = $storage;
  291. $set = true;
  292. }
  293. }
  294. if (!$set) {
  295. throw new \InvalidArgumentException(sprintf('storage of class "%s" must implement one of [%s]', get_class($storage), implode(', ', $this->storageMap)));
  296. }
  297. }
  298. }
  299. public function addResponseType(ResponseTypeInterface $responseType, $key = null)
  300. {
  301. if (isset($this->responseTypeMap[$key])) {
  302. if (!$responseType instanceof $this->responseTypeMap[$key]) {
  303. throw new \InvalidArgumentException(sprintf('responseType of type "%s" must implement interface "%s"', $key, $this->responseTypeMap[$key]));
  304. }
  305. $this->responseTypes[$key] = $responseType;
  306. } elseif (!is_null($key) && !is_numeric($key)) {
  307. throw new \InvalidArgumentException(sprintf('unknown responseType key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->responseTypeMap))));
  308. } else {
  309. $set = false;
  310. foreach ($this->responseTypeMap as $type => $interface) {
  311. if ($responseType instanceof $interface) {
  312. $this->responseTypes[$type] = $responseType;
  313. $set = true;
  314. }
  315. }
  316. if (!$set) {
  317. throw new \InvalidArgumentException(sprintf('Unknown response type %s. Please implement one of [%s]', get_class($responseType), implode(', ', $this->responseTypeMap)));
  318. }
  319. }
  320. }
  321. public function getScopeUtil()
  322. {
  323. if (!$this->scopeUtil) {
  324. $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null;
  325. $this->scopeUtil = new Scope($storage);
  326. }
  327. return $this->scopeUtil;
  328. }
  329. /**
  330. * every getter deserves a setter
  331. */
  332. public function setScopeUtil($scopeUtil)
  333. {
  334. $this->scopeUtil = $scopeUtil;
  335. }
  336. protected function createDefaultAuthorizeController()
  337. {
  338. if (!isset($this->storages['client'])) {
  339. throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the authorize server");
  340. }
  341. if (0 == count($this->responseTypes)) {
  342. $this->responseTypes = $this->getDefaultResponseTypes();
  343. }
  344. $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri')));
  345. return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
  346. }
  347. protected function createDefaultTokenController()
  348. {
  349. if (0 == count($this->grantTypes)) {
  350. $this->grantTypes = $this->getDefaultGrantTypes();
  351. }
  352. if (is_null($this->clientAssertionType)) {
  353. // see if HttpBasic assertion type is requred. If so, then create it from storage classes.
  354. foreach ($this->grantTypes as $grantType) {
  355. if (!$grantType instanceof ClientAssertionTypeInterface) {
  356. if (!isset($this->storages['client_credentials'])) {
  357. throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server");
  358. }
  359. $config = array_intersect_key($this->config, array('allow_credentials_in_request_body' => ''));
  360. $this->clientAssertionType = new HttpBasic($this->storages['client_credentials'], $config);
  361. break;
  362. }
  363. }
  364. }
  365. return new TokenController($this->getAccessTokenResponseType(), $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil());
  366. }
  367. protected function createDefaultResourceController()
  368. {
  369. if (!isset($this->storages['access_token'])) {
  370. throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface to use the resource server");
  371. }
  372. if (!$this->tokenType) {
  373. $this->tokenType = $this->getDefaultTokenType();
  374. }
  375. $config = array_intersect_key($this->config, array('www_realm' => ''));
  376. return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
  377. }
  378. protected function getDefaultTokenType()
  379. {
  380. $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
  381. return new Bearer($config);
  382. }
  383. protected function getDefaultResponseTypes()
  384. {
  385. $responseTypes = array();
  386. if (isset($this->storages['access_token'])) {
  387. $responseTypes['token'] = $this->getAccessTokenResponseType();
  388. }
  389. if (isset($this->storages['authorization_code'])) {
  390. $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime')));
  391. $responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
  392. }
  393. if (count($responseTypes) == 0) {
  394. throw new \LogicException("You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AccessTokenInterface or OAuth2\Storage\AuthorizationCodeInterface storage object");
  395. }
  396. return $responseTypes;
  397. }
  398. protected function getDefaultGrantTypes()
  399. {
  400. $grantTypes = array();
  401. if (isset($this->storages['user_credentials'])) {
  402. $grantTypes['password'] = new UserCredentials($this->storages['user_credentials']);
  403. }
  404. if (isset($this->storages['client_credentials'])) {
  405. $grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials']);
  406. }
  407. if (isset($this->storages['refresh_token'])) {
  408. $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token']);
  409. }
  410. if (isset($this->storages['authorization_code'])) {
  411. $grantTypes['authorization_code'] = new AuthorizationCode($this->storages['authorization_code']);
  412. }
  413. if (count($grantTypes) == 0) {
  414. throw new \LogicException("Unable to build default grant types - You must supply an array of grant_types in the constructor");
  415. }
  416. return $grantTypes;
  417. }
  418. protected function getAccessTokenResponseType()
  419. {
  420. if (isset($this->responseTypes['token'])) {
  421. return $this->responseTypes['token'];
  422. }
  423. if (!isset($this->storages['access_token'])) {
  424. throw new \LogicException("You must supply a response type implementing OAuth2\ResponseType\AccessTokenInterface, or a storage object implementing OAuth2\Storage\AccessTokenInterface to use the token server");
  425. }
  426. $refreshStorage = null;
  427. if (isset($this->storages['refresh_token'])) {
  428. $refreshStorage = $this->storages['refresh_token'];
  429. }
  430. $config = array_intersect_key($this->config, array_flip(explode(' ', 'access_lifetime refresh_token_lifetime')));
  431. $config['token_type'] = $this->tokenType ? $this->tokenType->getTokenType() : $this->getDefaultTokenType()->getTokenType();
  432. return new AccessToken($this->storages['access_token'], $refreshStorage, $config);
  433. }
  434. public function getResponse()
  435. {
  436. return $this->response;
  437. }
  438. public function getStorages()
  439. {
  440. return $this->storages;
  441. }
  442. public function getStorage($name)
  443. {
  444. return isset($this->storages[$name]) ? $this->storages[$name] : null;
  445. }
  446. public function getGrantTypes()
  447. {
  448. return $this->grantTypes;
  449. }
  450. public function getGrantType($name)
  451. {
  452. return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null;
  453. }
  454. public function getResponseTypes()
  455. {
  456. return $this->responseTypes;
  457. }
  458. public function getResponseType($name)
  459. {
  460. return isset($this->responseTypes[$name]) ? $this->responseTypes[$name] : null;
  461. }
  462. public function getTokenType()
  463. {
  464. return $this->tokenType;
  465. }
  466. public function getClientAssertionType()
  467. {
  468. return $this->clientAssertionType;
  469. }
  470. public function setConfig($name, $value)
  471. {
  472. $this->config[$name] = $value;
  473. }
  474. public function getConfig($name, $default = null)
  475. {
  476. return isset($this->config[$name]) ? $this->config[$name] : $default;
  477. }
  478. }