| 1: | <?php |
| 2: | |
| 3: | |
| 4: | |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | |
| 10: | |
| 11: | |
| 12: | |
| 13: | |
| 14: | |
| 15: | |
| 16: | |
| 17: | |
| 18: | |
| 19: | |
| 20: | |
| 21: | |
| 22: | |
| 23: | namespace SimpleID\Protocols\Connect; |
| 24: | |
| 25: | use Psr\Log\LogLevel; |
| 26: | use SimpleID\ModuleManager; |
| 27: | use SimpleID\Auth\AuthManager; |
| 28: | use SimpleID\Base\ScopeInfoCollectionEvent; |
| 29: | use SimpleID\Protocols\HTTPResponse; |
| 30: | use SimpleID\Protocols\ProtocolResult; |
| 31: | use SimpleID\Protocols\OAuth\OAuthEvent; |
| 32: | use SimpleID\Protocols\OAuth\OAuthProtectedResource; |
| 33: | use SimpleID\Protocols\OAuth\OAuthAuthRequestEvent; |
| 34: | use SimpleID\Protocols\OAuth\OAuthAuthGrantEvent; |
| 35: | use SimpleID\Protocols\OAuth\OAuthTokenGrantEvent; |
| 36: | use SimpleID\Protocols\OAuth\Response; |
| 37: | use SimpleID\Store\StoreManager; |
| 38: | use SimpleID\Util\Events\BaseDataCollectionEvent; |
| 39: | use SimpleJWT\Util\Helper; |
| 40: | use SimpleJWT\JWT; |
| 41: | use SimpleJWT\InvalidTokenException; |
| 42: | use SimpleJWT\Crypt\AlgorithmFactory; |
| 43: | use SimpleJWT\Crypt\AlgorithmInterface; |
| 44: | use SimpleJWT\Keys\KeySet; |
| 45: | use \Web; |
| 46: | |
| 47: | |
| 48: | |
| 49: | |
| 50: | class ConnectModule extends OAuthProtectedResource implements ProtocolResult { |
| 51: | |
| 52: | |
| 53: | |
| 54: | protected $oauth_include_request_body = true; |
| 55: | |
| 56: | static function init($f3) { |
| 57: | $f3->redirect('GET /.well-known/openid-configuration', '@oauth_metadata'); |
| 58: | $f3->route('GET|POST @connect_userinfo: /connect/userinfo', 'SimpleID\Protocols\Connect\ConnectModule->userinfo'); |
| 59: | $f3->route('GET @connect_jwks: /connect/jwks', 'SimpleID\Protocols\Connect\ConnectModule->jwks'); |
| 60: | } |
| 61: | |
| 62: | |
| 63: | public function __construct() { |
| 64: | parent::__construct(); |
| 65: | |
| 66: | $mgr = ModuleManager::instance(); |
| 67: | $mgr->loadModule('SimpleID\Protocols\OAuth\OAuthModule'); |
| 68: | |
| 69: | $this->checkConfig(); |
| 70: | } |
| 71: | |
| 72: | |
| 73: | |
| 74: | |
| 75: | protected function checkConfig() { |
| 76: | $config = $this->f3->get('config'); |
| 77: | |
| 78: | if (!is_readable($config['public_jwks_file'])) { |
| 79: | $this->logger->log(LogLevel::CRITICAL, 'Public JSON web key file not found.'); |
| 80: | $this->f3->error(500, $this->f3->get('intl.core.connect.missing_public_jwk', 'http://simpleid.org/docs/2/installing/#keys')); |
| 81: | } |
| 82: | |
| 83: | if (!is_readable($config['private_jwks_file'])) { |
| 84: | $this->logger->log(LogLevel::CRITICAL, 'Private JSON web key file not found.'); |
| 85: | $this->f3->error(500, $this->f3->get('intl.core.connect.missing_private_jwk', 'http://simpleid.org/docs/2/installing/#keys')); |
| 86: | } |
| 87: | } |
| 88: | |
| 89: | |
| 90: | |
| 91: | |
| 92: | |
| 93: | |
| 94: | |
| 95: | public function onOauthAuthResolve(OAuthEvent $event) { |
| 96: | $store = StoreManager::instance(); |
| 97: | $web = Web::instance(); |
| 98: | $request = $event->getRequest(); |
| 99: | $response = $event->getResponse(); |
| 100: | |
| 101: | |
| 102: | |
| 103: | if (isset($request['request_uri'])) { |
| 104: | $this->logger->log(LogLevel::INFO, 'OpenID request object: getting object from ' . $request['request_uri']); |
| 105: | |
| 106: | $parts = parse_url($request['request_uri']); |
| 107: | |
| 108: | $http_response = new HTTPResponse($web->request($request['request_uri'], [ 'headers' => [ 'Accept' => 'application/jwt,text/plain,application/octet-stream' ] ])); |
| 109: | |
| 110: | if ($http_response->isHTTPError()) { |
| 111: | $this->logger->log(LogLevel::ERROR, 'Cannot retrieve request file from request_uri:' . $request['request_uri']); |
| 112: | $response->setError('invalid_request_uri', 'cannot retrieve request file from request_uri'); |
| 113: | return; |
| 114: | } |
| 115: | |
| 116: | $request['request'] = $http_response->getBody(); |
| 117: | unset($request['request_uri']); |
| 118: | } |
| 119: | |
| 120: | |
| 121: | |
| 122: | if (isset($request['request'])) { |
| 123: | $this->logger->log(LogLevel::INFO, 'OpenID request object token: ' . $request['request']); |
| 124: | |
| 125: | |
| 126: | $client = $store->loadClient($request['client_id'], 'SimpleID\Protocols\OAuth\OAuthClient'); |
| 127: | |
| 128: | if (!isset($client['connect']['request_object_signing_alg'])) { |
| 129: | $this->logger->log(LogLevel::ERROR, 'Invalid OpenID request object: signature algorithm not registered'); |
| 130: | $response->setError('invalid_openid_request_object', 'signature algorithm not registered'); |
| 131: | return; |
| 132: | } |
| 133: | |
| 134: | $jwt_alg = (isset($client['connect']['request_object_signing_alg'])) ? $client['connect']['request_object_signing_alg'] : null; |
| 135: | $jwe_alg = (isset($client['connect']['request_object_encryption_alg'])) ? $client['connect']['request_object_encryption_alg'] : null; |
| 136: | $builder = new KeySetBuilder($client); |
| 137: | $set = $builder->addClientSecret()->addClientPublicKeys()->addServerPrivateKeys()->toKeySet(); |
| 138: | try { |
| 139: | AlgorithmFactory::addNoneAlg(); |
| 140: | $helper = new Helper($request['request']); |
| 141: | $jwt = $helper->decodeFully($set, $jwe_alg, $jwt_alg); |
| 142: | $request->loadData($jwt->getClaims()); |
| 143: | } catch (\UnexpectedValueException $e) { |
| 144: | $this->logger->log(LogLevel::ERROR, 'Invalid OpenID request object: ' . $e->getMessage()); |
| 145: | $response->setError('invalid_openid_request_object', $e->getMessage()); |
| 146: | return; |
| 147: | } |
| 148: | } |
| 149: | AlgorithmFactory::removeNoneAlg(); |
| 150: | |
| 151: | |
| 152: | if ($request->paramContains('scope', 'openid') && $request->paramContains('response_type', 'token') && !isset($request['nonce'])) { |
| 153: | $this->logger->log(LogLevel::ERROR, 'Protocol Error: nonce not set when using implicit flow'); |
| 154: | $response->setError('invalid_request', 'nonce not set when using implicit flow')->renderRedirect(); |
| 155: | return; |
| 156: | } |
| 157: | } |
| 158: | |
| 159: | |
| 160: | |
| 161: | |
| 162: | |
| 163: | |
| 164: | |
| 165: | |
| 166: | |
| 167: | |
| 168: | |
| 169: | |
| 170: | function onOAuthAuthRequestEvent(OAuthAuthRequestEvent $event) { |
| 171: | $request = $event->getRequest(); |
| 172: | $store = StoreManager::instance(); |
| 173: | $auth = AuthManager::instance(); |
| 174: | |
| 175: | $client = $store->loadClient($request['client_id'], 'SimpleID\Protocols\OAuth\OAuthClient'); |
| 176: | |
| 177: | |
| 178: | $request->setImmediate($request->paramContains('prompt', 'none')); |
| 179: | |
| 180: | if ($request->paramContains('prompt', 'login')) { |
| 181: | $this->f3->set('message', $this->f3->get('intl.common.reenter_credentials')); |
| 182: | $request->paramRemove('prompt', 'login'); |
| 183: | $event->setResult(self::CHECKID_REENTER_CREDENTIALS); |
| 184: | return; |
| 185: | } |
| 186: | |
| 187: | if ($request->paramContains('prompt', 'consent')) { |
| 188: | $request->paramRemove('prompt', 'consent'); |
| 189: | $event->setResult(self::CHECKID_APPROVAL_REQUIRED); |
| 190: | return; |
| 191: | } |
| 192: | |
| 193: | |
| 194: | if (isset($request['id_token_hint'])) { |
| 195: | try { |
| 196: | $jwt = JWT::deserialise($request['id_token_hint']); |
| 197: | $claims = $jwt['claims']; |
| 198: | $user_match = ($claims['sub'] == self::getSubject($auth->getUser(), $client)); |
| 199: | } catch (InvalidTokenException $e) { |
| 200: | $user_match = false; |
| 201: | } |
| 202: | if (!$user_match) { |
| 203: | $auth->logout(); |
| 204: | $event->setResult(self::CHECKID_LOGIN_REQUIRED); |
| 205: | return; |
| 206: | } |
| 207: | } |
| 208: | |
| 209: | |
| 210: | |
| 211: | if (isset($request['max_age'])) { |
| 212: | $max_age = $request['max_age']; |
| 213: | } elseif (isset($client['connect']) && isset($client['connect']['default_max_age'])) { |
| 214: | $max_age = $client['connect']['default_max_age']; |
| 215: | } else { |
| 216: | $max_age = -1; |
| 217: | } |
| 218: | |
| 219: | if (($max_age > -1) && $auth->isLoggedIn()) { |
| 220: | $auth_level = $auth->getAuthLevel(); |
| 221: | if ($auth_level == null) $auth_level = AuthManager::AUTH_LEVEL_SESSION; |
| 222: | |
| 223: | $auth_time = $auth->getAuthTime(); |
| 224: | if ($auth_time == null) $auth_time = 0; |
| 225: | |
| 226: | |
| 227: | |
| 228: | if (($auth_level < AuthManager::AUTH_LEVEL_CREDENTIALS) |
| 229: | || ((time() - $auth->getAuthTime()) > $max_age)) { |
| 230: | $this->f3->set('message', $this->f3->get('intl.common.reenter_credentials')); |
| 231: | $event->setResult(self::CHECKID_REENTER_CREDENTIALS); |
| 232: | return; |
| 233: | } |
| 234: | } |
| 235: | |
| 236: | if (isset($request['acr'])) { |
| 237: | $acr = $request['acr']; |
| 238: | } elseif (isset($client['connect']) && isset($client['connect']['default_acr'])) { |
| 239: | $acr = $client['connect']['default_acr']; |
| 240: | } else { |
| 241: | $acr = -1; |
| 242: | } |
| 243: | |
| 244: | if ($acr > -1) { |
| 245: | $event->setResult(self::CHECKID_INSUFFICIENT_TRUST); |
| 246: | return; |
| 247: | } |
| 248: | } |
| 249: | |
| 250: | |
| 251: | |
| 252: | |
| 253: | |
| 254: | |
| 255: | |
| 256: | |
| 257: | |
| 258: | |
| 259: | |
| 260: | |
| 261: | |
| 262: | |
| 263: | |
| 264: | |
| 265: | |
| 266: | |
| 267: | |
| 268: | |
| 269: | |
| 270: | function onOAuthAuthGrantEvent(OAuthAuthGrantEvent $event) { |
| 271: | |
| 272: | |
| 273: | |
| 274: | |
| 275: | |
| 276: | |
| 277: | $request = $event->getRequest(); |
| 278: | $response = $event->getResponse(); |
| 279: | $authorization = $event->getAuthorization(); |
| 280: | $scopes = $event->getRequestedScope(); |
| 281: | |
| 282: | if ($request->paramContains('scope', 'openid')) { |
| 283: | $user = AuthManager::instance()->getUser(); |
| 284: | |
| 285: | $client = StoreManager::instance()->loadClient($request['client_id'], 'SimpleID\Protocols\OAuth\OAuthClient'); |
| 286: | |
| 287: | if (isset($request['claims']) && is_string($request['claims'])) $request['claims'] = json_decode($request['claims'], true); |
| 288: | |
| 289: | |
| 290: | $claims_requested = (isset($request['claims']['id_token'])) ? $request['claims']['id_token'] : null; |
| 291: | $claims = $this->buildClaims($user, $client, 'id_token', $scopes, $claims_requested); |
| 292: | |
| 293: | if (isset($request['nonce'])) $claims['nonce'] = $request['nonce']; |
| 294: | |
| 295: | |
| 296: | if ($request->paramContains('response_type', 'id_token')) { |
| 297: | |
| 298: | |
| 299: | |
| 300: | $response->setResponseMode(Response::FRAGMENT_RESPONSE_MODE); |
| 301: | |
| 302: | |
| 303: | $jose = new JOSEResponse($this->getCanonicalHost(), $client, 'connect.id_token', $claims, 'RS256'); |
| 304: | |
| 305: | if (isset($response['code'])) $jose->setShortHashClaim('c_hash', $response['code']); |
| 306: | if (isset($response['access_token'])) $jose->setShortHashClaim('at_hash', $response['access_token']); |
| 307: | |
| 308: | $response['id_token'] = $jose->buildJOSE(); |
| 309: | } |
| 310: | |
| 311: | if ($request->paramContains('response_type', 'code')) { |
| 312: | |
| 313: | |
| 314: | |
| 315: | $authorization->additional['id_token_claims'] = $claims; |
| 316: | } |
| 317: | |
| 318: | |
| 319: | |
| 320: | if (isset($request['claims'])) { |
| 321: | $authorization->additional['claims'] = $request['claims']; |
| 322: | } |
| 323: | } |
| 324: | } |
| 325: | |
| 326: | |
| 327: | |
| 328: | |
| 329: | |
| 330: | |
| 331: | |
| 332: | |
| 333: | |
| 334: | function onOAuthTokenGrantEvent(OAuthTokenGrantEvent $event) { |
| 335: | $auth = $event->getAuthorization(); |
| 336: | $response = $event->getResponse(); |
| 337: | |
| 338: | if (($event->getGrantType() == 'authorization_code') && isset($auth->additional['id_token_claims'])) { |
| 339: | $client = $this->oauth->getClient(); |
| 340: | $claims = $auth->additional['id_token_claims']; |
| 341: | $access_token = $response['access_token']; |
| 342: | |
| 343: | |
| 344: | $jose = new JOSEResponse($this->getCanonicalHost(), $client, 'connect.id_token', $claims, 'RS256'); |
| 345: | $jose->setShortHashClaim('at_hash', $access_token); |
| 346: | |
| 347: | $response['id_token'] = $jose->buildJOSE(); |
| 348: | unset($auth->additional['id_token_claims']); |
| 349: | } |
| 350: | } |
| 351: | |
| 352: | |
| 353: | |
| 354: | |
| 355: | public function onOauthResponseTypes(BaseDataCollectionEvent $event) { |
| 356: | $event->addResult('id_token'); |
| 357: | } |
| 358: | |
| 359: | |
| 360: | |
| 361: | |
| 362: | |
| 363: | |
| 364: | |
| 365: | public function userinfo() { |
| 366: | $this->checkHttps('error'); |
| 367: | |
| 368: | $error = ''; |
| 369: | if (!$this->isTokenAuthorized('openid', $error)) { |
| 370: | $this->unAuthorizedError($error, null, [], 'json'); |
| 371: | } |
| 372: | |
| 373: | $authorization = $this->getAuthorization(); |
| 374: | |
| 375: | $user = $authorization->getOwner(); |
| 376: | |
| 377: | $client = $authorization->getClient('SimpleID\Protocols\OAuth\OAuthClient'); |
| 378: | $scope = $this->getAccessToken()->getScope(); |
| 379: | |
| 380: | $claims_requested = (isset($authorization->additional['claims']['userinfo'])) ? $authorization->additional['claims']['userinfo'] : null; |
| 381: | $claims = $this->buildClaims($user, $client, 'userinfo', $scope, $claims_requested); |
| 382: | if (count($claims) == 0) $this->unAuthorizedError('invalid_request'); |
| 383: | |
| 384: | $response = new JOSEResponse($this->getCanonicalHost(), $client, 'connect.userinfo', $claims); |
| 385: | $response->render(); |
| 386: | } |
| 387: | |
| 388: | |
| 389: | |
| 390: | |
| 391: | |
| 392: | |
| 393: | |
| 394: | |
| 395: | |
| 396: | |
| 397: | |
| 398: | |
| 399: | |
| 400: | |
| 401: | private function buildClaims($user, $client, $context, $scopes, $claims_requested = NULL) { |
| 402: | $auth = AuthManager::instance(); |
| 403: | $mgr = ModuleManager::instance(); |
| 404: | $dispatcher = \Events::instance(); |
| 405: | |
| 406: | $scope_info_event = new ScopeInfoCollectionEvent(); |
| 407: | $dispatcher->dispatch($scope_info_event); |
| 408: | $scope_info = $scope_info_event->getScopeInfoForType('oauth'); |
| 409: | |
| 410: | $claims = []; |
| 411: | $claims['sub'] = self::getSubject($user, $client); |
| 412: | |
| 413: | if ($claims_requested != null) { |
| 414: | foreach ($claims_requested as $claim => $properties) { |
| 415: | switch ($claim) { |
| 416: | case 'acr': |
| 417: | |
| 418: | break; |
| 419: | case 'updated_at': |
| 420: | |
| 421: | break; |
| 422: | default: |
| 423: | $consent_scope = null; |
| 424: | |
| 425: | foreach ($scope_info as $scope => $settings) { |
| 426: | if (!isset($settings['claims'])) continue; |
| 427: | if (in_array($claim, $settings['claims'])) $consent_scope = $scope; |
| 428: | } |
| 429: | if ($consent_scope == null) break; |
| 430: | |
| 431: | if (isset($user['userinfo'][$claim])) { |
| 432: | $claims[$claim] = $user['userinfo'][$claim]; |
| 433: | if ($claim == 'email') $claims['email_verified'] = false; |
| 434: | if ($claim == 'phone_number') $claims['phone_number_verified'] = false; |
| 435: | } |
| 436: | break; |
| 437: | } |
| 438: | } |
| 439: | } else { |
| 440: | foreach ([ 'profile', 'email', 'address', 'phone' ] as $scope) { |
| 441: | if (in_array($scope, $scopes)) { |
| 442: | if (isset($scope_info[$scope]['claims'])) { |
| 443: | foreach ($scope_info[$scope]['claims'] as $claim) { |
| 444: | if (isset($user['userinfo'][$claim])) $claims[$claim] = $user['userinfo'][$claim]; |
| 445: | if ($claim == 'email') $claims['email_verified'] = false; |
| 446: | if ($claim == 'phone_number') $claims['phone_number_verified'] = false; |
| 447: | } |
| 448: | } |
| 449: | } |
| 450: | } |
| 451: | } |
| 452: | |
| 453: | if ($context == 'id_token') { |
| 454: | $now = time(); |
| 455: | $claims['exp'] = $now + SIMPLEID_LONG_TOKEN_EXPIRES_IN - SIMPLEID_LONG_TOKEN_EXPIRES_BUFFER; |
| 456: | $claims['iat'] = $now; |
| 457: | $claims['auth_time'] = $auth->getAuthTime(); |
| 458: | $claims['acr'] = $auth->getACR(); |
| 459: | } |
| 460: | |
| 461: | $build_claims_event = new ConnectBuildClaimsEvent($user, $client, $context, $scopes, $claims_requested); |
| 462: | $build_claims_event->addResult($claims); |
| 463: | $dispatcher->dispatch($build_claims_event); |
| 464: | |
| 465: | return $build_claims_event->getResults(); |
| 466: | } |
| 467: | |
| 468: | |
| 469: | |
| 470: | |
| 471: | |
| 472: | |
| 473: | |
| 474: | |
| 475: | |
| 476: | |
| 477: | |
| 478: | |
| 479: | |
| 480: | |
| 481: | public static function getSubject($user, $client) { |
| 482: | if (isset($client['connect']['sector_identifier_uri'])) { |
| 483: | $sector_id = parse_url($client['connect']['sector_identifier_uri'], PHP_URL_HOST); |
| 484: | } elseif (is_string($client['oauth']['redirect_uris'])) { |
| 485: | $sector_id = parse_url($client['oauth']['redirect_uris'], PHP_URL_HOST); |
| 486: | } elseif (is_array($client['oauth']['redirect_uris']) && (count($client['oauth']['redirect_uris']) == 1)) { |
| 487: | $sector_id = parse_url($client['oauth']['redirect_uris'][0], PHP_URL_HOST); |
| 488: | } else { |
| 489: | $sector_id = $client->getStoreID(); |
| 490: | } |
| 491: | if (!$sector_id) return null; |
| 492: | |
| 493: | $claims = []; |
| 494: | |
| 495: | $subject_type = (isset($client['connect']['subject_type'])) ? $client['connect']['subject_type'] : 'pairwise'; |
| 496: | switch ($subject_type) { |
| 497: | case 'public': |
| 498: | return $user->getStoreID(); |
| 499: | case 'pairwise': |
| 500: | return $user->getPairwiseIdentity($sector_id); |
| 501: | default: |
| 502: | return null; |
| 503: | } |
| 504: | } |
| 505: | |
| 506: | |
| 507: | |
| 508: | |
| 509: | |
| 510: | |
| 511: | |
| 512: | public function onScopeInfoCollectionEvent(ScopeInfoCollectionEvent $event) { |
| 513: | $event->addScopeInfo('oauth', [ |
| 514: | 'openid' => [ |
| 515: | 'description' => $this->f3->get('intl.common.scope.id'), |
| 516: | 'weight' => -1 |
| 517: | ], |
| 518: | 'profile' => [ |
| 519: | 'description' => $this->f3->get('intl.common.scope.profile'), |
| 520: | 'claims' => ['name', 'family_name', 'given_name', 'middle_name', 'nickname', 'preferred_username', 'profile', 'picture', 'website', 'gender', 'birthdate', 'zoneinfo', 'locale' ] |
| 521: | ], |
| 522: | 'email' => [ |
| 523: | 'description' => $this->f3->get('intl.common.scope.email'), |
| 524: | 'claims' => [ 'email' ] |
| 525: | ], |
| 526: | 'address' => [ |
| 527: | 'description' => $this->f3->get('intl.common.scope.address'), |
| 528: | 'claims' => [ 'address' ] |
| 529: | ], |
| 530: | 'phone' => [ |
| 531: | 'description' => $this->f3->get('intl.common.scope.phone'), |
| 532: | 'claims' => [ 'phone_number' ] |
| 533: | ] |
| 534: | ]); |
| 535: | } |
| 536: | |
| 537: | |
| 538: | |
| 539: | |
| 540: | |
| 541: | |
| 542: | |
| 543: | public function onOauthMetadata(BaseDataCollectionEvent $event) { |
| 544: | $dispatcher = \Events::instance(); |
| 545: | |
| 546: | $scope_info_event = new ScopeInfoCollectionEvent(); |
| 547: | $dispatcher->dispatch($scope_info_event); |
| 548: | $scopes = $scope_info_event->getScopeInfoForType('oauth'); |
| 549: | |
| 550: | $jwt_signing_algs = AlgorithmFactory::getSupportedAlgs(AlgorithmInterface::SIGNATURE_ALGORITHM); |
| 551: | $jwt_encryption_algs = AlgorithmFactory::getSupportedAlgs(AlgorithmInterface::KEY_ALGORITHM); |
| 552: | $jwt_encryption_enc_algs = AlgorithmFactory::getSupportedAlgs(AlgorithmInterface::ENCRYPTION_ALGORITHM); |
| 553: | |
| 554: | $claims_supported = [ 'sub', 'iss', 'auth_time', 'acr' ]; |
| 555: | foreach ($scopes as $scope => $settings) { |
| 556: | if (isset($settings['claims'])) { |
| 557: | $claims_supported = array_merge($claims_supported, $settings['claims']); |
| 558: | } |
| 559: | } |
| 560: | |
| 561: | $config = [ |
| 562: | 'response_types_supported' => [ 'id_token', 'id_token token', 'code id_token', 'code id_token token' ], |
| 563: | 'userinfo_endpoint' => $this->getCanonicalURL('@connect_userinfo', '', 'https'), |
| 564: | 'jwks_uri' => $this->getCanonicalURL('@connect_jwks', '', 'https'), |
| 565: | 'acr_values_supported' => [], |
| 566: | 'subject_types_supported' => [ 'public', 'pairwise' ], |
| 567: | 'userinfo_signing_alg_values_supported' => $jwt_signing_algs, |
| 568: | 'userinfo_encryption_alg_values_supported' => $jwt_encryption_algs, |
| 569: | 'userinfo_encryption_enc_alg_values_supported' => $jwt_encryption_enc_algs, |
| 570: | 'id_token_signing_alg_values_supported' => $jwt_signing_algs, |
| 571: | 'id_token_encrpytion_alg_values_supported' => $jwt_encryption_algs, |
| 572: | 'id_token_encrpytion_enc_alg_values_supported' => $jwt_encryption_enc_algs, |
| 573: | 'request_object_signing_alg_values_supported' => array_merge($jwt_signing_algs, [ 'none' ]), |
| 574: | 'request_object_encryption_alg_values_supported' => $jwt_encryption_algs, |
| 575: | 'request_object_encryption_enc_alg_values_supported' => $jwt_encryption_enc_algs, |
| 576: | 'claim_types_supported' => [ 'normal' ], |
| 577: | 'claims_supported' => $claims_supported, |
| 578: | 'claims_parameter_supported' => true, |
| 579: | 'request_parameter_supported' => true, |
| 580: | 'request_uri_parameter_supported' => true, |
| 581: | 'require_request_uri_registration' => false, |
| 582: | ]; |
| 583: | |
| 584: | $event->addResult($config); |
| 585: | } |
| 586: | |
| 587: | |
| 588: | |
| 589: | |
| 590: | |
| 591: | |
| 592: | |
| 593: | public function jwks() { |
| 594: | $config = $this->f3->get('config'); |
| 595: | |
| 596: | if (!isset($config['public_jwks_file'])) { |
| 597: | $this->fatalError($this->f3->get('intl.core.connect.missing_jwks'), 404); |
| 598: | } |
| 599: | |
| 600: | $set = new KeySet(); |
| 601: | $file = file_get_contents($config['public_jwks_file']); |
| 602: | if ($file) $set->load($file); |
| 603: | |
| 604: | if (!$set->isPublic()) { |
| 605: | $this->fatalError($this->f3->get('intl.core.connect.jwks_not_public'), 401); |
| 606: | } |
| 607: | |
| 608: | header('Content-Type: application/jwk-set+json'); |
| 609: | header('Content-Disposition: inline; filename=jwks.json'); |
| 610: | print $set->toJWKS(); |
| 611: | } |
| 612: | } |
| 613: | ?> |
| 614: | |