1
0

Server.php 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. <?php
  2. namespace OAuth2;
  3. use OAuth2\Controller\ResourceControllerInterface;
  4. use OAuth2\Controller\ResourceController;
  5. use OAuth2\OpenID\Controller\UserInfoControllerInterface;
  6. use OAuth2\OpenID\Controller\UserInfoController;
  7. use OAuth2\OpenID\Controller\AuthorizeController as OpenIDAuthorizeController;
  8. use OAuth2\OpenID\ResponseType\AuthorizationCode as OpenIDAuthorizationCodeResponseType;
  9. use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
  10. use OAuth2\OpenID\GrantType\AuthorizationCode as OpenIDAuthorizationCodeGrantType;
  11. use OAuth2\Controller\AuthorizeControllerInterface;
  12. use OAuth2\Controller\AuthorizeController;
  13. use OAuth2\Controller\TokenControllerInterface;
  14. use OAuth2\Controller\TokenController;
  15. use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
  16. use OAuth2\ClientAssertionType\HttpBasic;
  17. use OAuth2\ResponseType\ResponseTypeInterface;
  18. use OAuth2\ResponseType\AuthorizationCode as AuthorizationCodeResponseType;
  19. use OAuth2\ResponseType\AccessToken;
  20. use OAuth2\ResponseType\JwtAccessToken;
  21. use OAuth2\OpenID\ResponseType\CodeIdToken;
  22. use OAuth2\OpenID\ResponseType\IdToken;
  23. use OAuth2\OpenID\ResponseType\IdTokenToken;
  24. use OAuth2\TokenType\TokenTypeInterface;
  25. use OAuth2\TokenType\Bearer;
  26. use OAuth2\GrantType\GrantTypeInterface;
  27. use OAuth2\GrantType\UserCredentials;
  28. use OAuth2\GrantType\ClientCredentials;
  29. use OAuth2\GrantType\RefreshToken;
  30. use OAuth2\GrantType\AuthorizationCode;
  31. use OAuth2\Storage\ClientCredentialsInterface;
  32. use OAuth2\Storage\ClientInterface;
  33. use OAuth2\Storage\JwtAccessToken as JwtAccessTokenStorage;
  34. use OAuth2\Storage\JwtAccessTokenInterface;
  35. use InvalidArgumentException;
  36. use LogicException;
  37. /**
  38. * Server class for OAuth2
  39. * This class serves as a convience class which wraps the other Controller classes
  40. *
  41. * @see \OAuth2\Controller\ResourceController
  42. * @see \OAuth2\Controller\AuthorizeController
  43. * @see \OAuth2\Controller\TokenController
  44. */
  45. class Server implements ResourceControllerInterface,
  46. AuthorizeControllerInterface,
  47. TokenControllerInterface,
  48. UserInfoControllerInterface
  49. {
  50. /**
  51. * @var ResponseInterface
  52. */
  53. protected $response;
  54. /**
  55. * @var array
  56. */
  57. protected $config;
  58. /**
  59. * @var array
  60. */
  61. protected $storages;
  62. /**
  63. * @var AuthorizeControllerInterface
  64. */
  65. protected $authorizeController;
  66. /**
  67. * @var TokenControllerInterface
  68. */
  69. protected $tokenController;
  70. /**
  71. * @var ResourceControllerInterface
  72. */
  73. protected $resourceController;
  74. /**
  75. * @var UserInfoControllerInterface
  76. */
  77. protected $userInfoController;
  78. /**
  79. * @var array
  80. */
  81. protected $grantTypes = [];
  82. /**
  83. * @var array
  84. */
  85. protected $responseTypes = [];
  86. /**
  87. * @var TokenTypeInterface
  88. */
  89. protected $tokenType;
  90. /**
  91. * @var ScopeInterface
  92. */
  93. protected $scopeUtil;
  94. /**
  95. * @var ClientAssertionTypeInterface
  96. */
  97. protected $clientAssertionType;
  98. /**
  99. * @var array
  100. */
  101. protected $storageMap = array(
  102. 'access_token' => 'OAuth2\Storage\AccessTokenInterface',
  103. 'authorization_code' => 'OAuth2\Storage\AuthorizationCodeInterface',
  104. 'client_credentials' => 'OAuth2\Storage\ClientCredentialsInterface',
  105. 'client' => 'OAuth2\Storage\ClientInterface',
  106. 'refresh_token' => 'OAuth2\Storage\RefreshTokenInterface',
  107. 'user_credentials' => 'OAuth2\Storage\UserCredentialsInterface',
  108. 'user_claims' => 'OAuth2\OpenID\Storage\UserClaimsInterface',
  109. 'public_key' => 'OAuth2\Storage\PublicKeyInterface',
  110. 'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface',
  111. 'scope' => 'OAuth2\Storage\ScopeInterface',
  112. );
  113. /**
  114. * @var array
  115. */
  116. protected $responseTypeMap = array(
  117. 'token' => 'OAuth2\ResponseType\AccessTokenInterface',
  118. 'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface',
  119. 'id_token' => 'OAuth2\OpenID\ResponseType\IdTokenInterface',
  120. 'id_token token' => 'OAuth2\OpenID\ResponseType\IdTokenTokenInterface',
  121. 'code id_token' => 'OAuth2\OpenID\ResponseType\CodeIdTokenInterface',
  122. );
  123. /**
  124. * @param mixed $storage (array or OAuth2\Storage) - single object or array of objects implementing the
  125. * required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
  126. * @param array $config specify a different token lifetime, token header name, etc
  127. * @param array $grantTypes An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens
  128. * @param array $responseTypes Response types to use. array keys should be "code" and "token" for
  129. * Access Token and Authorization Code response types
  130. * @param TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac"
  131. * @param ScopeInterface $scopeUtil The scope utility class to use to validate scope
  132. * @param ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic
  133. *
  134. * @ingroup oauth2_section_7
  135. */
  136. public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null)
  137. {
  138. $storage = is_array($storage) ? $storage : array($storage);
  139. $this->storages = array();
  140. foreach ($storage as $key => $service) {
  141. $this->addStorage($service, $key);
  142. }
  143. // merge all config values. These get passed to our controller objects
  144. $this->config = array_merge(array(
  145. 'use_jwt_access_tokens' => false,
  146. 'store_encrypted_token_string' => true,
  147. 'use_openid_connect' => false,
  148. 'id_lifetime' => 3600,
  149. 'access_lifetime' => 3600,
  150. 'www_realm' => 'Service',
  151. 'token_param_name' => 'access_token',
  152. 'token_bearer_header_name' => 'Bearer',
  153. 'enforce_state' => true,
  154. 'require_exact_redirect_uri' => true,
  155. 'allow_implicit' => false,
  156. 'allow_credentials_in_request_body' => true,
  157. 'allow_public_clients' => true,
  158. 'always_issue_new_refresh_token' => false,
  159. 'unset_refresh_token_after_use' => true,
  160. ), $config);
  161. foreach ($grantTypes as $key => $grantType) {
  162. $this->addGrantType($grantType, $key);
  163. }
  164. foreach ($responseTypes as $key => $responseType) {
  165. $this->addResponseType($responseType, $key);
  166. }
  167. $this->tokenType = $tokenType;
  168. $this->scopeUtil = $scopeUtil;
  169. $this->clientAssertionType = $clientAssertionType;
  170. if ($this->config['use_openid_connect']) {
  171. $this->validateOpenIdConnect();
  172. }
  173. }
  174. /**
  175. * @return AuthorizeControllerInterface
  176. */
  177. public function getAuthorizeController()
  178. {
  179. if (is_null($this->authorizeController)) {
  180. $this->authorizeController = $this->createDefaultAuthorizeController();
  181. }
  182. return $this->authorizeController;
  183. }
  184. /**
  185. * @return TokenController
  186. */
  187. public function getTokenController()
  188. {
  189. if (is_null($this->tokenController)) {
  190. $this->tokenController = $this->createDefaultTokenController();
  191. }
  192. return $this->tokenController;
  193. }
  194. /**
  195. * @return ResourceControllerInterface
  196. */
  197. public function getResourceController()
  198. {
  199. if (is_null($this->resourceController)) {
  200. $this->resourceController = $this->createDefaultResourceController();
  201. }
  202. return $this->resourceController;
  203. }
  204. /**
  205. * @return UserInfoControllerInterface
  206. */
  207. public function getUserInfoController()
  208. {
  209. if (is_null($this->userInfoController)) {
  210. $this->userInfoController = $this->createDefaultUserInfoController();
  211. }
  212. return $this->userInfoController;
  213. }
  214. /**
  215. * @param AuthorizeControllerInterface $authorizeController
  216. */
  217. public function setAuthorizeController(AuthorizeControllerInterface $authorizeController)
  218. {
  219. $this->authorizeController = $authorizeController;
  220. }
  221. /**
  222. * @param TokenControllerInterface $tokenController
  223. */
  224. public function setTokenController(TokenControllerInterface $tokenController)
  225. {
  226. $this->tokenController = $tokenController;
  227. }
  228. /**
  229. * @param ResourceControllerInterface $resourceController
  230. */
  231. public function setResourceController(ResourceControllerInterface $resourceController)
  232. {
  233. $this->resourceController = $resourceController;
  234. }
  235. /**
  236. * @param UserInfoControllerInterface $userInfoController
  237. */
  238. public function setUserInfoController(UserInfoControllerInterface $userInfoController)
  239. {
  240. $this->userInfoController = $userInfoController;
  241. }
  242. /**
  243. * Return claims about the authenticated end-user.
  244. * This would be called from the "/UserInfo" endpoint as defined in the spec.
  245. *
  246. * @param RequestInterface $request - Request object to grant access token
  247. * @param ResponseInterface $response - Response object containing error messages (failure) or user claims (success)
  248. * @return ResponseInterface
  249. *
  250. * @throws \InvalidArgumentException
  251. * @throws \LogicException
  252. *
  253. * @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
  254. */
  255. public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response = null)
  256. {
  257. $this->response = is_null($response) ? new Response() : $response;
  258. $this->getUserInfoController()->handleUserInfoRequest($request, $this->response);
  259. return $this->response;
  260. }
  261. /**
  262. * Grant or deny a requested access token.
  263. * This would be called from the "/token" endpoint as defined in the spec.
  264. * Obviously, you can call your endpoint whatever you want.
  265. *
  266. * @param RequestInterface $request - Request object to grant access token
  267. * @param ResponseInterface $response - Response object containing error messages (failure) or access token (success)
  268. * @return ResponseInterface
  269. *
  270. * @throws \InvalidArgumentException
  271. * @throws \LogicException
  272. *
  273. * @see http://tools.ietf.org/html/rfc6749#section-4
  274. * @see http://tools.ietf.org/html/rfc6749#section-10.6
  275. * @see http://tools.ietf.org/html/rfc6749#section-4.1.3
  276. *
  277. * @ingroup oauth2_section_4
  278. */
  279. public function handleTokenRequest(RequestInterface $request, ResponseInterface $response = null)
  280. {
  281. $this->response = is_null($response) ? new Response() : $response;
  282. $this->getTokenController()->handleTokenRequest($request, $this->response);
  283. return $this->response;
  284. }
  285. /**
  286. * @param RequestInterface $request - Request object to grant access token
  287. * @param ResponseInterface $response - Response object
  288. * @return mixed
  289. */
  290. public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null)
  291. {
  292. $this->response = is_null($response) ? new Response() : $response;
  293. $value = $this->getTokenController()->grantAccessToken($request, $this->response);
  294. return $value;
  295. }
  296. /**
  297. * Handle a revoke token request
  298. * This would be called from the "/revoke" endpoint as defined in the draft Token Revocation spec
  299. *
  300. * @see https://tools.ietf.org/html/rfc7009#section-2
  301. *
  302. * @param RequestInterface $request
  303. * @param ResponseInterface $response
  304. * @return Response|ResponseInterface
  305. */
  306. public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response = null)
  307. {
  308. $this->response = is_null($response) ? new Response() : $response;
  309. $this->getTokenController()->handleRevokeRequest($request, $this->response);
  310. return $this->response;
  311. }
  312. /**
  313. * Redirect the user appropriately after approval.
  314. *
  315. * After the user has approved or denied the resource request the
  316. * authorization server should call this function to redirect the user
  317. * appropriately.
  318. *
  319. * @param RequestInterface $request - The request should have the follow parameters set in the querystring:
  320. * - response_type: The requested response: an access token, an authorization code, or both.
  321. * - client_id: The client identifier as described in Section 2.
  322. * - redirect_uri: An absolute URI to which the authorization server will redirect the user-agent to when the
  323. * end-user authorization step is completed.
  324. * - scope: (optional) The scope of the resource request expressed as a list of space-delimited strings.
  325. * - state: (optional) An opaque value used by the client to maintain state between the request and callback.
  326. *
  327. * @param ResponseInterface $response - Response object
  328. * @param bool $is_authorized - TRUE or FALSE depending on whether the user authorized the access.
  329. * @param mixed $user_id - Identifier of user who authorized the client
  330. * @return ResponseInterface
  331. *
  332. * @see http://tools.ietf.org/html/rfc6749#section-4
  333. *
  334. * @ingroup oauth2_section_4
  335. */
  336. public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
  337. {
  338. $this->response = $response;
  339. $this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id);
  340. return $this->response;
  341. }
  342. /**
  343. * Pull the authorization request data out of the HTTP request.
  344. * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
  345. * by setting $config['enforce_redirect'] to true.
  346. * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
  347. * CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true.
  348. *
  349. * The draft specifies that the parameters should be retrieved from GET, override the Response
  350. * object to change this
  351. *
  352. * @param RequestInterface $request - Request object
  353. * @param ResponseInterface $response - Response object
  354. * @return bool
  355. *
  356. * The authorization parameters so the authorization server can prompt
  357. * the user for approval if valid.
  358. *
  359. * @see http://tools.ietf.org/html/rfc6749#section-4.1.1
  360. * @see http://tools.ietf.org/html/rfc6749#section-10.12
  361. *
  362. * @ingroup oauth2_section_3
  363. */
  364. public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response = null)
  365. {
  366. $this->response = is_null($response) ? new Response() : $response;
  367. $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response);
  368. return $value;
  369. }
  370. /**
  371. * @param RequestInterface $request - Request object
  372. * @param ResponseInterface $response - Response object
  373. * @param string $scope - Scope
  374. * @return mixed
  375. */
  376. public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null)
  377. {
  378. $this->response = is_null($response) ? new Response() : $response;
  379. $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope);
  380. return $value;
  381. }
  382. /**
  383. * @param RequestInterface $request - Request object
  384. * @param ResponseInterface $response - Response object
  385. * @return mixed
  386. */
  387. public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null)
  388. {
  389. $this->response = is_null($response) ? new Response() : $response;
  390. $value = $this->getResourceController()->getAccessTokenData($request, $this->response);
  391. return $value;
  392. }
  393. /**
  394. * @param GrantTypeInterface $grantType
  395. * @param mixed $identifier
  396. */
  397. public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
  398. {
  399. if (!is_string($identifier)) {
  400. $identifier = $grantType->getQueryStringIdentifier();
  401. }
  402. $this->grantTypes[$identifier] = $grantType;
  403. // persist added grant type down to TokenController
  404. if (!is_null($this->tokenController)) {
  405. $this->getTokenController()->addGrantType($grantType, $identifier);
  406. }
  407. }
  408. /**
  409. * Set a storage object for the server
  410. *
  411. * @param object $storage - An object implementing one of the Storage interfaces
  412. * @param mixed $key - If null, the storage is set to the key of each storage interface it implements
  413. *
  414. * @throws InvalidArgumentException
  415. * @see storageMap
  416. */
  417. public function addStorage($storage, $key = null)
  418. {
  419. // if explicitly set to a valid key, do not "magically" set below
  420. if (isset($this->storageMap[$key])) {
  421. if (!is_null($storage) && !$storage instanceof $this->storageMap[$key]) {
  422. throw new \InvalidArgumentException(sprintf('storage of type "%s" must implement interface "%s"', $key, $this->storageMap[$key]));
  423. }
  424. $this->storages[$key] = $storage;
  425. // special logic to handle "client" and "client_credentials" strangeness
  426. if ($key === 'client' && !isset($this->storages['client_credentials'])) {
  427. if ($storage instanceof ClientCredentialsInterface) {
  428. $this->storages['client_credentials'] = $storage;
  429. }
  430. } elseif ($key === 'client_credentials' && !isset($this->storages['client'])) {
  431. if ($storage instanceof ClientInterface) {
  432. $this->storages['client'] = $storage;
  433. }
  434. }
  435. } elseif (!is_null($key) && !is_numeric($key)) {
  436. throw new \InvalidArgumentException(sprintf('unknown storage key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->storageMap))));
  437. } else {
  438. $set = false;
  439. foreach ($this->storageMap as $type => $interface) {
  440. if ($storage instanceof $interface) {
  441. $this->storages[$type] = $storage;
  442. $set = true;
  443. }
  444. }
  445. if (!$set) {
  446. throw new \InvalidArgumentException(sprintf('storage of class "%s" must implement one of [%s]', get_class($storage), implode(', ', $this->storageMap)));
  447. }
  448. }
  449. }
  450. /**
  451. * @param ResponseTypeInterface $responseType
  452. * @param mixed $key
  453. *
  454. * @throws InvalidArgumentException
  455. */
  456. public function addResponseType(ResponseTypeInterface $responseType, $key = null)
  457. {
  458. $key = $this->normalizeResponseType($key);
  459. if (isset($this->responseTypeMap[$key])) {
  460. if (!$responseType instanceof $this->responseTypeMap[$key]) {
  461. throw new \InvalidArgumentException(sprintf('responseType of type "%s" must implement interface "%s"', $key, $this->responseTypeMap[$key]));
  462. }
  463. $this->responseTypes[$key] = $responseType;
  464. } elseif (!is_null($key) && !is_numeric($key)) {
  465. throw new \InvalidArgumentException(sprintf('unknown responseType key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->responseTypeMap))));
  466. } else {
  467. $set = false;
  468. foreach ($this->responseTypeMap as $type => $interface) {
  469. if ($responseType instanceof $interface) {
  470. $this->responseTypes[$type] = $responseType;
  471. $set = true;
  472. }
  473. }
  474. if (!$set) {
  475. throw new \InvalidArgumentException(sprintf('Unknown response type %s. Please implement one of [%s]', get_class($responseType), implode(', ', $this->responseTypeMap)));
  476. }
  477. }
  478. }
  479. /**
  480. * @return ScopeInterface
  481. */
  482. public function getScopeUtil()
  483. {
  484. if (!$this->scopeUtil) {
  485. $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null;
  486. $this->scopeUtil = new Scope($storage);
  487. }
  488. return $this->scopeUtil;
  489. }
  490. /**
  491. * @param ScopeInterface $scopeUtil
  492. */
  493. public function setScopeUtil($scopeUtil)
  494. {
  495. $this->scopeUtil = $scopeUtil;
  496. }
  497. /**
  498. * @return AuthorizeControllerInterface
  499. * @throws LogicException
  500. */
  501. protected function createDefaultAuthorizeController()
  502. {
  503. if (!isset($this->storages['client'])) {
  504. throw new \LogicException('You must supply a storage object implementing \OAuth2\Storage\ClientInterface to use the authorize server');
  505. }
  506. if (0 == count($this->responseTypes)) {
  507. $this->responseTypes = $this->getDefaultResponseTypes();
  508. }
  509. if ($this->config['use_openid_connect'] && !isset($this->responseTypes['id_token'])) {
  510. $this->responseTypes['id_token'] = $this->createDefaultIdTokenResponseType();
  511. if ($this->config['allow_implicit']) {
  512. $this->responseTypes['id_token token'] = $this->createDefaultIdTokenTokenResponseType();
  513. }
  514. }
  515. $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri')));
  516. if ($this->config['use_openid_connect']) {
  517. return new OpenIDAuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
  518. }
  519. return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
  520. }
  521. /**
  522. * @return TokenControllerInterface
  523. * @throws LogicException
  524. */
  525. protected function createDefaultTokenController()
  526. {
  527. if (0 == count($this->grantTypes)) {
  528. $this->grantTypes = $this->getDefaultGrantTypes();
  529. }
  530. if (is_null($this->clientAssertionType)) {
  531. // see if HttpBasic assertion type is requred. If so, then create it from storage classes.
  532. foreach ($this->grantTypes as $grantType) {
  533. if (!$grantType instanceof ClientAssertionTypeInterface) {
  534. if (!isset($this->storages['client_credentials'])) {
  535. throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server');
  536. }
  537. $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_credentials_in_request_body allow_public_clients')));
  538. $this->clientAssertionType = new HttpBasic($this->storages['client_credentials'], $config);
  539. break;
  540. }
  541. }
  542. }
  543. if (!isset($this->storages['client'])) {
  544. throw new LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server");
  545. }
  546. $accessTokenResponseType = $this->getAccessTokenResponseType();
  547. return new TokenController($accessTokenResponseType, $this->storages['client'], $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil());
  548. }
  549. /**
  550. * @return ResourceControllerInterface
  551. * @throws LogicException
  552. */
  553. protected function createDefaultResourceController()
  554. {
  555. if ($this->config['use_jwt_access_tokens']) {
  556. // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
  557. if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
  558. $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
  559. }
  560. } elseif (!isset($this->storages['access_token'])) {
  561. throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the resource server');
  562. }
  563. if (!$this->tokenType) {
  564. $this->tokenType = $this->getDefaultTokenType();
  565. }
  566. $config = array_intersect_key($this->config, array('www_realm' => ''));
  567. return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
  568. }
  569. /**
  570. * @return UserInfoControllerInterface
  571. * @throws LogicException
  572. */
  573. protected function createDefaultUserInfoController()
  574. {
  575. if ($this->config['use_jwt_access_tokens']) {
  576. // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
  577. if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
  578. $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
  579. }
  580. } elseif (!isset($this->storages['access_token'])) {
  581. throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the UserInfo server');
  582. }
  583. if (!isset($this->storages['user_claims'])) {
  584. throw new \LogicException('You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use the UserInfo server');
  585. }
  586. if (!$this->tokenType) {
  587. $this->tokenType = $this->getDefaultTokenType();
  588. }
  589. $config = array_intersect_key($this->config, array('www_realm' => ''));
  590. return new UserInfoController($this->tokenType, $this->storages['access_token'], $this->storages['user_claims'], $config, $this->getScopeUtil());
  591. }
  592. /**
  593. * @return Bearer
  594. */
  595. protected function getDefaultTokenType()
  596. {
  597. $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
  598. return new Bearer($config);
  599. }
  600. /**
  601. * @return array
  602. * @throws LogicException
  603. */
  604. protected function getDefaultResponseTypes()
  605. {
  606. $responseTypes = array();
  607. if ($this->config['allow_implicit']) {
  608. $responseTypes['token'] = $this->getAccessTokenResponseType();
  609. }
  610. if ($this->config['use_openid_connect']) {
  611. $responseTypes['id_token'] = $this->getIdTokenResponseType();
  612. if ($this->config['allow_implicit']) {
  613. $responseTypes['id_token token'] = $this->getIdTokenTokenResponseType();
  614. }
  615. }
  616. if (isset($this->storages['authorization_code'])) {
  617. $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime')));
  618. if ($this->config['use_openid_connect']) {
  619. if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
  620. throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true');
  621. }
  622. $responseTypes['code'] = new OpenIDAuthorizationCodeResponseType($this->storages['authorization_code'], $config);
  623. $responseTypes['code id_token'] = new CodeIdToken($responseTypes['code'], $responseTypes['id_token']);
  624. } else {
  625. $responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
  626. }
  627. }
  628. if (count($responseTypes) == 0) {
  629. throw new \LogicException('You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AuthorizationCodeInterface storage object or set "allow_implicit" to true and implement a OAuth2\Storage\AccessTokenInterface storage object');
  630. }
  631. return $responseTypes;
  632. }
  633. /**
  634. * @return array
  635. * @throws LogicException
  636. */
  637. protected function getDefaultGrantTypes()
  638. {
  639. $grantTypes = array();
  640. if (isset($this->storages['user_credentials'])) {
  641. $grantTypes['password'] = new UserCredentials($this->storages['user_credentials']);
  642. }
  643. if (isset($this->storages['client_credentials'])) {
  644. $config = array_intersect_key($this->config, array('allow_credentials_in_request_body' => ''));
  645. $grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials'], $config);
  646. }
  647. if (isset($this->storages['refresh_token'])) {
  648. $config = array_intersect_key($this->config, array_flip(explode(' ', 'always_issue_new_refresh_token unset_refresh_token_after_use')));
  649. $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config);
  650. }
  651. if (isset($this->storages['authorization_code'])) {
  652. if ($this->config['use_openid_connect']) {
  653. if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
  654. throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true');
  655. }
  656. $grantTypes['authorization_code'] = new OpenIDAuthorizationCodeGrantType($this->storages['authorization_code']);
  657. } else {
  658. $grantTypes['authorization_code'] = new AuthorizationCode($this->storages['authorization_code']);
  659. }
  660. }
  661. if (count($grantTypes) == 0) {
  662. throw new \LogicException('Unable to build default grant types - You must supply an array of grant_types in the constructor');
  663. }
  664. return $grantTypes;
  665. }
  666. /**
  667. * @return AccessToken
  668. */
  669. protected function getAccessTokenResponseType()
  670. {
  671. if (isset($this->responseTypes['token'])) {
  672. return $this->responseTypes['token'];
  673. }
  674. if ($this->config['use_jwt_access_tokens']) {
  675. return $this->createDefaultJwtAccessTokenResponseType();
  676. }
  677. return $this->createDefaultAccessTokenResponseType();
  678. }
  679. /**
  680. * @return IdToken
  681. */
  682. protected function getIdTokenResponseType()
  683. {
  684. if (isset($this->responseTypes['id_token'])) {
  685. return $this->responseTypes['id_token'];
  686. }
  687. return $this->createDefaultIdTokenResponseType();
  688. }
  689. /**
  690. * @return IdTokenToken
  691. */
  692. protected function getIdTokenTokenResponseType()
  693. {
  694. if (isset($this->responseTypes['id_token token'])) {
  695. return $this->responseTypes['id_token token'];
  696. }
  697. return $this->createDefaultIdTokenTokenResponseType();
  698. }
  699. /**
  700. * For Resource Controller
  701. *
  702. * @return JwtAccessTokenStorage
  703. * @throws LogicException
  704. */
  705. protected function createDefaultJwtAccessTokenStorage()
  706. {
  707. if (!isset($this->storages['public_key'])) {
  708. throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens');
  709. }
  710. $tokenStorage = null;
  711. if (!empty($this->config['store_encrypted_token_string']) && isset($this->storages['access_token'])) {
  712. $tokenStorage = $this->storages['access_token'];
  713. }
  714. // wrap the access token storage as required.
  715. return new JwtAccessTokenStorage($this->storages['public_key'], $tokenStorage);
  716. }
  717. /**
  718. * For Authorize and Token Controllers
  719. *
  720. * @return JwtAccessToken
  721. * @throws LogicException
  722. */
  723. protected function createDefaultJwtAccessTokenResponseType()
  724. {
  725. if (!isset($this->storages['public_key'])) {
  726. throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens');
  727. }
  728. $tokenStorage = null;
  729. if (isset($this->storages['access_token'])) {
  730. $tokenStorage = $this->storages['access_token'];
  731. }
  732. $refreshStorage = null;
  733. if (isset($this->storages['refresh_token'])) {
  734. $refreshStorage = $this->storages['refresh_token'];
  735. }
  736. $config = array_intersect_key($this->config, array_flip(explode(' ', 'store_encrypted_token_string issuer access_lifetime refresh_token_lifetime')));
  737. return new JwtAccessToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config);
  738. }
  739. /**
  740. * @return AccessToken
  741. * @throws LogicException
  742. */
  743. protected function createDefaultAccessTokenResponseType()
  744. {
  745. if (!isset($this->storages['access_token'])) {
  746. 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");
  747. }
  748. $refreshStorage = null;
  749. if (isset($this->storages['refresh_token'])) {
  750. $refreshStorage = $this->storages['refresh_token'];
  751. }
  752. $config = array_intersect_key($this->config, array_flip(explode(' ', 'access_lifetime refresh_token_lifetime')));
  753. $config['token_type'] = $this->tokenType ? $this->tokenType->getTokenType() : $this->getDefaultTokenType()->getTokenType();
  754. return new AccessToken($this->storages['access_token'], $refreshStorage, $config);
  755. }
  756. /**
  757. * @return IdToken
  758. * @throws LogicException
  759. */
  760. protected function createDefaultIdTokenResponseType()
  761. {
  762. if (!isset($this->storages['user_claims'])) {
  763. throw new LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use openid connect");
  764. }
  765. if (!isset($this->storages['public_key'])) {
  766. throw new LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use openid connect");
  767. }
  768. $config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime')));
  769. return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config);
  770. }
  771. /**
  772. * @return IdTokenToken
  773. */
  774. protected function createDefaultIdTokenTokenResponseType()
  775. {
  776. return new IdTokenToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType());
  777. }
  778. /**
  779. * @throws InvalidArgumentException
  780. */
  781. protected function validateOpenIdConnect()
  782. {
  783. $authCodeGrant = $this->getGrantType('authorization_code');
  784. if (!empty($authCodeGrant) && !$authCodeGrant instanceof OpenIDAuthorizationCodeGrantType) {
  785. throw new \InvalidArgumentException('You have enabled OpenID Connect, but supplied a grant type that does not support it.');
  786. }
  787. }
  788. /**
  789. * @param string $name
  790. * @return string
  791. */
  792. protected function normalizeResponseType($name)
  793. {
  794. // for multiple-valued response types - make them alphabetical
  795. if (!empty($name) && false !== strpos($name, ' ')) {
  796. $types = explode(' ', $name);
  797. sort($types);
  798. $name = implode(' ', $types);
  799. }
  800. return $name;
  801. }
  802. /**
  803. * @return mixed
  804. */
  805. public function getResponse()
  806. {
  807. return $this->response;
  808. }
  809. /**
  810. * @return array
  811. */
  812. public function getStorages()
  813. {
  814. return $this->storages;
  815. }
  816. /**
  817. * @param string $name
  818. * @return object|null
  819. */
  820. public function getStorage($name)
  821. {
  822. return isset($this->storages[$name]) ? $this->storages[$name] : null;
  823. }
  824. /**
  825. * @return array
  826. */
  827. public function getGrantTypes()
  828. {
  829. return $this->grantTypes;
  830. }
  831. /**
  832. * @param string $name
  833. * @return object|null
  834. */
  835. public function getGrantType($name)
  836. {
  837. return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null;
  838. }
  839. /**
  840. * @return array
  841. */
  842. public function getResponseTypes()
  843. {
  844. return $this->responseTypes;
  845. }
  846. /**
  847. * @param string $name
  848. * @return object|null
  849. */
  850. public function getResponseType($name)
  851. {
  852. // for multiple-valued response types - make them alphabetical
  853. $name = $this->normalizeResponseType($name);
  854. return isset($this->responseTypes[$name]) ? $this->responseTypes[$name] : null;
  855. }
  856. /**
  857. * @return TokenTypeInterface
  858. */
  859. public function getTokenType()
  860. {
  861. return $this->tokenType;
  862. }
  863. /**
  864. * @return ClientAssertionTypeInterface
  865. */
  866. public function getClientAssertionType()
  867. {
  868. return $this->clientAssertionType;
  869. }
  870. /**
  871. * @param string $name
  872. * @param mixed $value
  873. */
  874. public function setConfig($name, $value)
  875. {
  876. $this->config[$name] = $value;
  877. }
  878. /**
  879. * @param string $name
  880. * @param mixed $default
  881. * @return mixed
  882. */
  883. public function getConfig($name, $default = null)
  884. {
  885. return isset($this->config[$name]) ? $this->config[$name] : $default;
  886. }
  887. }