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