diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6c249..a46360e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v3.0.0] - 2023-05-05 + +### Added + +- Add option for showing/hiding ID Token from dashboard + +### Changed + +- Update composer dependencies +- Use `jumbojett/openid-connect-php` from composer + +### Fixed + +- Use font awesome from composer +- Use minified CSS and JS +- Handle issuer with trailing slash correctly + +### Removed + +- Remove support for PHP 5 + ## [v2.3.1] - 2022-09-07 ### Fixed diff --git a/README.md b/README.md index 9d2460e..ab5012c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ First you need to install apache and composer ```shell sudo apt-get update -sudo apt-get install apache2 curl php-cli git +sudo apt-get install apache2 curl php-cli php-json php-xml git php -r "copy('https://getcomposer.org/installer', '/tmp/composer-setup.php');" sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer ``` @@ -55,18 +55,26 @@ tar -zxvf simple-oidc-client-php-X.Y.Z.tar.gz ## Simple OIDC Client - authentication -Now that we have everything we need, we can configure our login settings in +Now that you have everything you need, you can configure your login settings in `config.php`. +First, copy the configuration file, using the command: + +```shell +cp example-config.php config.php +``` + +Then open the file and configure the portal. ```php =5.6", - "phpseclib/phpseclib" : "^2.0", - "ext-json": "*", - "ext-curl": "*", + "php": ">=7.0", "twbs/bootstrap": "^4.0", - "paragonie/random_compat":"2.0.19", "components/jquery": "^3.3", - "components/font-awesome": "^5.15" + "components/font-awesome": "^5.15", + "jumbojett/openid-connect-php": "^0.9" }, "archive" : { "exclude" : [ diff --git a/example-config.php b/example-config.php index bae0157..a0593d9 100644 --- a/example-config.php +++ b/example-config.php @@ -23,9 +23,10 @@ $accessTokenNote = "NOTE: New access tokens expire in 1 hour."; $manageTokenNote = "You can manage your refresh tokens in the following link: "; $manageTokens = $issuer . "manage/user/services"; -$sessionName = "oidc-client"; +$sessionName = "simple-oidc-client-php"; // This value must be the same with the name of the parent directory $sessionLifetime = 60 * 60; // must be equal to access token validation time in seconds $bannerText = ""; $bannerType = "info"; // Select one of "info", "warning", "error" or "success" $allowIntrospection = false; -$enableActiveTokensTable = false; +$enableActiveTokensTable = false; // This option works only for MITREid Connect based OPs +$showIdToken = false; diff --git a/refreshtoken.php b/refreshtoken.php index 4d253a0..42595b5 100644 --- a/refreshtoken.php +++ b/refreshtoken.php @@ -21,6 +21,13 @@
+ +
+

ID Token:

+ + +
+

Access Token:

diff --git a/resources/controllers/session.php b/resources/controllers/session.php index cd0f91b..c3cfa8f 100644 --- a/resources/controllers/session.php +++ b/resources/controllers/session.php @@ -58,22 +58,26 @@ $sub = $oidc->requestUserInfo('sub'); if ($sub) { $accessToken = $_SESSION['access_token']; + $idToken = $_SESSION['id_token']; $_SESSION['refresh_token'] = $refreshToken; } unset($_SESSION['action']); } else { $accessToken = $_SESSION['access_token']; + $idToken = $oidc->getIdToken(); $refreshToken = $_SESSION['refresh_token']; unset($_SESSION['action']); } } else { $oidc->authenticate(); $accessToken = $oidc->getAccessToken(); + $idToken = $oidc->getIdToken(); $refreshToken = $oidc->getRefreshToken(); $sub = $oidc->requestUserInfo('sub'); if ($sub) { $_SESSION['sub'] = $sub; $_SESSION['access_token'] = $accessToken; + $_SESSION['id_token'] = $idToken; $_SESSION['refresh_token'] = $refreshToken; $_SESSION['CREATED'] = time(); } diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php deleted file mode 100644 index 2bad8b5..0000000 --- a/src/OpenIDConnectClient.php +++ /dev/null @@ -1,1945 +0,0 @@ - - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - */ - -namespace Jumbojett; - -/** - * - * JWT signature verification support by Jonathan Reed - * Licensed under the same license as the rest of this file. - * - * phpseclib is required to validate the signatures of some tokens. - * It can be downloaded from: http://phpseclib.sourceforge.net/ - */ - -if (!class_exists('\phpseclib3\Crypt\RSA') && !class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { - user_error('Unable to find phpseclib Crypt/RSA.php. Ensure phpseclib is installed and in include_path before you include this file'); -} - -/** - * A wrapper around base64_decode which decodes Base64URL-encoded data, - * which is not the same alphabet as base64. - * @param string $base64url - * @return bool|string - */ -function base64url_decode($base64url) { - return base64_decode(b64url2b64($base64url)); -} - -/** - * Per RFC4648, "base64 encoding with URL-safe and filename-safe - * alphabet". This just replaces characters 62 and 63. None of the - * reference implementations seem to restore the padding if necessary, - * but we'll do it anyway. - * @param string $base64url - * @return string - */ -function b64url2b64($base64url) { - // "Shouldn't" be necessary, but why not - $padding = strlen($base64url) % 4; - if ($padding > 0) { - $base64url .= str_repeat('=', 4 - $padding); - } - return strtr($base64url, '-_', '+/'); -} - - -/** - * OpenIDConnect Exception Class - */ -class OpenIDConnectClientException extends \Exception -{ - -} - -/** - * Require the CURL and JSON PHP extensions to be installed - */ -if (!function_exists('curl_init')) { - throw new OpenIDConnectClientException('OpenIDConnect needs the CURL PHP extension.'); -} -if (!function_exists('json_decode')) { - throw new OpenIDConnectClientException('OpenIDConnect needs the JSON PHP extension.'); -} - -/** - * - * Please note this class stores nonces by default in $_SESSION['openid_connect_nonce'] - * - */ -class OpenIDConnectClient -{ - - /** - * @var string arbitrary id value - */ - private $clientID; - - /** - * @var string arbitrary name value - */ - private $clientName; - - /** - * @var string arbitrary secret value - */ - private $clientSecret; - - /** - * @var array holds the provider configuration - */ - private $providerConfig = []; - - /** - * @var string http proxy if necessary - */ - private $httpProxy; - - /** - * @var string full system path to the SSL certificate - */ - private $certPath; - - /** - * @var bool Verify SSL peer on transactions - */ - private $verifyPeer = true; - - /** - * @var bool Verify peer hostname on transactions - */ - private $verifyHost = true; - - /** - * @var string if we acquire an access token it will be stored here - */ - protected $accessToken; - - /** - * @var string if we acquire a refresh token it will be stored here - */ - private $refreshToken; - - /** - * @var string if we acquire an id token it will be stored here - */ - protected $idToken; - - /** - * @var string stores the token response - */ - private $tokenResponse; - - /** - * @var array holds scopes - */ - private $scopes = []; - - /** - * @var int|null Response code from the server - */ - private $responseCode; - - /** - * @var array holds response types - */ - private $responseTypes = []; - - /** - * @var array holds a cache of info returned from the user info endpoint - */ - private $userInfo = []; - - /** - * @var array holds authentication parameters - */ - private $authParams = []; - - /** - * @var array holds additional registration parameters for example post_logout_redirect_uris - */ - private $registrationParams = []; - - /** - * @var mixed holds well-known openid server properties - */ - private $wellKnown = false; - - /** - * @var mixed holds well-known opendid configuration parameters, like policy for MS Azure AD B2C User Flow - * @see https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview - */ - private $wellKnownConfigParameters = []; - - /** - * @var int timeout (seconds) - */ - protected $timeOut = 60; - - /** - * @var int leeway (seconds) - */ - private $leeway = 300; - - /** - * @var array holds response types - */ - private $additionalJwks = []; - - /** - * @var array holds verified jwt claims - */ - protected $verifiedClaims = []; - - /** - * @var callable|null validator function for issuer claim - */ - private $issuerValidator; - - /** - * @var bool Allow OAuth 2 implicit flow; see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth - */ - private $allowImplicitFlow = false; - - /** - * @var string - */ - private $redirectURL; - - /** - * @var int defines which URL-encoding http_build_query() uses - */ - protected $encType = PHP_QUERY_RFC1738; - - /** - * @var bool Enable or disable upgrading to HTTPS by paying attention to HTTP header HTTP_UPGRADE_INSECURE_REQUESTS - */ - protected $httpUpgradeInsecureRequests = true; - - /** - * @var string holds code challenge method for PKCE mode - * @see https://tools.ietf.org/html/rfc7636 - */ - private $codeChallengeMethod = false; - - /** - * @var array holds PKCE supported algorithms - */ - private $pkceAlgs = ['S256' => 'sha256', 'plain' => false]; - - /** - * @param $provider_url string optional - * - * @param $client_id string optional - * @param $client_secret string optional - * @param null $issuer - */ - public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null) { - $this->setProviderURL($provider_url); - if ($issuer === null) { - $this->setIssuer($provider_url); - } else { - $this->setIssuer($issuer); - } - - $this->clientID = $client_id; - $this->clientSecret = $client_secret; - } - - /** - * @param $provider_url - */ - public function setProviderURL($provider_url) { - $this->providerConfig['providerUrl'] = $provider_url; - } - - /** - * @param $issuer - */ - public function setIssuer($issuer) { - $this->providerConfig['issuer'] = $issuer; - } - - /** - * @param $response_types - */ - public function setResponseTypes($response_types) { - $this->responseTypes = array_merge($this->responseTypes, (array)$response_types); - } - - /** - * @return bool - * @throws OpenIDConnectClientException - */ - public function authenticate() { - - // Do a preemptive check to see if the provider has thrown an error from a previous redirect - if (isset($_REQUEST['error'])) { - $desc = isset($_REQUEST['error_description']) ? ' Description: ' . $_REQUEST['error_description'] : ''; - throw new OpenIDConnectClientException('Error: ' . $_REQUEST['error'] .$desc); - } - - // If we have an authorization code then proceed to request a token - if (isset($_REQUEST['code'])) { - - $code = $_REQUEST['code']; - $token_json = $this->requestTokens($code); - - // Throw an error if the server returns one - if (isset($token_json->error)) { - if (isset($token_json->error_description)) { - throw new OpenIDConnectClientException($token_json->error_description); - } - throw new OpenIDConnectClientException('Got response: ' . $token_json->error); - } - - // Do an OpenID Connect session check - if ($_REQUEST['state'] !== $this->getState()) { - throw new OpenIDConnectClientException('Unable to determine state'); - } - - // Cleanup state - $this->unsetState(); - - if (!property_exists($token_json, 'id_token')) { - throw new OpenIDConnectClientException('User did not authorize openid scope.'); - } - - $claims = $this->decodeJWT($token_json->id_token, 1); - - // Verify the signature - if ($this->canVerifySignatures()) { - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); - } - if (!$this->verifyJWTsignature($token_json->id_token)) { - throw new OpenIDConnectClientException ('Unable to verify signature'); - } - } else { - user_error('Warning: JWT signature verification unavailable.'); - } - - // Save the id token - $this->idToken = $token_json->id_token; - - // Save the access token - $this->accessToken = $token_json->access_token; - - // If this is a valid claim - if ($this->verifyJWTclaims($claims, $token_json->access_token)) { - - // Clean up the session a little - $this->unsetNonce(); - - // Save the full response - $this->tokenResponse = $token_json; - - // Save the verified claims - $this->verifiedClaims = $claims; - - // Save the refresh token, if we got one - if (isset($token_json->refresh_token)) { - $this->refreshToken = $token_json->refresh_token; - } - - // Success! - return true; - - } - - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); - } - - if ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) { - // if we have no code but an id_token use that - $id_token = $_REQUEST['id_token']; - - $accessToken = null; - if (isset($_REQUEST['access_token'])) { - $accessToken = $_REQUEST['access_token']; - } - - // Do an OpenID Connect session check - if ($_REQUEST['state'] !== $this->getState()) { - throw new OpenIDConnectClientException('Unable to determine state'); - } - - // Cleanup state - $this->unsetState(); - - $claims = $this->decodeJWT($id_token, 1); - - // Verify the signature - if ($this->canVerifySignatures()) { - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); - } - if (!$this->verifyJWTsignature($id_token)) { - throw new OpenIDConnectClientException ('Unable to verify signature'); - } - } else { - user_error('Warning: JWT signature verification unavailable.'); - } - - // Save the id token - $this->idToken = $id_token; - - // If this is a valid claim - if ($this->verifyJWTclaims($claims, $accessToken)) { - - // Clean up the session a little - $this->unsetNonce(); - - // Save the verified claims - $this->verifiedClaims = $claims; - - // Save the access token - if ($accessToken) { - $this->accessToken = $accessToken; - } - - // Success! - return true; - - } - - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); - } - - $this->requestAuthorization(); - return false; - } - - /** - * It calls the end-session endpoint of the OpenID Connect provider to notify the OpenID - * Connect provider that the end-user has logged out of the relying party site - * (the client application). - * - * @param string $idToken ID token (obtained at login) - * @param string|null $redirect URL to which the RP is requesting that the End-User's User Agent - * be redirected after a logout has been performed. The value MUST have been previously - * registered with the OP. Value can be null. - * - * @throws OpenIDConnectClientException - */ - public function signOut($idToken, $redirect) { - $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); - - $signout_params = null; - if($redirect === null){ - $signout_params = ['id_token_hint' => $idToken]; - } - else { - $signout_params = [ - 'id_token_hint' => $idToken, - 'post_logout_redirect_uri' => $redirect]; - } - - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->encType); - $this->redirect($signout_endpoint); - } - - /** - * @param array $scope - example: openid, given_name, etc... - */ - public function addScope($scope) { - $this->scopes = array_merge($this->scopes, (array)$scope); - } - - /** - * @param array $param - example: prompt=login - */ - public function addAuthParam($param) { - $this->authParams = array_merge($this->authParams, (array)$param); - } - - /** - * @param array $param - example: post_logout_redirect_uris=[http://example.com/successful-logout] - */ - public function addRegistrationParam($param) { - $this->registrationParams = array_merge($this->registrationParams, (array)$param); - } - - /** - * @param $jwk object - example: (object) ['kid' => ..., 'nbf' => ..., 'use' => 'sig', 'kty' => "RSA", 'e' => "", 'n' => ""] - */ - protected function addAdditionalJwk($jwk) { - $this->additionalJwks[] = $jwk; - } - - /** - * Get's anything that we need configuration wise including endpoints, and other values - * - * @param string $param - * @param string $default optional - * @throws OpenIDConnectClientException - * @return string|array - * - */ - protected function getProviderConfigValue($param, $default = null) { - - // If the configuration value is not available, attempt to fetch it from a well known config endpoint - // This is also known as auto "discovery" - if (!isset($this->providerConfig[$param])) { - $this->providerConfig[$param] = $this->getWellKnownConfigValue($param, $default); - } - - return $this->providerConfig[$param]; - } - - /** - * Get's anything that we need configuration wise including endpoints, and other values - * - * @param string $param - * @param string $default optional - * @throws OpenIDConnectClientException - * @return string - * - */ - private function getWellKnownConfigValue($param, $default = null) { - - // If the configuration value is not available, attempt to fetch it from a well known config endpoint - // This is also known as auto "discovery" - if(!$this->wellKnown) { - $well_known_config_url = rtrim($this->getProviderURL(), '/') . '/.well-known/openid-configuration'; - if (count($this->wellKnownConfigParameters) > 0){ - $well_known_config_url .= '?' . http_build_query($this->wellKnownConfigParameters) ; - } - $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); - } - - $value = false; - if(isset($this->wellKnown->{$param})){ - $value = $this->wellKnown->{$param}; - } - - if ($value) { - return $value; - } - - if (isset($default)) { - // Uses default value if provided - return $default; - } - - throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); - } - - /** - * Set optionnal parameters for .well-known/openid-configuration - * - * @param string $param - * - */ - public function setWellKnownConfigParameters(array $params = []){ - $this->wellKnownConfigParameters=$params; - } - - - /** - * @param string $url Sets redirect URL for auth flow - */ - public function setRedirectURL ($url) { - if (parse_url($url,PHP_URL_HOST) !== false) { - $this->redirectURL = $url; - } - } - - /** - * Gets the URL of the current page we are on, encodes, and returns it - * - * @return string - */ - public function getRedirectURL() { - - // If the redirect URL has been set then return it. - if (property_exists($this, 'redirectURL') && $this->redirectURL) { - return $this->redirectURL; - } - - // Other-wise return the URL of the current page - - /** - * Thank you - * http://stackoverflow.com/questions/189113/how-do-i-get-current-page-full-url-in-php-on-a-windows-iis-server - */ - - /* - * Compatibility with multiple host headers. - * The problem with SSL over port 80 is resolved and non-SSL over port 443. - * Support of 'ProxyReverse' configurations. - */ - - if ($this->httpUpgradeInsecureRequests && isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] === '1')) { - $protocol = 'https'; - } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { - $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO']; - } elseif (isset($_SERVER['REQUEST_SCHEME'])) { - $protocol = $_SERVER['REQUEST_SCHEME']; - } elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { - $protocol = 'https'; - } else { - $protocol = 'http'; - } - - if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $port = intval($_SERVER['HTTP_X_FORWARDED_PORT']); - } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = intval($_SERVER['SERVER_PORT']); - } elseif ($protocol === 'https') { - $port = 443; - } else { - $port = 80; - } - - if (isset($_SERVER['HTTP_HOST'])) { - $host = explode(':', $_SERVER['HTTP_HOST'])[0]; - } elseif (isset($_SERVER['SERVER_NAME'])) { - $host = $_SERVER['SERVER_NAME']; - } elseif (isset($_SERVER['SERVER_ADDR'])) { - $host = $_SERVER['SERVER_ADDR']; - } else { - return 'http:///'; - } - - $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; - - $explodedRequestUri = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI']) : []; - return sprintf('%s://%s%s/%s', $protocol, $host, $port, trim(reset($explodedRequestUri), '/')); - } - - /** - * Used for arbitrary value generation for nonces and state - * - * @return string - * @throws OpenIDConnectClientException - */ - protected function generateRandString() { - // Error and Exception need to be catched in this order, see https://github.com/paragonie/random_compat/blob/master/README.md - // random_compat polyfill library should be removed if support for PHP versions < 7 is dropped - try { - return \bin2hex(\random_bytes(16)); - } catch (Error $e) { - throw new OpenIDConnectClientException('Random token generation failed.'); - } catch (Exception $e) { - throw new OpenIDConnectClientException('Random token generation failed.'); - }; - } - - /** - * Start Here - * @return void - * @throws OpenIDConnectClientException - */ - private function requestAuthorization() { - - $auth_endpoint = $this->getProviderConfigValue('authorization_endpoint'); - $response_type = 'code'; - - // Generate and store a nonce in the session - // The nonce is an arbitrary value - $nonce = $this->setNonce($this->generateRandString()); - - // State essentially acts as a session key for OIDC - $state = $this->setState($this->generateRandString()); - - $auth_params = array_merge($this->authParams, [ - 'response_type' => $response_type, - 'redirect_uri' => $this->getRedirectURL(), - 'client_id' => $this->clientID, - 'nonce' => $nonce, - 'state' => $state, - 'scope' => 'openid' - ]); - - // If the client has been registered with additional scopes - if (count($this->scopes) > 0) { - $auth_params = array_merge($auth_params, ['scope' => implode(' ', array_merge($this->scopes, ['openid']))]); - } - - // If the client has been registered with additional response types - if (count($this->responseTypes) > 0) { - $auth_params = array_merge($auth_params, ['response_type' => implode(' ', $this->responseTypes)]); - } - - // If the client supports Proof Key for Code Exchange (PKCE) - $codeChallengeMethod = $this->getCodeChallengeMethod(); - if (!empty($codeChallengeMethod) && in_array($codeChallengeMethod, $this->getProviderConfigValue('code_challenge_methods_supported', []), true)) { - $codeVerifier = bin2hex(random_bytes(64)); - $this->setCodeVerifier($codeVerifier); - if (!empty($this->pkceAlgs[$codeChallengeMethod])) { - $codeChallenge = rtrim(strtr(base64_encode(hash($this->pkceAlgs[$codeChallengeMethod], $codeVerifier, true)), '+/', '-_'), '='); - } else { - $codeChallenge = $codeVerifier; - } - $auth_params = array_merge($auth_params, [ - 'code_challenge' => $codeChallenge, - 'code_challenge_method' => $codeChallengeMethod - ]); - } - - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, '', '&', $this->encType); - - $this->commitSession(); - $this->redirect($auth_endpoint); - } - - /** - * Requests a client credentials token - * - * @throws OpenIDConnectClientException - */ - public function requestClientCredentialsToken() { - $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - - $headers = []; - - $grant_type = 'client_credentials'; - - $post_data = [ - 'grant_type' => $grant_type, - 'client_id' => $this->clientID, - 'client_secret' => $this->clientSecret, - 'scope' => implode(' ', $this->scopes) - ]; - - // Convert token params to string format - $post_params = http_build_query($post_data, '', '&', $this->encType); - - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); - } - - /** - * Requests a resource owner token - * (Defined in https://tools.ietf.org/html/rfc6749#section-4.3) - * - * @param boolean $bClientAuth Indicates that the Client ID and Secret be used for client authentication - * @return mixed - * @throws OpenIDConnectClientException - */ - public function requestResourceOwnerToken($bClientAuth = FALSE) { - $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - - $headers = []; - - $grant_type = 'password'; - - $post_data = [ - 'grant_type' => $grant_type, - 'username' => $this->authParams['username'], - 'password' => $this->authParams['password'], - 'scope' => implode(' ', $this->scopes) - ]; - - //For client authentication include the client values - if($bClientAuth) { - $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { - $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - } else { - $post_data['client_id'] = $this->clientID; - $post_data['client_secret'] = $this->clientSecret; - } - } - - // Convert token params to string format - $post_params = http_build_query($post_data, '', '&', $this->encType); - - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); - } - - - /** - * Requests ID and Access tokens - * - * @param string $code - * @param string[] $headers Extra HTTP headers to pass to the token endpoint - * @return mixed - * @throws OpenIDConnectClientException - */ - protected function requestTokens($code, $headers = array()) { - $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); - - $grant_type = 'authorization_code'; - - $token_params = [ - 'grant_type' => $grant_type, - 'code' => $code, - 'redirect_uri' => $this->getRedirectURL(), - 'client_id' => $this->clientID, - 'client_secret' => $this->clientSecret - ]; - - $authorizationHeader = null; - # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { - $authorizationHeader = 'Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret)); - unset($token_params['client_secret']); - unset($token_params['client_id']); - } - - $ccm = $this->getCodeChallengeMethod(); - $cv = $this->getCodeVerifier(); - if (!empty($ccm) && !empty($cv)) { - $cs = $this->getClientSecret(); - if (empty($cs)) { - $authorizationHeader = null; - unset($token_params['client_secret']); - } - $token_params = array_merge($token_params, [ - 'client_id' => $this->clientID, - 'code_verifier' => $this->getCodeVerifier() - ]); - } - - // Convert token params to string format - $token_params = http_build_query($token_params, '', '&', $this->encType); - - if (null !== $authorizationHeader) { - $headers[] = $authorizationHeader; - } - - $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); - - return $this->tokenResponse; - } - - /** - * Request RFC8693 Token Exchange - * https://datatracker.ietf.org/doc/html/rfc8693 - * - * @param string $subjectToken - * @param string $subjectTokenType - * @param string $audience - * @return mixed - * @throws OpenIDConnectClientException - */ - public function requestTokenExchange($subjectToken, $subjectTokenType, $audience = '') { - $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); - $headers = []; - $grant_type = 'urn:ietf:params:oauth:grant-type:token-exchange'; - - $post_data = array( - 'grant_type' => $grant_type, - 'subject_token_type' => $subjectTokenType, - 'subject_token' => $subjectToken, - 'client_id' => $this->clientID, - 'client_secret' => $this->clientSecret, - 'scope' => implode(' ', $this->scopes) - ); - - if (!empty($audience)) { - $post_data['audience'] = $audience; - } - - # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { - $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($post_data['client_secret']); - unset($post_data['client_id']); - } - - // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); - - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); - } - - - /** - * Requests Access token with refresh token - * - * @param string $refresh_token - * @return mixed - * @throws OpenIDConnectClientException - */ - public function refreshToken($refresh_token) { - $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); - - $headers = []; - - $grant_type = 'refresh_token'; - - $token_params = [ - 'grant_type' => $grant_type, - 'refresh_token' => $refresh_token, - 'client_id' => $this->clientID, - 'client_secret' => $this->clientSecret, - 'scope' => implode(' ', $this->scopes), - ]; - - # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { - $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($token_params['client_secret']); - unset($token_params['client_id']); - } - - // Convert token params to string format - $token_params = http_build_query($token_params, '', '&', $this->encType); - - $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); - - if (isset($json->access_token)) { - $this->accessToken = $json->access_token; - } - - if (isset($json->refresh_token)) { - $this->refreshToken = $json->refresh_token; - } - - return $json; - } - - /** - * @param array $keys - * @param array $header - * @throws OpenIDConnectClientException - * @return object - */ - private function getKeyForHeader($keys, $header) { - foreach ($keys as $key) { - if ($key->kty === 'RSA') { - if (!isset($header->kid) || $key->kid === $header->kid) { - return $key; - } - } else { - if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { - return $key; - } - } - } - if ($this->additionalJwks) { - foreach ($this->additionalJwks as $key) { - if ($key->kty === 'RSA') { - if (!isset($header->kid) || $key->kid === $header->kid) { - return $key; - } - } else { - if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { - return $key; - } - } - } - } - if (isset($header->kid)) { - throw new OpenIDConnectClientException('Unable to find a key for (algorithm, kid):' . $header->alg . ', ' . $header->kid . ')'); - } - - throw new OpenIDConnectClientException('Unable to find a key for RSA'); - } - - - /** - * @param string $hashtype - * @param object $key - * @param $payload - * @param $signature - * @param $signatureType - * @return bool - * @throws OpenIDConnectClientException - */ - private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $signatureType) { - if (!class_exists('\phpseclib3\Crypt\RSA') && !class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { - throw new OpenIDConnectClientException('Crypt_RSA support unavailable.'); - } - if (!(property_exists($key, 'n') && property_exists($key, 'e'))) { - throw new OpenIDConnectClientException('Malformed key object'); - } - - /* We already have base64url-encoded data, so re-encode it as - regular base64 and use the XML key format for simplicity. - */ - $public_key_xml = "\r\n". - ' ' . b64url2b64($key->n) . "\r\n" . - ' ' . b64url2b64($key->e) . "\r\n" . - ''; - if (class_exists('\phpseclib3\Crypt\RSA', false)) { - $key = \phpseclib3\Crypt\PublicKeyLoader::load($public_key_xml) - ->withHash($hashtype); - if ($signatureType === 'PSS') { - $key = $key->withMGFHash($hashtype) - ->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS); - } else { - $key = $key->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PKCS1); - } - return $key->verify($payload, $signature); - } elseif (class_exists('Crypt_RSA', false)) { - $rsa = new Crypt_RSA(); - $rsa->setHash($hashtype); - if ($signatureType === 'PSS') { - $rsa->setMGFHash($hashtype); - } - $rsa->loadKey($public_key_xml, Crypt_RSA::PUBLIC_FORMAT_XML); - $rsa->setSignatureMode($signatureType === 'PSS' ? Crypt_RSA::SIGNATURE_PSS : Crypt_RSA::SIGNATURE_PKCS1); - return $rsa->verify($payload, $signature); - } else { - $rsa = new \phpseclib\Crypt\RSA(); - $rsa->setHash($hashtype); - if ($signatureType === 'PSS') { - $rsa->setMGFHash($hashtype); - } - $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); - $rsa->setSignatureMode($signatureType === 'PSS' ? \phpseclib\Crypt\RSA::SIGNATURE_PSS : \phpseclib\Crypt\RSA::SIGNATURE_PKCS1); - return $rsa->verify($payload, $signature); - } - } - - /** - * @param string $hashtype - * @param object $key - * @param $payload - * @param $signature - * @return bool - * @throws OpenIDConnectClientException - */ - private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) - { - if (!function_exists('hash_hmac')) { - throw new OpenIDConnectClientException('hash_hmac support unavailable.'); - } - - $expected=hash_hmac($hashtype, $payload, $key, true); - - if (function_exists('hash_equals')) { - return hash_equals($signature, $expected); - } - - return self::hashEquals($signature, $expected); - } - - /** - * @param string $jwt encoded JWT - * @throws OpenIDConnectClientException - * @return bool - */ - public function verifyJWTsignature($jwt) { - if (!\is_string($jwt)) { - throw new OpenIDConnectClientException('Error token is not a string'); - } - $parts = explode('.', $jwt); - if (!isset($parts[0])) { - throw new OpenIDConnectClientException('Error missing part 0 in token'); - } - $signature = base64url_decode(array_pop($parts)); - if (false === $signature || '' === $signature) { - throw new OpenIDConnectClientException('Error decoding signature from token'); - } - $header = json_decode(base64url_decode($parts[0])); - if (null === $header || !\is_object($header)) { - throw new OpenIDConnectClientException('Error decoding JSON from token header'); - } - if (!isset($header->alg)) { - throw new OpenIDConnectClientException('Error missing signature type in token header'); - } - - $payload = implode('.', $parts); - switch ($header->alg) { - case 'RS256': - case 'PS256': - case 'RS384': - case 'RS512': - $hashtype = 'sha' . substr($header->alg, 2); - $signatureType = $header->alg === 'PS256' ? 'PSS' : ''; - - if (isset($header->jwk)) { - $jwk = $header->jwk; - } else { - $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); - if ($jwks === NULL) { - throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); - } - $jwk = $this->getKeyForHeader($jwks->keys, $header); - } - - $verified = $this->verifyRSAJWTsignature($hashtype, - $jwk, - $payload, $signature, $signatureType); - break; - case 'HS256': - case 'HS512': - case 'HS384': - $hashtype = 'SHA' . substr($header->alg, 2); - $verified = $this->verifyHMACJWTsignature($hashtype, $this->getClientSecret(), $payload, $signature); - break; - default: - throw new OpenIDConnectClientException('No support for signature type: ' . $header->alg); - } - return $verified; - } - - /** - * @param string $iss - * @return bool - * @throws OpenIDConnectClientException - */ - protected function validateIssuer($iss) { - if ($this->issuerValidator !== null) { - return $this->issuerValidator->__invoke($iss); - } - - return ($iss === $this->getIssuer() || $iss === $this->getWellKnownIssuer() || $iss === $this->getWellKnownIssuer(true)); - } - - /** - * @param object $claims - * @param string|null $accessToken - * @return bool - */ - protected function verifyJWTclaims($claims, $accessToken = null) { - if(isset($claims->at_hash) && isset($accessToken)) { - if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none') { - $bit = substr($this->getIdTokenHeader()->alg, 2, 3); - } else { - // TODO: Error case. throw exception??? - $bit = '256'; - } - $len = ((int)$bit)/16; - $expected_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); - } - return (($this->validateIssuer($claims->iss)) - && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) - && (!isset($claims->nonce) || $claims->nonce === $this->getNonce()) - && ( !isset($claims->exp) || ((gettype($claims->exp) === 'integer') && ($claims->exp >= time() - $this->leeway))) - && ( !isset($claims->nbf) || ((gettype($claims->nbf) === 'integer') && ($claims->nbf <= time() + $this->leeway))) - && ( !isset($claims->at_hash) || !isset($accessToken) || $claims->at_hash === $expected_at_hash ) - ); - } - - /** - * @param string $str - * @return string - */ - protected function urlEncode($str) { - $enc = base64_encode($str); - $enc = rtrim($enc, '='); - $enc = strtr($enc, '+/', '-_'); - return $enc; - } - - /** - * @param string $jwt encoded JWT - * @param int $section the section we would like to decode - * @return object - */ - protected function decodeJWT($jwt, $section = 0) { - - $parts = explode('.', $jwt); - return json_decode(base64url_decode($parts[$section])); - } - - /** - * - * @param string|null $attribute optional - * - * Attribute Type Description - * user_id string REQUIRED Identifier for the End-User at the Issuer. - * name string End-User's full name in displayable form including all name parts, ordered according to End-User's locale and preferences. - * given_name string Given name or first name of the End-User. - * family_name string Surname or last name of the End-User. - * middle_name string Middle name of the End-User. - * nickname string Casual name of the End-User that may or may not be the same as the given_name. For instance, a nickname value of Mike might be returned alongside a given_name value of Michael. - * profile string URL of End-User's profile page. - * picture string URL of the End-User's profile picture. - * website string URL of End-User's web page or blog. - * email string The End-User's preferred e-mail address. - * verified boolean True if the End-User's e-mail address has been verified; otherwise false. - * gender string The End-User's gender: Values defined by this specification are female and male. Other values MAY be used when neither of the defined values are applicable. - * birthday string The End-User's birthday, represented as a date string in MM/DD/YYYY format. The year MAY be 0000, indicating that it is omitted. - * zoneinfo string String from zoneinfo [zoneinfo] time zone database. For example, Europe/Paris or America/Los_Angeles. - * locale string The End-User's locale, represented as a BCP47 [RFC5646] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; Implementations MAY choose to accept this locale syntax as well. - * phone_number string The End-User's preferred telephone number. E.164 [E.164] is RECOMMENDED as the format of this Claim. For example, +1 (425) 555-1212 or +56 (2) 687 2400. - * address JSON object The End-User's preferred address. The value of the address member is a JSON [RFC4627] structure containing some or all of the members defined in Section 2.4.2.1. - * updated_time string Time the End-User's information was last updated, represented as a RFC 3339 [RFC3339] datetime. For example, 2011-01-03T23:58:42+0000. - * - * @return mixed - * - * @throws OpenIDConnectClientException - */ - public function requestUserInfo($attribute = null) { - - $user_info_endpoint = $this->getProviderConfigValue('userinfo_endpoint'); - $schema = 'openid'; - - $user_info_endpoint .= '?schema=' . $schema; - - //The accessToken has to be sent in the Authorization header. - // Accept json to indicate response type - $headers = ["Authorization: Bearer {$this->accessToken}", - 'Accept: application/json']; - - $user_json = json_decode($this->fetchURL($user_info_endpoint,null,$headers)); - if ($this->getResponseCode() <> 200) { - throw new OpenIDConnectClientException('The communication to retrieve user data has failed with status code '.$this->getResponseCode()); - } - $this->userInfo = $user_json; - - if($attribute === null) { - return $this->userInfo; - } - - if (property_exists($this->userInfo, $attribute)) { - return $this->userInfo->$attribute; - } - - return null; - } - - /** - * - * @param string|null $attribute optional - * - * Attribute Type Description - * exp int Expires at - * nbf int Not before - * ver string Version - * iss string Issuer - * sub string Subject - * aud string Audience - * nonce string nonce - * iat int Issued At - * auth_time int Authenatication time - * oid string Object id - * - * @return mixed - * - */ - public function getVerifiedClaims($attribute = null) { - - if($attribute === null) { - return $this->verifiedClaims; - } - - if (property_exists($this->verifiedClaims, $attribute)) { - return $this->verifiedClaims->$attribute; - } - - return null; - } - - /** - * @param string $url - * @param string | null $post_body string If this is set the post type will be POST - * @param array $headers Extra headers to be send with the request. Format as 'NameHeader: ValueHeader' - * @throws OpenIDConnectClientException - * @return mixed - */ - protected function fetchURL($url, $post_body = null, $headers = []) { - - // OK cool - then let's create a new cURL resource handle - $ch = curl_init(); - - // Determine whether this is a GET or POST - if ($post_body !== null) { - // curl_setopt($ch, CURLOPT_POST, 1); - // Alows to keep the POST method even after redirect - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); - - // Default content type is form encoded - $content_type = 'application/x-www-form-urlencoded'; - - // Determine if this is a JSON payload and add the appropriate content type - if (is_object(json_decode($post_body))) { - $content_type = 'application/json'; - } - - // Add POST-specific headers - $headers[] = "Content-Type: {$content_type}"; - - } - - // If we set some headers include them - if(count($headers) > 0) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - // Set URL to download - curl_setopt($ch, CURLOPT_URL, $url); - - if (isset($this->httpProxy)) { - curl_setopt($ch, CURLOPT_PROXY, $this->httpProxy); - } - - // Include header in result? (0 = yes, 1 = no) - curl_setopt($ch, CURLOPT_HEADER, 0); - - // Allows to follow redirect - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - - /** - * Set cert - * Otherwise ignore SSL peer verification - */ - if (isset($this->certPath)) { - curl_setopt($ch, CURLOPT_CAINFO, $this->certPath); - } - - if($this->verifyHost) { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - } else { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - } - - if($this->verifyPeer) { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - } else { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - } - - // Should cURL return or print out the data? (true = return, false = print) - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Timeout in seconds - curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeOut); - - // Download the given URL, and return output - $output = curl_exec($ch); - - // HTTP Response code from server may be required from subclass - $info = curl_getinfo($ch); - $this->responseCode = $info['http_code']; - - if ($output === false) { - throw new OpenIDConnectClientException('Curl error: (' . curl_errno($ch) . ') ' . curl_error($ch)); - } - - // Close the cURL resource, and free system resources - curl_close($ch); - - return $output; - } - - /** - * @param bool $appendSlash - * @return string - * @throws OpenIDConnectClientException - */ - public function getWellKnownIssuer($appendSlash = false) { - - return $this->getWellKnownConfigValue('issuer') . ($appendSlash ? '/' : ''); - } - - /** - * @return string - * @throws OpenIDConnectClientException - */ - public function getIssuer() { - - if (!isset($this->providerConfig['issuer'])) { - throw new OpenIDConnectClientException('The issuer has not been set'); - } - - return $this->providerConfig['issuer']; - } - - /** - * @return mixed - * @throws OpenIDConnectClientException - */ - public function getProviderURL() { - if (!isset($this->providerConfig['providerUrl'])) { - throw new OpenIDConnectClientException('The provider URL has not been set'); - } - - return $this->providerConfig['providerUrl']; - } - - /** - * @param string $url - */ - public function redirect($url) { - header('Location: ' . $url); - exit; - } - - /** - * @param string $httpProxy - */ - public function setHttpProxy($httpProxy) { - $this->httpProxy = $httpProxy; - } - - /** - * @param string $certPath - */ - public function setCertPath($certPath) { - $this->certPath = $certPath; - } - - /** - * @return string|null - */ - public function getCertPath() { - return $this->certPath; - } - - /** - * @param bool $verifyPeer - */ - public function setVerifyPeer($verifyPeer) { - $this->verifyPeer = $verifyPeer; - } - - /** - * @param bool $verifyHost - */ - public function setVerifyHost($verifyHost) { - $this->verifyHost = $verifyHost; - } - - - /** - * Controls whether http header HTTP_UPGRADE_INSECURE_REQUESTS should be considered - * defaults to true - * @param bool $httpUpgradeInsecureRequests - */ - public function setHttpUpgradeInsecureRequests($httpUpgradeInsecureRequests) { - $this->httpUpgradeInsecureRequests = $httpUpgradeInsecureRequests; - } - - /** - * @return bool - */ - public function getVerifyHost() { - return $this->verifyHost; - } - - /** - * @return bool - */ - public function getVerifyPeer() { - return $this->verifyPeer; - } - - /** - * @return bool - */ - public function getHttpUpgradeInsecureRequests() - { - return $this->httpUpgradeInsecureRequests; - } - - /** - * Use this for custom issuer validation - * The given function should accept the issuer string from the JWT claim as the only argument - * and return true if the issuer is valid, otherwise return false - * - * @param callable $issuerValidator - */ - public function setIssuerValidator($issuerValidator) { - $this->issuerValidator = $issuerValidator; - } - - /** - * @param bool $allowImplicitFlow - */ - public function setAllowImplicitFlow($allowImplicitFlow) { - $this->allowImplicitFlow = $allowImplicitFlow; - } - - /** - * @return bool - */ - public function getAllowImplicitFlow() { - return $this->allowImplicitFlow; - } - - /** - * - * Use this to alter a provider's endpoints and other attributes - * - * @param array $array - * simple key => value - */ - public function providerConfigParam($array) { - $this->providerConfig = array_merge($this->providerConfig, $array); - } - - /** - * @param string $clientSecret - */ - public function setClientSecret($clientSecret) { - $this->clientSecret = $clientSecret; - } - - /** - * @param string $clientID - */ - public function setClientID($clientID) { - $this->clientID = $clientID; - } - - - /** - * Dynamic registration - * - * @throws OpenIDConnectClientException - */ - public function register() { - - $registration_endpoint = $this->getProviderConfigValue('registration_endpoint'); - - $send_object = (object ) array_merge($this->registrationParams, [ - 'redirect_uris' => [$this->getRedirectURL()], - 'client_name' => $this->getClientName() - ]); - - $response = $this->fetchURL($registration_endpoint, json_encode($send_object)); - - $json_response = json_decode($response); - - // Throw some errors if we encounter them - if ($json_response === false) { - throw new OpenIDConnectClientException('Error registering: JSON response received from the server was invalid.'); - } - - if (isset($json_response->{'error_description'})) { - throw new OpenIDConnectClientException($json_response->{'error_description'}); - } - - $this->setClientID($json_response->{'client_id'}); - - // The OpenID Connect Dynamic registration protocol makes the client secret optional - // and provides a registration access token and URI endpoint if it is not present - if (isset($json_response->{'client_secret'})) { - $this->setClientSecret($json_response->{'client_secret'}); - } else { - throw new OpenIDConnectClientException('Error registering: - Please contact the OpenID Connect provider and obtain a Client ID and Secret directly from them'); - } - - } - - /** - * Introspect a given token - either access token or refresh token. - * @see https://tools.ietf.org/html/rfc7662 - * - * @param string $token - * @param string $token_type_hint - * @param string|null $clientId - * @param string|null $clientSecret - * @return mixed - * @throws OpenIDConnectClientException - */ - public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { - $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); - - $post_data = ['token' => $token]; - - if ($token_type_hint) { - $post_data['token_type_hint'] = $token_type_hint; - } - $clientId = $clientId !== null ? $clientId : $this->clientID; - $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; - - // Convert token params to string format - $post_params = http_build_query($post_data, '', '&'); - $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), - 'Accept: application/json']; - - return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); - } - - /** - * Revoke a given token - either access token or refresh token. - * @see https://tools.ietf.org/html/rfc7009 - * - * @param string $token - * @param string $token_type_hint - * @param string|null $clientId - * @param string|null $clientSecret - * @return mixed - * @throws OpenIDConnectClientException - */ - public function revokeToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { - $revocation_endpoint = $this->getProviderConfigValue('revocation_endpoint'); - - $post_data = ['token' => $token]; - - if ($token_type_hint) { - $post_data['token_type_hint'] = $token_type_hint; - } - $clientId = $clientId !== null ? $clientId : $this->clientID; - $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; - - // Convert token params to string format - $post_params = http_build_query($post_data, '', '&'); - $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), - 'Accept: application/json']; - - return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers)); - } - - /** - * @return string - */ - public function getClientName() { - return $this->clientName; - } - - /** - * @param string $clientName - */ - public function setClientName($clientName) { - $this->clientName = $clientName; - } - - /** - * @return string - */ - public function getClientID() { - return $this->clientID; - } - - /** - * @return string - */ - public function getClientSecret() { - return $this->clientSecret; - } - - /** - * @return bool - */ - public function canVerifySignatures() { - return class_exists('\phpseclib3\Crypt\RSA') || class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); - } - - /** - * Set the access token. - * - * May be required for subclasses of this Client. - * - * @param string $accessToken - * @return void - */ - public function setAccessToken($accessToken) { - $this->accessToken = $accessToken; - } - - /** - * @return string - */ - public function getAccessToken() { - return $this->accessToken; - } - - /** - * @return string - */ - public function getRefreshToken() { - return $this->refreshToken; - } - - /** - * @return string - */ - public function getIdToken() { - return $this->idToken; - } - - /** - * @return object - */ - public function getAccessTokenHeader() { - return $this->decodeJWT($this->accessToken); - } - - /** - * @return object - */ - public function getAccessTokenPayload() { - return $this->decodeJWT($this->accessToken, 1); - } - - /** - * @return object - */ - public function getIdTokenHeader() { - return $this->decodeJWT($this->idToken); - } - - /** - * @return object - */ - public function getIdTokenPayload() { - return $this->decodeJWT($this->idToken, 1); - } - - /** - * @return string - */ - public function getTokenResponse() { - return $this->tokenResponse; - } - - /** - * Stores nonce - * - * @param string $nonce - * @return string - */ - protected function setNonce($nonce) { - $this->setSessionKey('openid_connect_nonce', $nonce); - return $nonce; - } - - /** - * Get stored nonce - * - * @return string - */ - protected function getNonce() { - return $this->getSessionKey('openid_connect_nonce'); - } - - /** - * Cleanup nonce - * - * @return void - */ - protected function unsetNonce() { - $this->unsetSessionKey('openid_connect_nonce'); - } - - /** - * Stores $state - * - * @param string $state - * @return string - */ - protected function setState($state) { - $this->setSessionKey('openid_connect_state', $state); - return $state; - } - - /** - * Get stored state - * - * @return string - */ - protected function getState() { - return $this->getSessionKey('openid_connect_state'); - } - - /** - * Cleanup state - * - * @return void - */ - protected function unsetState() { - $this->unsetSessionKey('openid_connect_state'); - } - - /** - * Stores $codeVerifier - * - * @param string $codeVerifier - * @return string - */ - protected function setCodeVerifier($codeVerifier) { - $this->setSessionKey('openid_connect_code_verifier', $codeVerifier); - return $codeVerifier; - } - - /** - * Get stored codeVerifier - * - * @return string - */ - protected function getCodeVerifier() { - return $this->getSessionKey('openid_connect_code_verifier'); - } - - /** - * Cleanup state - * - * @return void - */ - protected function unsetCodeVerifier() { - $this->unsetSessionKey('openid_connect_code_verifier'); - } - - /** - * Get the response code from last action/curl request. - * - * @return int - */ - public function getResponseCode() { - return $this->responseCode; - } - - /** - * Set timeout (seconds) - * - * @param int $timeout - */ - public function setTimeout($timeout) { - $this->timeOut = $timeout; - } - - /** - * @return int - */ - public function getTimeout() { - return $this->timeOut; - } - - /** - * Safely calculate length of binary string - * @param string $str - * @return int - */ - private static function safeLength($str) { - if (function_exists('mb_strlen')) { - return mb_strlen($str, '8bit'); - } - return strlen($str); - } - - /** - * Where hash_equals is not available, this provides a timing-attack safe string comparison - * @param string $str1 - * @param string $str2 - * @return bool - */ - private static function hashEquals($str1, $str2) { - $len1=static::safeLength($str1); - $len2=static::safeLength($str2); - - //compare strings without any early abort... - $len = min($len1, $len2); - $status = 0; - for ($i = 0; $i < $len; $i++) { - $status |= (ord($str1[$i]) ^ ord($str2[$i])); - } - //if strings were different lengths, we fail - $status |= ($len1 ^ $len2); - return ($status === 0); - } - - /** - * Use session to manage a nonce - */ - protected function startSession() { - if (session_status() === PHP_SESSION_NONE) { - @session_start(); - } - } - - protected function commitSession() { - $this->startSession(); - - session_write_close(); - } - - protected function getSessionKey($key) { - $this->startSession(); - - if (array_key_exists($key, $_SESSION)) { - return $_SESSION[$key]; - } - return false; - } - - protected function setSessionKey($key, $value) { - $this->startSession(); - - $_SESSION[$key] = $value; - } - - protected function unsetSessionKey($key) { - $this->startSession(); - - unset($_SESSION[$key]); - } - - public function setUrlEncoding($curEncoding) { - switch ($curEncoding) - { - case PHP_QUERY_RFC1738: - $this->encType = PHP_QUERY_RFC1738; - break; - - case PHP_QUERY_RFC3986: - $this->encType = PHP_QUERY_RFC3986; - break; - - default: - break; - } - - } - - /** - * @return array - */ - public function getScopes() { - return $this->scopes; - } - - /** - * @return array - */ - public function getResponseTypes() { - return $this->responseTypes; - } - - /** - * @return array - */ - public function getAuthParams() { - return $this->authParams; - } - - /** - * @return callable - */ - public function getIssuerValidator() { - return $this->issuerValidator; - } - - /** - * @return int - */ - public function getLeeway() { - return $this->leeway; - } - - /** - * @return string - */ - public function getCodeChallengeMethod() { - return $this->codeChallengeMethod; - } - - /** - * @param string $codeChallengeMethod - */ - public function setCodeChallengeMethod($codeChallengeMethod) { - $this->codeChallengeMethod = $codeChallengeMethod; - } -}