From e629d9a798c86594e3c90b49b365715a51cbaf06 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Sun, 14 Jan 2018 18:16:52 -0500 Subject: [PATCH 001/223] Happy new year! --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 3754a49c..3e07ec6a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -2,7 +2,7 @@ /** * - * Copyright MITRE 2017 + * Copyright MITRE 2018 * * OpenIDConnectClient for PHP5 * Author: Michael Jett From 7660622fbc5eb3e93c709891f05574b984252e57 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Thu, 15 Feb 2018 10:37:09 -0500 Subject: [PATCH 002/223] Prepare for v0.4 release --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ea3e1c..8f9ecf42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] + +### Added +* + +### Changed +* + +### Removed +* + + +## [0.4] ### Added * Timeout is configurable via setTimeout method. This addresses issue #94. * Add the ability to authenticate using the Resource Owner flow (with or without the Client ID and ClientSecret). This addresses issue #98 From c6319d64881788cfc4816ab8ca3f3742d81ec20c Mon Sep 17 00:00:00 2001 From: jumbojett Date: Fri, 16 Feb 2018 06:52:30 -0500 Subject: [PATCH 003/223] Documentation updates for include path. --- CHANGELOG.md | 5 ++++- README.md | 2 +- client_example.php | 10 ++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9ecf42..368c360e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* ### Changed * @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed * +## [0.4.1] +## Changed +* Documentation updates for include path. ## [0.4] ### Added diff --git a/README.md b/README.md index 614c7d24..cb7f76bd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ composer require jumbojett/openid-connect-php ``` 2. Include composer autoloader ```php -require '/vendor/autoload.php'; +require __DIR__ . '/vendor/autoload.php'; ``` ## Example 1: Basic Client ## diff --git a/client_example.php b/client_example.php index a9cfc8c3..1ab25511 100644 --- a/client_example.php +++ b/client_example.php @@ -21,13 +21,15 @@ * */ -require "../../autoload.php"; +require __DIR__ . '/vendor/autoload.php'; use Jumbojett\OpenIDConnectClient; -$oidc = new OpenIDConnectClient('http://myproviderURL.com/', - 'ClientIDHere', - 'ClientSecretHere'); +$oidc = new OpenIDConnectClient( + 'http://myproviderURL.com/', + 'ClientIDHere', + 'ClientSecretHere' +); $oidc->authenticate(); $name = $oidc->requestUserInfo('given_name'); From 469dcd89f80bfc9812d785810e38d66665f7ae2e Mon Sep 17 00:00:00 2001 From: Simon Haeussler Date: Wed, 28 Mar 2018 19:47:27 +0200 Subject: [PATCH 004/223] Add option to add additional JSON Web Keys from subclasses. --- src/OpenIDConnectClient.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 3e07ec6a..6d522a55 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -189,6 +189,11 @@ class OpenIDConnectClient */ protected $timeOut = 60; + /** + * @var array holds response types + */ + private $additionalJwks = array(); + /** * @param $provider_url string optional * @@ -343,6 +348,13 @@ public function addAuthParam($param) { $this->authParams = array_merge($this->authParams, (array)$param); } + /** + * @param $jwk object - example: (object) array('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 * @@ -627,6 +639,19 @@ private function get_key_for_header($keys, $header) { } } } + if ($this->additionalJwks) { + foreach ($this->additionalJwks as $key) { + if ($key->kty == 'RSA') { + if (!isset($header->kid) || $key->kid == $header->kid) { + return $key; + } + } else { + if ($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 . ')'); } else { From 5aad0499b4a52910f596cf634d52d265231b11e9 Mon Sep 17 00:00:00 2001 From: Simon Haeussler Date: Wed, 28 Mar 2018 19:49:55 +0200 Subject: [PATCH 005/223] Implement implicit flow from OpenID connect. --- README.md | 18 ++++++ src/OpenIDConnectClient.php | 112 ++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/README.md b/README.md index cb7f76bd..5bce59b5 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,24 @@ $token = $oidc->requestResourceOwnerToken(TRUE)->access_token; ``` +## Example 6: Basic client for implicit flow e.g. with Azure AD B2C (see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth) ## + +```php +use Jumbojett\OpenIDConnectClient; + +$oidc = new OpenIDConnectClient('https://id.provider.com', + 'ClientIDHere', + 'ClientSecretHere'); +$oidc->setResponseTypes(array('id_token')); +$oidc->addScope(array('openid')); +$oidc->setAllowImplicitFlow(true); +$oidc->addAuthParam(array('response_mode' => 'form_post')); +$oidc->setCertPath('/path/to/my.cert'); +$oidc->authenticate(); +$sub = $oidc->getVerifiedClaims('sub'); + +``` + ## Development Environments ## In some cases you may need to disable SSL security on on your development systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6d522a55..2c903bdc 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -194,6 +194,16 @@ class OpenIDConnectClient */ private $additionalJwks = array(); + /** + * @var array holds verified jwt claims + */ + private $verifiedClaims = array(); + + /** + * @var bool Allow OAuth 2 implicit flow; see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth + */ + private $allowImplicitFlow = false; + /** * @param $provider_url string optional * @@ -288,6 +298,9 @@ public function authenticate() { // Save the access token $this->accessToken = $token_json->access_token; + // 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; @@ -297,7 +310,61 @@ public function authenticate() { } else { throw new OpenIDConnectClientException ("Unable to verify JWT claims"); } + } elseif ($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."); + } + + // If this is a valid claim + if ($this->verifyJWTclaims($claims, $accessToken)) { + + // Clean up the session a little + $this->unsetNonce(); + + // Save the id token + $this->idToken = $token_json->id_token; + + // Save the verified claims + $this->verifiedClaims = $claims; + + // Save the access token + if ($accessToken) $this->accessToken = $access_token; + // Save the refresh token, if we got one + if (isset($token_json->refresh_token)) $this->refreshToken = $token_json->refresh_token; + + // Success! + return true; + + } else { + throw new OpenIDConnectClientException ("Unable to verify JWT claims"); + } } else { $this->requestAuthorization(); @@ -850,6 +917,36 @@ public function requestUserInfo($attribute = null) { } } + /** + * + * @param $attribute string 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; + } else if (array_key_exists($attribute, $this->verifiedClaims)) { + return $this->verifiedClaims->$attribute; + } else { + return null; + } + } + /** * @param $url * @param null $post_body string If this is set the post type will be POST @@ -1018,6 +1115,21 @@ public function getVerifyPeer() return $this->verifyPeer; } + /** + * @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 From 39c0f1f3a7c68c02ad0c63264bbaeba6da31d202 Mon Sep 17 00:00:00 2001 From: Simon Haeussler Date: Wed, 28 Mar 2018 19:52:01 +0200 Subject: [PATCH 006/223] Verify jwt claim by also comparing against the issuer from .well-known information (with and without slash. Mainly targeting Azure AD B2C. --- src/OpenIDConnectClient.php | 62 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2c903bdc..1142f759 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -436,28 +436,43 @@ private 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])) { - if(!$this->wellKnown){ - $well_known_config_url = rtrim($this->getProviderURL(),"/") . "/.well-known/openid-configuration"; - $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); - } - - $value = false; - if(isset($this->wellKnown->{$param})){ - $value = $this->wellKnown->{$param}; - } + $this->providerConfig[$param] = $this->getWellKnownConfigValue($param, $default); + } - if ($value) { - $this->providerConfig[$param] = $value; - } elseif(isset($default)) { - // Uses default value if provided - $this->providerConfig[$param] = $default; - } else { - throw new OpenIDConnectClientException("The provider {$param} has not been set. Make sure your provider has a well known configuration available."); - } + return $this->providerConfig[$param]; + } + + /** + * Get's anything that we need configuration wise including endpoints, and other values + * + * @param $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"; + $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); } - return $this->providerConfig[$param]; + $value = false; + if(isset($this->wellKnown->{$param})){ + $value = $this->wellKnown->{$param}; + } + + if ($value) { + return $value; + } elseif(isset($default)) { + // Uses default value if provided + return $default; + } else { + throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); + } } @@ -836,7 +851,7 @@ private function verifyJWTclaims($claims, $accessToken = null) { $len = ((int)$bit)/16; $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } - return (($claims->iss == $this->getProviderURL()) + return (($claims->iss == $this->getProviderURL() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) && ($claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time()) @@ -1042,6 +1057,15 @@ protected function fetchURL($url, $post_body = null,$headers = array()) { return $output; } + /** + * @return string + * @throws OpenIDConnectClientException + */ + public function getWellKnownIssuer($appendSlash) { + + return $this->getWellKnownConfigValue('issuer') . ($appendSlash ? '/' : ''); + } + /** * @return string * @throws OpenIDConnectClientException From a0146c60caf24a96fc9e0f8909607a0bef49e9e9 Mon Sep 17 00:00:00 2001 From: Simon Haeussler Date: Wed, 28 Mar 2018 20:53:38 +0200 Subject: [PATCH 007/223] Make appendSlash-Argument optional. --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 1142f759..4b161a8d 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1061,7 +1061,7 @@ protected function fetchURL($url, $post_body = null,$headers = array()) { * @return string * @throws OpenIDConnectClientException */ - public function getWellKnownIssuer($appendSlash) { + public function getWellKnownIssuer($appendSlash = false) { return $this->getWellKnownConfigValue('issuer') . ($appendSlash ? '/' : ''); } From 57a3dafadc01177b5b2686f5abeea6a5196e3146 Mon Sep 17 00:00:00 2001 From: Simon Haeussler Date: Wed, 4 Apr 2018 11:51:09 +0200 Subject: [PATCH 008/223] Save id_token from request in implicit workflow. --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 4b161a8d..560803b5 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -348,7 +348,7 @@ public function authenticate() { $this->unsetNonce(); // Save the id token - $this->idToken = $token_json->id_token; + $this->idToken = $id_token; // Save the verified claims $this->verifiedClaims = $claims; From 5ccd26d02307002a1b599800eaa79dfddfb98145 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 9 Apr 2018 09:30:22 -0400 Subject: [PATCH 009/223] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 368c360e..fee62ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed * +## [0.5.0] +## Added +* Implement Azure AD B2C Implicit Workflow + ## [0.4.1] ## Changed * Documentation updates for include path. From 9eb080b8af3c3d42bd53b2b139a5950d178640f8 Mon Sep 17 00:00:00 2001 From: "D. Shulgin" Date: Wed, 23 May 2018 14:46:24 +1100 Subject: [PATCH 010/223] -save access token to oidc instance -check if refresh_token was received --- src/OpenIDConnectClient.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 560803b5..a22cb48a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -698,7 +698,11 @@ public function refreshToken($refresh_token) { $token_params = http_build_query($token_params, null, '&'); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); - $this->refreshToken = $json->refresh_token; + $this->accessToken = $json->access_token; + + if (isset($json->refresh_token)) { + $this->refreshToken = $json->refresh_token; + } return $json; } From 1b5d8a9ee2bd7bfdebe23902623fdcc0eb2c91a7 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 23 May 2018 07:39:46 -0400 Subject: [PATCH 011/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fee62ed1..2f2c8afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ### Changed -* +* refreshToken method update #124 ### Removed * From 68b328a1d465aa78a0e4e79e4ffa8ef690ca461e Mon Sep 17 00:00:00 2001 From: Hallgeir Lien Date: Tue, 5 Jun 2018 15:19:03 +0200 Subject: [PATCH 012/223] Add optional parameter to override valid issuer. This is required to support Azure Active Directory. For instance, for Azure AD, ProviderURL may be: https://login.windows.net/common or https://login.windows.net/tenant-id) While the issuer claim actually is: https://sts.windows.net/(tenant-id) --- src/OpenIDConnectClient.php | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a22cb48a..b87d537a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -211,8 +211,14 @@ class OpenIDConnectClient * @param $client_secret string optional * */ - public function __construct($provider_url = null, $client_id = null, $client_secret = null) { + 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; } @@ -221,9 +227,16 @@ public function __construct($provider_url = null, $client_id = null, $client_sec * @param $provider_url */ public function setProviderURL($provider_url) { - $this->providerConfig['issuer'] = $provider_url; + $this->providerConfig['providerUrl'] = $provider_url; } + /** + * @param $provider_url + */ + public function setIssuer($issuer) { + $this->providerConfig['issuer'] = $issuer; + } + /** * @param $response_types */ @@ -855,7 +868,7 @@ private function verifyJWTclaims($claims, $accessToken = null) { $len = ((int)$bit)/16; $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } - return (($claims->iss == $this->getProviderURL() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) + return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) && ($claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time()) @@ -1074,15 +1087,23 @@ public function getWellKnownIssuer($appendSlash = false) { * @return string * @throws OpenIDConnectClientException */ - public function getProviderURL() { + public function getIssuer() { if (!isset($this->providerConfig['issuer'])) { - throw new OpenIDConnectClientException("The provider URL has not been set"); + throw new OpenIDConnectClientException("The issuer has not been set"); } else { return $this->providerConfig['issuer']; } } + public function getProviderURL() { + if (!isset($this->providerConfig['providerUrl'])) { + throw new OpenIDConnectClientException("The provider URL has not been set"); + } else { + return $this->providerConfig['providerUrl']; + } + } + /** * @param $url */ From f4a39819719d35171aa12eaecb14ab6ee4b4b7cb Mon Sep 17 00:00:00 2001 From: Hallgeir Lien Date: Tue, 5 Jun 2018 15:25:49 +0200 Subject: [PATCH 013/223] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2c8afd..727f01d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). ### Changed * refreshToken method update #124 From 1d4f6437279f262a77e1504829827367194c56a1 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 11 Jun 2018 09:13:35 -0400 Subject: [PATCH 014/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727f01d3..7546c75b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). ### Changed From f0e4fce82dde846711580cd4b66acc96b1ea6d04 Mon Sep 17 00:00:00 2001 From: nemoneph Date: Wed, 20 Jun 2018 10:03:34 +0200 Subject: [PATCH 015/223] verifyJWTsignature() method private -> public In some case we just need to verify the signature of the JWT. (Ex: frontend which send a request with the JWT (Autorization Header) to a backend, the backend can't trust the frontend and must verify the validity of the JWT signature.) It's a helper to have directly access to the method verifyJWTsignature(). More infos: https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b87d537a..dff26b1c 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -821,7 +821,7 @@ private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) * @throws OpenIDConnectClientException * @return bool */ - private function verifyJWTsignature($jwt) { + public function verifyJWTsignature($jwt) { $parts = explode(".", $jwt); $signature = base64url_decode(array_pop($parts)); $header = json_decode(base64url_decode($parts[0])); From 7a0c01edf7ecc0f339b3f33fd26fae2bab5d7881 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 20 Jun 2018 05:13:42 -0400 Subject: [PATCH 016/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7546c75b..6a52b314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* verifyJWTsignature() method private -> public #126 * Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). From 1799bde093a99f976a2e8044a5ea7736cca5530b Mon Sep 17 00:00:00 2001 From: TheCrealm Date: Wed, 20 Jun 2018 13:53:26 +0200 Subject: [PATCH 017/223] Change signOut() Method parameter $accessToken -> $idToken to prevent confusion about Access and ID Token usage. --- src/OpenIDConnectClient.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index dff26b1c..9a85d88a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -391,22 +391,22 @@ public function authenticate() { * Connect provider that the end-user has logged out of the relying party site * (the client application). * - * @param string $accessToken ID token (obtained at login) + * @param string $idToken ID token (obtained at login) * @param string $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. * */ - public function signOut($accessToken, $redirect) { + public function signOut($idToken, $redirect) { $signout_endpoint = $this->getProviderConfigValue("end_session_endpoint"); $signout_params = null; if($redirect == null){ - $signout_params = array('id_token_hint' => $accessToken); + $signout_params = array('id_token_hint' => $idToken); } else { $signout_params = array( - 'id_token_hint' => $accessToken, + 'id_token_hint' => $idToken, 'post_logout_redirect_uri' => $redirect); } From cac2e6ecb036be9903a7a06810055dd62e91476a Mon Sep 17 00:00:00 2001 From: TheCrealm Date: Wed, 20 Jun 2018 13:57:10 +0200 Subject: [PATCH 018/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a52b314..f4aa4b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. * verifyJWTsignature() method private -> public #126 * Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). From 261e8292aa6ce8e8090d332fba27e2e21b825199 Mon Sep 17 00:00:00 2001 From: nyndo Date: Wed, 20 Jun 2018 16:26:52 +0200 Subject: [PATCH 019/223] Leeway due to clock skew when checking exp and nbf Added five minutes leeway due to clock skew between openidconnect server and client. --- src/OpenIDConnectClient.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index dff26b1c..a7467382 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -189,6 +189,11 @@ class OpenIDConnectClient */ protected $timeOut = 60; + /** + * @var int leeway (seconds) + */ + private $leeway = 300; + /** * @var array holds response types */ @@ -871,8 +876,8 @@ private function verifyJWTclaims($claims, $accessToken = null) { return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) && ($claims->nonce == $this->getNonce()) - && ( !isset($claims->exp) || $claims->exp >= time()) - && ( !isset($claims->nbf) || $claims->nbf <= time()) + && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) + && ( !isset($claims->nbf) || $claims->nbf <= time() + $this->leeway) && ( !isset($claims->at_hash) || $claims->at_hash == $expecte_at_hash ) ); } From 4d8c3d6ff5dd4fb13602fe9b26566a2adacf2aa9 Mon Sep 17 00:00:00 2001 From: nyndo Date: Wed, 20 Jun 2018 16:31:13 +0200 Subject: [PATCH 020/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a52b314..a9a6f3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* Added five minutes leeway due to clock skew between openidconnect server and client. * verifyJWTsignature() method private -> public #126 * Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). From a6323fb3f4df1e31258646bd98a60c9e65705045 Mon Sep 17 00:00:00 2001 From: AlK2x Date: Thu, 21 Jun 2018 19:02:08 +0300 Subject: [PATCH 021/223] Save access_token from request in implicit workflow --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index dff26b1c..e00759a1 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -367,7 +367,7 @@ public function authenticate() { $this->verifiedClaims = $claims; // Save the access token - if ($accessToken) $this->accessToken = $access_token; + if ($accessToken) $this->accessToken = $accessToken; // Save the refresh token, if we got one if (isset($token_json->refresh_token)) $this->refreshToken = $token_json->refresh_token; From e9a7e0a1a7f9bce708b6882daa49c432dab591be Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Sun, 24 Jun 2018 09:54:23 -0400 Subject: [PATCH 022/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a52b314..de09baa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* Fix save access_token from request in implicit flow authentication #129 * verifyJWTsignature() method private -> public #126 * Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). From 5a94ce870cdba1e14a35f62cb294c21e5628ac33 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Tue, 17 Jul 2018 07:31:35 -0400 Subject: [PATCH 023/223] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2283bf7..f2901a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +* + +### Changed +* + +### Removed +* + +## [0.6.0] + ### Added * Added five minutes leeway due to clock skew between openidconnect server and client. * Fix save access_token from request in implicit flow authentication #129 From c000f31a3352ad6f7e2fabca5aa30f4f8b9f9579 Mon Sep 17 00:00:00 2001 From: Laino Santos Date: Mon, 30 Jul 2018 22:21:27 -0300 Subject: [PATCH 024/223] Decouple session manipulation, it's allow use of other session libraries --- src/OpenIDConnectClient.php | 54 +++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6ac33147..6bbfcc50 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -23,13 +23,6 @@ namespace Jumbojett; -/** - * Use session to manage a nonce - */ -if (!isset($_SESSION)) { - @session_start(); -} - /** * * JWT signature verification support by Jonathan Reed @@ -595,7 +588,7 @@ private function requestAuthorization() { $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&'); - session_commit(); + $this->commitSession(); $this->redirect($auth_endpoint); } @@ -1358,7 +1351,7 @@ public function getTokenResponse() { * @return string */ protected function setNonce($nonce) { - $_SESSION['openid_connect_nonce'] = $nonce; + $this->setSessionKey('openid_connect_nonce', $nonce); return $nonce; } @@ -1368,7 +1361,7 @@ protected function setNonce($nonce) { * @return string */ protected function getNonce() { - return $_SESSION['openid_connect_nonce']; + return $this->getSessionKey('openid_connect_nonce'); } /** @@ -1377,7 +1370,7 @@ protected function getNonce() { * @return void */ protected function unsetNonce() { - unset($_SESSION['openid_connect_nonce']); + $this->unsetSessionKey('openid_connect_nonce'); } /** @@ -1387,7 +1380,7 @@ protected function unsetNonce() { * @return string */ protected function setState($state) { - $_SESSION['openid_connect_state'] = $state; + $this->setSessionKey('openid_connect_state', $state); return $state; } @@ -1397,7 +1390,7 @@ protected function setState($state) { * @return string */ protected function getState() { - return $_SESSION['openid_connect_state']; + return $this->getSessionKey('openid_connect_state'); } /** @@ -1406,7 +1399,7 @@ protected function getState() { * @return void */ protected function unsetState() { - unset($_SESSION['openid_connect_state']); + $this->unsetSessionKey('openid_connect_state'); } /** @@ -1468,4 +1461,37 @@ private static function hashEquals($str1, $str2) $status |= ($len1 ^ $len2); return ($status === 0); } + + /** + * Use session to manage a nonce + */ + protected function startSession() { + if (!isset($_SESSION)) { + @session_start(); + } + } + + protected function commitSession() { + $this->startSession(); + + session_commit(); + } + + protected function getSessionKey($key) { + $this->startSession(); + + return $_SESSION[$key]; + } + + protected function setSessionKey($key, $value) { + $this->startSession(); + + $_SESSION[$key] = $value; + } + + protected function unsetSessionKey($key) { + $this->startSession(); + + unset($_SESSION[$key]); + } } From d5fa589a54a8c280de84857f59b6fdca29082af9 Mon Sep 17 00:00:00 2001 From: Ben Yitzhaki Date: Thu, 6 Sep 2018 11:34:57 +0300 Subject: [PATCH 025/223] disabled autoload for Crypt_RSA --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6ac33147..55453734 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -786,7 +786,7 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { " " . b64url2b64($key->n) . "\r\n" . " " . b64url2b64($key->e) . "\r\n" . ""; - if(class_exists('Crypt_RSA')) { + if(class_exists('Crypt_RSA', false)) { $rsa = new Crypt_RSA(); $rsa->setHash($hashtype); $rsa->loadKey($public_key_xml, Crypt_RSA::PUBLIC_FORMAT_XML); From 0384bf6a50c98acf68a434351ff65f7c02001244 Mon Sep 17 00:00:00 2001 From: Ben Yitzhaki Date: Thu, 6 Sep 2018 12:01:33 +0300 Subject: [PATCH 026/223] makre refreshToken() method tolerant for errors in case the refresh token is invalid the code was throwing an unexpected exception. this should handle it and make it more tolerant for those scenarios --- src/OpenIDConnectClient.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 55453734..a9392086 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -716,8 +716,11 @@ public function refreshToken($refresh_token) { $token_params = http_build_query($token_params, null, '&'); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); - $this->accessToken = $json->access_token; - + + if (isset($json->access_token)) { + $this->accessToken = $json->access_token; + } + if (isset($json->refresh_token)) { $this->refreshToken = $json->refresh_token; } From 619927de0012f7a60a06ddb9553de88fe0466400 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 7 Sep 2018 16:23:47 -0400 Subject: [PATCH 027/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2901a63..b1324425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ### Changed -* +* disabled autoload for Crypt_RSA + makre refreshToken() method tolerant for errors #137 ### Removed * From b932a79f5f7352f0cb2df03bd40802a6938a2010 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Sat, 15 Sep 2018 13:36:10 -0700 Subject: [PATCH 028/223] Add "license" field to composer.json See for more details. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index ed43fe8f..071a0d2a 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "jumbojett/openid-connect-php", "description": "Bare-bones OpenID Connect client", + "license": "Apache-2.0", "require": { "php": ">=5.4", "phpseclib/phpseclib" : "2.0.1", From 48328bd24f369acd0eb37794b4f5b4ac8ff58b26 Mon Sep 17 00:00:00 2001 From: Eduardo Potter Date: Fri, 5 Oct 2018 13:06:07 +0200 Subject: [PATCH 029/223] Ensure key_alg is set when getting key --- src/OpenIDConnectClient.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a9392086..f16a2007 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -193,7 +193,7 @@ class OpenIDConnectClient * @var int leeway (seconds) */ private $leeway = 300; - + /** * @var array holds response types */ @@ -223,7 +223,7 @@ public function __construct($provider_url = null, $client_id = null, $client_sec } else { $this->setIssuer($issuer); } - + $this->clientID = $client_id; $this->clientSecret = $client_secret; } @@ -241,7 +241,7 @@ public function setProviderURL($provider_url) { public function setIssuer($issuer) { $this->providerConfig['issuer'] = $issuer; } - + /** * @param $response_types */ @@ -627,7 +627,7 @@ public function requestClientCredentialsToken() { /** * Requests a resource owner token * (Defined in https://tools.ietf.org/html/rfc6749#section-4.3) - * + * * @param $bClientAuth boolean Indicates that the Client ID and Secret be used for client authentication */ public function requestResourceOwnerToken($bClientAuth = FALSE) { @@ -716,11 +716,11 @@ public function refreshToken($refresh_token) { $token_params = http_build_query($token_params, null, '&'); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); - + if (isset($json->access_token)) { $this->accessToken = $json->access_token; } - + if (isset($json->refresh_token)) { $this->refreshToken = $json->refresh_token; } @@ -741,7 +741,7 @@ private function get_key_for_header($keys, $header) { return $key; } } else { - if ($key->alg == $header->alg && $key->kid == $header->kid) { + if (isset($key->alg) && $key->alg == $header->alg && $key->kid == $header->kid) { return $key; } } @@ -753,7 +753,7 @@ private function get_key_for_header($keys, $header) { return $key; } } else { - if ($key->alg == $header->alg && $key->kid == $header->kid) { + if (isset($key->alg) && $key->alg == $header->alg && $key->kid == $header->kid) { return $key; } } @@ -802,7 +802,7 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { } return $rsa->verify($payload, $signature); } - + /** * @param string $hashtype * @param object $key @@ -854,7 +854,7 @@ public function verifyJWTsignature($jwt) { case 'HS384': $hashtype = 'SHA' . substr($header->alg, 2); $verified = $this->verifyHMACJWTsignature($hashtype, $this->getClientSecret(), $payload, $signature); - break; + break; default: throw new OpenIDConnectClientException('No support for signature type: ' . $header->alg); } @@ -1111,7 +1111,7 @@ public function getProviderURL() { return $this->providerConfig['providerUrl']; } } - + /** * @param $url */ @@ -1436,7 +1436,7 @@ public function getTimeout() { return $this->timeOut; } - + /** * Safely calculate length of binary string * @param string From 65c6bbd6e2184c014acfd2f57200e2cb54451a15 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 5 Oct 2018 15:47:24 -0400 Subject: [PATCH 030/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1324425..db08b32d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* Ensure key_alg is set when getting key #139 ### Changed * disabled autoload for Crypt_RSA + makre refreshToken() method tolerant for errors #137 From bf810737c156a9620df7d7d820a1f800a0e07970 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 5 Oct 2018 15:48:29 -0400 Subject: [PATCH 031/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db08b32d..def3e62d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* Add "license" field to composer.json #138 * Ensure key_alg is set when getting key #139 ### Changed From 3837319244e7115a11a94f059eba39eebf55e2e6 Mon Sep 17 00:00:00 2001 From: Nikolay Ivanov Date: Fri, 12 Oct 2018 11:47:07 +0300 Subject: [PATCH 032/223] Add option to send additional registration parameters like post_logout_redirect_uris. --- src/OpenIDConnectClient.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index f16a2007..844d6adb 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -179,6 +179,11 @@ class OpenIDConnectClient */ private $authParams = array(); + /** + * @var array holds additional registration parameters for example post_logout_redirect_uris + */ + private $registrationParams = array(); + /** * @var mixed holds well-known openid server properties */ @@ -433,6 +438,13 @@ public function addAuthParam($param) { $this->authParams = array_merge($this->authParams, (array)$param); } + /** + * @param $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) array('kid' => ..., 'nbf' => ..., 'use' => 'sig', 'kty' => "RSA", 'e' => "", 'n' => "") */ @@ -1222,10 +1234,10 @@ public function register() { $registration_endpoint = $this->getProviderConfigValue('registration_endpoint'); - $send_object = (object)array( + $send_object = (object ) array_merge($this->registrationParams, array( 'redirect_uris' => array($this->getRedirectURL()), 'client_name' => $this->getClientName() - ); + )); $response = $this->fetchURL($registration_endpoint, json_encode($send_object)); From a5d8686f4dca2da75bbfd72dfafc736e5b2609d4 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 15 Oct 2018 16:32:30 -0400 Subject: [PATCH 033/223] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index def3e62d..fc99e6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +* + +### Changed +* + +### Removed +* + +## [0.7.0] + ### Added * Add "license" field to composer.json #138 * Ensure key_alg is set when getting key #139 +* Add option to send additional registration parameters like post_logout_redirect_uris. #140 ### Changed * disabled autoload for Crypt_RSA + makre refreshToken() method tolerant for errors #137 From f55cc87dfdba5f8cacd41d936df0fd3ab088a0db Mon Sep 17 00:00:00 2001 From: Dominik Pesch Date: Fri, 23 Nov 2018 15:57:07 +0100 Subject: [PATCH 034/223] Prevent errors on invalid token in verifyJWTsignature() `verifyJWTsignature` runs into several php errors/warnings, if the token `$jwt` was not well formatted, incomplete or empty. This fix adds some checks to verify intermediate results: 1. `$jwt` has to be a string (if not `explode()` could fail) 2. `$parts[0]` does not exists if `$token` contains any dot 3. `$signature` should not be `false` or empty 4. `$header` could be null if `json_decode` fails 5. `$header->alg` could not exists After this fix `verifyJWTsignature()` should be more robust against incomplete or invalid formatted token. --- src/OpenIDConnectClient.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 844d6adb..738a0413 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -842,15 +842,30 @@ private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) * @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'); + } $payload = implode(".", $parts); $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); if ($jwks === NULL) { throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); } $verified = false; + if (!isset($header->alg)) { + throw new OpenIDConnectClientException('Error missing signature type in token header'); + } switch ($header->alg) { case 'RS256': case 'RS384': From 89ef781c8f3571d6a74fd64f2636012459303ecb Mon Sep 17 00:00:00 2001 From: Dominik Pesch Date: Fri, 23 Nov 2018 15:58:07 +0100 Subject: [PATCH 035/223] Added not to changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc99e6a0..dcc7a9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* Fix `verifyJWTsignature()`: verify JWT to prevent php errors and warnings on invalid token +* ### Changed * From 0592a80ff1fb0b96ffe77c51bc2ac744a0aec81b Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Thu, 6 Dec 2018 09:21:49 -0500 Subject: [PATCH 036/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc7a9ef..43109764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ### Changed -* +* Decouple session manipulation, it's allow use of other session libraries #134 ### Removed * From 3b1fdc92205c0a53f42482507e59618507345f08 Mon Sep 17 00:00:00 2001 From: Caleb Bertsch Date: Wed, 19 Dec 2018 14:14:33 -0500 Subject: [PATCH 037/223] Broadens version requirements of the phpseclib/phpseclib package. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 071a0d2a..6fbe8446 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "Apache-2.0", "require": { "php": ">=5.4", - "phpseclib/phpseclib" : "2.0.1", + "phpseclib/phpseclib" : "~2.0", "ext-json": "*", "ext-curl": "*" }, From 459dddff0b71eb210cadb18cffa7f887464c48e2 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 19 Dec 2018 18:15:33 -0500 Subject: [PATCH 038/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43109764..f93f4de2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * Decouple session manipulation, it's allow use of other session libraries #134 +* Broaden version requirements of the phpseclib/phpseclib package. #144 ### Removed * From 149022f92d680e106759102af57cbc083727d5df Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 2 Jan 2019 14:04:48 -0500 Subject: [PATCH 039/223] Updating for release of 0.8 --- CHANGELOG.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f93f4de2..80508418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* Fix `verifyJWTsignature()`: verify JWT to prevent php errors and warnings on invalid token -* +* ### Changed -* Decouple session manipulation, it's allow use of other session libraries #134 -* Broaden version requirements of the phpseclib/phpseclib package. #144 +* ### Removed * +## [0.8.0] + +### Added +* Fix `verifyJWTsignature()`: verify JWT to prevent php errors and warnings on invalid token + +### Changed +* Decouple session manipulation, it's allow use of other session libraries #134 +* Broaden version requirements of the phpseclib/phpseclib package. #144 + ## [0.7.0] ### Added From 08c29b063803538f345183b66ac05cff7ed6eb9b Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 2 Jan 2019 14:05:13 -0500 Subject: [PATCH 040/223] Happy new year! --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 4832c3b7..873363b8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -2,7 +2,7 @@ /** * - * Copyright MITRE 2018 + * Copyright MITRE 2019 * * OpenIDConnectClient for PHP5 * Author: Michael Jett From ca001ebd629c449a05aa8d7b20cf333bc997afa1 Mon Sep 17 00:00:00 2001 From: Bradley Cornford Date: Wed, 23 Jan 2019 09:04:53 +0000 Subject: [PATCH 041/223] Updated OpenIDConnectClient to conditionally verify nonce In order to comply with theopenid connect spec, verifyJWTclaims method within the client needs to check a nonce is set, and only verifies if it does. See: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest --- src/OpenIDConnectClient.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 873363b8..359259c0 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -898,7 +898,8 @@ private function verifyJWTclaims($claims, $accessToken = null) { } return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) - && ($claims->nonce == $this->getNonce()) + // Nonce can be optionally returned, only validate if we have it + && ( !isset($claims->nonce) || $claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) && ( !isset($claims->nbf) || $claims->nbf <= time() + $this->leeway) && ( !isset($claims->at_hash) || $claims->at_hash == $expecte_at_hash ) From 45f9a7b35247f98613b0a60caa06f9488cf3bc77 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 23 Jan 2019 15:04:03 -0500 Subject: [PATCH 042/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80508418..0204b9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* Updated OpenIDConnectClient to conditionally verify nonce #146 ### Changed * From 1384df0134c46bc33ff80dce1ba557df7397ec08 Mon Sep 17 00:00:00 2001 From: Greg Rundlett Date: Tue, 29 Jan 2019 22:05:16 -0500 Subject: [PATCH 043/223] Make compatible with the LDS OpenIDConnect system The spec indicates that a JWT or a JSON response type can be made. Accept headers will indicate that we prefer to handle that type. --- src/OpenIDConnectClient.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 359259c0..b3b8de08 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -962,8 +962,10 @@ public function requestUserInfo($attribute = null) { $user_info_endpoint .= "?schema=" . $schema; - //The accessToken has to be send in the Authorization header, so we create a new array with only this header. - $headers = array("Authorization: Bearer {$this->accessToken}"); + //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)); From 4c32ee3ca8708b3e35da9d583b6a1b7bfb6bdcae Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 8 Feb 2019 10:00:42 -0500 Subject: [PATCH 044/223] Revert "Updated OpenIDConnectClient to conditionally verify nonce" --- src/OpenIDConnectClient.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 359259c0..873363b8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -898,8 +898,7 @@ private function verifyJWTclaims($claims, $accessToken = null) { } return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) - // Nonce can be optionally returned, only validate if we have it - && ( !isset($claims->nonce) || $claims->nonce == $this->getNonce()) + && ($claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) && ( !isset($claims->nbf) || $claims->nbf <= time() + $this->leeway) && ( !isset($claims->at_hash) || $claims->at_hash == $expecte_at_hash ) From 6eeefce95922d809cc6eda743942c84ae52f08fb Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 8 Feb 2019 10:05:56 -0500 Subject: [PATCH 045/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0204b9ac..6a4a5ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* Updated OpenIDConnectClient to conditionally verify nonce #146 +* ~Updated OpenIDConnectClient to conditionally verify nonce #146~ ### Changed * From 2421fa8946a3106bc5465d708ae651449439ab15 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Fri, 8 Mar 2019 08:28:52 -0500 Subject: [PATCH 046/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4a5ce6..e271028a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* Adding a header to indicate JSON as the return type for userinfo endpoint #151 * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ ### Changed From 46e5b5938e01bf83a55c217b5593b34b54885a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:16:33 +0100 Subject: [PATCH 047/223] Cleanup PHPDoc --- src/OpenIDConnectClient.php | 125 +++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index bd875e7d..b430555b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1,5 +1,4 @@ setProviderURL($provider_url); @@ -233,8 +235,8 @@ public function setProviderURL($provider_url) { $this->providerConfig['providerUrl'] = $provider_url; } - /** - * @param $provider_url + /** + * @param $issuer */ public function setIssuer($issuer) { $this->providerConfig['issuer'] = $issuer; @@ -399,6 +401,7 @@ public function authenticate() { * 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($accessToken, $redirect) { $signout_endpoint = $this->getProviderConfigValue("end_session_endpoint"); @@ -418,21 +421,21 @@ public function signOut($accessToken, $redirect) { } /** - * @param $scope - example: openid, given_name, etc... + * @param array $scope - example: openid, given_name, etc... */ public function addScope($scope) { $this->scopes = array_merge($this->scopes, (array)$scope); } /** - * @param $param - example: prompt=login + * @param array $param - example: prompt=login */ public function addAuthParam($param) { $this->authParams = array_merge($this->authParams, (array)$param); } /** - * @param $param - example: post_logout_redirect_uris=[http://example.com/successful-logout] + * @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); @@ -448,7 +451,7 @@ protected function addAdditionalJwk($jwk) { /** * Get's anything that we need configuration wise including endpoints, and other values * - * @param $param + * @param string $param * @param string $default optional * @throws OpenIDConnectClientException * @return string @@ -468,7 +471,7 @@ private function getProviderConfigValue($param, $default = null) { /** * Get's anything that we need configuration wise including endpoints, and other values * - * @param $param + * @param string $param * @param string $default optional * @throws OpenIDConnectClientException * @return string @@ -566,6 +569,7 @@ protected function generateRandString() { /** * Start Here * @return void + * @throws OpenIDConnectClientException */ private function requestAuthorization() { @@ -607,6 +611,7 @@ private function requestAuthorization() { /** * Requests a client credentials token * + * @throws OpenIDConnectClientException */ public function requestClientCredentialsToken() { $token_endpoint = $this->getProviderConfigValue("token_endpoint"); @@ -629,11 +634,13 @@ public function requestClientCredentialsToken() { } - /** + /** * Requests a resource owner token * (Defined in https://tools.ietf.org/html/rfc6749#section-4.3) * - * @param $bClientAuth boolean Indicates that the Client ID and Secret be used for client authentication + * @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"); @@ -662,13 +669,12 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } - - /** * Requests ID and Access tokens * - * @param $code + * @param string $code * @return mixed + * @throws OpenIDConnectClientException */ private function requestTokens($code) { $token_endpoint = $this->getProviderConfigValue("token_endpoint"); @@ -702,8 +708,9 @@ private function requestTokens($code) { /** * Requests Access token with refresh token * - * @param $code + * @param string $refresh_token * @return mixed + * @throws OpenIDConnectClientException */ public function refreshToken($refresh_token) { $token_endpoint = $this->getProviderConfigValue("token_endpoint"); @@ -772,12 +779,13 @@ private function get_key_for_header($keys, $header) { } - /** * @param string $hashtype * @param object $key - * @throws OpenIDConnectClientException + * @param $payload + * @param $signature * @return bool + * @throws OpenIDConnectClientException */ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { if (!class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { @@ -811,8 +819,10 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { /** * @param string $hashtype * @param object $key - * @throws OpenIDConnectClientException + * @param $payload + * @param $signature * @return bool + * @throws OpenIDConnectClientException */ private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) { @@ -830,7 +840,7 @@ private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) } /** - * @param $jwt string encoded JWT + * @param string $jwt encoded JWT * @throws OpenIDConnectClientException * @return bool */ @@ -883,7 +893,9 @@ public function verifyJWTsignature($jwt) { /** * @param object $claims + * @param string|null $accessToken * @return bool + * @throws OpenIDConnectClientException */ private function verifyJWTclaims($claims, $accessToken = null) { if(isset($claims->at_hash) && isset($accessToken)){ @@ -917,7 +929,7 @@ protected function urlEncode($str) { } /** - * @param $jwt string encoded JWT + * @param string $jwt encoded JWT * @param int $section the section we would like to decode * @return object */ @@ -929,7 +941,7 @@ private function decodeJWT($jwt, $section = 0) { /** * - * @param $attribute string optional + * @param string $attribute optional * * Attribute Type Description * user_id string REQUIRED Identifier for the End-User at the Issuer. @@ -953,6 +965,7 @@ private function decodeJWT($jwt, $section = 0) { * * @return mixed * + * @throws OpenIDConnectClientException */ public function requestUserInfo($attribute = null) { @@ -963,7 +976,7 @@ public function requestUserInfo($attribute = null) { //The accessToken has to be sent in the Authorization header. // Accept json to indicate response type - $headers = ["Authorization: Bearer {$this->accessToken}", + $headers = ["Authorization: Bearer {$this->accessToken}", "Accept: application/json"]; $user_json = json_decode($this->fetchURL($user_info_endpoint,null,$headers)); @@ -981,7 +994,7 @@ public function requestUserInfo($attribute = null) { /** * - * @param $attribute string optional + * @param string $attribute optional * * Attribute Type Description * exp int Expires at @@ -1010,13 +1023,13 @@ public function getVerifiedClaims($attribute = null) { } /** - * @param $url - * @param 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' + * @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 = array()) { + protected function fetchURL($url, $post_body = null, $headers = array()) { // OK cool - then let's create a new cURL resource handle @@ -1043,7 +1056,7 @@ protected function fetchURL($url, $post_body = null,$headers = array()) { } - // If we set some heaers include them + // If we set some headers include them if(count($headers) > 0) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } @@ -1105,6 +1118,7 @@ protected function fetchURL($url, $post_body = null,$headers = array()) { } /** + * @param bool $appendSlash * @return string * @throws OpenIDConnectClientException */ @@ -1126,7 +1140,11 @@ public function getIssuer() { } } - public function getProviderURL() { + /** + * @return mixed + * @throws OpenIDConnectClientException + */ + public function getProviderURL() { if (!isset($this->providerConfig['providerUrl'])) { throw new OpenIDConnectClientException("The provider URL has not been set"); } else { @@ -1135,7 +1153,7 @@ public function getProviderURL() { } /** - * @param $url + * @param string $url */ public function redirect($url) { header('Location: ' . $url); @@ -1143,14 +1161,14 @@ public function redirect($url) { } /** - * @param $httpProxy + * @param string $httpProxy */ public function setHttpProxy($httpProxy) { $this->httpProxy = $httpProxy; } /** - * @param $certPath + * @param string $certPath */ public function setCertPath($certPath) { $this->certPath = $certPath; @@ -1213,7 +1231,7 @@ public function getAllowImplicitFlow() * * Use this to alter a provider's endpoints and other attributes * - * @param $array + * @param array $array * simple key => value */ public function providerConfigParam($array) { @@ -1221,14 +1239,14 @@ public function providerConfigParam($array) { } /** - * @param $clientSecret + * @param string $clientSecret */ public function setClientSecret($clientSecret) { $this->clientSecret = $clientSecret; } /** - * @param $clientID + * @param string $clientID */ public function setClientID($clientID) { $this->clientID = $clientID; @@ -1274,14 +1292,14 @@ public function register() { } /** - * @return mixed + * @return string */ public function getClientName() { return $this->clientName; } /** - * @param $clientName + * @param string $clientName */ public function setClientName($clientName) { $this->clientName = $clientName; @@ -1313,8 +1331,7 @@ public function canVerifySignatures() { * * May be required for subclasses of this Client. * - * @param mixed $accessToken - * + * @param string $accessToken * @return void */ public function setAccessToken($accessToken) { @@ -1343,34 +1360,35 @@ public function getIdToken() { } /** - * @return array + * @return object */ public function getAccessTokenHeader() { return $this->decodeJWT($this->accessToken, 0); } /** - * @return array + * @return object */ public function getAccessTokenPayload() { return $this->decodeJWT($this->accessToken, 1); } /** - * @return array + * @return object */ public function getIdTokenHeader() { return $this->decodeJWT($this->idToken, 0); } /** - * @return array + * @return object */ public function getIdTokenPayload() { return $this->decodeJWT($this->idToken, 1); } + /** - * @return array + * @return string */ public function getTokenResponse() { return $this->tokenResponse; @@ -1454,6 +1472,9 @@ public function setTimeout($timeout) $this->timeOut = $timeout; } + /** + * @return int + */ public function getTimeout() { return $this->timeOut; @@ -1461,7 +1482,7 @@ public function getTimeout() /** * Safely calculate length of binary string - * @param string + * @param string $str * @return int */ private static function safeLength($str) @@ -1474,8 +1495,8 @@ private static function safeLength($str) /** * Where has_equals is not available, this provides a timing-attack safe string comparison - * @param $str1 - * @param $str2 + * @param string $str1 + * @param string $str2 * @return bool */ private static function hashEquals($str1, $str2) From 2a76b578e6c562af36a68f29e21c3aaf00f243bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:17:43 +0100 Subject: [PATCH 048/223] Replace unnecessary double quotes with single quotes --- src/OpenIDConnectClient.php | 110 ++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b430555b..cf41f718 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -57,7 +57,7 @@ function b64url2b64($base64url) { // "Shouldn't" be necessary, but why not $padding = strlen($base64url) % 4; if ($padding > 0) { - $base64url .= str_repeat("=", 4 - $padding); + $base64url .= str_repeat('=', 4 - $padding); } return strtr($base64url, '-_', '+/'); } @@ -257,14 +257,14 @@ 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); + $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"])) { + if (isset($_REQUEST['code'])) { - $code = $_REQUEST["code"]; + $code = $_REQUEST['code']; $token_json = $this->requestTokens($code); // Throw an error if the server returns one @@ -277,14 +277,14 @@ public function authenticate() { // Do an OpenID Connect session check if ($_REQUEST['state'] != $this->getState()) { - throw new OpenIDConnectClientException("Unable to determine state"); + 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."); + throw new OpenIDConnectClientException('User did not authorize openid scope.'); } $claims = $this->decodeJWT($token_json->id_token, 1); @@ -292,13 +292,13 @@ public function authenticate() { // 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"); + 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"); + throw new OpenIDConnectClientException ('Unable to verify signature'); } } else { - user_error("Warning: JWT signature verification unavailable."); + user_error('Warning: JWT signature verification unavailable.'); } // If this is a valid claim @@ -326,20 +326,20 @@ public function authenticate() { return true; } else { - throw new OpenIDConnectClientException ("Unable to verify JWT claims"); + throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } - } elseif ($this->allowImplicitFlow && isset($_REQUEST["id_token"])) { + } elseif ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) { // if we have no code but an id_token use that - $id_token = $_REQUEST["id_token"]; + $id_token = $_REQUEST['id_token']; $accessToken = null; - if (isset($_REQUEST["access_token"])) { - $accessToken = $_REQUEST["access_token"]; + 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"); + throw new OpenIDConnectClientException('Unable to determine state'); } // Cleanup state @@ -350,13 +350,13 @@ public function authenticate() { // 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"); + 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"); + throw new OpenIDConnectClientException ('Unable to verify signature'); } } else { - user_error("Warning: JWT signature verification unavailable."); + user_error('Warning: JWT signature verification unavailable.'); } // If this is a valid claim @@ -381,7 +381,7 @@ public function authenticate() { return true; } else { - throw new OpenIDConnectClientException ("Unable to verify JWT claims"); + throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } } else { @@ -404,7 +404,7 @@ public function authenticate() { * @throws OpenIDConnectClientException */ public function signOut($accessToken, $redirect) { - $signout_endpoint = $this->getProviderConfigValue("end_session_endpoint"); + $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); $signout_params = null; if($redirect == null){ @@ -482,7 +482,7 @@ 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"; + $well_known_config_url = rtrim($this->getProviderURL(), '/') . '/.well-known/openid-configuration'; $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); } @@ -536,25 +536,25 @@ public function getRedirectURL() { * Support of 'ProxyReverse' configurations. */ - if (isset($_SERVER["HTTP_UPGRADE_INSECURE_REQUESTS"]) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] == 1)) { + if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] == 1)) { $protocol = 'https'; } else { $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] ?: @$_SERVER['REQUEST_SCHEME'] - ?: ((isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https" : "http"); + ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'); } $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) - ?: @intval($_SERVER["SERVER_PORT"]) + ?: @intval($_SERVER['SERVER_PORT']) ?: (($protocol === 'https') ? 443 : 80); - $host = @explode(":", $_SERVER['HTTP_HOST'])[0] + $host = @explode(':', $_SERVER['HTTP_HOST'])[0] ?: @$_SERVER['SERVER_NAME'] ?: @$_SERVER['SERVER_ADDR']; $port = (443 == $port) || (80 == $port) ? '' : ':' . $port; - return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset(explode("?", $_SERVER['REQUEST_URI'])), '/')); + return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset(explode('?', $_SERVER['REQUEST_URI'])), '/')); } /** @@ -573,8 +573,8 @@ protected function generateRandString() { */ private function requestAuthorization() { - $auth_endpoint = $this->getProviderConfigValue("authorization_endpoint"); - $response_type = "code"; + $auth_endpoint = $this->getProviderConfigValue('authorization_endpoint'); + $response_type = 'code'; // Generate and store a nonce in the session // The nonce is an arbitrary value @@ -614,11 +614,11 @@ private function requestAuthorization() { * @throws OpenIDConnectClientException */ public function requestClientCredentialsToken() { - $token_endpoint = $this->getProviderConfigValue("token_endpoint"); + $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $headers = []; - $grant_type = "client_credentials"; + $grant_type = 'client_credentials'; $post_data = array( 'grant_type' => $grant_type, @@ -643,11 +643,11 @@ public function requestClientCredentialsToken() { * @throws OpenIDConnectClientException */ public function requestResourceOwnerToken($bClientAuth = FALSE) { - $token_endpoint = $this->getProviderConfigValue("token_endpoint"); + $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $headers = []; - $grant_type = "password"; + $grant_type = 'password'; $post_data = array( 'grant_type' => $grant_type, @@ -677,12 +677,12 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { * @throws OpenIDConnectClientException */ private function requestTokens($code) { - $token_endpoint = $this->getProviderConfigValue("token_endpoint"); - $token_endpoint_auth_methods_supported = $this->getProviderConfigValue("token_endpoint_auth_methods_supported", ['client_secret_basic']); + $token_endpoint = $this->getProviderConfigValue('token_endpoint'); + $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); $headers = []; - $grant_type = "authorization_code"; + $grant_type = 'authorization_code'; $token_params = array( 'grant_type' => $grant_type, @@ -713,9 +713,9 @@ private function requestTokens($code) { * @throws OpenIDConnectClientException */ public function refreshToken($refresh_token) { - $token_endpoint = $this->getProviderConfigValue("token_endpoint"); + $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - $grant_type = "refresh_token"; + $grant_type = 'refresh_token'; $token_params = array( 'grant_type' => $grant_type, @@ -799,9 +799,9 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { 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" . - ""; + ' ' . b64url2b64($key->n) . "\r\n" . + ' ' . b64url2b64($key->e) . "\r\n" . + ''; if(class_exists('Crypt_RSA', false)) { $rsa = new Crypt_RSA(); $rsa->setHash($hashtype); @@ -848,7 +848,7 @@ public function verifyJWTsignature($jwt) { if (!\is_string($jwt)) { throw new OpenIDConnectClientException('Error token is not a string'); } - $parts = explode(".", $jwt); + $parts = explode('.', $jwt); if (!isset($parts[0])) { throw new OpenIDConnectClientException('Error missing part 0 in token'); } @@ -860,7 +860,7 @@ public function verifyJWTsignature($jwt) { if (null === $header || !\is_object($header)) { throw new OpenIDConnectClientException('Error decoding JSON from token header'); } - $payload = implode(".", $parts); + $payload = implode('.', $parts); $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); if ($jwks === NULL) { throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); @@ -923,8 +923,8 @@ private function verifyJWTclaims($claims, $accessToken = null) { */ protected function urlEncode($str) { $enc = base64_encode($str); - $enc = rtrim($enc, "="); - $enc = strtr($enc, "+/", "-_"); + $enc = rtrim($enc, '='); + $enc = strtr($enc, '+/', '-_'); return $enc; } @@ -935,7 +935,7 @@ protected function urlEncode($str) { */ private function decodeJWT($jwt, $section = 0) { - $parts = explode(".", $jwt); + $parts = explode('.', $jwt); return json_decode(base64url_decode($parts[$section])); } @@ -969,15 +969,15 @@ private function decodeJWT($jwt, $section = 0) { */ public function requestUserInfo($attribute = null) { - $user_info_endpoint = $this->getProviderConfigValue("userinfo_endpoint"); + $user_info_endpoint = $this->getProviderConfigValue('userinfo_endpoint'); $schema = 'openid'; - $user_info_endpoint .= "?schema=" . $schema; + $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"]; + 'Accept: application/json']; $user_json = json_decode($this->fetchURL($user_info_endpoint,null,$headers)); @@ -1039,7 +1039,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { 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_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); // Default content type is form encoded @@ -1134,7 +1134,7 @@ public function getWellKnownIssuer($appendSlash = false) { public function getIssuer() { if (!isset($this->providerConfig['issuer'])) { - throw new OpenIDConnectClientException("The issuer has not been set"); + throw new OpenIDConnectClientException('The issuer has not been set'); } else { return $this->providerConfig['issuer']; } @@ -1146,7 +1146,7 @@ public function getIssuer() { */ public function getProviderURL() { if (!isset($this->providerConfig['providerUrl'])) { - throw new OpenIDConnectClientException("The provider URL has not been set"); + throw new OpenIDConnectClientException('The provider URL has not been set'); } else { return $this->providerConfig['providerUrl']; } @@ -1273,7 +1273,7 @@ public function register() { // Throw some errors if we encounter them if ($json_response === false) { - throw new OpenIDConnectClientException("Error registering: JSON response received from the server was invalid."); + throw new OpenIDConnectClientException('Error registering: JSON response received from the server was invalid.'); } elseif (isset($json_response->{'error_description'})) { throw new OpenIDConnectClientException($json_response->{'error_description'}); } @@ -1285,8 +1285,8 @@ public function register() { 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"); + throw new OpenIDConnectClientException('Error registering: + Please contact the OpenID Connect provider and obtain a Client ID and Secret directly from them'); } } From 1f37892c169963b6d96508c894334b1f1b6126c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:19:04 +0100 Subject: [PATCH 049/223] Use original function names instead of aliases --- src/OpenIDConnectClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index cf41f718..2921ca22 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -593,12 +593,12 @@ private function requestAuthorization() { )); // If the client has been registered with additional scopes - if (sizeof($this->scopes) > 0) { + if (count($this->scopes) > 0) { $auth_params = array_merge($auth_params, array('scope' => implode(' ', $this->scopes))); } // If the client has been registered with additional response types - if (sizeof($this->responseTypes) > 0) { + if (count($this->responseTypes) > 0) { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } @@ -1527,7 +1527,7 @@ protected function startSession() { protected function commitSession() { $this->startSession(); - session_commit(); + session_write_close(); } protected function getSessionKey($key) { From 5ab801eefb48a46ea0e02e2c96bddcea82cb8da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:20:35 +0100 Subject: [PATCH 050/223] Remove unnecessary default values --- src/OpenIDConnectClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2921ca22..11c8ddae 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -157,7 +157,7 @@ class OpenIDConnectClient /** * @var int|null Response code from the server */ - private $responseCode = null; + private $responseCode; /** * @var array holds response types @@ -1363,7 +1363,7 @@ public function getIdToken() { * @return object */ public function getAccessTokenHeader() { - return $this->decodeJWT($this->accessToken, 0); + return $this->decodeJWT($this->accessToken); } /** @@ -1377,7 +1377,7 @@ public function getAccessTokenPayload() { * @return object */ public function getIdTokenHeader() { - return $this->decodeJWT($this->idToken, 0); + return $this->decodeJWT($this->idToken); } /** From 9187c0b0b2ed5c99ab00d75f1b13b30effe442df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:23:38 +0100 Subject: [PATCH 051/223] Explicit declare field $redirectURL --- src/OpenIDConnectClient.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 11c8ddae..e40a51d1 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -208,6 +208,10 @@ class OpenIDConnectClient * @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; /** * @param $provider_url string optional From 1e653843aa25020e676b947fac21cf57356ba3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:26:13 +0100 Subject: [PATCH 052/223] Remove unused code --- src/OpenIDConnectClient.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e40a51d1..52f2db55 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -378,9 +378,6 @@ public function authenticate() { // Save the access token if ($accessToken) $this->accessToken = $accessToken; - // Save the refresh token, if we got one - if (isset($token_json->refresh_token)) $this->refreshToken = $token_json->refresh_token; - // Success! return true; @@ -869,7 +866,6 @@ public function verifyJWTsignature($jwt) { if ($jwks === NULL) { throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); } - $verified = false; if (!isset($header->alg)) { throw new OpenIDConnectClientException('Error missing signature type in token header'); } From e9cdf5621ebb05b8d1c16b8b5fc1ffc06df33a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:27:40 +0100 Subject: [PATCH 053/223] Fix indent --- src/OpenIDConnectClient.php | 152 ++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 52f2db55..60cfa20c 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -57,7 +57,7 @@ function b64url2b64($base64url) { // "Shouldn't" be necessary, but why not $padding = strlen($base64url) % 4; if ($padding > 0) { - $base64url .= str_repeat('=', 4 - $padding); + $base64url .= str_repeat('=', 4 - $padding); } return strtr($base64url, '-_', '+/'); } @@ -222,11 +222,11 @@ class OpenIDConnectClient */ 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); - } + if ($issuer == null) { + $this->setIssuer($provider_url); + } else { + $this->setIssuer($issuer); + } $this->clientID = $client_id; $this->clientSecret = $client_secret; @@ -284,8 +284,8 @@ public function authenticate() { throw new OpenIDConnectClientException('Unable to determine state'); } - // Cleanup state - $this->unsetState(); + // Cleanup state + $this->unsetState(); if (!property_exists($token_json, 'id_token')) { throw new OpenIDConnectClientException('User did not authorize openid scope.'); @@ -295,7 +295,7 @@ public function authenticate() { // Verify the signature if ($this->canVerifySignatures()) { - if (!$this->getProviderConfigValue('jwks_uri')) { + 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)) { @@ -311,7 +311,7 @@ public function authenticate() { // Clean up the session a little $this->unsetNonce(); - // Save the full response + // Save the full response $this->tokenResponse = $token_json; // Save the id token @@ -409,10 +409,10 @@ public function signOut($accessToken, $redirect) { $signout_params = null; if($redirect == null){ - $signout_params = array('id_token_hint' => $accessToken); + $signout_params = array('id_token_hint' => $accessToken); } else { - $signout_params = array( + $signout_params = array( 'id_token_hint' => $accessToken, 'post_logout_redirect_uri' => $redirect); } @@ -542,16 +542,16 @@ public function getRedirectURL() { } else { $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] ?: @$_SERVER['REQUEST_SCHEME'] - ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'); + ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'); } $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) - ?: @intval($_SERVER['SERVER_PORT']) - ?: (($protocol === 'https') ? 443 : 80); + ?: @intval($_SERVER['SERVER_PORT']) + ?: (($protocol === 'https') ? 443 : 80); $host = @explode(':', $_SERVER['HTTP_HOST'])[0] - ?: @$_SERVER['SERVER_NAME'] - ?: @$_SERVER['SERVER_ADDR']; + ?: @$_SERVER['SERVER_NAME'] + ?: @$_SERVER['SERVER_ADDR']; $port = (443 == $port) || (80 == $port) ? '' : ':' . $port; @@ -742,24 +742,24 @@ public function refreshToken($refresh_token) { } /** - * @param array $keys - * @param array $header - * @throws OpenIDConnectClientException - * @return object - */ - private function get_key_for_header($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) { + * @param array $keys + * @param array $header + * @throws OpenIDConnectClientException + * @return object + */ + private function get_key_for_header($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) { @@ -771,13 +771,13 @@ private function get_key_for_header($keys, $header) { } } } - } - if (isset($header->kid)) { - throw new OpenIDConnectClientException('Unable to find a key for (algorithm, kid):' . $header->alg . ', ' . $header->kid . ')'); - } else { - throw new OpenIDConnectClientException('Unable to find a key for RSA'); - } - } + } + if (isset($header->kid)) { + throw new OpenIDConnectClientException('Unable to find a key for (algorithm, kid):' . $header->alg . ', ' . $header->kid . ')'); + } else { + throw new OpenIDConnectClientException('Unable to find a key for RSA'); + } + } /** @@ -803,17 +803,17 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { ' ' . b64url2b64($key->n) . "\r\n" . ' ' . b64url2b64($key->e) . "\r\n" . ''; - if(class_exists('Crypt_RSA', false)) { - $rsa = new Crypt_RSA(); - $rsa->setHash($hashtype); - $rsa->loadKey($public_key_xml, Crypt_RSA::PUBLIC_FORMAT_XML); - $rsa->signatureMode = Crypt_RSA::SIGNATURE_PKCS1; - } else { - $rsa = new \phpseclib\Crypt\RSA(); - $rsa->setHash($hashtype); - $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); - $rsa->signatureMode = \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; - } + if(class_exists('Crypt_RSA', false)) { + $rsa = new Crypt_RSA(); + $rsa->setHash($hashtype); + $rsa->loadKey($public_key_xml, Crypt_RSA::PUBLIC_FORMAT_XML); + $rsa->signatureMode = Crypt_RSA::SIGNATURE_PKCS1; + } else { + $rsa = new \phpseclib\Crypt\RSA(); + $rsa->setHash($hashtype); + $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); + $rsa->signatureMode = \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; + } return $rsa->verify($payload, $signature); } @@ -870,23 +870,23 @@ public function verifyJWTsignature($jwt) { throw new OpenIDConnectClientException('Error missing signature type in token header'); } switch ($header->alg) { - case 'RS256': - case 'RS384': - case 'RS512': - $hashtype = 'sha' . substr($header->alg, 2); - - $verified = $this->verifyRSAJWTsignature($hashtype, - $this->get_key_for_header($jwks->keys, $header), - $payload, $signature); - 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); + case 'RS256': + case 'RS384': + case 'RS512': + $hashtype = 'sha' . substr($header->alg, 2); + + $verified = $this->verifyRSAJWTsignature($hashtype, + $this->get_key_for_header($jwks->keys, $header), + $payload, $signature); + 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; } @@ -898,7 +898,7 @@ public function verifyJWTsignature($jwt) { * @throws OpenIDConnectClientException */ private function verifyJWTclaims($claims, $accessToken = null) { - if(isset($claims->at_hash) && isset($accessToken)){ + if(isset($claims->at_hash) && isset($accessToken)){ if(isset($this->getAccessTokenHeader()->alg) && $this->getAccessTokenHeader()->alg != 'none'){ $bit = substr($this->getAccessTokenHeader()->alg, 2, 3); }else{ @@ -975,7 +975,7 @@ public function requestUserInfo($attribute = null) { $user_info_endpoint .= '?schema=' . $schema; //The accessToken has to be sent in the Authorization header. - // Accept json to indicate response type + // Accept json to indicate response type $headers = ["Authorization: Bearer {$this->accessToken}", 'Accept: application/json']; @@ -1038,7 +1038,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { // 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 + // Alows to keep the POST method even after redirect curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); @@ -1058,7 +1058,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { // If we set some headers include them if(count($headers) > 0) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } // Set URL to download @@ -1071,7 +1071,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { // Include header in result? (0 = yes, 1 = no) curl_setopt($ch, CURLOPT_HEADER, 0); - // Allows to follow redirect + // Allows to follow redirect curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); /** @@ -1323,7 +1323,7 @@ public function getClientSecret() { * @return bool */ public function canVerifySignatures() { - return class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); + return class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); } /** From 107f3fb5426f02e7e77cafa6ffe48ab47f8f012b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 13 Mar 2019 11:34:45 +0100 Subject: [PATCH 054/223] Cleanup conditional code flow for better readability --- src/OpenIDConnectClient.php | 73 ++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 60cfa20c..735c4d00 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -324,15 +324,19 @@ public function authenticate() { $this->verifiedClaims = $claims; // Save the refresh token, if we got one - if (isset($token_json->refresh_token)) $this->refreshToken = $token_json->refresh_token; + if (isset($token_json->refresh_token)) { + $this->refreshToken = $token_json->refresh_token; + } // Success! return true; - } else { - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } - } elseif ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) { + + 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']; @@ -376,20 +380,21 @@ public function authenticate() { $this->verifiedClaims = $claims; // Save the access token - if ($accessToken) $this->accessToken = $accessToken; + if ($accessToken) { + $this->accessToken = $accessToken; + } // Success! return true; - } else { - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } - } else { - $this->requestAuthorization(); - return false; + throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } + $this->requestAuthorization(); + return false; + } /** @@ -494,12 +499,14 @@ private function getWellKnownConfigValue($param, $default = null) { if ($value) { return $value; - } elseif(isset($default)) { + } + + if (isset($default)) { // Uses default value if provided return $default; - } else { - throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); } + + throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); } @@ -774,9 +781,9 @@ private function get_key_for_header($keys, $header) { } if (isset($header->kid)) { throw new OpenIDConnectClientException('Unable to find a key for (algorithm, kid):' . $header->alg . ', ' . $header->kid . ')'); - } else { - throw new OpenIDConnectClientException('Unable to find a key for RSA'); } + + throw new OpenIDConnectClientException('Unable to find a key for RSA'); } @@ -792,7 +799,7 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { if (!class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { throw new OpenIDConnectClientException('Crypt_RSA support unavailable.'); } - if (!(property_exists($key, 'n') and property_exists($key, 'e'))) { + if (!(property_exists($key, 'n') && property_exists($key, 'e'))) { throw new OpenIDConnectClientException('Malformed key object'); } @@ -835,9 +842,9 @@ private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) if (function_exists('hash_equals')) { return hash_equals($signature, $expected); - } else { - return self::hashEquals($signature, $expected); } + + return self::hashEquals($signature, $expected); } /** @@ -909,7 +916,7 @@ private function verifyJWTclaims($claims, $accessToken = null) { $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) - && (($claims->aud == $this->clientID) || (in_array($this->clientID, $claims->aud))) + && (($claims->aud == $this->clientID) || in_array($this->clientID, $claims->aud)) && ($claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) && ( !isset($claims->nbf) || $claims->nbf <= time() + $this->leeway) @@ -985,11 +992,13 @@ public function requestUserInfo($attribute = null) { if($attribute === null) { return $this->userInfo; - } else if (array_key_exists($attribute, $this->userInfo)) { + } + + if (array_key_exists($attribute, $this->userInfo)) { return $this->userInfo->$attribute; - } else { - return null; } + + return null; } /** @@ -1015,11 +1024,13 @@ public function getVerifiedClaims($attribute = null) { if($attribute === null) { return $this->verifiedClaims; - } else if (array_key_exists($attribute, $this->verifiedClaims)) { + } + + if (array_key_exists($attribute, $this->verifiedClaims)) { return $this->verifiedClaims->$attribute; - } else { - return null; } + + return null; } /** @@ -1135,9 +1146,9 @@ public function getIssuer() { if (!isset($this->providerConfig['issuer'])) { throw new OpenIDConnectClientException('The issuer has not been set'); - } else { - return $this->providerConfig['issuer']; } + + return $this->providerConfig['issuer']; } /** @@ -1147,9 +1158,9 @@ public function getIssuer() { public function getProviderURL() { if (!isset($this->providerConfig['providerUrl'])) { throw new OpenIDConnectClientException('The provider URL has not been set'); - } else { - return $this->providerConfig['providerUrl']; } + + return $this->providerConfig['providerUrl']; } /** @@ -1274,7 +1285,9 @@ public function register() { // Throw some errors if we encounter them if ($json_response === false) { throw new OpenIDConnectClientException('Error registering: JSON response received from the server was invalid.'); - } elseif (isset($json_response->{'error_description'})) { + } + + if (isset($json_response->{'error_description'})) { throw new OpenIDConnectClientException($json_response->{'error_description'}); } From 249ef9c74f33cf508eca20ae06266f2bb2f73875 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 13 Mar 2019 09:00:19 -0400 Subject: [PATCH 055/223] Update CHANGELOG.md --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e271028a..a45b1f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ ### Changed -* +* Bugfix/code cleanup #152 + * Cleanup PHPDoc #46e5b59 + * Replace unnecessary double quotes with single quotes #2a76b57 + * Use original function names instead of aliases #1f37892 + * Remove unnecessary default values #5ab801e + * Explicit declare field $redirectURL #9187c0b + * Remove unused code #1e65384 + * Fix indent #e9cdf56 + * Cleanup conditional code flow for better readability #107f3fb ### Removed * From 12e1378579d5c573266349c77701a01474528833 Mon Sep 17 00:00:00 2001 From: Nikita Frolov Date: Fri, 5 Apr 2019 19:15:24 +0300 Subject: [PATCH 056/223] #154 --- src/OpenIDConnectClient.php | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 735c4d00..8fd673cb 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -213,6 +213,8 @@ class OpenIDConnectClient */ private $redirectURL; + private $enc_type = PHP_QUERY_RFC1738; + /** * @param $provider_url string optional * @@ -422,7 +424,7 @@ public function signOut($accessToken, $redirect) { 'post_logout_redirect_uri' => $redirect); } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&'); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->$enc_type); $this->redirect($signout_endpoint); } @@ -610,7 +612,7 @@ private function requestAuthorization() { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&'); + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->$enc_type); $this->commitSession(); $this->redirect($auth_endpoint); @@ -636,7 +638,7 @@ public function requestClientCredentialsToken() { ); // Convert token params to string format - $post_params = http_build_query($post_data, null, '&'); + $post_params = http_build_query($post_data, null, '&', $this->$enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -671,7 +673,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&'); + $post_params = http_build_query($post_data, null, '&', $this->$enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -707,7 +709,7 @@ private function requestTokens($code) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&'); + $token_params = http_build_query($token_params, null, '&', $this->$enc_type); return json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -733,7 +735,7 @@ public function refreshToken($refresh_token) { ); // Convert token params to string format - $token_params = http_build_query($token_params, null, '&'); + $token_params = http_build_query($token_params, null, '&', $this->$enc_type); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); @@ -1560,4 +1562,22 @@ protected function unsetSessionKey($key) { unset($_SESSION[$key]); } + + public function setUrlEncoding($curEncoding) + { + switch ($curEncoding) + { + case PHP_QUERY_RFC1738: + $this->enc_type = PHP_QUERY_RFC1738; + break; + + case PHP_QUERY_RFC3986: + $this->enc_type = PHP_QUERY_RFC3986; + break; + + default: + break; + } + + } } From b69569291a03619e98e95f751d08f8ae9c131b3a Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 8 Apr 2019 09:20:45 -0400 Subject: [PATCH 057/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45b1f3d..0df7d6f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added * Adding a header to indicate JSON as the return type for userinfo endpoint #151 * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ +* Add possibility to change enc_type parameter for http_build_query #155 ### Changed * Bugfix/code cleanup #152 From 5ca3acf094108330109210de35a9e5e9ecf71555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 8 Apr 2019 12:21:45 +0200 Subject: [PATCH 058/223] Add implementation for Token Introspection https://tools.ietf.org/html/rfc7662 --- CHANGELOG.md | 1 + README.md | 15 +++++++++++++++ src/OpenIDConnectClient.php | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df7d6f0..49d2740c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Adding a header to indicate JSON as the return type for userinfo endpoint #151 * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ * Add possibility to change enc_type parameter for http_build_query #155 +* Adding OAuth 2.0 Token Introspection #156 ### Changed * Bugfix/code cleanup #152 diff --git a/README.md b/README.md index 5bce59b5..8ea18a9d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,21 @@ $sub = $oidc->getVerifiedClaims('sub'); ``` +## Example 7: Introspection of an access token (see https://tools.ietf.org/html/rfc7662) ## + +```php +use Jumbojett\OpenIDConnectClient; + +$oidc = new OpenIDConnectClient('https://id.provider.com', + 'ClientIDHere', + 'ClientSecretHere'); +$data = $oidc->introspectToken('an.access-token.as.given'); +if (!$data->active) { + // the token is no longer usable +} + +``` + ## Development Environments ## In some cases you may need to disable SSL security on on your development systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 8fd673cb..2ec02291 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1306,6 +1306,33 @@ public function register() { } + /** + * 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 + * @return mixed + * @throws OpenIDConnectClientException + */ + public function introspectToken($token, $token_type_hint = '') { + $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); + + $post_data = array( + 'token' => $token, + ); + if ($token_type_hint) { + $post_data['token_type_hint'] = $token_type_hint; + } + + // Convert token params to string format + $post_params = http_build_query($post_data, null, '&'); + $headers = ['Authorization: Basic ' . base64_encode($this->clientID . ':' . $this->clientSecret), + 'Accept: application/json']; + + return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); + } + /** * @return string */ From 368dedb0dd65d7ce06d1a4d2594230878190865e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 15 Apr 2019 08:06:00 +0200 Subject: [PATCH 059/223] Add optional parameters clientId/clientSecret for introspection --- src/OpenIDConnectClient.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2ec02291..bf8e991b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1312,10 +1312,12 @@ public function register() { * * @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 = '') { + public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); $post_data = array( @@ -1324,10 +1326,12 @@ public function introspectToken($token, $token_type_hint = '') { 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, null, '&'); - $headers = ['Authorization: Basic ' . base64_encode($this->clientID . ':' . $this->clientSecret), + $headers = ['Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret), 'Accept: application/json']; return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); From ee245d05eb438d35276cd5a88e5b0bcf26481fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 15 Apr 2019 09:27:25 +0200 Subject: [PATCH 060/223] Fix $this->$enc-type to $this->enc-type --- src/OpenIDConnectClient.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index bf8e991b..2aa5bbb2 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -424,7 +424,7 @@ public function signOut($accessToken, $redirect) { 'post_logout_redirect_uri' => $redirect); } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->$enc_type); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->enc_type); $this->redirect($signout_endpoint); } @@ -612,7 +612,7 @@ private function requestAuthorization() { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->$enc_type); + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); $this->commitSession(); $this->redirect($auth_endpoint); @@ -638,7 +638,7 @@ public function requestClientCredentialsToken() { ); // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->$enc_type); + $post_params = http_build_query($post_data, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -673,7 +673,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->$enc_type); + $post_params = http_build_query($post_data, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -709,7 +709,7 @@ private function requestTokens($code) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->$enc_type); + $token_params = http_build_query($token_params, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -735,7 +735,7 @@ public function refreshToken($refresh_token) { ); // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->$enc_type); + $token_params = http_build_query($token_params, null, '&', $this->enc_type); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); From 57673181035d48c932434d31680f623f1496a68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 15 Apr 2019 09:27:25 +0200 Subject: [PATCH 061/223] Fix $this->$enc-type to $this->enc-type --- src/OpenIDConnectClient.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2ec02291..30d26854 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -424,7 +424,7 @@ public function signOut($accessToken, $redirect) { 'post_logout_redirect_uri' => $redirect); } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->$enc_type); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->enc_type); $this->redirect($signout_endpoint); } @@ -612,7 +612,7 @@ private function requestAuthorization() { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->$enc_type); + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); $this->commitSession(); $this->redirect($auth_endpoint); @@ -638,7 +638,7 @@ public function requestClientCredentialsToken() { ); // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->$enc_type); + $post_params = http_build_query($post_data, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -673,7 +673,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->$enc_type); + $post_params = http_build_query($post_data, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -709,7 +709,7 @@ private function requestTokens($code) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->$enc_type); + $token_params = http_build_query($token_params, null, '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -735,7 +735,7 @@ public function refreshToken($refresh_token) { ); // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->$enc_type); + $token_params = http_build_query($token_params, null, '&', $this->enc_type); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); From 190482b282f763932b48d245f491ee84a8289fb5 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 15 Apr 2019 05:35:47 -0400 Subject: [PATCH 062/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d2740c..07e6f0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ * Add possibility to change enc_type parameter for http_build_query #155 * Adding OAuth 2.0 Token Introspection #156 +* Add optional parameters clientId/clientSecret for introspection #157 ### Changed * Bugfix/code cleanup #152 From 6f23fbb2bbfe29fb3eff4900d684b9eb164333ac Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 15 Apr 2019 05:37:37 -0400 Subject: [PATCH 063/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07e6f0ec..6c2be5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ * Add possibility to change enc_type parameter for http_build_query #155 * Adding OAuth 2.0 Token Introspection #156 -* Add optional parameters clientId/clientSecret for introspection #157 +* Add optional parameters clientId/clientSecret for introspection #157 & #158 ### Changed * Bugfix/code cleanup #152 From 2277717777448709d37c9ae2fc10ed488ed9d87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 26 Apr 2019 13:44:25 +0200 Subject: [PATCH 064/223] Add OAuth 2.0 Token Revocation - rfc7009 --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2be5ea..58bcbd09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Add possibility to change enc_type parameter for http_build_query #155 * Adding OAuth 2.0 Token Introspection #156 * Add optional parameters clientId/clientSecret for introspection #157 & #158 +* Adding OAuth 2.0 Token Revocation #160 ### Changed * Bugfix/code cleanup #152 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2aa5bbb2..48f44baa 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1337,6 +1337,37 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, 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 = array( + '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, null, '&'); + $headers = ['Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret), + 'Accept: application/json']; + + return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers)); + } + /** * @return string */ From 62d557c86d9b2b8607e254064bda400c8fccf656 Mon Sep 17 00:00:00 2001 From: "Florian.Kick" Date: Tue, 4 Jun 2019 12:29:50 +0200 Subject: [PATCH 065/223] added issuer validator --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bcbd09..5eb528f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Adding OAuth 2.0 Token Introspection #156 * Add optional parameters clientId/clientSecret for introspection #157 & #158 * Adding OAuth 2.0 Token Revocation #160 +* Adding issuer validator #145 ### Changed * Bugfix/code cleanup #152 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 48f44baa..a74b3e17 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -204,6 +204,11 @@ class OpenIDConnectClient */ private $verifiedClaims = array(); + /** + * @var callable 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 */ @@ -232,6 +237,10 @@ public function __construct($provider_url = null, $client_id = null, $client_sec $this->clientID = $client_id; $this->clientSecret = $client_secret; + + $this->issuerValidator = function($iss){ + return ($iss == $this->getIssuer() || $iss == $this->getWellKnownIssuer() || $iss == $this->getWellKnownIssuer(true)); + }; } /** @@ -917,7 +926,7 @@ private function verifyJWTclaims($claims, $accessToken = null) { $len = ((int)$bit)/16; $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } - return (($claims->iss == $this->getIssuer() || $claims->iss == $this->getWellKnownIssuer() || $claims->iss == $this->getWellKnownIssuer(true)) + return (($this->issuerValidator->__invoke($claims->iss)) && (($claims->aud == $this->clientID) || in_array($this->clientID, $claims->aud)) && ($claims->nonce == $this->getNonce()) && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) @@ -1225,6 +1234,17 @@ public function getVerifyPeer() return $this->verifyPeer; } + /** + * 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 */ From b5a06660633fb6a5fc1fe65327c52ecff4fd3870 Mon Sep 17 00:00:00 2001 From: Mario Korth Date: Wed, 26 Jun 2019 10:44:17 +0200 Subject: [PATCH 066/223] Added strict type comparisons --- src/OpenIDConnectClient.php | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a74b3e17..c4a72889 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -229,7 +229,7 @@ class OpenIDConnectClient */ public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null) { $this->setProviderURL($provider_url); - if ($issuer == null) { + if ($issuer === null) { $this->setIssuer($provider_url); } else { $this->setIssuer($issuer); @@ -239,7 +239,7 @@ public function __construct($provider_url = null, $client_id = null, $client_sec $this->clientSecret = $client_secret; $this->issuerValidator = function($iss){ - return ($iss == $this->getIssuer() || $iss == $this->getWellKnownIssuer() || $iss == $this->getWellKnownIssuer(true)); + return ($iss === $this->getIssuer() || $iss === $this->getWellKnownIssuer() || $iss === $this->getWellKnownIssuer(true)); }; } @@ -291,7 +291,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if ($_REQUEST['state'] != $this->getState()) { + if ($_REQUEST['state'] !== $this->getState()) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -357,7 +357,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if ($_REQUEST['state'] != $this->getState()) { + if ($_REQUEST['state'] !== $this->getState()) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -424,7 +424,7 @@ public function signOut($accessToken, $redirect) { $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); $signout_params = null; - if($redirect == null){ + if($redirect === null){ $signout_params = array('id_token_hint' => $accessToken); } else { @@ -555,12 +555,12 @@ public function getRedirectURL() { * Support of 'ProxyReverse' configurations. */ - if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] == 1)) { + if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] === '1')) { $protocol = 'https'; } else { $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] ?: @$_SERVER['REQUEST_SCHEME'] - ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'); + ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http'); } $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) @@ -571,7 +571,7 @@ public function getRedirectURL() { ?: @$_SERVER['SERVER_NAME'] ?: @$_SERVER['SERVER_ADDR']; - $port = (443 == $port) || (80 == $port) ? '' : ':' . $port; + $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset(explode('?', $_SERVER['REQUEST_URI'])), '/')); } @@ -712,7 +712,7 @@ private function requestTokens($code) { ); # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported)) { + if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { $headers = ['Authorization: Basic ' . base64_encode($this->clientID . ':' . $this->clientSecret)]; unset($token_params['client_secret']); } @@ -767,24 +767,24 @@ public function refreshToken($refresh_token) { */ private function get_key_for_header($keys, $header) { foreach ($keys as $key) { - if ($key->kty == 'RSA') { - if (!isset($header->kid) || $key->kid == $header->kid) { + 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) { + 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) { + 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) { + if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { return $key; } } @@ -917,7 +917,7 @@ public function verifyJWTsignature($jwt) { */ private function verifyJWTclaims($claims, $accessToken = null) { if(isset($claims->at_hash) && isset($accessToken)){ - if(isset($this->getAccessTokenHeader()->alg) && $this->getAccessTokenHeader()->alg != 'none'){ + if(isset($this->getAccessTokenHeader()->alg) && $this->getAccessTokenHeader()->alg !== 'none'){ $bit = substr($this->getAccessTokenHeader()->alg, 2, 3); }else{ // TODO: Error case. throw exception??? @@ -927,12 +927,12 @@ private function verifyJWTclaims($claims, $accessToken = null) { $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } return (($this->issuerValidator->__invoke($claims->iss)) - && (($claims->aud == $this->clientID) || in_array($this->clientID, $claims->aud)) - && ($claims->nonce == $this->getNonce()) - && ( !isset($claims->exp) || $claims->exp >= time() - $this->leeway) - && ( !isset($claims->nbf) || $claims->nbf <= time() + $this->leeway) - && ( !isset($claims->at_hash) || $claims->at_hash == $expecte_at_hash ) - ); + && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) + && ($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) || $claims->at_hash === $expecte_at_hash ) + ); } /** @@ -1058,7 +1058,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { $ch = curl_init(); // Determine whether this is a GET or POST - if ($post_body != null) { + 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'); From 8af5437678fa606f6ea285fa8ac8a7b451420fd6 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 26 Jun 2019 08:50:55 -0400 Subject: [PATCH 067/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb528f8..95f88360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Remove unused code #1e65384 * Fix indent #e9cdf56 * Cleanup conditional code flow for better readability #107f3fb + * Added strict type comparisons #167 ### Removed * From e03638e17ecc8b58bcce5ea7ecddacf59fc5c091 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 3 Sep 2019 10:03:54 -0400 Subject: [PATCH 068/223] getRedirectURL avoid PHP 7.1+ warning on reset() reset() desires a variable --- src/OpenIDConnectClient.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c4a72889..238b70e2 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -572,8 +572,10 @@ public function getRedirectURL() { ?: @$_SERVER['SERVER_ADDR']; $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; + + $uriSplit = explode("?", $_SERVER['REQUEST_URI']); - return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset(explode('?', $_SERVER['REQUEST_URI'])), '/')); + return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset($uriSplit), '/')); } /** From 27039ea293550ded90478f733f5e969e7af518f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 13 Sep 2019 13:08:00 +0200 Subject: [PATCH 069/223] Adding signing algorithm PS256 --- .gitignore | 3 +++ CHANGELOG.md | 1 + composer.json | 11 ++++++++++- src/OpenIDConnectClient.php | 17 +++++++++++++---- tests/TokenVerificationTest.php | 32 ++++++++++++++++++++++++++++++++ tests/data/jwks-ps256.json | 12 ++++++++++++ 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 tests/TokenVerificationTest.php create mode 100644 tests/data/jwks-ps256.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e0d383dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea +/vendor +/composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f88360..fff69fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Add optional parameters clientId/clientSecret for introspection #157 & #158 * Adding OAuth 2.0 Token Revocation #160 * Adding issuer validator #145 +* Adding signing algorithm PS256 #180 ### Changed * Bugfix/code cleanup #152 diff --git a/composer.json b/composer.json index 6fbe8446..b1aa1dd4 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,11 @@ "php": ">=5.4", "phpseclib/phpseclib" : "~2.0", "ext-json": "*", - "ext-curl": "*" + "ext-curl": "*", + "phpunit/phpunit": "^4.8" + }, + "require-dev": { + "roave/security-advisories": "dev-master" }, "archive" : { "exclude" : [ @@ -15,5 +19,10 @@ }, "autoload" : { "classmap": [ "src/"] + }, + "config" : { + "platform": { + "php": "5.4" } + } } diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c4a72889..65cb9d7d 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -803,10 +803,11 @@ private function get_key_for_header($keys, $header) { * @param object $key * @param $payload * @param $signature + * @param $signatureType * @return bool * @throws OpenIDConnectClientException */ - private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { + private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $signatureType) { if (!class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { throw new OpenIDConnectClientException('Crypt_RSA support unavailable.'); } @@ -824,13 +825,19 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature) { if(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->signatureMode = Crypt_RSA::SIGNATURE_PKCS1; + $rsa->signatureMode = $signatureType === 'PSS' ? Crypt_RSA::SIGNATURE_PSS : Crypt_RSA::SIGNATURE_PKCS1; } 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->signatureMode = \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; + $rsa->signatureMode = $signatureType === 'PSS' ? \phpseclib\Crypt\RSA::SIGNATURE_PSS : \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; } return $rsa->verify($payload, $signature); } @@ -889,13 +896,15 @@ public function verifyJWTsignature($jwt) { } switch ($header->alg) { case 'RS256': + case 'PS256': case 'RS384': case 'RS512': $hashtype = 'sha' . substr($header->alg, 2); + $signatureType = $header->alg === 'PS256' ? 'PSS' : ''; $verified = $this->verifyRSAJWTsignature($hashtype, $this->get_key_for_header($jwks->keys, $header), - $payload, $signature); + $payload, $signature, $signatureType); break; case 'HS256': case 'HS512': diff --git a/tests/TokenVerificationTest.php b/tests/TokenVerificationTest.php new file mode 100644 index 00000000..a10392be --- /dev/null +++ b/tests/TokenVerificationTest.php @@ -0,0 +1,32 @@ +getMockBuilder(OpenIDConnectClient::class)->setMethods(['fetchUrl'])->getMock(); + $client->method('fetchUrl')->willReturn(file_get_contents(__DIR__ . "/data/jwks-$alg.json")); + $client->setProviderURL('https://jwt.io/'); + $client->providerConfigParam(['jwks_uri' => 'https://jwt.io/.well-known/jwks.json']); + $verified = $client->verifyJWTsignature($jwt); + self::assertTrue($verified); + $client->setAccessToken($jwt); + } + + public function providesTokens() + { + return [ + 'PS256' => ['ps256', 'eyJhbGciOiJQUzI1NiIsImtpZCI6Imtvbm5lY3RkLXRva2Vucy1zaWduaW5nLWtleSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJrcG9wLWh0dHBzOi8va29wYW5vLmRlbW8vbWVldC8iLCJleHAiOjE1NjgzNzE0NjEsImp0aSI6IkpkR0tDbEdOTXl2VXJpcmlRRUlWUXZCVmttT2FfQkRjIiwiaWF0IjoxNTY4MzcxMjIxLCJpc3MiOiJodHRwczovL2tvcGFuby5kZW1vIiwic3ViIjoiUHpUVWp3NHBlXzctWE5rWlBILXJxVHE0MTQ1Z3lDdlRvQmk4V1E5bFBrcW5rbEc1aktvRU5LM21Qb0I1WGY1ZTM5dFRMR2RKWXBMNEJubXFnelpaX0FAa29ubmVjdCIsImtjLmlzQWNjZXNzVG9rZW4iOnRydWUsImtjLmF1dGhvcml6ZWRTY29wZXMiOlsicHJvZmlsZSIsImVtYWlsIiwia29wYW5vL2t3bSIsImtvcGFuby9nYyIsImtvcGFuby9rdnMiLCJvcGVuaWQiXSwia2MuYXV0aG9yaXplZENsYWltcyI6eyJpZF90b2tlbiI6eyJuYW1lIjpudWxsfX0sImtjLmlkZW50aXR5Ijp7ImtjLmkuZG4iOiJKb25hcyBCcmVra2UiLCJrYy5pLmlkIjoiQUFBQUFLd2hxVkJBMCs1SXN4bjdwMU13UkNVQkFBQUFCZ0FBQUJzQUFBQk5VVDA5QUFBQUFBPT0iLCJrYy5pLnVuIjoidXNlcjEiLCJrYy5pLnVzIjoiTVEifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWtjIn0.hGRuXvul2kOiALHexwYp5MBEJVwz1YV3ehyM3AOuwCoK2w5sJxdciqqY_TfXCKyO6nAEbYLK3J0CBOjfup_IG0aCZcwzjto8khYlc4ezXkGnFsbJBNQdDGkpHtWnioWx-OJ3cXvY9F8aOvjaq0gw11ZDAcqQl0g7LTbJ9-J_yx0pmy3NGai2JB30Fh1OgSDzYfxWnE0RRgZG-x68e65RXfSBaEGW85OUh4wihxO2zdTGAHJ3Iq_-QAG4yRbXZtLx3ZspG7LNmqG-YE3huy3Rd8u3xrJNhmUOfEnz3x07q7VW0cj9NedX98BAbj3iNvksQsE0oG0J_f_Tu8Ai8VbWB72sJuXZWxANDKdz0BBYLzXhsjXkNByRq9x3zqDVsX-cVHei_XudxEOVRBjhkvW2MmIjcAHNKCKsdar865-gFG9McP4PCcBlY28tC0Cvnzyi83LBfpGRXdl6MJunnUsKQ1C79iCoVI1doK1erFN959Q-TGJfJA3Tr5LNpuGawB5rpe1nDGWvmYhg3uYfNl8uTTyvNgvvejcflEb2DURuXdqABuSiP7RkDWYtzx6mq49G0tRxelBbvyjQ2id2QjmRRdQ6dHEZ2NCJ51b8OFoDJBtxN1CD62TTxa3FUqCdZAPAUR3hHn_69vYq82MR514s-Gb67A6j2PbMPFATQP2UdK8'] + ]; + } +} diff --git a/tests/data/jwks-ps256.json b/tests/data/jwks-ps256.json new file mode 100644 index 00000000..a73a1e08 --- /dev/null +++ b/tests/data/jwks-ps256.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "kty": "RSA", + "use": "sig", + "kid": "konnectd-tokens-signing-key", + "n": "10hb3pFUVcqJcS-d1pLCkFTyTqVD1GavlAai582CoRwFcyIQxCPJz0LJVgkUNwxSRkY0g0PcgFN_MmuuzpFXMkkiMIC9O_KwnuL34FrbijZvcGpnDn7kb9KAM883OVTr_w3wFeQIyh0ksSwVQ9CxVQ-ZeCXP73CCGk99uDb8SeF8_vncXJmaak99pK6HKJteSLkA-Ywxo9HOINZK2vW06UYcSkeoQnSI27Cd5-T6GVgqKH0Su4c5Ydou_w0tL_UkbZA4fIbMZC6dtWmBQf6tyYsCM9fbWNIVOj_7WlWcAOSTFNF2We2dxJrOzt6vDND3k1nCgg_EEM6cgBO3swUCktTFuQxo1sryYX5WXz9wnJb38b9mTXhOeF0bd9y_VQq8erSlcyRu8UGzX65tIf534hLL16KQaHbjROGSQvzqFrISmSBjBTjkPedTZSYOhiVJ95-em_Y6uLi-T7V4bs4dcg3oa0H_glXltoC9JxzS6gfMGGLgh-NpGEOdC_QosyzVVfzT70TurOGnsB1_VcAm_fK-T1Zv_ztpr5OZNfXWXC3Pfq_3sxP5HDKMk8luZ7LOWk7HVSYBdCFmOM1A3KmHNS2fEs-QHIr-XjYQ7QrXsRFP3dmoEPfiYlu03m8Xs3UMB70eGeGQx7OhZSuogxV_oCfApV5EJfuz97tVmOg8iMs", + "e": "AQAB" + } + ], + "kty": "" +} From 6dd8d00659ab4094068830cac0a62756562005f3 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 16 Sep 2019 16:56:22 +0545 Subject: [PATCH 070/223] move phpunit to require-dev --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b1aa1dd4..ae8ef23a 100644 --- a/composer.json +++ b/composer.json @@ -6,10 +6,10 @@ "php": ">=5.4", "phpseclib/phpseclib" : "~2.0", "ext-json": "*", - "ext-curl": "*", - "phpunit/phpunit": "^4.8" + "ext-curl": "*" }, "require-dev": { + "phpunit/phpunit": "^4.8", "roave/security-advisories": "dev-master" }, "archive" : { From 06cf1b20a189ddeda0cb0a91888c396111270fc8 Mon Sep 17 00:00:00 2001 From: "Florian.Kick" Date: Wed, 16 Oct 2019 16:42:18 +0200 Subject: [PATCH 071/223] added getters for parameters that already have public setters --- src/OpenIDConnectClient.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 65cb9d7d..d2379f64 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1671,4 +1671,30 @@ public function setUrlEncoding($curEncoding) } } + + /** + * @return array + */ + public function getScopes() + { + return $this->scopes; + } + + /** + * @return array + */ + public function getResponseTypes() + { + return $this->responseTypes; + } + + /** + * @return array + */ + public function getAuthParams() + { + return $this->authParams; + } + + } From db00da4c1d44fab455d6b5ddba325bd99a78c435 Mon Sep 17 00:00:00 2001 From: "Florian.Kick" Date: Wed, 16 Oct 2019 17:08:39 +0200 Subject: [PATCH 072/223] made some members and methods protected instead of private --- src/OpenIDConnectClient.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index d2379f64..d88dae1d 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -132,7 +132,7 @@ class OpenIDConnectClient /** * @var string if we acquire an access token it will be stored here */ - private $accessToken; + protected $accessToken; /** * @var string if we acquire a refresh token it will be stored here @@ -142,7 +142,7 @@ class OpenIDConnectClient /** * @var string if we acquire an id token it will be stored here */ - private $idToken; + protected $idToken; /** * @var string stores the token response @@ -202,7 +202,7 @@ class OpenIDConnectClient /** * @var array holds verified jwt claims */ - private $verifiedClaims = array(); + protected $verifiedClaims = array(); /** * @var callable validator function for issuer claim @@ -218,7 +218,7 @@ class OpenIDConnectClient */ private $redirectURL; - private $enc_type = PHP_QUERY_RFC1738; + protected $enc_type = PHP_QUERY_RFC1738; /** * @param $provider_url string optional @@ -474,7 +474,7 @@ protected function addAdditionalJwk($jwk) { * @return string * */ - private function getProviderConfigValue($param, $default = null) { + 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" @@ -695,7 +695,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { * @return mixed * @throws OpenIDConnectClientException */ - private function requestTokens($code) { + protected function requestTokens($code) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -720,8 +720,9 @@ private function requestTokens($code) { // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); - return json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); + $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); + return $this->tokenResponse; } /** @@ -922,9 +923,8 @@ public function verifyJWTsignature($jwt) { * @param object $claims * @param string|null $accessToken * @return bool - * @throws OpenIDConnectClientException */ - private function verifyJWTclaims($claims, $accessToken = null) { + protected function verifyJWTclaims($claims, $accessToken = null) { if(isset($claims->at_hash) && isset($accessToken)){ if(isset($this->getAccessTokenHeader()->alg) && $this->getAccessTokenHeader()->alg !== 'none'){ $bit = substr($this->getAccessTokenHeader()->alg, 2, 3); @@ -960,7 +960,7 @@ protected function urlEncode($str) { * @param int $section the section we would like to decode * @return object */ - private function decodeJWT($jwt, $section = 0) { + protected function decodeJWT($jwt, $section = 0) { $parts = explode('.', $jwt); return json_decode(base64url_decode($parts[$section])); From dc355ad315b37e80a415a488ea5dfd1c53c29079 Mon Sep 17 00:00:00 2001 From: "Florian.Kick" Date: Mon, 21 Oct 2019 12:04:48 +0200 Subject: [PATCH 073/223] added getter for issuer validator --- src/OpenIDConnectClient.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index d88dae1d..a3501fb5 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1696,5 +1696,11 @@ public function getAuthParams() return $this->authParams; } - + /** + * @return callable + */ + public function getIssuerValidator() + { + return $this->issuerValidator; + } } From 063ef2259b48234a99e0d1c92898f1c31af5b075 Mon Sep 17 00:00:00 2001 From: "Florian.Kick" Date: Mon, 21 Oct 2019 12:11:35 +0200 Subject: [PATCH 074/223] added getter for leeway --- src/OpenIDConnectClient.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a3501fb5..c69ce0fe 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -933,15 +933,15 @@ protected function verifyJWTclaims($claims, $accessToken = null) { $bit = '256'; } $len = ((int)$bit)/16; - $expecte_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); + $expected_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } return (($this->issuerValidator->__invoke($claims->iss)) && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) && ($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) || $claims->at_hash === $expecte_at_hash ) - ); + && ( !isset($claims->at_hash) || $claims->at_hash === $expected_at_hash ) + ); } /** @@ -1703,4 +1703,12 @@ public function getIssuerValidator() { return $this->issuerValidator; } + + /** + * @return int + */ + public function getLeeway() + { + return $this->leeway; + } } From 4beca0cedb7f2a2681f2e7a3ec6fc4694819ab22 Mon Sep 17 00:00:00 2001 From: Bruno Penso Date: Mon, 9 Dec 2019 08:40:28 -0300 Subject: [PATCH 075/223] Update OpenIDConnectClient.php Check status code of requestUserInfo --- src/OpenIDConnectClient.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 65cb9d7d..4840dc40 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1007,7 +1007,9 @@ public function requestUserInfo($attribute = null) { '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) { From 3d868b7a1a1a2e7cfa71126743d1322c82722bdc Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 9 Dec 2019 08:22:05 -0500 Subject: [PATCH 076/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fff69fae..98a4e205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Adding OAuth 2.0 Token Revocation #160 * Adding issuer validator #145 * Adding signing algorithm PS256 #180 +* Check http staus of request user info #186 ### Changed * Bugfix/code cleanup #152 From 1549834e6a765cdf754809dc3667f3ce6b1fbbd7 Mon Sep 17 00:00:00 2001 From: Stijn Mathysen Date: Tue, 24 Dec 2019 08:34:32 +0100 Subject: [PATCH 077/223] php 7.4 deprecates array_key_exists on objects, use property_exists instead --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 4840dc40..38fe684e 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1016,7 +1016,7 @@ public function requestUserInfo($attribute = null) { return $this->userInfo; } - if (array_key_exists($attribute, $this->userInfo)) { + if (property_exists($this->userInfo, $attribute)) { return $this->userInfo->$attribute; } @@ -1048,7 +1048,7 @@ public function getVerifiedClaims($attribute = null) { return $this->verifiedClaims; } - if (array_key_exists($attribute, $this->verifiedClaims)) { + if (property_exists($this->verifiedClaims, $attribute)) { return $this->verifiedClaims->$attribute; } From cb07ebf0a544b94b711be0d2ef1728928f1d9d2a Mon Sep 17 00:00:00 2001 From: Stijn Mathysen Date: Tue, 24 Dec 2019 08:54:38 +0100 Subject: [PATCH 078/223] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a4e205..4d141f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* php 7.4 deprecates array_key_exists on objects, use property_exists in getVerifiedClaims and requestUserInfo * Adding a header to indicate JSON as the return type for userinfo endpoint #151 * ~Updated OpenIDConnectClient to conditionally verify nonce #146~ * Add possibility to change enc_type parameter for http_build_query #155 From 5806cf3e35be8817711fae33faec2aa6cfef554f Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Mon, 6 Jan 2020 17:12:10 +0100 Subject: [PATCH 079/223] URL encode basic auth user and password URL encode basic auth user and password according to https://tools.ietf.org/html/rfc6749#section-2.3.1 --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 4840dc40..7b3beb71 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -713,7 +713,7 @@ private function requestTokens($code) { # 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($this->clientID . ':' . $this->clientSecret)]; + $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; unset($token_params['client_secret']); } From 3950e135e96ba50f0d900ccad43ad7ce950f6fb9 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Mon, 6 Jan 2020 17:16:01 +0100 Subject: [PATCH 080/223] Updated changelog about url encoded client id/secret --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a4e205..535b0dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Adding OAuth 2.0 Token Revocation #160 * Adding issuer validator #145 * Adding signing algorithm PS256 #180 -* Check http staus of request user info #186 +* Check http status of request user info #186 +* URL encode clientId and clientSecret when using basic authentication, according to https://tools.ietf.org/html/rfc6749#section-2.3.1 #192 ### Changed * Bugfix/code cleanup #152 From a50eb933927a8b5cf534d6c4a547d50cc79db16d Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Mon, 6 Jan 2020 17:19:09 +0100 Subject: [PATCH 081/223] Fixed other basic auth authentication client id/secret encoding --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 7b3beb71..2c653473 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1362,7 +1362,7 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, // Convert token params to string format $post_params = http_build_query($post_data, null, '&'); - $headers = ['Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret), + $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); @@ -1393,7 +1393,7 @@ public function revokeToken($token, $token_type_hint = '', $clientId = null, $cl // Convert token params to string format $post_params = http_build_query($post_data, null, '&'); - $headers = ['Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret), + $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers)); From 970d8a3006ed7570a92604971040e97e0e62ef8e Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Tue, 7 Jan 2020 07:45:12 -0500 Subject: [PATCH 082/223] =?UTF-8?q?Happy=20new=20year=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2c653473..c8ee7e5b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1,7 +1,7 @@ From 540c88c6ea676b5bf8dd3a1d79972f3728d5bed6 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Tue, 7 Jan 2020 07:49:57 -0500 Subject: [PATCH 083/223] Cleaning up documentation --- src/OpenIDConnectClient.php | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c8ee7e5b..c5510f7b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -970,25 +970,25 @@ private function decodeJWT($jwt, $section = 0) { * * @param string $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. + * 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 * @@ -1028,16 +1028,16 @@ public function requestUserInfo($attribute = null) { * @param string $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 + * 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 * From d7cb65683ed2d2df82c45f1d498aa6f2d6292147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 8 Jan 2020 08:52:48 +0100 Subject: [PATCH 084/223] Adjust PHPDoc to state that null is also allowed --- src/OpenIDConnectClient.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c5510f7b..fd86b426 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -414,7 +414,7 @@ public function authenticate() { * (the client application). * * @param string $accessToken ID token (obtained at login) - * @param string $redirect URL to which the RP is requesting that the End-User's User Agent + * @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. * @@ -968,7 +968,7 @@ private function decodeJWT($jwt, $section = 0) { /** * - * @param string $attribute optional + * @param string|null $attribute optional * * Attribute Type Description * user_id string REQUIRED Identifier for the End-User at the Issuer. @@ -1007,9 +1007,9 @@ public function requestUserInfo($attribute = null) { '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()); - } + 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) { @@ -1025,7 +1025,7 @@ public function requestUserInfo($attribute = null) { /** * - * @param string $attribute optional + * @param string|null $attribute optional * * Attribute Type Description * exp int Expires at From c85ea8697515a1e04e5bb7fbe8f0b6485596a186 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Thu, 9 Jan 2020 08:10:09 -0500 Subject: [PATCH 085/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 535b0dcc..8c2e3650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Adding signing algorithm PS256 #180 * Check http status of request user info #186 * URL encode clientId and clientSecret when using basic authentication, according to https://tools.ietf.org/html/rfc6749#section-2.3.1 #192 +* Adjust PHPDoc to state that null is also allowed #193 ### Changed * Bugfix/code cleanup #152 From 94b3e42de55ddb3074ce11c6d999c8bff2cf4efd Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Thu, 5 Mar 2020 19:28:48 -0500 Subject: [PATCH 086/223] Fix at_hash verification --- src/OpenIDConnectClient.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 3463e0fc..aaf5dbae 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -316,6 +316,12 @@ public function authenticate() { 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)) { @@ -325,12 +331,6 @@ public function authenticate() { // Save the full response $this->tokenResponse = $token_json; - // Save the id token - $this->idToken = $token_json->id_token; - - // Save the access token - $this->accessToken = $token_json->access_token; - // Save the verified claims $this->verifiedClaims = $claims; @@ -378,15 +378,15 @@ public function authenticate() { 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 id token - $this->idToken = $id_token; - // Save the verified claims $this->verifiedClaims = $claims; @@ -926,8 +926,8 @@ public function verifyJWTsignature($jwt) { */ private function verifyJWTclaims($claims, $accessToken = null) { if(isset($claims->at_hash) && isset($accessToken)){ - if(isset($this->getAccessTokenHeader()->alg) && $this->getAccessTokenHeader()->alg !== 'none'){ - $bit = substr($this->getAccessTokenHeader()->alg, 2, 3); + if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none'){ + $bit = substr($this->getIdTokenHeader()->alg, 2, 3); }else{ // TODO: Error case. throw exception??? $bit = '256'; From cdf454f0eb0863775977c474c1e8c0e459d8f11d Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 6 Mar 2020 11:28:04 +0100 Subject: [PATCH 087/223] Bugfix: openid scope was omitted when additional scopes were registered --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 3463e0fc..6c8acdb7 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -613,7 +613,7 @@ private function requestAuthorization() { // If the client has been registered with additional scopes if (count($this->scopes) > 0) { - $auth_params = array_merge($auth_params, array('scope' => implode(' ', $this->scopes))); + $auth_params = array_merge($auth_params, array('scope' => implode(' ', array_merge($this->scopes, array('openid'))))); } // If the client has been registered with additional response types From 0817149209384dfa185abbbd77970a8eed1dc67d Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 6 Mar 2020 11:29:22 +0100 Subject: [PATCH 088/223] added description for Bugfix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd41a381..84caa54a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Fix indent #e9cdf56 * Cleanup conditional code flow for better readability #107f3fb * Added strict type comparisons #167 +* Bugfix: required `openid` scope was omitted when additional scopes were registered using `addScope` method. This resulted in failing OpenID process. ### Removed * From a57ab1e26086732e8eb02576c60beed4b491ced3 Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Mon, 9 Mar 2020 11:43:53 +0100 Subject: [PATCH 089/223] Delete LICENSE.txt --- LICENSE.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 51fca54c..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,11 +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. From 9bc028df584a9c740510f63edc6ca5f95e416aad Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Mon, 9 Mar 2020 11:47:32 +0100 Subject: [PATCH 090/223] Create LICENSE file from github template --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. From 87eefea40abd9cea0290aa9b6cdb314fd0037fe2 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 9 Mar 2020 09:26:39 -0400 Subject: [PATCH 091/223] Prep for release --- CHANGELOG.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84caa54a..70373e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +* + +### Changed +* + +### Removed +* + + +## [0.9.0] + ### Added * php 7.4 deprecates array_key_exists on objects, use property_exists in getVerifiedClaims and requestUserInfo * Adding a header to indicate JSON as the return type for userinfo endpoint #151 @@ -33,9 +45,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Added strict type comparisons #167 * Bugfix: required `openid` scope was omitted when additional scopes were registered using `addScope` method. This resulted in failing OpenID process. -### Removed -* - ## [0.8.0] ### Added From 1affa0a2accd94926705a09205111030c11c4b15 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 11 Mar 2020 06:52:27 -0400 Subject: [PATCH 092/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70373e3a..ef564aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * ### Changed -* +* Fix at_hash verification #200 ### Removed * From 103cd522d9fb6ba5a31052b404a7cbfc578c84b5 Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Wed, 25 Mar 2020 08:41:16 -0400 Subject: [PATCH 093/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef564aba..fc9265f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * Fix at_hash verification #200 +* Getters for public parameters #204 ### Removed * From dd44c1ca7e45d35dcd8f32ea503b545149bc6562 Mon Sep 17 00:00:00 2001 From: Jon Erickson Date: Mon, 6 Apr 2020 11:19:18 -0700 Subject: [PATCH 094/223] Removed client ID query parameter when making a token request using Basic Auth. --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9265f6..fcdced04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * Fix at_hash verification #200 * Getters for public parameters #204 +* Removed client ID query parameter when making a token request using Basic Auth ### Removed * diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index df4de6ef..28536ffc 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -715,6 +715,7 @@ protected function requestTokens($code) { 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 From cf184a41fa362cff02c2c7547563491fabfc944a Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 1 May 2020 11:44:22 -0400 Subject: [PATCH 095/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f88360..d1e49b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Fix indent #e9cdf56 * Cleanup conditional code flow for better readability #107f3fb * Added strict type comparisons #167 + * getRedirectURL() will not log a warning for PHP 7.1+ ### Removed * From 2e090cf346c0a3eeb0e6e481888d1f1ee835f849 Mon Sep 17 00:00:00 2001 From: Seth Date: Mon, 11 May 2020 14:48:04 -0400 Subject: [PATCH 096/223] Content-Length header may be sent in proxy CONNECT Since the body isn't sent during the proxy CONNECT, the content-length doesn't match, and the proxy terminates the connection. This effects servers using curl < 7.42.1. Some description about the issue described here: https://daniel.haxx.se/blog/2014/04/04/curl-and-proxy-headers/ The curl version in question seems old, but is still the default on versions of RedHat/CentOS up to 7. --- src/OpenIDConnectClient.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index df4de6ef..b0d1a34d 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1085,7 +1085,6 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { // Add POST-specific headers $headers[] = "Content-Type: {$content_type}"; - $headers[] = 'Content-Length: ' . strlen($post_body); } From 81362350d1ec3a4a709209171ff3f76e1070c322 Mon Sep 17 00:00:00 2001 From: Seth Date: Mon, 11 May 2020 14:50:31 -0400 Subject: [PATCH 097/223] Changelog regarding content-length header --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9265f6..616d33a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Getters for public parameters #204 ### Removed -* +* Removed explicit content-length header - caused issues with proxy servers ## [0.9.0] From db1e1596887ec20ac9dd3f7606979542c189441f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Capil=C3=A9?= Date: Mon, 18 May 2020 18:24:06 -0300 Subject: [PATCH 098/223] enable client_basic_auth for refresh token --- src/OpenIDConnectClient.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b0d1a34d..79e878e8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -734,6 +734,9 @@ protected function requestTokens($code) { */ 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', 'client_secret_post']); + + $headers = []; $grant_type = 'refresh_token'; @@ -744,6 +747,12 @@ public function refreshToken($refresh_token) { 'client_secret' => $this->clientSecret, ); + # 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']); + } + // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); From 8b27bef8a1b15628e5476b1b4d97ff8d347b48fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Capil=C3=A9?= Date: Mon, 18 May 2020 19:01:12 -0300 Subject: [PATCH 099/223] adding missing headers for basic auth --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 79e878e8..6edef047 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -756,7 +756,7 @@ public function refreshToken($refresh_token) { // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); - $json = json_decode($this->fetchURL($token_endpoint, $token_params)); + $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); if (isset($json->access_token)) { $this->accessToken = $json->access_token; From c1d3d05e3cd85f7f38b81f309c7ef2dd2c4c8e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Capil=C3=A9?= Date: Mon, 18 May 2020 20:38:03 -0300 Subject: [PATCH 100/223] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 616d33a5..22ef8303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * Fix at_hash verification #200 * Getters for public parameters #204 +* Enabled `client_secret_basic` authentication on `refreshToken()` ### Removed * Removed explicit content-length header - caused issues with proxy servers From 840572c4e5112945152b85c799189acf3797edfa Mon Sep 17 00:00:00 2001 From: Kieran Foxley-Jones <2211195+KieranFJ@users.noreply.github.com> Date: Tue, 7 Jul 2020 11:04:19 +0100 Subject: [PATCH 101/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e08ef15a..97061044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Fix at_hash verification #200 * Getters for public parameters #204 * Removed client ID query parameter when making a token request using Basic Auth +* Added scope parameter to refresh token request ### Removed * Removed explicit content-length header - caused issues with proxy servers From 45808a447921a5a254834dc5c994477d9362e7e3 Mon Sep 17 00:00:00 2001 From: Kieran Foxley-Jones <2211195+KieranFJ@users.noreply.github.com> Date: Tue, 7 Jul 2020 11:09:05 +0100 Subject: [PATCH 102/223] Add scope to refreshToken request Some providers require a scope when requesting a token refresh. Uses the same process as requestResourceOwnerToken() due to the RFC requiring the scope to be the same for an access_token request and for a refresh token request --- src/OpenIDConnectClient.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2872e711..b5360031 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -743,6 +743,7 @@ public function refreshToken($refresh_token) { 'refresh_token' => $refresh_token, 'client_id' => $this->clientID, 'client_secret' => $this->clientSecret, + 'scope' => implode(' ', $this->scopes), ); // Convert token params to string format From 43d4c96774d2a73289a790b3b93ce8854a3b470c Mon Sep 17 00:00:00 2001 From: Kieran Foxley-Jones <2211195+KieranFJ@users.noreply.github.com> Date: Tue, 7 Jul 2020 11:10:05 +0100 Subject: [PATCH 103/223] Update OpenIDConnectClient.php --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b5360031..3b11b9e5 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -743,7 +743,7 @@ public function refreshToken($refresh_token) { 'refresh_token' => $refresh_token, 'client_id' => $this->clientID, 'client_secret' => $this->clientSecret, - 'scope' => implode(' ', $this->scopes), + 'scope' => implode(' ', $this->scopes), ); // Convert token params to string format From 8b1d15be8b009166cf388d56f6f23a114c27ec4b Mon Sep 17 00:00:00 2001 From: Namo Date: Tue, 7 Jul 2020 21:23:19 -0400 Subject: [PATCH 104/223] Added cURL error code to existing error message This should make the cURL error message easier to debug, especially for those occasions where `curl_error()` just returns NULL :) --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2872e711..bb78ae76 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1141,7 +1141,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { $this->responseCode = $info['http_code']; if ($output === false) { - throw new OpenIDConnectClientException('Curl error: ' . curl_error($ch)); + throw new OpenIDConnectClientException('Curl error: (' . curl_errno($ch) . ') ' . curl_error($ch)); } // Close the cURL resource, and free system resources From 8509ec2e93a2de6117b873c1eec80b492422c386 Mon Sep 17 00:00:00 2001 From: Luc Chauvin Date: Thu, 23 Jul 2020 15:44:44 +0200 Subject: [PATCH 105/223] Add support for MS Azure Active Directory B2C user flows see : https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview --- CHANGELOG.md | 2 +- src/OpenIDConnectClient.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e08ef15a..522c2f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* +* Add support for MS Azure Active Directory B2C user flows ### Changed * Fix at_hash verification #200 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index bb78ae76..f13f6eaf 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -184,6 +184,12 @@ class OpenIDConnectClient */ 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 = array(); + /** * @var int timeout (seconds) */ @@ -500,6 +506,9 @@ private function getWellKnownConfigValue($param, $default = null) { // 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)); } @@ -520,6 +529,16 @@ private function getWellKnownConfigValue($param, $default = null) { 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 From 7f5940c0beed4723e017bffbf7dbe2539851b40c Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Mon, 27 Jul 2020 09:33:23 +0200 Subject: [PATCH 106/223] use random_bytes instead of uniqid for token generation; added random_compat library for php < 7 --- composer.json | 3 ++- src/OpenIDConnectClient.php | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ae8ef23a..bc1b6b44 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "php": ">=5.4", "phpseclib/phpseclib" : "~2.0", "ext-json": "*", - "ext-curl": "*" + "ext-curl": "*", + "paragonie/random_compat": ">=2" }, "require-dev": { "phpunit/phpunit": "^4.8", diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2872e711..2cb465f6 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -580,9 +580,18 @@ public function getRedirectURL() { * Used for arbitrary value generation for nonces and state * * @return string + * @throws OpenIDConnectClientException */ protected function generateRandString() { - return md5(uniqid(rand(), TRUE)); + // 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.'); + }; } /** From dc963615943a206a3acc338fc1ccc1ea0f6e616b Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Mon, 27 Jul 2020 11:30:20 +0200 Subject: [PATCH 107/223] Use of `random_bytes()` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e08ef15a..846db912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Fix at_hash verification #200 * Getters for public parameters #204 * Removed client ID query parameter when making a token request using Basic Auth +* Use of `random_bytes()` for token generation instead of `uniqid()`; polyfill for PHP < 7.0 provided. ### Removed * Removed explicit content-length header - caused issues with proxy servers From 9f15504105640b03277d7174b62fbfc0f5e608ab Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Thu, 27 Aug 2020 12:47:41 -0400 Subject: [PATCH 108/223] Prepping for release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522c2f41..23e3d8d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [0.9.1] ### Added * Add support for MS Azure Active Directory B2C user flows From ce972309057b3cf1967ba559346e9d6d3d667503 Mon Sep 17 00:00:00 2001 From: nikosev Date: Thu, 29 Oct 2020 14:22:46 +0200 Subject: [PATCH 109/223] Add support for PKCE --- CHANGELOG.md | 5 +++ README.md | 14 +++++++ src/OpenIDConnectClient.php | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e3d8d0..7d6eca88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added +* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currentlly the supported methods are 'plain' and 'S256'. + ## [0.9.1] ### Added diff --git a/README.md b/README.md index 8ea18a9d..7044f216 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,20 @@ if (!$data->active) { ``` +## Example 8: PKCE Client ## + +```php +use Jumbojett\OpenIDConnectClient; + +$oidc = new OpenIDConnectClient('https://id.provider.com', + 'ClientIDHere', + null); +$oidc->setCodeChallengeMethod('S256'); +$oidc->authenticate(); +$name = $oidc->requestUserInfo('given_name'); + +``` + ## Development Environments ## In some cases you may need to disable SSL security on on your development systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index f13f6eaf..2b3f11e1 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -226,6 +226,17 @@ class OpenIDConnectClient protected $enc_type = PHP_QUERY_RFC1738; + /** + * @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 = array('S256' => 'sha256', 'plain' => false); + /** * @param $provider_url string optional * @@ -640,6 +651,21 @@ private function requestAuthorization() { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } + // If the client supports Proof Key for Code Exchange (PKCE) + if (!empty($this->getCodeChallengeMethod()) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { + $codeVerifier = bin2hex(random_bytes(64)); + $this->setCodeVerifier($codeVerifier); + if (!empty($this->pkceAlgs[$this->getCodeChallengeMethod()])) { + $codeChallenge = rtrim(strtr(base64_encode(hash($this->pkceAlgs[$this->getCodeChallengeMethod()], $codeVerifier, true)), '+/', '-_'), '='); + } else { + $codeChallenge = $codeVerifier; + } + $auth_params = array_merge($auth_params, array( + 'code_challenge' => $codeChallenge, + 'code_challenge_method' => $this->getCodeChallengeMethod() + )); + } + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); $this->commitSession(); @@ -737,6 +763,15 @@ protected function requestTokens($code) { unset($token_params['client_id']); } + if (!empty($this->getCodeVerifier())) { + $headers = []; + unset($token_params['client_secret']); + $token_params = array_merge($token_params, array( + 'client_id' => $this->clientID, + 'code_verifier' => $this->getCodeVerifier() + )); + } + // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); @@ -1579,6 +1614,35 @@ 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. * @@ -1732,4 +1796,18 @@ public function getLeeway() { return $this->leeway; } + + /** + * @return string + */ + public function getCodeChallengeMethod() { + return $this->codeChallengeMethod; + } + + /** + * @param string $codeChallengeMethod + */ + public function setCodeChallengeMethod($codeChallengeMethod) { + $this->codeChallengeMethod = $codeChallengeMethod; + } } From bca40d14ae580fa7bd3e2f243d0ca6a7983d4d2f Mon Sep 17 00:00:00 2001 From: nikosev Date: Mon, 2 Nov 2020 19:38:45 +0200 Subject: [PATCH 110/223] Fix "Undefined index:openid_connect_code_verifier" --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2b3f11e1..188c33f9 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -763,7 +763,7 @@ protected function requestTokens($code) { unset($token_params['client_id']); } - if (!empty($this->getCodeVerifier())) { + if (!empty($this->getCodeChallengeMethod()) && !empty($this->getCodeVerifier())) { $headers = []; unset($token_params['client_secret']); $token_params = array_merge($token_params, array( From 5b17a572de1b188d777c7c090d020274cb0c3220 Mon Sep 17 00:00:00 2001 From: nikosev Date: Mon, 2 Nov 2020 19:40:46 +0200 Subject: [PATCH 111/223] Require paragonie/random_compat library --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index ae8ef23a..4c1612ae 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "require": { "php": ">=5.4", "phpseclib/phpseclib" : "~2.0", + "paragonie/random_compat":"2.0.19", "ext-json": "*", "ext-curl": "*" }, From 721a96e2d6b474a402114c1d6408137e713ecc27 Mon Sep 17 00:00:00 2001 From: nikosev Date: Mon, 2 Nov 2020 20:07:14 +0200 Subject: [PATCH 112/223] Fix typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6eca88..6541bc0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currentlly the supported methods are 'plain' and 'S256'. +* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently the supported methods are 'plain' and 'S256'. ## [0.9.1] From 4f95102af87f86c43e8191ec6e90d9f35ed1ce5f Mon Sep 17 00:00:00 2001 From: Michael Jett Date: Mon, 16 Nov 2020 09:48:22 -0500 Subject: [PATCH 113/223] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b4a8835..3185924c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [0.9.2] ### Added * Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently the supported methods are 'plain' and 'S256'. From b4eacdb384bc39fedf04c4dd16faedf3ba1668f3 Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 4 Dec 2020 19:49:18 +0100 Subject: [PATCH 114/223] use correct naming for ID token parameter in signOut method --- src/OpenIDConnectClient.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..a512443a 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -430,23 +430,23 @@ public function authenticate() { * Connect provider that the end-user has logged out of the relying party site * (the client application). * - * @param string $accessToken ID token (obtained at login) + * @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($accessToken, $redirect) { + public function signOut($idToken, $redirect) { $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); $signout_params = null; if($redirect === null){ - $signout_params = array('id_token_hint' => $accessToken); + $signout_params = array('id_token_hint' => $idToken); } else { $signout_params = array( - 'id_token_hint' => $accessToken, + 'id_token_hint' => $idToken, 'post_logout_redirect_uri' => $redirect); } From 9d2e5aa018bb2f49072b0ace7f49e70bdd8a6d9f Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 4 Dec 2020 19:53:05 +0100 Subject: [PATCH 115/223] =?UTF-8?q?has=5Fequals=20=E2=86=92=20hash=5Fequal?= =?UTF-8?q?s=20in=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a512443a..1a6e513f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1694,7 +1694,7 @@ private static function safeLength($str) } /** - * Where has_equals is not available, this provides a timing-attack safe string comparison + * Where hash_equals is not available, this provides a timing-attack safe string comparison * @param string $str1 * @param string $str2 * @return bool From f4ef5062946ccaac756b8bd03c1a8a867586c7b6 Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 4 Dec 2020 20:49:24 +0100 Subject: [PATCH 116/223] unify formatting, use short array syntax --- src/OpenIDConnectClient.php | 137 ++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 78 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 1a6e513f..ddf1fb00 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -107,7 +107,7 @@ class OpenIDConnectClient /** * @var array holds the provider configuration */ - private $providerConfig = array(); + private $providerConfig = []; /** * @var string http proxy if necessary @@ -152,7 +152,7 @@ class OpenIDConnectClient /** * @var array holds scopes */ - private $scopes = array(); + private $scopes = []; /** * @var int|null Response code from the server @@ -162,22 +162,22 @@ class OpenIDConnectClient /** * @var array holds response types */ - private $responseTypes = array(); + private $responseTypes = []; /** * @var array holds a cache of info returned from the user info endpoint */ - private $userInfo = array(); + private $userInfo = []; /** * @var array holds authentication parameters */ - private $authParams = array(); + private $authParams = []; /** * @var array holds additional registration parameters for example post_logout_redirect_uris */ - private $registrationParams = array(); + private $registrationParams = []; /** * @var mixed holds well-known openid server properties @@ -188,7 +188,7 @@ class OpenIDConnectClient * @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 = array(); + private $wellKnownConfigParameters = []; /** * @var int timeout (seconds) @@ -203,12 +203,12 @@ class OpenIDConnectClient /** * @var array holds response types */ - private $additionalJwks = array(); + private $additionalJwks = []; /** * @var array holds verified jwt claims */ - protected $verifiedClaims = array(); + protected $verifiedClaims = []; /** * @var callable validator function for issuer claim @@ -219,6 +219,7 @@ class OpenIDConnectClient * @var bool Allow OAuth 2 implicit flow; see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth */ private $allowImplicitFlow = false; + /** * @var string */ @@ -235,7 +236,7 @@ class OpenIDConnectClient /** * @var array holds PKCE supported algorithms */ - private $pkceAlgs = array('S256' => 'sha256', 'plain' => false); + private $pkceAlgs = ['S256' => 'sha256', 'plain' => false]; /** * @param $provider_url string optional @@ -422,7 +423,6 @@ public function authenticate() { $this->requestAuthorization(); return false; - } /** @@ -442,12 +442,12 @@ public function signOut($idToken, $redirect) { $signout_params = null; if($redirect === null){ - $signout_params = array('id_token_hint' => $idToken); + $signout_params = ['id_token_hint' => $idToken]; } else { - $signout_params = array( + $signout_params = [ 'id_token_hint' => $idToken, - 'post_logout_redirect_uri' => $redirect); + 'post_logout_redirect_uri' => $redirect]; } $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->enc_type); @@ -476,7 +476,7 @@ public function addRegistrationParam($param) { } /** - * @param $jwk object - example: (object) array('kid' => ..., 'nbf' => ..., 'use' => 'sig', 'kty' => "RSA", 'e' => "", 'n' => "") + * @param $jwk object - example: (object) ['kid' => ..., 'nbf' => ..., 'use' => 'sig', 'kty' => "RSA", 'e' => "", 'n' => ""] */ protected function addAdditionalJwk($jwk) { $this->additionalJwks[] = $jwk; @@ -641,23 +641,23 @@ private function requestAuthorization() { // State essentially acts as a session key for OIDC $state = $this->setState($this->generateRandString()); - $auth_params = array_merge($this->authParams, array( + $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, array('scope' => implode(' ', array_merge($this->scopes, array('openid'))))); + $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, array('response_type' => implode(' ', $this->responseTypes))); + $auth_params = array_merge($auth_params, ['response_type' => implode(' ', $this->responseTypes)]); } // If the client supports Proof Key for Code Exchange (PKCE) @@ -669,10 +669,10 @@ private function requestAuthorization() { } else { $codeChallenge = $codeVerifier; } - $auth_params = array_merge($auth_params, array( + $auth_params = array_merge($auth_params, [ 'code_challenge' => $codeChallenge, 'code_challenge_method' => $this->getCodeChallengeMethod() - )); + ]); } $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); @@ -693,12 +693,12 @@ public function requestClientCredentialsToken() { $grant_type = 'client_credentials'; - $post_data = array( + $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, null, '&', $this->enc_type); @@ -706,7 +706,6 @@ public function requestClientCredentialsToken() { 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) @@ -722,12 +721,12 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { $grant_type = 'password'; - $post_data = array( + $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) { @@ -757,15 +756,15 @@ protected function requestTokens($code) { $grant_type = 'authorization_code'; - $token_params = array( + $token_params = [ 'grant_type' => $grant_type, 'code' => $code, 'redirect_uri' => $this->getRedirectURL(), 'client_id' => $this->clientID, 'client_secret' => $this->clientSecret - ); + ]; - # Consider Basic authentication if provider config is set this way + // 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']); @@ -775,10 +774,10 @@ protected function requestTokens($code) { if (!empty($this->getCodeChallengeMethod()) && !empty($this->getCodeVerifier())) { $headers = []; unset($token_params['client_secret']); - $token_params = array_merge($token_params, array( + $token_params = array_merge($token_params, [ 'client_id' => $this->clientID, 'code_verifier' => $this->getCodeVerifier() - )); + ]); } // Convert token params to string format @@ -801,12 +800,12 @@ public function refreshToken($refresh_token) { $grant_type = 'refresh_token'; - $token_params = array( + $token_params = [ 'grant_type' => $grant_type, 'refresh_token' => $refresh_token, 'client_id' => $this->clientID, 'client_secret' => $this->clientSecret, - ); + ]; // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); @@ -989,10 +988,10 @@ public function verifyJWTsignature($jwt) { * @return bool */ protected function verifyJWTclaims($claims, $accessToken = null) { - if(isset($claims->at_hash) && isset($accessToken)){ - if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none'){ + if(isset($claims->at_hash) && isset($accessToken)) { + if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none') { $bit = substr($this->getIdTokenHeader()->alg, 2, 3); - }else{ + } else { // TODO: Error case. throw exception??? $bit = '256'; } @@ -1126,8 +1125,7 @@ public function getVerifiedClaims($attribute = null) { * @throws OpenIDConnectClientException * @return mixed */ - protected function fetchURL($url, $post_body = null, $headers = array()) { - + protected function fetchURL($url, $post_body = null, $headers = []) { // OK cool - then let's create a new cURL resource handle $ch = curl_init(); @@ -1273,8 +1271,7 @@ public function setCertPath($certPath) { /** * @return string|null */ - public function getCertPath() - { + public function getCertPath() { return $this->certPath; } @@ -1295,16 +1292,14 @@ public function setVerifyHost($verifyHost) { /** * @return bool */ - public function getVerifyHost() - { + public function getVerifyHost() { return $this->verifyHost; } /** * @return bool */ - public function getVerifyPeer() - { + public function getVerifyPeer() { return $this->verifyPeer; } @@ -1315,7 +1310,7 @@ public function getVerifyPeer() * * @param callable $issuerValidator */ - public function setIssuerValidator($issuerValidator){ + public function setIssuerValidator($issuerValidator) { $this->issuerValidator = $issuerValidator; } @@ -1329,8 +1324,7 @@ public function setAllowImplicitFlow($allowImplicitFlow) { /** * @return bool */ - public function getAllowImplicitFlow() - { + public function getAllowImplicitFlow() { return $this->allowImplicitFlow; } @@ -1369,10 +1363,10 @@ public function register() { $registration_endpoint = $this->getProviderConfigValue('registration_endpoint'); - $send_object = (object ) array_merge($this->registrationParams, array( - 'redirect_uris' => array($this->getRedirectURL()), + $send_object = (object ) array_merge($this->registrationParams, [ + 'redirect_uris' => [$this->getRedirectURL()], 'client_name' => $this->getClientName() - )); + ]); $response = $this->fetchURL($registration_endpoint, json_encode($send_object)); @@ -1414,9 +1408,8 @@ public function register() { public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); - $post_data = array( - 'token' => $token, - ); + $post_data = ['token' => $token]; + if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } @@ -1445,9 +1438,8 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, public function revokeToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $revocation_endpoint = $this->getProviderConfigValue('revocation_endpoint'); - $post_data = array( - 'token' => $token, - ); + $post_data = ['token' => $token]; + if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } @@ -1657,8 +1649,7 @@ protected function unsetCodeVerifier() { * * @return int */ - public function getResponseCode() - { + public function getResponseCode() { return $this->responseCode; } @@ -1667,16 +1658,14 @@ public function getResponseCode() * * @param int $timeout */ - public function setTimeout($timeout) - { + public function setTimeout($timeout) { $this->timeOut = $timeout; } /** * @return int */ - public function getTimeout() - { + public function getTimeout() { return $this->timeOut; } @@ -1685,8 +1674,7 @@ public function getTimeout() * @param string $str * @return int */ - private static function safeLength($str) - { + private static function safeLength($str) { if (function_exists('mb_strlen')) { return mb_strlen($str, '8bit'); } @@ -1699,8 +1687,7 @@ private static function safeLength($str) * @param string $str2 * @return bool */ - private static function hashEquals($str1, $str2) - { + private static function hashEquals($str1, $str2) { $len1=static::safeLength($str1); $len2=static::safeLength($str2); @@ -1748,8 +1735,7 @@ protected function unsetSessionKey($key) { unset($_SESSION[$key]); } - public function setUrlEncoding($curEncoding) - { + public function setUrlEncoding($curEncoding) { switch ($curEncoding) { case PHP_QUERY_RFC1738: @@ -1769,40 +1755,35 @@ public function setUrlEncoding($curEncoding) /** * @return array */ - public function getScopes() - { + public function getScopes() { return $this->scopes; } /** * @return array */ - public function getResponseTypes() - { + public function getResponseTypes() { return $this->responseTypes; } /** * @return array */ - public function getAuthParams() - { + public function getAuthParams() { return $this->authParams; } /** * @return callable */ - public function getIssuerValidator() - { + public function getIssuerValidator() { return $this->issuerValidator; } /** * @return int */ - public function getLeeway() - { + public function getLeeway() { return $this->leeway; } From 296b8db23b256773b11253099148df88ebeb0600 Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 4 Dec 2020 20:57:09 +0100 Subject: [PATCH 117/223] change non public method and variable to camel case --- src/OpenIDConnectClient.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ddf1fb00..1c2cac0d 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -225,7 +225,7 @@ class OpenIDConnectClient */ private $redirectURL; - protected $enc_type = PHP_QUERY_RFC1738; + protected $encType = PHP_QUERY_RFC1738; /** * @var string holds code challenge method for PKCE mode @@ -450,7 +450,7 @@ public function signOut($idToken, $redirect) { 'post_logout_redirect_uri' => $redirect]; } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->enc_type); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->encType); $this->redirect($signout_endpoint); } @@ -675,7 +675,7 @@ private function requestAuthorization() { ]); } - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->encType); $this->commitSession(); $this->redirect($auth_endpoint); @@ -701,7 +701,7 @@ public function requestClientCredentialsToken() { ]; // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); + $post_params = http_build_query($post_data, null, '&', $this->encType); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -735,7 +735,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); + $post_params = http_build_query($post_data, null, '&', $this->encType); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -781,7 +781,7 @@ protected function requestTokens($code) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->enc_type); + $token_params = http_build_query($token_params, null, '&', $this->encType); $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -808,7 +808,7 @@ public function refreshToken($refresh_token) { ]; // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->enc_type); + $token_params = http_build_query($token_params, null, '&', $this->encType); $json = json_decode($this->fetchURL($token_endpoint, $token_params)); @@ -829,7 +829,7 @@ public function refreshToken($refresh_token) { * @throws OpenIDConnectClientException * @return object */ - private function get_key_for_header($keys, $header) { + private function getKeyForHeader($keys, $header) { foreach ($keys as $key) { if ($key->kty === 'RSA') { if (!isset($header->kid) || $key->kid === $header->kid) { @@ -967,7 +967,7 @@ public function verifyJWTsignature($jwt) { $signatureType = $header->alg === 'PS256' ? 'PSS' : ''; $verified = $this->verifyRSAJWTsignature($hashtype, - $this->get_key_for_header($jwks->keys, $header), + $this->getKeyForHeader($jwks->keys, $header), $payload, $signature, $signatureType); break; case 'HS256': @@ -1739,11 +1739,11 @@ public function setUrlEncoding($curEncoding) { switch ($curEncoding) { case PHP_QUERY_RFC1738: - $this->enc_type = PHP_QUERY_RFC1738; + $this->encType = PHP_QUERY_RFC1738; break; case PHP_QUERY_RFC3986: - $this->enc_type = PHP_QUERY_RFC3986; + $this->encType = PHP_QUERY_RFC3986; break; default: From 751a018fdb7a8698291b63b96cfef73ca1f96d5d Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Fri, 4 Dec 2020 21:08:53 +0100 Subject: [PATCH 118/223] add comment for encType variable --- src/OpenIDConnectClient.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 1c2cac0d..13cdb1b4 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -225,6 +225,9 @@ class OpenIDConnectClient */ private $redirectURL; + /** + * @var int defines which URL-encoding http_build_query() uses + */ protected $encType = PHP_QUERY_RFC1738; /** From 024cbf0e45a2d90cc8a049385b06878501cfed4a Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Sat, 5 Dec 2020 12:48:17 +0100 Subject: [PATCH 119/223] remove duplicate paragonie/random_compat dependency --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 9c52f558..bc1b6b44 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,6 @@ "require": { "php": ">=5.4", "phpseclib/phpseclib" : "~2.0", - "paragonie/random_compat":"2.0.19", "ext-json": "*", "ext-curl": "*", "paragonie/random_compat": ">=2" From 698bc5954c10e3ba7dd5ffd9a607692d60e57aa8 Mon Sep 17 00:00:00 2001 From: JuliusPC Date: Sat, 5 Dec 2020 16:38:10 +0100 Subject: [PATCH 120/223] add get/setHttpUpgradeInsecureRequests(), fixes #174 --- CHANGELOG.md | 5 +++++ README.md | 8 +++++++- src/OpenIDConnectClient.php | 25 ++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3185924c..93eab569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## master + +### Added +* it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` + ## [0.9.2] ### Added diff --git a/README.md b/README.md index 7044f216..9a3baca4 100644 --- a/README.md +++ b/README.md @@ -153,10 +153,16 @@ $oidc->setVerifyHost(false); $oidc->setVerifyPeer(false); ``` +Also, your local system might not support HTTPS, so you might disable uprading to it: + +```php +$oidc->httpUpgradeInsecureRequests(false); +``` + ### Todo ### - Dynamic registration does not support registration auth tokens and endpoints [1]: http://openid.net/specs/openid-connect-basic-1_0-15.html#id_res ## Contributing ### - - All pull requests, once merged, should be added to the changelog.md file. + - All pull requests, once merged, should be added to the CHANGELOG.md file. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..eb04496b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -226,6 +226,11 @@ class OpenIDConnectClient protected $enc_type = 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 @@ -585,7 +590,7 @@ public function getRedirectURL() { * Support of 'ProxyReverse' configurations. */ - if (isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] === '1')) { + if ($this->httpUpgradeInsecureRequests && isset($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS']) && ($_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'] === '1')) { $protocol = 'https'; } else { $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] @@ -1292,6 +1297,16 @@ 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 */ @@ -1308,6 +1323,14 @@ 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 From 10a8fe54c3d869a4dbbb4e8f10d416a24855ab57 Mon Sep 17 00:00:00 2001 From: Michael Couillard Date: Sat, 5 Dec 2020 11:28:48 -0500 Subject: [PATCH 121/223] removed a leftover from conflict resolution; whitespace fix --- CHANGELOG.md | 3 --- src/OpenIDConnectClient.php | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eab8a143..8d7ecdc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,9 +56,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Added strict type comparisons #167 * Bugfix: required `openid` scope was omitted when additional scopes were registered using `addScope` method. This resulted in failing OpenID process. -### Removed -* - ## [0.8.0] ### Added diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 80880706..d6430c3c 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -603,7 +603,7 @@ public function getRedirectURL() { $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; - $uriSplit = explode("?", $_SERVER['REQUEST_URI']); + $uriSplit = explode("?", $_SERVER['REQUEST_URI']); return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset($uriSplit), '/')); } From c4d45cbc4ccbda110d45b4cc2a704fe411c8afbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Sj=C3=B6str=C3=B6m?= Date: Tue, 9 Mar 2021 10:56:00 +0100 Subject: [PATCH 122/223] Check if session key exists before accessing it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I ran into this issue when trying to set up a basic OAuth flow using this library and Keycloak. The issue was introduced by ce97230 which checks for PKCE support, the way it does this is to call the function getCodeVerifier() which in turn calls getSessionKey() which returns a specified key from _SESSION. This will fail when setting up a client without PKCE because the key will not exist. With this commit a sanity check is introduced which first checks if the key exists in _SESSION before returning it, otherwise just returning false. This should not affect existing library functionality. Signed-off-by: Erik Sjöström --- src/OpenIDConnectClient.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..f55ba28f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1733,7 +1733,10 @@ protected function commitSession() { protected function getSessionKey($key) { $this->startSession(); - return $_SESSION[$key]; + if (array_key_exists($key, $_SESSION)) { + return $_SESSION[$key]; + } + return false; } protected function setSessionKey($key, $value) { From ac71a4c3eece3a176a84456afd34fc808391a75d Mon Sep 17 00:00:00 2001 From: Ingo Dittmar Date: Wed, 14 Apr 2021 15:28:55 +0200 Subject: [PATCH 123/223] Fix php5.4 empty-bug --- src/OpenIDConnectClient.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..5ca8317b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -661,7 +661,8 @@ private function requestAuthorization() { } // If the client supports Proof Key for Code Exchange (PKCE) - if (!empty($this->getCodeChallengeMethod()) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { + $ccm = $this->getCodeChallengeMethod(); // php5.4 fix, see https://stackoverflow.com/a/4328049 + if (!empty($ccm) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { $codeVerifier = bin2hex(random_bytes(64)); $this->setCodeVerifier($codeVerifier); if (!empty($this->pkceAlgs[$this->getCodeChallengeMethod()])) { @@ -772,7 +773,9 @@ protected function requestTokens($code) { unset($token_params['client_id']); } - if (!empty($this->getCodeChallengeMethod()) && !empty($this->getCodeVerifier())) { + $ccm = $this->getCodeChallengeMethod(); // php5.4 fix, see https://stackoverflow.com/a/4328049 + $cv = $this->getCodeVerifier(); // php5.4 fix, see https://stackoverflow.com/a/4328049 + if (!empty($ccm) && !empty($cv)) { $headers = []; unset($token_params['client_secret']); $token_params = array_merge($token_params, array( From 79915d0b34bf0f33135caf557d6cdbe603bf262b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zden=C4=9Bk=20Zaho=C5=99?= Date: Wed, 12 May 2021 16:52:13 +0200 Subject: [PATCH 124/223] Prevent notice "Only variables can be passed by reference" [Fixes #261] --- src/OpenIDConnectClient.php | 3 ++- tests/OpenIDConnectClientTest.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/OpenIDConnectClientTest.php diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..06cd83ac 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -603,7 +603,8 @@ public function getRedirectURL() { $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; - return sprintf('%s://%s%s/%s', $protocol, $host, $port, @trim(reset(explode('?', $_SERVER['REQUEST_URI'])), '/')); + $explodedRequestUri = isset($_SERVER['REQUEST_URI']) ? explode('?', $_SERVER['REQUEST_URI']) : []; + return sprintf('%s://%s%s/%s', $protocol, $host, $port, trim(reset($explodedRequestUri), '/')); } /** diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php new file mode 100644 index 00000000..e08efea8 --- /dev/null +++ b/tests/OpenIDConnectClientTest.php @@ -0,0 +1,20 @@ +getRedirectURL()); + + $_SERVER['SERVER_NAME'] = 'domain.test'; + $_SERVER['REQUEST_URI'] = '/path/index.php?foo=bar&baz#fragment'; + self::assertSame('http://domain.test/path/index.php', $client->getRedirectURL()); + } +} From 92533ec42974790b12d2f996eb00249b23744c9a Mon Sep 17 00:00:00 2001 From: olivier Date: Mon, 5 Jul 2021 14:34:24 +0200 Subject: [PATCH 125/223] Auth basic for requestResourceOwnerToken added --- src/OpenIDConnectClient.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..bbc76d23 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -715,7 +715,7 @@ public function requestClientCredentialsToken() { * @return mixed * @throws OpenIDConnectClientException */ - public function requestResourceOwnerToken($bClientAuth = FALSE) { + public function requestResourceOwnerToken($bClientAuth = FALSE) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $headers = []; @@ -731,8 +731,13 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { //For client authentication include the client values if($bClientAuth) { - $post_data['client_id'] = $this->clientID; - $post_data['client_secret'] = $this->clientSecret; + $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 From 29e2abcb1f5e8baa6f9fcf8fb6320f9e21fdfbc0 Mon Sep 17 00:00:00 2001 From: Olivier JARRY Date: Mon, 5 Jul 2021 14:48:01 +0200 Subject: [PATCH 126/223] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9c52f558..5bbfe36b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "jumbojett/openid-connect-php", + "name": "aureolebigben/openid-connect-php", "description": "Bare-bones OpenID Connect client", "license": "Apache-2.0", "require": { From fc5ad50c22e7bcdc80c6d179b9a69b8a0429e064 Mon Sep 17 00:00:00 2001 From: Olivier JARRY Date: Mon, 5 Jul 2021 15:05:48 +0200 Subject: [PATCH 127/223] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5bbfe36b..9c52f558 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "aureolebigben/openid-connect-php", + "name": "jumbojett/openid-connect-php", "description": "Bare-bones OpenID Connect client", "license": "Apache-2.0", "require": { From 9d9ba022997a83677987f23f22cf523556ba154e Mon Sep 17 00:00:00 2001 From: Ilja Neumann Date: Tue, 14 Sep 2021 17:02:51 +0200 Subject: [PATCH 128/223] Implement RFC8693 Token Exchange Request --- src/OpenIDConnectClient.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..15ae84c2 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -789,6 +789,39 @@ protected function requestTokens($code) { 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'); + $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; + $grant_type = 'urn:ietf:params:oauth:grant-type:token-exchange'; + + $post_data = array( + 'grant_type' => $grant_type, + 'subject_token_type' => $subjectTokenType, + 'subject_token' => $subjectToken, + 'scope' => implode(' ', $this->scopes) + ); + + if (!empty($audience)) { + $post_data['audience'] = $audience; + } + + // 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 * From 56620ba388f8099da702d3a0d94997f0cb118ec5 Mon Sep 17 00:00:00 2001 From: Ilja Neumann Date: Wed, 15 Sep 2021 16:12:15 +0200 Subject: [PATCH 129/223] Token Exchange: Add support for client_secret_post --- src/OpenIDConnectClient.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 15ae84c2..86b0c0d9 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -801,13 +801,16 @@ protected function requestTokens($code) { */ public function requestTokenExchange($subjectToken, $subjectTokenType, $audience = '') { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); - $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; + $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) ); @@ -815,6 +818,13 @@ public function requestTokenExchange($subjectToken, $subjectTokenType, $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); From eaeb52064ec9dd0fe96b20df0f076b743fa9df63 Mon Sep 17 00:00:00 2001 From: Eloi Rivard Date: Fri, 1 Oct 2021 16:53:12 +0200 Subject: [PATCH 130/223] verifyJWTclaims: fixed an exception when $accessToken is null --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ee81b02c..7ed05bb5 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1004,7 +1004,7 @@ protected function verifyJWTclaims($claims, $accessToken = null) { && ($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) || $claims->at_hash === $expected_at_hash ) + && ( !isset($claims->at_hash) || !isset($accessToken) || $claims->at_hash === $expected_at_hash ) ); } From 24b7bd6a1be48e4d2a5e3e7b627f94590336e84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:01:24 +0100 Subject: [PATCH 131/223] Removed some comments --- src/OpenIDConnectClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c7dc9011..72646a42 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -666,7 +666,7 @@ private function requestAuthorization() { } // If the client supports Proof Key for Code Exchange (PKCE) - $ccm = $this->getCodeChallengeMethod(); // php5.4 fix, see https://stackoverflow.com/a/4328049 + $ccm = $this->getCodeChallengeMethod(); if (!empty($ccm) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { $codeVerifier = bin2hex(random_bytes(64)); $this->setCodeVerifier($codeVerifier); @@ -778,8 +778,8 @@ protected function requestTokens($code) { unset($token_params['client_id']); } - $ccm = $this->getCodeChallengeMethod(); // php5.4 fix, see https://stackoverflow.com/a/4328049 - $cv = $this->getCodeVerifier(); // php5.4 fix, see https://stackoverflow.com/a/4328049 + $ccm = $this->getCodeChallengeMethod(); + $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { $headers = []; unset($token_params['client_secret']); From 2fcbc64d9b50ad436537cc13cc43c04a1faa723d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:09:05 +0100 Subject: [PATCH 132/223] Changelog --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa12eadf..5074140e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -* getRedirectURL() will not log a warning for PHP 7.1+ -* it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` +* getRedirectURL() will not log a warning for PHP 7.1+ #179 +* it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 +* bugfix in verifyJWTclaims when $accessToken is empty and $claims->at_hash is not #276 +* bugfix with the `empty` function in PHP 5.4 #267 ## [0.9.2] @@ -16,7 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.9.1] ### Added -* Add support for MS Azure Active Directory B2C user flows +* Add support for MS Azure Active Directory B2C user flows ### Changed * Fix at_hash verification #200 From 56aa80e010ce5eae437d016a6cf04c9dd44763ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:19:29 +0100 Subject: [PATCH 133/223] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5074140e..f470d4b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] * getRedirectURL() will not log a warning for PHP 7.1+ #179 * it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 +* bugfix in getSessionKey when _SESSION key does not exist #251 * bugfix in verifyJWTclaims when $accessToken is empty and $claims->at_hash is not #276 * bugfix with the `empty` function in PHP 5.4 #267 From 4ef5208d849f0a029abda9db1188b3bf8d6cf6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:27:58 +0100 Subject: [PATCH 134/223] Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1ce834..4291f103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * getRedirectURL() will not log a warning for PHP 7.1+ #179 * it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 * bugfix in getSessionKey when _SESSION key does not exist #251 - Added scope parameter to refresh token request #255 +* Added scope parameter to refresh token request #225 * bugfix in verifyJWTclaims when $accessToken is empty and $claims->at_hash is not #276 * bugfix with the `empty` function in PHP 5.4 #267 From 47506dd9f16ac30e5caab5a22cedc20e2a6eb665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:40:06 +0100 Subject: [PATCH 135/223] unit tests with GHA --- .github/workflows/build.yml | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..d8b82623 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +--- +name: build + +on: [push, pull_request] + +env: + DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi" + +jobs: + phpunit: + name: PHP ${{ matrix.php }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies + run: composer update $DEFAULT_COMPOSER_FLAGS + - name: Run unit tests + run: vendor/bin/phpunit --verbose --colors=always tests From 556803a71f292b5f3d7fa6a22efe946b4de80ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:45:05 +0100 Subject: [PATCH 136/223] Version 0.9.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4291f103..5c3786c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [0.9.3] * getRedirectURL() will not log a warning for PHP 7.1+ #179 * it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 * bugfix in getSessionKey when _SESSION key does not exist #251 From 3a0c3a502881de75e157e928bb1b8de1a8eeed7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sat, 20 Nov 2021 18:46:53 +0100 Subject: [PATCH 137/223] Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3786c9..047d6002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.9.3] + +### Added + * getRedirectURL() will not log a warning for PHP 7.1+ #179 * it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 * bugfix in getSessionKey when _SESSION key does not exist #251 From 712bab2401bb98d2983b1b814c3e569ada472e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 21 Nov 2021 17:55:22 +0100 Subject: [PATCH 138/223] Changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047d6002..97736df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [unreleased] + +### Added + +* Basic auth support for requestResourceOwnerToken #271 + ## [0.9.3] ### Added From 0ccd324421e5764dfb5b70a5380f3cf0af143ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 21 Nov 2021 18:11:41 +0100 Subject: [PATCH 139/223] =?UTF-8?q?GHA:=20stop=20testing=20PHP=C2=A05.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8b82623..7cd1d242 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] steps: - name: Checkout From 11785b426281e66e472ce86c4afab3e7d82aab45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 21 Nov 2021 18:38:14 +0100 Subject: [PATCH 140/223] Version 0.9.4 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8791e9..fc9ce992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [unreleased] +## [0.9.4] ### Added From 83481eb27ddde1d65b50a50291df8cda445f928a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 24 Nov 2021 14:50:51 +0100 Subject: [PATCH 141/223] Changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa998e4..71a82272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.9.5] + +### Changed + +* signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. #127 + ## [0.9.4] ### Added @@ -103,7 +109,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). ### Changed -* signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. #127 * refreshToken method update #124 ### Removed From 7b94db5ccd5cb7685669525c729a259853f729f8 Mon Sep 17 00:00:00 2001 From: Ian Jenkins Date: Wed, 24 Nov 2021 16:00:47 +0000 Subject: [PATCH 142/223] Add failing test for null nonce on claims causing an exception --- phpunit.xml.dist | 28 ++++++++++++++++++++++++ tests/OpenIDConnectClientTest.php | 36 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 phpunit.xml.dist diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..df8ef259 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + ./tests + + + + + ./src + + ./vendor + ./tests + + + + diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index e08efea8..2ec80b1d 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -1,6 +1,7 @@ getRedirectURL()); } + + public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() + { + $fakeClaims = new \StdClass(); + $fakeClaims->iss = 'fake-issuer'; + $fakeClaims->aud = 'fake-client-id'; + $fakeClaims->nonce = null; + + $_REQUEST['id_token'] = 'abc.123.xyz'; + $_REQUEST['state'] = false; + $_SESSION['openid_connect_state'] = false; + + /** @var OpenIDConnectClient | PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTsignature'])->getMock(); + $client->method('decodeJWT')->willReturn($fakeClaims); + $client->method('getProviderConfigValue')->with('jwks_uri')->willReturn(true); + $client->method('verifyJWTsignature')->willReturn(true); + + $client->setClientID('fake-client-id'); + $client->setIssuer('fake-issuer'); + $client->setIssuerValidator(function() { + return true; + }); + $client->setAllowImplicitFlow(true); + $client->setProviderURL('https://jwt.io/'); + + try { + $authenticated = $client->authenticate(); + $this->assertTrue($authenticated); + } catch ( OpenIDConnectClientException $e ) { + if ( $e->getMessage() === 'Unable to verify JWT claims' ) { + self::fail( 'OpenIDConnectClientException was thrown when it should not have been.' ); + } + } + } } From 31913bfc361c0de89a2a0aa4a42424eaab6fc98d Mon Sep 17 00:00:00 2001 From: Ian Jenkins Date: Wed, 24 Nov 2021 16:02:51 +0000 Subject: [PATCH 143/223] Fix for claims containing a null nonce. Nonce is optional according to the open ID spec, so we shouldn't throw an exception if a nonce is null (or not set) within the claims. This was working previously but a change to how session keys are checked here: https://github.com/jumbojett/OpenID-Connect-PHP/pull/251 meant that the nonce check was now strict and `null !== false`. This fixes by checking first that the nonce is set before checking it matches the nonce in the session. --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 77530f45..7fbcdb23 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1026,7 +1026,7 @@ protected function verifyJWTclaims($claims, $accessToken = null) { } return (($this->issuerValidator->__invoke($claims->iss)) && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) - && ($claims->nonce === $this->getNonce()) + && (!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 ) From 9b04bf471813bb2bb44d74ffcefc6645bfa4681b Mon Sep 17 00:00:00 2001 From: Ian Jenkins Date: Wed, 24 Nov 2021 16:06:51 +0000 Subject: [PATCH 144/223] Add changelog entry for missing nonce issue --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a82272..a93d6ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed * signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. #127 +* Fixed issue where missing nonce within the claims was causing an exception. #280 ## [0.9.4] From 15cd81fab020b0d6deca64c8d9df447b282c83b1 Mon Sep 17 00:00:00 2001 From: Freddie Leeman Date: Tue, 11 Jan 2022 10:13:28 +0100 Subject: [PATCH 145/223] Replace error control operator "@" with isset() Error suppression should be avoided when possible. --- src/OpenIDConnectClient.php | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 7fbcdb23..0ca0e5ba 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -592,19 +592,33 @@ public function getRedirectURL() { 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 = @$_SERVER['HTTP_X_FORWARDED_PROTO'] - ?: @$_SERVER['REQUEST_SCHEME'] - ?: ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http'); + $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; } - $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) - ?: @intval($_SERVER['SERVER_PORT']) - ?: (($protocol === 'https') ? 443 : 80); - - $host = @explode(':', $_SERVER['HTTP_HOST'])[0] - ?: @$_SERVER['SERVER_NAME'] - ?: @$_SERVER['SERVER_ADDR']; + if (isset($_SERVER['HTTP_HOST'])) { + $host = explode(':', $_SERVER['HTTP_HOST'])[0]; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $host = $_SERVER['SERVER_NAME']; + } else { + $host = $_SERVER['SERVER_ADDR']; + } $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; From 5b7fe8f63e3f113d6384c4977df0a8024188f660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zden=C4=9Bk=20Zaho=C5=99?= Date: Wed, 12 May 2021 15:18:52 +0200 Subject: [PATCH 146/223] Support for phpseclib v3 [Resolves #253] --- composer.json | 7 +------ src/OpenIDConnectClient.php | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index bc1b6b44..b23f23f2 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "Apache-2.0", "require": { "php": ">=5.4", - "phpseclib/phpseclib" : "~2.0", + "phpseclib/phpseclib" : "~2.0 || ^3.0", "ext-json": "*", "ext-curl": "*", "paragonie/random_compat": ">=2" @@ -20,10 +20,5 @@ }, "autoload" : { "classmap": [ "src/"] - }, - "config" : { - "platform": { - "php": "5.4" } - } } diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 7fbcdb23..ddc21f32 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -31,7 +31,7 @@ * It can be downloaded from: http://phpseclib.sourceforge.net/ */ -if (!class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { +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'); } @@ -898,7 +898,7 @@ private function get_key_for_header($keys, $header) { * @throws OpenIDConnectClientException */ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $signatureType) { - if (!class_exists('\phpseclib\Crypt\RSA') && !class_exists('Crypt_RSA')) { + 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'))) { @@ -912,14 +912,25 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $s ' ' . b64url2b64($key->n) . "\r\n" . ' ' . b64url2b64($key->e) . "\r\n" . ''; - if(class_exists('Crypt_RSA', false)) { + 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->signatureMode = $signatureType === 'PSS' ? Crypt_RSA::SIGNATURE_PSS : Crypt_RSA::SIGNATURE_PKCS1; + $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); @@ -927,9 +938,9 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $s $rsa->setMGFHash($hashtype); } $rsa->loadKey($public_key_xml, \phpseclib\Crypt\RSA::PUBLIC_FORMAT_XML); - $rsa->signatureMode = $signatureType === 'PSS' ? \phpseclib\Crypt\RSA::SIGNATURE_PSS : \phpseclib\Crypt\RSA::SIGNATURE_PKCS1; + $rsa->setSignatureMode($signatureType === 'PSS' ? \phpseclib\Crypt\RSA::SIGNATURE_PSS : \phpseclib\Crypt\RSA::SIGNATURE_PKCS1); + return $rsa->verify($payload, $signature); } - return $rsa->verify($payload, $signature); } /** @@ -1537,7 +1548,7 @@ public function getClientSecret() { * @return bool */ public function canVerifySignatures() { - return class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); + return class_exists('\phpseclib3\Crypt\RSA') || class_exists('\phpseclib\Crypt\RSA') || class_exists('Crypt_RSA'); } /** From 44ff466717743a48f1a75b7df8a175fc83666c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zden=C4=9Bk=20Zaho=C5=99?= Date: Wed, 12 May 2021 15:27:41 +0200 Subject: [PATCH 147/223] Support for phpseclib v3 [Resolves #253] --- CHANGELOG.md | 5 +++++ composer.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a93d6ba8..85c0b889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added +* Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. + ## [0.9.5] ### Changed diff --git a/composer.json b/composer.json index b23f23f2..f8da5351 100644 --- a/composer.json +++ b/composer.json @@ -20,5 +20,10 @@ }, "autoload" : { "classmap": [ "src/"] + }, + "config" : { + "platform": { + "php": "5.4" + } } } From c5bb1de23cba2f1ff4da82b3a27bdc56872b7aa3 Mon Sep 17 00:00:00 2001 From: Freddie Leeman Date: Fri, 14 Jan 2022 08:46:36 +0100 Subject: [PATCH 148/223] Fix to pass unit test Patch to fix unit test --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 0ca0e5ba..18c9c5b8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -616,7 +616,7 @@ public function getRedirectURL() { $host = explode(':', $_SERVER['HTTP_HOST'])[0]; } elseif (isset($_SERVER['SERVER_NAME'])) { $host = $_SERVER['SERVER_NAME']; - } else { + } elseif (isset($_SERVER['SERVER_ADDR'])) { $host = $_SERVER['SERVER_ADDR']; } From 0097efe09265f7dca67ae4cd5ff7d72c76d1b162 Mon Sep 17 00:00:00 2001 From: Freddie Leeman Date: Fri, 14 Jan 2022 09:04:05 +0100 Subject: [PATCH 149/223] Return false if host cannot be determined. --- src/OpenIDConnectClient.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 18c9c5b8..98ce2471 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -618,6 +618,8 @@ public function getRedirectURL() { $host = $_SERVER['SERVER_NAME']; } elseif (isset($_SERVER['SERVER_ADDR'])) { $host = $_SERVER['SERVER_ADDR']; + } else { + return false; } $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; From 9429ba09cab9a0ee20b30872fdcb44d52a3e9d55 Mon Sep 17 00:00:00 2001 From: Freddie Leeman Date: Fri, 14 Jan 2022 09:10:09 +0100 Subject: [PATCH 150/223] test expects 'http:///' Maybe this is not the way to go, and we need to adjust the test. There is no scenario where this will happen though. --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 98ce2471..aefd497e 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -619,7 +619,7 @@ public function getRedirectURL() { } elseif (isset($_SERVER['SERVER_ADDR'])) { $host = $_SERVER['SERVER_ADDR']; } else { - return false; + return 'http:///'; } $port = (443 === $port) || (80 === $port) ? '' : ':' . $port; From c0e124ac7a089c1ae7a7dd1772beb337b5db76a2 Mon Sep 17 00:00:00 2001 From: winstonpersonify <34104877+winstonpersonify@users.noreply.github.com> Date: Tue, 8 Mar 2022 13:58:34 -0600 Subject: [PATCH 151/223] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a3baca4..e0977931 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ $oidc->setVerifyPeer(false); Also, your local system might not support HTTPS, so you might disable uprading to it: ```php -$oidc->httpUpgradeInsecureRequests(false); +$oidc->setHttpUpgradeInsecureRequests(false); ``` ### Todo ### From 0821bdcef9759ba1349bcd110bd3d27f42b6335a Mon Sep 17 00:00:00 2001 From: Juan Manuel Lallana Date: Tue, 8 Mar 2022 18:15:35 -0300 Subject: [PATCH 152/223] Fix php 8.1 --- src/OpenIDConnectClient.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 4023216e..76436a9c 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -455,7 +455,7 @@ public function signOut($idToken, $redirect) { 'post_logout_redirect_uri' => $redirect); } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, null, '&', $this->enc_type); + $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->enc_type); $this->redirect($signout_endpoint); } @@ -698,7 +698,7 @@ private function requestAuthorization() { )); } - $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, '', '&', $this->enc_type); $this->commitSession(); $this->redirect($auth_endpoint); @@ -724,7 +724,7 @@ public function requestClientCredentialsToken() { ); // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); + $post_params = http_build_query($post_data, '', '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -764,7 +764,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); + $post_params = http_build_query($post_data, '', '&', $this->enc_type); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } @@ -812,7 +812,7 @@ protected function requestTokens($code) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->enc_type); + $token_params = http_build_query($token_params, '', '&', $this->enc_type); $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -850,7 +850,7 @@ public function refreshToken($refresh_token) { } // Convert token params to string format - $token_params = http_build_query($token_params, null, '&', $this->enc_type); + $token_params = http_build_query($token_params, '', '&', $this->enc_type); $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); @@ -1494,7 +1494,7 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; // Convert token params to string format - $post_params = http_build_query($post_data, null, '&'); + $post_params = http_build_query($post_data, '', '&'); $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; @@ -1525,7 +1525,7 @@ public function revokeToken($token, $token_type_hint = '', $clientId = null, $cl $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; // Convert token params to string format - $post_params = http_build_query($post_data, null, '&'); + $post_params = http_build_query($post_data, '', '&'); $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; From 14322abbd6c8c702465b1917cbb4e9ccea2f60d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:17:13 +0100 Subject: [PATCH 153/223] Run GH workflow on PHP 8.0 and 8.1 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cd1d242..2033968d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] steps: - name: Checkout From 414c093a18a28a4075e819a4c9eea1c06a28fea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:33:41 +0100 Subject: [PATCH 154/223] tests: run test cases on all supported PHP versions --- .gitignore | 1 + composer.json | 9 ++------- tests/OpenIDConnectClientTest.php | 6 ++++-- tests/TokenVerificationTest.php | 6 ++++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index e0d383dc..e55efdfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea /vendor /composer.lock +.phpunit.result.cache diff --git a/composer.json b/composer.json index f8da5351..6d218ccf 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,8 @@ "paragonie/random_compat": ">=2" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "roave/security-advisories": "dev-master" + "roave/security-advisories": "dev-master", + "yoast/phpunit-polyfills": "^1.0" }, "archive" : { "exclude" : [ @@ -20,10 +20,5 @@ }, "autoload" : { "classmap": [ "src/"] - }, - "config" : { - "platform": { - "php": "5.4" - } } } diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 2ec80b1d..59a8a39c 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -2,8 +2,10 @@ use Jumbojett\OpenIDConnectClient; use Jumbojett\OpenIDConnectClientException; +use PHPUnit\Framework\MockObject\MockObject; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class OpenIDConnectClientTest extends PHPUnit_Framework_TestCase +class OpenIDConnectClientTest extends TestCase { /** * @return void @@ -30,7 +32,7 @@ public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() $_REQUEST['state'] = false; $_SESSION['openid_connect_state'] = false; - /** @var OpenIDConnectClient | PHPUnit_Framework_MockObject_MockObject $client */ + /** @var OpenIDConnectClient | MockObject $client */ $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTsignature'])->getMock(); $client->method('decodeJWT')->willReturn($fakeClaims); $client->method('getProviderConfigValue')->with('jwks_uri')->willReturn(true); diff --git a/tests/TokenVerificationTest.php b/tests/TokenVerificationTest.php index a10392be..58449924 100644 --- a/tests/TokenVerificationTest.php +++ b/tests/TokenVerificationTest.php @@ -2,8 +2,10 @@ use Jumbojett\OpenIDConnectClient; +use PHPUnit\Framework\MockObject\MockObject; +use Yoast\PHPUnitPolyfills\TestCases\TestCase; -class TokenVerificationTest extends PHPUnit_Framework_TestCase +class TokenVerificationTest extends TestCase { /** * @param $alg @@ -13,7 +15,7 @@ class TokenVerificationTest extends PHPUnit_Framework_TestCase */ public function testTokenVerification($alg, $jwt) { - /** @var OpenIDConnectClient | PHPUnit_Framework_MockObject_MockObject $client */ + /** @var OpenIDConnectClient | MockObject $client */ $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['fetchUrl'])->getMock(); $client->method('fetchUrl')->willReturn(file_get_contents(__DIR__ . "/data/jwks-$alg.json")); $client->setProviderURL('https://jwt.io/'); From d163bf59355c7737d04ffafee1875217bfa2488c Mon Sep 17 00:00:00 2001 From: Ilja Neumann Date: Wed, 4 Aug 2021 11:21:49 +0200 Subject: [PATCH 155/223] fix: Don't unset headers when using code challenge --- CHANGELOG.md | 3 ++- src/OpenIDConnectClient.php | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c0b889..b55b2e15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -* Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. +* Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. #260 +* Support client_secret on token endpoint with PKCE. #293 ## [0.9.5] diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 76436a9c..1ba6930b 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -803,8 +803,10 @@ protected function requestTokens($code) { $ccm = $this->getCodeChallengeMethod(); $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { - $headers = []; - unset($token_params['client_secret']); + if (empty($this->getClientSecret())) { + $headers = []; + unset($token_params['client_secret']); + } $token_params = array_merge($token_params, array( 'client_id' => $this->clientID, 'code_verifier' => $this->getCodeVerifier() From 16ebfc2d3c2976aed9cef950131a56d5ed2fa2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 28 Mar 2022 20:38:54 +0200 Subject: [PATCH 156/223] fix: allow serializing OpenIDConnectClient --- CHANGELOG.md | 4 ++++ src/OpenIDConnectClient.php | 21 +++++++++++++++------ tests/OpenIDConnectClientTest.php | 7 +++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55b2e15..b1244af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. #260 * Support client_secret on token endpoint with PKCE. #293 +### Changed + +* Allow serializing `OpenIDConnectClient` using `serialize()` + ## [0.9.5] ### Changed diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 1ba6930b..922c2cc7 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -211,7 +211,7 @@ class OpenIDConnectClient protected $verifiedClaims = array(); /** - * @var callable validator function for issuer claim + * @var callable|null validator function for issuer claim */ private $issuerValidator; @@ -259,10 +259,6 @@ public function __construct($provider_url = null, $client_id = null, $client_sec $this->clientID = $client_id; $this->clientSecret = $client_secret; - - $this->issuerValidator = function($iss){ - return ($iss === $this->getIssuer() || $iss === $this->getWellKnownIssuer() || $iss === $this->getWellKnownIssuer(true)); - }; } /** @@ -1037,6 +1033,19 @@ public function verifyJWTsignature($jwt) { 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 @@ -1053,7 +1062,7 @@ protected function verifyJWTclaims($claims, $accessToken = null) { $len = ((int)$bit)/16; $expected_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } - return (($this->issuerValidator->__invoke($claims->iss)) + 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))) diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 59a8a39c..7abf3116 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -55,4 +55,11 @@ public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() } } } + + public function testSerialize() + { + $client = new OpenIDConnectClient('https://example.com', 'foo', 'bar', 'baz'); + $serialized = serialize($client); + $this->assertInstanceOf('Jumbojett\OpenIDConnectClient', unserialize($serialized)); + } } From 1a2844c75a3350ea88ce3ddb92c3f72ca2a988ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 29 Mar 2022 22:02:28 +0200 Subject: [PATCH 157/223] feat: allow passing custom headers to requestTokens() --- CHANGELOG.md | 4 +++- src/OpenIDConnectClient.php | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1244af7..3516c3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added + * Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. #260 * Support client_secret on token endpoint with PKCE. #293 +* Added new parameter to `requestTokens()` to pass custom HTTP headers #297 ### Changed -* Allow serializing `OpenIDConnectClient` using `serialize()` +* Allow serializing `OpenIDConnectClient` using `serialize()` #295 ## [0.9.5] diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 922c2cc7..0b8890bb 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -770,15 +770,14 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { * 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) { + 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']); - $headers = []; - $grant_type = 'authorization_code'; $token_params = array( @@ -789,9 +788,10 @@ protected function requestTokens($code) { '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)) { - $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; + $authorizationHeader = 'Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret)); unset($token_params['client_secret']); unset($token_params['client_id']); } @@ -800,7 +800,7 @@ protected function requestTokens($code) { $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { if (empty($this->getClientSecret())) { - $headers = []; + $authorizationHeader = null; unset($token_params['client_secret']); } $token_params = array_merge($token_params, array( @@ -812,6 +812,10 @@ protected function requestTokens($code) { // Convert token params to string format $token_params = http_build_query($token_params, '', '&', $this->enc_type); + if (null !== $authorizationHeader) { + $headers[] = $authorizationHeader; + } + $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); return $this->tokenResponse; From 8b85f7c526f031c8fd1c1042e682a9388c4be7cd Mon Sep 17 00:00:00 2001 From: Samuel NELA Date: Sat, 9 Apr 2022 14:59:36 +0200 Subject: [PATCH 158/223] Fix typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0977931..6689f46d 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ $name = $oidc->requestUserInfo('given_name'); ## Development Environments ## -In some cases you may need to disable SSL security on on your development systems. +In some cases you may need to disable SSL security on your development systems. Note: This is not recommended on production systems. ```php @@ -153,7 +153,7 @@ $oidc->setVerifyHost(false); $oidc->setVerifyPeer(false); ``` -Also, your local system might not support HTTPS, so you might disable uprading to it: +Also, your local system might not support HTTPS, so you might disable upgrading to it: ```php $oidc->setHttpUpgradeInsecureRequests(false); From 5b0b423d5db7702012415b9071c41f665e674bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 8 May 2022 15:27:57 +0200 Subject: [PATCH 159/223] Version 0.9.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3516c3ed..836f26d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [0.9.6] ### Added From 82eeeaa42531b338fca5e35d773b5232f1349878 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Mon, 6 Jun 2022 08:06:45 -0400 Subject: [PATCH 160/223] Fix PHP 5.4 bug Fix the following error: Fatal error: Can't use method return value in write context in (...)/src/OpenIDConnectClient.php on line 802 --- src/OpenIDConnectClient.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 0b8890bb..42944d84 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -799,7 +799,8 @@ protected function requestTokens($code, $headers = array()) { $ccm = $this->getCodeChallengeMethod(); $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { - if (empty($this->getClientSecret())) { + $cs = $this->getClientSecret(); + if (empty($cs)) { $authorizationHeader = null; unset($token_params['client_secret']); } From a5510d2801d51b62221071751403ad9091d9374c Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Mon, 13 Jun 2022 13:56:52 +0200 Subject: [PATCH 161/223] Added userInfo response type check to handle signed and encrypted responses --- src/OpenIDConnectClient.php | 106 ++++++++++++++---- src/interfaces/HandleJweResponseInterface.php | 13 +++ 2 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 src/interfaces/HandleJweResponseInterface.php diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 0b8890bb..51ab3bc6 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -22,6 +22,8 @@ namespace Jumbojett; +use Jumbojett\Interfaces\HandleJweResponseInterface; + /** * * JWT signature verification support by Jonathan Reed @@ -159,6 +161,11 @@ class OpenIDConnectClient */ private $responseCode; + /** + * @var string|null Content type from the server + */ + private $responseContentType; + /** * @var array holds response types */ @@ -242,6 +249,11 @@ class OpenIDConnectClient */ private $pkceAlgs = array('S256' => 'sha256', 'plain' => false); + /** + * @var HandleJweResponseInterface|null + */ + private $jweResponseHandler; + /** * @param $provider_url string optional * @@ -282,6 +294,13 @@ public function setResponseTypes($response_types) { $this->responseTypes = array_merge($this->responseTypes, (array)$response_types); } + /** + * @param HandleJweResponseInterface $jwe_response_handler + */ + public function setJweResponseHandler($jwe_response_handler) { + $this->jweResponseHandler = $jwe_response_handler; + } + /** * @return bool * @throws OpenIDConnectClientException @@ -323,16 +342,7 @@ public function authenticate() { $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.'); - } + $this->verifySignatures($token_json->id_token); // Save the id token $this->idToken = $token_json->id_token; @@ -385,16 +395,7 @@ public function authenticate() { $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.'); - } + $this->verifySignatures($id_token); // Save the id token $this->idToken = $id_token; @@ -793,7 +794,7 @@ protected function requestTokens($code, $headers = array()) { 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']); + unset($token_params['client_id']); } $ccm = $this->getCodeChallengeMethod(); @@ -1037,6 +1038,25 @@ public function verifyJWTsignature($jwt) { return $verified; } + /** + * @param string $jwt encoded JWT + * @return void + * @throws OpenIDConnectClientException + */ + public function verifySignatures($jwt) + { + 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($jwt)) { + throw new OpenIDConnectClientException ('Unable to verify signature'); + } + } else { + user_error('Warning: JWT signature verification unavailable.'); + } + } + /** * @param string $iss * @return bool @@ -1137,10 +1157,41 @@ public function requestUserInfo($attribute = null) { $headers = ["Authorization: Bearer {$this->accessToken}", 'Accept: application/json']; - $user_json = json_decode($this->fetchURL($user_info_endpoint,null,$headers)); + $response = $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()); } + + // When we receive application/jwt, the UserInfo Response is signed and/or encrypted. + if ($this->getResponseContentType() === 'application/jwt' ) { + // Check if the response is encrypted + $jwtHeaders = $this->decodeJWT($response); + if (isset($jwtHeaders->enc)) { + // If we don't have a JWE handler then throw error + if ($this->jweResponseHandler === null) { + throw new OpenIDConnectClientException('JWE response handler not set'); + } + + // Handle JWE + $jwt = $this->jweResponseHandler->handleJweResponse($response); + } + + // Verify the signature + $this->verifySignatures($jwt); + + // Get claims from JWT + $claims = $this->decodeJWT($jwt, 1); + + // Verify the JWT claims + if (!$this->verifyJWTclaims($claims)) { + throw new OpenIDConnectClientException('Invalid JWT signature'); + } + + $user_json = $claims; + } else { + $user_json = json_decode($response); + } + $this->userInfo = $user_json; if($attribute === null) { @@ -1269,6 +1320,7 @@ protected function fetchURL($url, $post_body = null, $headers = array()) { // HTTP Response code from server may be required from subclass $info = curl_getinfo($ch); $this->responseCode = $info['http_code']; + $this->responseContentType = $info['content_type']; if ($output === false) { throw new OpenIDConnectClientException('Curl error: (' . curl_errno($ch) . ') ' . curl_error($ch)); @@ -1747,6 +1799,16 @@ public function getResponseCode() return $this->responseCode; } + /** + * Get the content type from last action/curl request. + * + * @return string|null + */ + public function getResponseContentType() + { + return $this->responseContentType; + } + /** * Set timeout (seconds) * diff --git a/src/interfaces/HandleJweResponseInterface.php b/src/interfaces/HandleJweResponseInterface.php new file mode 100644 index 00000000..3eef5b2c --- /dev/null +++ b/src/interfaces/HandleJweResponseInterface.php @@ -0,0 +1,13 @@ + Date: Tue, 28 Jun 2022 16:36:42 +0200 Subject: [PATCH 162/223] Use session_status() to check if session should be started (#306) Closes #306 Ref: https://www.php.net/manual/de/function.session-status.php#123404 --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 0b8890bb..d1777a2f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1804,7 +1804,7 @@ private static function hashEquals($str1, $str2) * Use session to manage a nonce */ protected function startSession() { - if (!isset($_SESSION)) { + if (session_status() === PHP_SESSION_NONE) { @session_start(); } } From ca7429b218d5f043cc440dcd98fc27e4e0f1e418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 5 Jul 2022 08:55:57 +0200 Subject: [PATCH 163/223] feat: verify JWT using JWK header --- CHANGELOG.md | 12 ++++++++++++ src/OpenIDConnectClient.php | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 836f26d2..2e152976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [unreleased] + +### Added + +* Support for Self-Contained JWTs. #308 +* Support for RFC8693 Token Exchange Request. #275 + +### Fixed + +* PHP 5.4 compatibility. #304 +* Use session_status(). #306 + ## [0.9.6] ### Added diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 12f63c17..6fd6d117 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1051,14 +1051,11 @@ public function verifyJWTsignature($jwt) { if (null === $header || !\is_object($header)) { throw new OpenIDConnectClientException('Error decoding JSON from token header'); } - $payload = implode('.', $parts); - $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); - if ($jwks === NULL) { - throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); - } if (!isset($header->alg)) { throw new OpenIDConnectClientException('Error missing signature type in token header'); } + + $payload = implode('.', $parts); switch ($header->alg) { case 'RS256': case 'PS256': @@ -1067,8 +1064,18 @@ public function verifyJWTsignature($jwt) { $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, - $this->getKeyForHeader($jwks->keys, $header), + $jwk, $payload, $signature, $signatureType); break; case 'HS256': From 2ca058133baeda85638b15faefea4b488ec9213d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 13 Jul 2022 12:47:55 +0200 Subject: [PATCH 164/223] release 0.9.7 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e152976..e0f09105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +## [0.9.7] + ### Added * Support for Self-Contained JWTs. #308 From d6aa989f48e00f16ba54307096ebf34bc345c1cf Mon Sep 17 00:00:00 2001 From: Andrei Popa Date: Thu, 21 Jul 2022 19:44:32 +0300 Subject: [PATCH 165/223] feat: add client_secret_jwt support --- src/OpenIDConnectClient.php | 84 +++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6fd6d117..a84b8a09 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -798,6 +798,21 @@ protected function requestTokens($code, $headers = array()) { unset($token_params['client_id']); } + if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); + + if(isset($this->providerConfig['client_assertion'])){ + $client_assertion = $this->getProviderConfigValue('client_assertion'); + } + else{ + $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('token_endpoint')); + } + + $token_params['client_assertion_type'] = $client_assertion_type; + $token_params['client_assertion'] = $client_assertion; + unset($token_params['client_secret']); + } + $ccm = $this->getCodeChallengeMethod(); $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { @@ -897,6 +912,21 @@ public function refreshToken($refresh_token) { unset($token_params['client_id']); } + if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); + $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('token_endpoint')); + + $token_params["grant_type"] = "urn:ietf:params:oauth:grant-type:token-exchange"; + $token_params["subject_token"] = $refresh_token; + $token_params["audience"] = $this->clientID; + $token_params["subject_token_type"] = "urn:ietf:params:oauth:token-type:refresh_token"; + $token_params["requested_token_type"] = "urn:ietf:params:oauth:token-type:access_token"; + $token_params['client_assertion_type']=$client_assertion_type; + $token_params['client_assertion'] = $client_assertion; + + 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); @@ -1059,11 +1089,11 @@ public function verifyJWTsignature($jwt) { switch ($header->alg) { case 'RS256': case 'PS256': + case 'PS512': case 'RS384': case 'RS512': $hashtype = 'sha' . substr($header->alg, 2); - $signatureType = $header->alg === 'PS256' ? 'PSS' : ''; - + $signatureType = $header->alg === 'PS256' || $header->alg === 'PS512' ? 'PSS' : ''; if (isset($header->jwk)) { $jwk = $header->jwk; } else { @@ -1546,6 +1576,7 @@ public function register() { */ public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); + $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); $post_data = ['token' => $token]; @@ -1556,10 +1587,20 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, $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']; + if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); + $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('introspection_endpoint')); + + $post_data['client_assertion_type']=$client_assertion_type; + $post_data['client_assertion'] = $client_assertion; + $headers = ['Accept: application/json']; + } + + $post_params = http_build_query($post_data, '', '&'); + return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); } @@ -1877,6 +1918,43 @@ protected function unsetSessionKey($key) { unset($_SESSION[$key]); } + protected function getJWTClientAssertion($aud) { + $jti = hash('sha256',bin2hex(random_bytes(64))); + + $now = time(); + + $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']); + $payload = json_encode([ + 'sub' => $this->getClientID(), + 'iss' => $this->getClientID(), + 'aud' => $aud, + 'jti' => $jti, + 'exp' => $now + 3600, + 'iat' => $now, + ]); + // Encode Header to Base64Url String + $base64UrlHeader = $this->urlEncode($header); + + + // Encode Payload to Base64Url String + $base64UrlPayload = $this->urlEncode($payload); + + // Create Signature Hash + $signature = hash_hmac( + 'sha256', + $base64UrlHeader . "." . $base64UrlPayload, + $this->getClientSecret(), + true + ); + + // Encode Signature to Base64Url String + $base64UrlSignature = $this->urlEncode($signature); + + $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature; + + return $jwt; + } + public function setUrlEncoding($curEncoding) { switch ($curEncoding) { From 53057138f712f9b5aa31ee8e255bbf6cbf1afeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:26:19 +0200 Subject: [PATCH 166/223] fix: use empty array as fallback if the IdP is not exposing/supporting code_challenge_methods_supported in well-known configuration --- src/OpenIDConnectClient.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6fd6d117..2bad8b57 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -492,7 +492,7 @@ protected function addAdditionalJwk($jwk) { * @param string $param * @param string $default optional * @throws OpenIDConnectClientException - * @return string + * @return string|array * */ protected function getProviderConfigValue($param, $default = null) { @@ -682,18 +682,18 @@ private function requestAuthorization() { } // If the client supports Proof Key for Code Exchange (PKCE) - $ccm = $this->getCodeChallengeMethod(); - if (!empty($ccm) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { + $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[$this->getCodeChallengeMethod()])) { - $codeChallenge = rtrim(strtr(base64_encode(hash($this->pkceAlgs[$this->getCodeChallengeMethod()], $codeVerifier, true)), '+/', '-_'), '='); + 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' => $this->getCodeChallengeMethod() + 'code_challenge_method' => $codeChallengeMethod ]); } From 1f800145f64e1151f9d623dedc71a66f4143341f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:48:50 +0200 Subject: [PATCH 167/223] Release 0.9.8 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f09105..b2d8ccaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +## [0.9.8] + +## Fixed + +* Do not use PKCE if IdP does not support it. #317 + ## [0.9.7] ### Added From 1956de3b320037dc84b69f42c21f3c08244e77fe Mon Sep 17 00:00:00 2001 From: timvisee Date: Fri, 26 Aug 2022 16:13:34 +0200 Subject: [PATCH 168/223] Use consistent spacing --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2bad8b57..3fff8d4f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -558,7 +558,7 @@ public function setWellKnownConfigParameters(array $params = []){ /** * @param string $url Sets redirect URL for auth flow */ - public function setRedirectURL ($url) { + public function setRedirectURL($url) { if (parse_url($url,PHP_URL_HOST) !== false) { $this->redirectURL = $url; } From ffb8d383a3e9431dd8bea3583948625686009768 Mon Sep 17 00:00:00 2001 From: Andrei Popa <49556356+andreipopa-who@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:49:04 +0300 Subject: [PATCH 169/223] linting: Update src/OpenIDConnectClient.php Co-authored-by: Rick Lambrechts --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index a84b8a09..fcd03763 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -811,7 +811,7 @@ protected function requestTokens($code, $headers = array()) { $token_params['client_assertion_type'] = $client_assertion_type; $token_params['client_assertion'] = $client_assertion; unset($token_params['client_secret']); - } + } $ccm = $this->getCodeChallengeMethod(); $cv = $this->getCodeVerifier(); From e8db27450b7c999736a94cd74baa47c336b46934 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 15 Sep 2022 14:16:31 +0200 Subject: [PATCH 170/223] use correct types --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 81567375..312899fc 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1010,7 +1010,7 @@ private function verifyRSAJWTsignature($hashtype, $key, $payload, $signature, $s /** * @param string $hashtype - * @param object $key + * @param string $key * @param $payload * @param $signature * @return bool @@ -1068,7 +1068,7 @@ public function verifyJWTsignature($jwt) { if (isset($header->jwk)) { $jwk = $header->jwk; } else { - $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); + $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri')), false); if ($jwks === NULL) { throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); } From 1b4b69fea60dd28e735659c4ca453a75ec3e556a Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 15 Sep 2022 14:17:07 +0200 Subject: [PATCH 171/223] set response as jwt when not jwe --- src/OpenIDConnectClient.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 312899fc..5ea7fada 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1227,6 +1227,9 @@ public function requestUserInfo($attribute = null) { // Handle JWE $jwt = $this->jweResponseHandler->handleJweResponse($response); + } else { + // If the response is not encrypted then it must be signed + $jwt = $response; } // Verify the signature From 5ed9bd9ba2d89a16f22274813a3b757ab38c660d Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 15 Sep 2022 22:38:30 +0200 Subject: [PATCH 172/223] Added id token jwe decryption --- src/OpenIDConnectClient.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 5ea7fada..bacd1399 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -343,13 +343,25 @@ public function authenticate() { throw new OpenIDConnectClientException('User did not authorize openid scope.'); } - $claims = $this->decodeJWT($token_json->id_token, 1); + $id_token = $token_json->id_token; + $idTokenHeaders = $this->decodeJWT($id_token); + if (isset($idTokenHeaders->enc)) { + // If we don't have a JWE handler then throw error + if ($this->jweResponseHandler === null) { + throw new OpenIDConnectClientException('JWE response handler not set'); + } + + // Handle JWE + $id_token = $this->jweResponseHandler->handleJweResponse($id_token); + } + + $claims = $this->decodeJWT($id_token, 1); // Verify the signature - $this->verifySignatures($token_json->id_token); + $this->verifySignatures($id_token); // Save the id token - $this->idToken = $token_json->id_token; + $this->idToken = $id_token; // Save the access token $this->accessToken = $token_json->access_token; From 5832c8e3a8ba41a0f73cf7db329e3e78c791a8f2 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 15 Sep 2022 23:00:26 +0200 Subject: [PATCH 173/223] Added support for private_key_jwt authentication method --- src/OpenIDConnectClient.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2bad8b57..034a8696 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -215,6 +215,11 @@ class OpenIDConnectClient */ private $issuerValidator; + /** + * @var callable|null generator function for private key jwt client authentication + */ + private $privateKeyJwtGenerator; + /** * @var bool Allow OAuth 2 implicit flow; see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth */ @@ -798,6 +803,12 @@ protected function requestTokens($code, $headers = array()) { unset($token_params['client_id']); } + // When there is a private key jwt generator and it is supported then use it as client authentication + if ($this->privateKeyJwtGenerator !== null && in_array('private_key_jwt', $token_endpoint_auth_methods_supported, true)) { + $token_params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; + $token_params['client_assertion'] = $this->privateKeyJwtGenerator($token_endpoint); + } + $ccm = $this->getCodeChallengeMethod(); $cv = $this->getCodeVerifier(); if (!empty($ccm) && !empty($cv)) { @@ -1453,6 +1464,18 @@ public function setIssuerValidator($issuerValidator) { $this->issuerValidator = $issuerValidator; } + /** + * Use this for private_key_jwt client authentication + * The given function should accept the token_endpoint string as the only argument + * and return a jwt signed with your private key according to: + * https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + * + * @param callable $privateKeyJwtGenerator + */ + public function setPrivateKeyJwtGenerator($privateKeyJwtGenerator) { + $this->privateKeyJwtGenerator = $privateKeyJwtGenerator; + } + /** * @param bool $allowImplicitFlow */ @@ -1922,6 +1945,14 @@ public function getIssuerValidator() { return $this->issuerValidator; } + + /** + * @return callable + */ + public function getPrivateKeyJwtGenerator() { + return $this->privateKeyJwtGenerator; + } + /** * @return int */ From e535cbc49e0eab8e9923136ea24c9cd428a4f115 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 15 Sep 2022 23:04:47 +0200 Subject: [PATCH 174/223] use __invoke for supporting older php versions --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 034a8696..dbb2c696 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -806,7 +806,7 @@ protected function requestTokens($code, $headers = array()) { // When there is a private key jwt generator and it is supported then use it as client authentication if ($this->privateKeyJwtGenerator !== null && in_array('private_key_jwt', $token_endpoint_auth_methods_supported, true)) { $token_params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; - $token_params['client_assertion'] = $this->privateKeyJwtGenerator($token_endpoint); + $token_params['client_assertion'] = $this->privateKeyJwtGenerator->__invoke($token_endpoint); } $ccm = $this->getCodeChallengeMethod(); From bddb3bf98eac5c1952946fe98df030ec8f3fa275 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Fri, 16 Sep 2022 16:23:22 +0200 Subject: [PATCH 175/223] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d8ccaa..8af3bf6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +* Added support for `private_key_jwt` Client Authentication method #322 ## [0.9.8] From f3196f3e10e5897739a70985db005843984f6142 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Fri, 16 Sep 2022 16:40:12 +0200 Subject: [PATCH 176/223] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d8ccaa..67dde9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +* Support for signed and encrypted UserInfo response. #305 +* Support for signed and encrypted ID Token. #305 ## [0.9.8] From ed0e30a9bb96eb115aa5ded30a016e346c19c37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 26 Sep 2022 08:11:03 +0200 Subject: [PATCH 177/223] fix: harden self signed JWK header --- CHANGELOG.md | 4 ++++ src/OpenIDConnectClient.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d8ccaa..e70f1e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +## Fixed + +* Harden self-signed JWK header usage. #323 + ## [0.9.8] ## Fixed diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2bad8b57..7c358307 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1066,6 +1066,7 @@ public function verifyJWTsignature($jwt) { if (isset($header->jwk)) { $jwk = $header->jwk; + $this->verifyJWKHeader($jwk); } else { $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri'))); if ($jwks === NULL) { @@ -1942,4 +1943,12 @@ public function getCodeChallengeMethod() { public function setCodeChallengeMethod($codeChallengeMethod) { $this->codeChallengeMethod = $codeChallengeMethod; } + + /** + * @throws OpenIDConnectClientException + */ + protected function verifyJWKHeader($jwk) + { + throw new OpenIDConnectClientException('Self signed JWK header is not valid'); + } } From eeb23ddc71fcf5d57523497d845ab287eb2b7cca Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 28 Sep 2022 14:30:32 +1000 Subject: [PATCH 178/223] Merge latest and fix conflicts --- src/OpenIDConnectClient.php | 130 ++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e4a4beab..30deae6f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -246,6 +246,16 @@ class OpenIDConnectClient */ private $pkceAlgs = ['S256' => 'sha256', 'plain' => false]; + /** + * @var string if we acquire a sid in back-channel logout it will be stored here + */ + private $backChannelSid; + + /** + * @var string if we acquire a sub in back-channel logout it will be stored here + */ + private $backChannelSubject; + /** * @param $provider_url string optional * @@ -458,6 +468,112 @@ public function signOut($idToken, $redirect) { $this->redirect($signout_endpoint); } + + /** + * Decode and then verify a logout token sent as part of + * back-channel logout flows. + * + * This function should be evaluated as a boolean check + * in your route that receives the POST request for back- + * channel logout executed from the OP. + * + * @return bool + * @throws OpenIDConnectClientException + */ + public function verifyLogoutToken() + { + if (isset($_REQUEST['logout_token'])) { + $logout_token = $_REQUEST['logout_token']; + + $claims = $this->decodeJWT($logout_token, 1); + + // Verify the signature + if ($this->canVerifySignatures()) { + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); + } + if (!$this->verifyJWTsignature($logout_token)) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); + } + } + else { + user_error('Warning: JWT signature verification unavailable'); + } + + // Verify Logout Token Claims + if ($this->verifyLogoutTokenClaims($claims, $logout_token)) { + $this->logoutToken = $logout_token; + $this->verifiedClaims = $claims; + return true; + } + else { + return false; + } + } + else { + throw new OpenIDConnectClientException('Back-channel logout: There was no logout_token in the request'); + } + } + + /** + * Verify each claim in the logout token according to the + * spec for back-channel authentication. + * + * @param object $claims + * @return bool + */ + public function verifyLogoutTokenClaims($claims) + { + // Verify that the Logout Token doesn't contain a nonce Claim. + if (isset($claims->nonce)) { + return false; + } + + // Verify that the logout token contains a sub or sid, or both + if (!isset($claims->sid) && !isset($claims->sub)) { + return false; + } + // Set the sid, which could be used to map to a session in + // the RP, and therefore be used to help destroy the RP's + // session. + if (isset($claims->sid)) { + $this->backChannelSid = $claims->sid; + } + + // Set the sub, which could be used to map to a session in + // the RP, and therefore be used to help destroy the RP's + // session. + if (isset($claims->sub)) { + $this->backChannelSubject = $claims->sub; + } + + // Verify that the Logout Token contains an events Claim whose + // value is a JSON object containing the member name + // http://schemas.openid.net/event/backchannel-logout + if (isset($claims->events)) { + $events = (array) $claims->events; + if (!isset($events['http://schemas.openid.net/event/backchannel-logout']) || + !is_object($events['http://schemas.openid.net/event/backchannel-logout'])) { + return false; + } + } + + // Validate the iss + if (!$this->validateIssuer($claims->iss)) { + return false; + } + // Validate the aud + if ((!$claims->aud === $this->clientID) || (!in_array($this->clientID, $claims->aud, true))) { + return false; + } + // Validate the iat. At this point we can return true if it is ok + if (isset($claims->iat) && ((gettype($claims->iat) === 'integer') && ($claims->iat <= time() + $this->leeway))) { + return true; + } else { + return false; + } + } + /** * @param array $scope - example: openid, given_name, etc... */ @@ -1951,4 +2067,18 @@ protected function verifyJWKHeader($jwk) { throw new OpenIDConnectClientException('Self signed JWK header is not valid'); } + + /* + * @return string + */ + public function getSidFromBackChannel() { + return $this->backChannelSid; + } + + /** + * @return string + */ + public function getSubjectFromBackChannel() { + return $this->backChannelSubject; + } } From c7c6aca5ec9cfe23205ca3b280a069cb8a4c1a1e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 28 Sep 2022 14:49:41 +1000 Subject: [PATCH 179/223] Add changelog entry and documentation in the README for back-channel logout --- CHANGELOG.md | 1 + README.md | 47 +++++++++++++++++++++++++++++++++++++ src/OpenIDConnectClient.php | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e70f1e6c..2a6617df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Fixed * Harden self-signed JWK header usage. #323 +* Added support for back-channel logout. #302 ## [0.9.8] diff --git a/README.md b/README.md index 6689f46d..d4ae347d 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,53 @@ $name = $oidc->requestUserInfo('given_name'); ``` +## Example 9: Back-channel logout ## + +Back-channel authentication assumes you can end a session on the server side on behalf of the user (without relying +on their browser). The request is a POST from the OP direct to your RP. In this way, the use of this library can +ensure your RP performs 'single sign out' for the user even if they didn't have your RP open in a browser or other +device, but still had an active session there. + +Either the sid or the sub may be accessible from the logout token sent from the OP. You can use either +`getSidFromBackChannel()` or `getSubFromBackChannel()` to retrieve them if it is helpful to match them to a session +in order to destroy it. + +The below ensures the use of this library to ensure validation of the back-channel logout token, but is afterward +just a hypothetical way of finding such a session and destroying it. Adjust it to the needs of your RP. + +```php + +function handleLogout() { + // NOTE: assumes that $this->oidc is an instance of OpenIDConnectClient() + if ($this->oidc->verifyLogoutToken()) { + $sid = $this->oidc->getSidFromBackChannel(); + + if (isset($sid)) { + // Somehow find the session based on the $sid and + // destroy it. This depends on your RP's design, + // there is nothing in the OIDC spec to mandate how. + // + // In this example, we find a Redis key, which was + // previously stored using the sid we obtained from + // the access token after login. + // + // The value of the Redis key is that of the user's + // session ID specific to this hypothetical RP app. + // + // We then switch to that session and destroy it. + $this->redis->connect('127.0.0.1', 6379); + $session_id_to_destroy = $this->redis->get($sid); + if ($session_id_to_destroy) { + session_commit(); + session_id($session_id_to_destroy); // switches to that session + session_start(); + $_SESSION = array(); // effectively ends the session + } + } + } +} + +``` ## Development Environments ## In some cases you may need to disable SSL security on your development systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 30deae6f..a180c0c8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -517,7 +517,7 @@ public function verifyLogoutToken() /** * Verify each claim in the logout token according to the - * spec for back-channel authentication. + * spec for back-channel logout. * * @param object $claims * @return bool From 1161b7771bd64843c6fef423f8f1e49bfaec29c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:52:19 +0200 Subject: [PATCH 180/223] fix: $this->enc_type -> $this->encType --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 5dfcc4a2..7ab04939 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -647,7 +647,7 @@ protected function generateRandString() { throw new OpenIDConnectClientException('Random token generation failed.'); } catch (Exception $e) { throw new OpenIDConnectClientException('Random token generation failed.'); - }; + } } /** @@ -872,7 +872,7 @@ public function requestTokenExchange($subjectToken, $subjectTokenType, $audience } // Convert token params to string format - $post_params = http_build_query($post_data, null, '&', $this->enc_type); + $post_params = http_build_query($post_data, null, '&', $this->encType); return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); } From db9a25c3cf1e751190523b6b2dc0b9c4c0ed747f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:03:49 +0200 Subject: [PATCH 181/223] chore: code cleanup of back-channel PR #302 --- src/OpenIDConnectClient.php | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 2485184c..e96316df 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -378,7 +378,6 @@ public function authenticate() { // Success! return true; - } throw new OpenIDConnectClientException ('Unable to verify JWT claims'); @@ -479,8 +478,8 @@ public function signOut($idToken, $redirect) { * back-channel logout flows. * * This function should be evaluated as a boolean check - * in your route that receives the POST request for back- - * channel logout executed from the OP. + * in your route that receives the POST request for back-channel + * logout executed from the OP. * * @return bool * @throws OpenIDConnectClientException @@ -506,18 +505,15 @@ public function verifyLogoutToken() } // Verify Logout Token Claims - if ($this->verifyLogoutTokenClaims($claims, $logout_token)) { - $this->logoutToken = $logout_token; + if ($this->verifyLogoutTokenClaims($claims)) { $this->verifiedClaims = $claims; return true; } - else { - return false; - } - } - else { - throw new OpenIDConnectClientException('Back-channel logout: There was no logout_token in the request'); + + return false; } + + throw new OpenIDConnectClientException('Back-channel logout: There was no logout_token in the request'); } /** @@ -526,6 +522,7 @@ public function verifyLogoutToken() * * @param object $claims * @return bool + * @throws OpenIDConnectClientException */ public function verifyLogoutTokenClaims($claims) { @@ -572,11 +569,11 @@ public function verifyLogoutTokenClaims($claims) return false; } // Validate the iat. At this point we can return true if it is ok - if (isset($claims->iat) && ((gettype($claims->iat) === 'integer') && ($claims->iat <= time() + $this->leeway))) { + if (isset($claims->iat) && ((is_int($claims->iat)) && ($claims->iat <= time() + $this->leeway))) { return true; - } else { - return false; } + + return false; } /** @@ -770,6 +767,7 @@ protected function generateRandString() { * Start Here * @return void * @throws OpenIDConnectClientException + * @throws \Exception */ private function requestAuthorization() { From f69b40f755f6a38ce4c1be2e347aa3faa7c82d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:33:32 +0200 Subject: [PATCH 182/223] Release 0.9.9 --- CHANGELOG.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8af6a2..54aac495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [unreleased] +## [0.9.9] + +### Added + +* Added support for back-channel logout. #302 * Added support for `private_key_jwt` Client Authentication method #322 ## Fixed * Harden self-signed JWK header usage. #323 -* Added support for back-channel logout. #302 ## [0.9.8] @@ -64,13 +67,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 * bugfix in getSessionKey when _SESSION key does not exist #251 * Added scope parameter to refresh token request #225 -* bugfix in verifyJWTclaims when $accessToken is empty and $claims->at_hash is not #276 +* bugfix in `verifyJWTclaims` when $accessToken is empty and $claims->at_hash is not #276 * bugfix with the `empty` function in PHP 5.4 #267 ## [0.9.2] ### Added -* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently the supported methods are 'plain' and 'S256'. +* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently, the supported methods are 'plain' and 'S256'. ## [0.9.1] @@ -133,7 +136,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * Add option to send additional registration parameters like post_logout_redirect_uris. #140 ### Changed -* disabled autoload for Crypt_RSA + makre refreshToken() method tolerant for errors #137 +* disabled autoload for Crypt_RSA + make refreshToken() method tolerant for errors #137 ### Removed * @@ -143,7 +146,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added * Added five minutes leeway due to clock skew between openidconnect server and client. * Fix save access_token from request in implicit flow authentication #129 -* verifyJWTsignature() method private -> public #126 +* `verifyJWTsignature()` method private -> public #126 * Support for providers where provider/login URL is not the same as the issuer URL. #125 * Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). From 89bdf7c067c4db67964a9b16eacc7c42d37ea4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:04:48 +0200 Subject: [PATCH 183/223] fix: client_secret_jwt and private_key_jwt support is disabled by default --- README.md | 29 +++++++++++++++++--- src/OpenIDConnectClient.php | 44 +++++++++++++++++++++++++------ tests/OpenIDConnectClientTest.php | 26 ++++++++++++++++++ 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d4ae347d..a44b8cb0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ PHP OpenID Connect Basic Client ======================== A simple library that allows an application to authenticate a user through the basic OpenID Connect flow. This library hopes to encourage OpenID Connect use by making it simple enough for a developer with little knowledge of -the OpenID Connect protocol to setup authentication. +the OpenID Connect protocol to set up authentication. A special thanks goes to Justin Richer and Amanda Anganes for their help and support of the protocol. @@ -12,11 +12,12 @@ A special thanks goes to Justin Richer and Amanda Anganes for their help and sup 3. JSON extension ## Install ## - 1. Install library using composer +1. Install library using composer ``` composer require jumbojett/openid-connect-php ``` - 2. Include composer autoloader + +2. Include composer autoloader ```php require __DIR__ . '/vendor/autoload.php'; ``` @@ -191,6 +192,28 @@ function handleLogout() { ``` +## Example 10: Enable Token Endpoint Auth Methods ## + +By default, only `client_secret_basic` is enabled on client side which was the only supported for a long time. +Recently `client_secret_jwt` and `private_key_jwt` have been added, but they remain disabled until explicitly enabled. + +```php +use Jumbojett\OpenIDConnectClient; + +$oidc = new OpenIDConnectClient('https://id.provider.com', + 'ClientIDHere', + null); +# enable 'client_secret_basic' and 'client_secret_jwt' +$oidc->setTokenEndpointAuthMethodsSupported(['client_secret_basic', 'client_secret_jwt']); + +# for 'private_key_jwt' in addition also the generator function has to be set. +$oidc->setTokenEndpointAuthMethodsSupported(['private_key_jwt']); +$oidc->setPrivateKeyJwtGenerator(function(string $token_endpoint) { + # TODO: what ever is necessary +}) +``` + + ## Development Environments ## In some cases you may need to disable SSL security on your development systems. Note: This is not recommended on production systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e0c279be..bac366cf 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -261,6 +261,11 @@ class OpenIDConnectClient */ private $backChannelSubject; + /** + * @var array list of supported auth methods + */ + private $token_endpoint_auth_methods_supported = ['client_secret_basic']; + /** * @param $provider_url string optional * @@ -597,6 +602,14 @@ public function addRegistrationParam($param) { $this->registrationParams = array_merge($this->registrationParams, (array)$param); } + /** + * @param array $token_endpoint_auth_methods_supported + */ + public function setTokenEndpointAuthMethodsSupported($token_endpoint_auth_methods_supported) + { + $this->token_endpoint_auth_methods_supported = $token_endpoint_auth_methods_supported; + } + /** * @param $jwk object - example: (object) ['kid' => ..., 'nbf' => ..., 'use' => 'sig', 'kty' => "RSA", 'e' => "", 'n' => ""] */ @@ -872,7 +885,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { //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)) { + if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; } else { $post_data['client_id'] = $this->clientID; @@ -911,19 +924,19 @@ protected function requestTokens($code, $headers = array()) { $authorizationHeader = null; # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $authorizationHeader = 'Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret)); unset($token_params['client_secret']); unset($token_params['client_id']); } // When there is a private key jwt generator and it is supported then use it as client authentication - if ($this->privateKeyJwtGenerator !== null && in_array('private_key_jwt', $token_endpoint_auth_methods_supported, true)) { + if ($this->privateKeyJwtGenerator !== null && $this->supportsAuthMethod('private_key_jwt', $token_endpoint_auth_methods_supported)) { $token_params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; $token_params['client_assertion'] = $this->privateKeyJwtGenerator->__invoke($token_endpoint); } - if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); if(isset($this->providerConfig['client_assertion'])){ @@ -994,7 +1007,7 @@ public function requestTokenExchange($subjectToken, $subjectTokenType, $audience } # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; unset($post_data['client_secret']); unset($post_data['client_id']); @@ -1031,13 +1044,13 @@ public function refreshToken($refresh_token) { ]; # Consider Basic authentication if provider config is set this way - if (in_array('client_secret_basic', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; unset($token_params['client_secret']); unset($token_params['client_id']); } - if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('token_endpoint')); @@ -1728,7 +1741,7 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), 'Accept: application/json']; - if (in_array('client_secret_jwt', $token_endpoint_auth_methods_supported, true)) { + if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { $client_assertion_type = $this->getProviderConfigValue('client_assertion_type'); $client_assertion = $this->getJWTClientAssertion($this->getProviderConfigValue('introspection_endpoint')); @@ -2188,4 +2201,19 @@ public function getSidFromBackChannel() { public function getSubjectFromBackChannel() { return $this->backChannelSubject; } + + /** + * @param string $auth_method + * @param array $token_endpoint_auth_methods_supported + * @return bool + */ + public function supportsAuthMethod($auth_method, $token_endpoint_auth_methods_supported) + { + # client_secret_jwt has to explicitly be enabled + if (!in_array($auth_method, $this->token_endpoint_auth_methods_supported, true)) { + return false; + } + + return in_array($auth_method, $token_endpoint_auth_methods_supported, true); + } } diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 7abf3116..dd3321bb 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -62,4 +62,30 @@ public function testSerialize() $serialized = serialize($client); $this->assertInstanceOf('Jumbojett\OpenIDConnectClient', unserialize($serialized)); } + + /** + * @dataProvider provider + */ + public function testAuthMethodSupport($expected, $authMethod, $clientAuthMethods, $idpAuthMethods) + { + $client = new OpenIDConnectClient(); + if ($clientAuthMethods !== null) { + $client->setTokenEndpointAuthMethodsSupported($clientAuthMethods); + } + $this->assertEquals($expected, $client->supportsAuthMethod($authMethod, $idpAuthMethods)); + } + + public function provider() + { + return [ + 'client_secret_basic - default config' => [true, 'client_secret_basic', null, ['client_secret_basic']], + + 'client_secret_jwt - default config' => [false, 'client_secret_jwt', null, ['client_secret_basic', 'client_secret_jwt']], + 'client_secret_jwt - explicitly enabled' => [true, 'client_secret_jwt', ['client_secret_jwt'], ['client_secret_basic', 'client_secret_jwt']], + + 'private_key_jwt - default config' => [false, 'private_key_jwt', null, ['client_secret_basic', 'client_secret_jwt', 'private_key_jwt']], + 'private_key_jwt - explicitly enabled' => [true, 'private_key_jwt', ['private_key_jwt'], ['client_secret_basic', 'client_secret_jwt', 'private_key_jwt']], + + ]; + } } From 45aac47b525f0483dd4db3324bb1f1cab4666061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 30 Sep 2022 14:34:46 +0200 Subject: [PATCH 184/223] Release v0.9.10 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54aac495..eacd7d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.9.10] + +## Fixed + +* `private_key_jwt` and `client_secret_jwt` need to explicitly be enabled #331 + + ## [0.9.9] ### Added * Added support for back-channel logout. #302 * Added support for `private_key_jwt` Client Authentication method #322 +* Added support for `client_secret_jwt` Client Authentication method #324 +* Added PS512 encryption support #342 ## Fixed From 4338e8535e64cc2551af7df23cb696b87ec117d4 Mon Sep 17 00:00:00 2001 From: rvogel Date: Fri, 30 Sep 2022 15:49:26 +0200 Subject: [PATCH 185/223] Fix LogoutToken verification for single value `aud` claims ... and add UnitTests for `verifyLogoutTokenClaims`. See https://github.com/jumbojett/OpenID-Connect-PHP/issues/333 --- src/OpenIDConnectClient.php | 4 +- tests/OpenIDConnectClientTest.php | 147 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index bac366cf..0d720c86 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -570,7 +570,9 @@ public function verifyLogoutTokenClaims($claims) return false; } // Validate the aud - if ((!$claims->aud === $this->clientID) || (!in_array($this->clientID, $claims->aud, true))) { + $auds = $claims->aud; + $auds = is_array( $auds ) ? $auds : [ $auds ]; + if (!in_array($this->clientID, $auds, true)) { return false; } // Validate the iat. At this point we can return true if it is ok diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index dd3321bb..fd022116 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -88,4 +88,151 @@ public function provider() ]; } + + /** + * @covers Jumbojett\\OpenIDConnectClient::verifyLogoutTokenClaims + * @dataProvider provideTestVerifyLogoutTokenClaimsData + */ + public function testVerifyLogoutTokenClaims( $claims, $expectedResult ) + { + /** @var OpenIDConnectClient | MockObject $client */ + $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTsignature'])->getMock(); + + $client->setClientID('fake-client-id'); + $client->setIssuer('fake-issuer'); + $client->setIssuerValidator(function() { + return true; + }); + $client->setProviderURL('https://jwt.io/'); + + $actualResult = $client->verifyLogoutTokenClaims( $claims ); + + $this->assertEquals( $expectedResult, $actualResult ); + } + + /** + * @return array + */ + public function provideTestVerifyLogoutTokenClaimsData() { + return [ + 'valid-single-aud' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => 'fake-client-id', + 'sid' => 'fake-client-sid', + 'sub' => 'fake-client-sub', + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + ], + true + ], + 'valid-multiple-auds' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'sub' => 'fake-client-sub', + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + ], + true + ], + 'invalid-no-sid-and-no-sub' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + ], + false + ], + 'valid-no-sid' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sub' => 'fake-client-sub', + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + ], + true + ], + 'valid-no-sub' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + ], + true + ], + 'invalid-with-nonce' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'iat' => time(), + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ], + 'nonce' => 'must-not-be-set' + ], + false + ], + 'invalid-no-events' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'iat' => time(), + 'nonce' => 'must-not-be-set' + ], + false + ], + 'invalid-no-backchannel-event' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'iat' => time(), + 'events' => (object) [], + 'nonce' => 'must-not-be-set' + ], + false + ], + 'invalid-no-iat' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ] + ], + false + ], + 'invalid-bad-iat' => [ + (object)[ + 'iss' => 'fake-issuer', + 'aud' => [ 'fake-client-id', 'some-other-aud' ], + 'sid' => 'fake-client-sid', + 'iat' => time() + 301, + 'events' => (object) [ + 'http://schemas.openid.net/event/backchannel-logout' => (object)[] + ] + ], + false + ], + ]; + } } From 7cb759016ea9217154b2dab901125419d1a3d509 Mon Sep 17 00:00:00 2001 From: rvogel Date: Fri, 30 Sep 2022 15:53:11 +0200 Subject: [PATCH 186/223] Add Changelog entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eacd7d3c..86de431d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.9.11] + +## Fixed + +* `verifyLogoutToken` fails with single-value `aud` claim #333 + ## [0.9.10] ## Fixed From 05964b3ab3b26677d8465ca5da9091181d1a94f8 Mon Sep 17 00:00:00 2001 From: rvogel Date: Fri, 30 Sep 2022 15:57:29 +0200 Subject: [PATCH 187/223] Remove unnecessary method overrides in UnitTest --- tests/OpenIDConnectClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index fd022116..d82234ed 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -96,7 +96,7 @@ public function provider() public function testVerifyLogoutTokenClaims( $claims, $expectedResult ) { /** @var OpenIDConnectClient | MockObject $client */ - $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTsignature'])->getMock(); + $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT'])->getMock(); $client->setClientID('fake-client-id'); $client->setIssuer('fake-issuer'); From e3c3f9a647647d97feabc6098f1fe6e1e7687e0a Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 6 Oct 2022 16:40:42 +0200 Subject: [PATCH 188/223] Removed interface and added function that can be extended to add the jwe functionality --- src/OpenIDConnectClient.php | 38 ++++++------------- src/interfaces/HandleJweResponseInterface.php | 13 ------- 2 files changed, 12 insertions(+), 39 deletions(-) delete mode 100644 src/interfaces/HandleJweResponseInterface.php diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c325ed77..28670e18 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -22,8 +22,6 @@ namespace Jumbojett; -use Jumbojett\Interfaces\HandleJweResponseInterface; - /** * * JWT signature verification support by Jonathan Reed @@ -273,11 +271,6 @@ class OpenIDConnectClient */ private $token_endpoint_auth_methods_supported = ['client_secret_basic']; - /** - * @var HandleJweResponseInterface|null - */ - private $jweResponseHandler; - /** * @param $provider_url string optional * @@ -318,13 +311,6 @@ public function setResponseTypes($response_types) { $this->responseTypes = array_merge($this->responseTypes, (array)$response_types); } - /** - * @param HandleJweResponseInterface $jwe_response_handler - */ - public function setJweResponseHandler($jwe_response_handler) { - $this->jweResponseHandler = $jwe_response_handler; - } - /** * @return bool * @throws OpenIDConnectClientException @@ -366,13 +352,8 @@ public function authenticate() { $id_token = $token_json->id_token; $idTokenHeaders = $this->decodeJWT($id_token); if (isset($idTokenHeaders->enc)) { - // If we don't have a JWE handler then throw error - if ($this->jweResponseHandler === null) { - throw new OpenIDConnectClientException('JWE response handler not set'); - } - // Handle JWE - $id_token = $this->jweResponseHandler->handleJweResponse($id_token); + $id_token = $this->handleJweResponse($id_token); } $claims = $this->decodeJWT($id_token, 1); @@ -1401,13 +1382,8 @@ public function requestUserInfo($attribute = null) { // Check if the response is encrypted $jwtHeaders = $this->decodeJWT($response); if (isset($jwtHeaders->enc)) { - // If we don't have a JWE handler then throw error - if ($this->jweResponseHandler === null) { - throw new OpenIDConnectClientException('JWE response handler not set'); - } - // Handle JWE - $jwt = $this->jweResponseHandler->handleJweResponse($response); + $jwt = $this->handleJweResponse($response); } else { // If the response is not encrypted then it must be signed $jwt = $response; @@ -2265,6 +2241,16 @@ protected function verifyJWKHeader($jwk) throw new OpenIDConnectClientException('Self signed JWK header is not valid'); } + /** + * @param string $jwe The JWE to decrypt + * @return string the JWT payload + * @throws OpenIDConnectClientException + */ + protected function handleJweResponse($jwe) + { + throw new OpenIDConnectClientException('JWE response is not supported, please extend the class and implement this method'); + } + /* * @return string */ diff --git a/src/interfaces/HandleJweResponseInterface.php b/src/interfaces/HandleJweResponseInterface.php deleted file mode 100644 index 3eef5b2c..00000000 --- a/src/interfaces/HandleJweResponseInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Tue, 22 Nov 2022 22:38:35 +0100 Subject: [PATCH 189/223] docs: fix getSubjectFromBackChannel in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a44b8cb0..79318e50 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ ensure your RP performs 'single sign out' for the user even if they didn't have device, but still had an active session there. Either the sid or the sub may be accessible from the logout token sent from the OP. You can use either -`getSidFromBackChannel()` or `getSubFromBackChannel()` to retrieve them if it is helpful to match them to a session +`getSidFromBackChannel()` or `getSubjectFromBackChannel()` to retrieve them if it is helpful to match them to a session in order to destroy it. The below ensures the use of this library to ensure validation of the back-channel logout token, but is afterward From 7a7dbec1b9cc2fd957de02641e2d171049608cad Mon Sep 17 00:00:00 2001 From: Akhil Date: Mon, 12 Dec 2022 17:33:42 +0530 Subject: [PATCH 190/223] Fix return type --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index bac366cf..24e15399 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1882,7 +1882,7 @@ public function getIdTokenPayload() { } /** - * @return string + * @return object */ public function getTokenResponse() { return $this->tokenResponse; From a4776d1749b6bf319ae2da02f914b4675f67a9fd Mon Sep 17 00:00:00 2001 From: Anthimidis Nikos Date: Tue, 3 Jan 2023 16:40:14 +0200 Subject: [PATCH 191/223] Add an extra check on $_REQUEST['state'] --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 28670e18..5046ceb1 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -338,7 +338,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if ($_REQUEST['state'] !== $this->getState()) { + if (isset($_REQUEST['state']) && ($_REQUEST['state'] !== $this->getState())) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -401,7 +401,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if ($_REQUEST['state'] !== $this->getState()) { + if (isset($_REQUEST['state']) && ($_REQUEST['state'] !== $this->getState())) { throw new OpenIDConnectClientException('Unable to determine state'); } From ed5ccd933f646797bd4d50073ecdeafbb7553159 Mon Sep 17 00:00:00 2001 From: Anthimidis Nikos Date: Tue, 10 Jan 2023 10:32:03 +0200 Subject: [PATCH 192/223] Fix if statement to throw error instead of bypass Fix code to throw error instead of by passing the if statement, after @LauJosefen notice. --- src/OpenIDConnectClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 5046ceb1..a7bacf94 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -338,7 +338,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if (isset($_REQUEST['state']) && ($_REQUEST['state'] !== $this->getState())) { + if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -401,7 +401,7 @@ public function authenticate() { } // Do an OpenID Connect session check - if (isset($_REQUEST['state']) && ($_REQUEST['state'] !== $this->getState())) { + if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { throw new OpenIDConnectClientException('Unable to determine state'); } From 8a80c1a0b8071216f26f058803ea7c9d6bdeac5c Mon Sep 17 00:00:00 2001 From: Akhil Date: Thu, 12 Jan 2023 23:55:03 +0530 Subject: [PATCH 193/223] Correct variable docstring --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 24e15399..1df58878 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -145,7 +145,7 @@ class OpenIDConnectClient protected $idToken; /** - * @var string stores the token response + * @var object stores the token response */ private $tokenResponse; From 13e86af95132aaf94278e96f628928cd21c57f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ou=C5=A1ek?= Date: Fri, 13 Jan 2023 07:44:36 +0100 Subject: [PATCH 194/223] docs: fix changelog format correct Keep a changelog style --- CHANGELOG.md | 226 ++++++++++++++++++++++++--------------------------- 1 file changed, 104 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2908a89c..12952f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,189 +1,171 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). +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). -## [unreleased] -* Support for signed and encrypted UserInfo response. #305 -* Support for signed and encrypted ID Token. #305 +## [Unreleased] -## [0.9.10] - -## Fixed +### Added +- Support for signed and encrypted UserInfo response. #305 +- Support for signed and encrypted ID Token. #305 -* `private_key_jwt` and `client_secret_jwt` need to explicitly be enabled #331 +## [0.9.10] - 2022-09-30 +### Fixed +- `private_key_jwt` and `client_secret_jwt` need to explicitly be enabled #331 -## [0.9.9] +## [0.9.9] - 2022-09-28 ### Added +- Added support for back-channel logout. #302 +- Added support for `private_key_jwt` Client Authentication method #322 +- Added support for `client_secret_jwt` Client Authentication method #324 +- Added PS512 encryption support #342 -* Added support for back-channel logout. #302 -* Added support for `private_key_jwt` Client Authentication method #322 -* Added support for `client_secret_jwt` Client Authentication method #324 -* Added PS512 encryption support #342 - -## Fixed - -* Harden self-signed JWK header usage. #323 - -## [0.9.8] +### Fixed +- Harden self-signed JWK header usage. #323 -## Fixed +## [0.9.8] - 2022-08-05 -* Do not use PKCE if IdP does not support it. #317 +### Fixed +- Do not use PKCE if IdP does not support it. #317 -## [0.9.7] +## [0.9.7] - 2022-07-13 ### Added - -* Support for Self-Contained JWTs. #308 -* Support for RFC8693 Token Exchange Request. #275 +- Support for Self-Contained JWTs. #308 +- Support for RFC8693 Token Exchange Request. #275 ### Fixed +- PHP 5.4 compatibility. #304 +- Use session_status(). #306 -* PHP 5.4 compatibility. #304 -* Use session_status(). #306 - -## [0.9.6] +## [0.9.6] - 2022-05-08 ### Added - -* Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. #260 -* Support client_secret on token endpoint with PKCE. #293 -* Added new parameter to `requestTokens()` to pass custom HTTP headers #297 +- Support for [phpseclib/phpseclib](https://phpseclib.com/) version **3**. #260 +- Support client_secret on token endpoint with PKCE. #293 +- Added new parameter to `requestTokens()` to pass custom HTTP headers #297 ### Changed +- Allow serializing `OpenIDConnectClient` using `serialize()` #295 -* Allow serializing `OpenIDConnectClient` using `serialize()` #295 - -## [0.9.5] +## [0.9.5] - 2021-11-24 ### Changed +- signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. #127 +- Fixed issue where missing nonce within the claims was causing an exception. #280 -* signOut() Method parameter $accessToken -> $idToken to prevent confusion about access and id tokens usage. #127 -* Fixed issue where missing nonce within the claims was causing an exception. #280 - -## [0.9.4] +## [0.9.4] - 2021-11-21 ### Added +- Enabled `client_secret_basic` authentication on `refreshToken()` #215 +- Basic auth support for requestResourceOwnerToken #271 -* Enabled `client_secret_basic` authentication on `refreshToken()` #215 -* Basic auth support for requestResourceOwnerToken #271 - -## [0.9.3] +## [0.9.3] - 2021-11-20 ### Added +- getRedirectURL() will not log a warning for PHP 7.1+ #179 +- it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 +- bugfix in getSessionKey when _SESSION key does not exist #251 +- Added scope parameter to refresh token request #225 +- bugfix in `verifyJWTclaims` when $accessToken is empty and $claims->at_hash is not #276 +- bugfix with the `empty` function in PHP 5.4 #267 -* getRedirectURL() will not log a warning for PHP 7.1+ #179 -* it is now possible to disable upgrading from HTTP to HTTPS for development purposes by calling `setHttpUpgradeInsecureRequests(false)` #241 -* bugfix in getSessionKey when _SESSION key does not exist #251 -* Added scope parameter to refresh token request #225 -* bugfix in `verifyJWTclaims` when $accessToken is empty and $claims->at_hash is not #276 -* bugfix with the `empty` function in PHP 5.4 #267 - -## [0.9.2] +## [0.9.2] - 2020-11-16 ### Added -* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently, the supported methods are 'plain' and 'S256'. +- Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currently, the supported methods are 'plain' and 'S256'. -## [0.9.1] +## [0.9.1] - 2020-08-27 ### Added -* Add support for MS Azure Active Directory B2C user flows +- Add support for MS Azure Active Directory B2C user flows ### Changed -* Fix at_hash verification #200 -* Getters for public parameters #204 -* Removed client ID query parameter when making a token request using Basic Auth -* Use of `random_bytes()` for token generation instead of `uniqid()`; polyfill for PHP < 7.0 provided. +- Fix at_hash verification #200 +- Getters for public parameters #204 +- Removed client ID query parameter when making a token request using Basic Auth +- Use of `random_bytes()` for token generation instead of `uniqid()`; polyfill for PHP < 7.0 provided. ### Removed -* Removed explicit content-length header - caused issues with proxy servers - +- Removed explicit content-length header - caused issues with proxy servers -## [0.9.0] +## [0.9.0] - 2020-03-09 ### Added -* php 7.4 deprecates array_key_exists on objects, use property_exists in getVerifiedClaims and requestUserInfo -* Adding a header to indicate JSON as the return type for userinfo endpoint #151 -* ~Updated OpenIDConnectClient to conditionally verify nonce #146~ -* Add possibility to change enc_type parameter for http_build_query #155 -* Adding OAuth 2.0 Token Introspection #156 -* Add optional parameters clientId/clientSecret for introspection #157 & #158 -* Adding OAuth 2.0 Token Revocation #160 -* Adding issuer validator #145 -* Adding signing algorithm PS256 #180 -* Check http status of request user info #186 -* URL encode clientId and clientSecret when using basic authentication, according to https://tools.ietf.org/html/rfc6749#section-2.3.1 #192 -* Adjust PHPDoc to state that null is also allowed #193 +- php 7.4 deprecates array_key_exists on objects, use property_exists in getVerifiedClaims and requestUserInfo +- Adding a header to indicate JSON as the return type for userinfo endpoint #151 +- ~Updated OpenIDConnectClient to conditionally verify nonce #146~ +- Add possibility to change enc_type parameter for http_build_query #155 +- Adding OAuth 2.0 Token Introspection #156 +- Add optional parameters clientId/clientSecret for introspection #157 & #158 +- Adding OAuth 2.0 Token Revocation #160 +- Adding issuer validator #145 +- Adding signing algorithm PS256 #180 +- Check http status of request user info #186 +- URL encode clientId and clientSecret when using basic authentication, according to https://tools.ietf.org/html/rfc6749#section-2.3.1 #192 +- Adjust PHPDoc to state that null is also allowed #193 ### Changed -* Bugfix/code cleanup #152 - * Cleanup PHPDoc #46e5b59 - * Replace unnecessary double quotes with single quotes #2a76b57 - * Use original function names instead of aliases #1f37892 - * Remove unnecessary default values #5ab801e - * Explicit declare field $redirectURL #9187c0b - * Remove unused code #1e65384 - * Fix indent #e9cdf56 - * Cleanup conditional code flow for better readability #107f3fb - * Added strict type comparisons #167 -* Bugfix: required `openid` scope was omitted when additional scopes were registered using `addScope` method. This resulted in failing OpenID process. - -## [0.8.0] +- Bugfix/code cleanup #152 +- Cleanup PHPDoc #46e5b59 +- Replace unnecessary double quotes with single quotes #2a76b57 +- Use original function names instead of aliases #1f37892 +- Remove unnecessary default values #5ab801e +- Explicit declare field $redirectURL #9187c0b +- Remove unused code #1e65384 +- Fix indent #e9cdf56 +- Cleanup conditional code flow for better readability #107f3fb +- Added strict type comparisons #167 +- Bugfix: required `openid` scope was omitted when additional scopes were registered using `addScope` method. This resulted in failing OpenID process. + +## [0.8.0] - 2019-01-02 ### Added -* Fix `verifyJWTsignature()`: verify JWT to prevent php errors and warnings on invalid token +- Fix `verifyJWTsignature()`: verify JWT to prevent php errors and warnings on invalid token ### Changed -* Decouple session manipulation, it's allow use of other session libraries #134 -* Broaden version requirements of the phpseclib/phpseclib package. #144 +- Decouple session manipulation, it's allow use of other session libraries #134 +- Broaden version requirements of the phpseclib/phpseclib package. #144 -## [0.7.0] +## [0.7.0] - 2018-10-15 ### Added -* Add "license" field to composer.json #138 -* Ensure key_alg is set when getting key #139 -* Add option to send additional registration parameters like post_logout_redirect_uris. #140 +- Add "license" field to composer.json #138 +- Ensure key_alg is set when getting key #139 +- Add option to send additional registration parameters like post_logout_redirect_uris. #140 ### Changed -* disabled autoload for Crypt_RSA + make refreshToken() method tolerant for errors #137 +- disabled autoload for Crypt_RSA + make refreshToken() method tolerant for errors #137 -### Removed -* - -## [0.6.0] +## [0.6.0] - 2018-07-17 ### Added -* Added five minutes leeway due to clock skew between openidconnect server and client. -* Fix save access_token from request in implicit flow authentication #129 -* `verifyJWTsignature()` method private -> public #126 -* Support for providers where provider/login URL is not the same as the issuer URL. #125 -* Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). +- Added five minutes leeway due to clock skew between openidconnect server and client. +- Fix save access_token from request in implicit flow authentication #129 +- `verifyJWTsignature()` method private -> public #126 +- Support for providers where provider/login URL is not the same as the issuer URL. #125 +- Support for providers that has a different login URL from the issuer URL, for instance Azure Active Directory. Here, the provider URL is on the format: https://login.windows.net/(tenant-id), while the issuer claim actually is on the format: https://sts.windows.net/(tenant-id). ### Changed -* refreshToken method update #124 - -### Removed -* - -## [0.5.0] -## Added -* Implement Azure AD B2C Implicit Workflow +- refreshToken method update #124 -## [0.4.1] -## Changed -* Documentation updates for include path. +## [0.5.0] - 2018-04-09 -## [0.4] ### Added -* Timeout is configurable via setTimeout method. This addresses issue #94. -* Add the ability to authenticate using the Resource Owner flow (with or without the Client ID and ClientSecret). This addresses issue #98 -* Add support for HS256, HS512 and HS384 signatures -* Removed unused calls to $this->getProviderConfigValue("token_endpoint_… +- Implement Azure AD B2C Implicit Workflow + +## [0.4.1] - 2018-02-16 ### Changed +- Documentation updates for include path. -### Removed +## [0.4.0] - 2018-02-15 + +### Added +- Timeout is configurable via setTimeout method. This addresses issue #94. +- Add the ability to authenticate using the Resource Owner flow (with or without the Client ID and ClientSecret). This addresses issue #98 +- Add support for HS256, HS512 and HS384 signatures +- Removed unused calls to $this->getProviderConfigValue("token_endpoint_… From b6cc8138cf529b8c062b1a8f5083bd8438f0848a Mon Sep 17 00:00:00 2001 From: Anthimidis Nikos Date: Thu, 19 Jan 2023 16:50:55 +0200 Subject: [PATCH 195/223] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2908a89c..c866c179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +* Fixed issue on authentication for php8. #354 * Support for signed and encrypted UserInfo response. #305 * Support for signed and encrypted ID Token. #305 From e94b9eb331504c9c6c130b9186f2984480f8de78 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Wed, 29 Mar 2023 10:25:53 +0200 Subject: [PATCH 196/223] chore: Update construct typehint in docblock (#364) * Update construct typehint in docblock * Update changelog --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a60e9d..924e5889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed issue on authentication for php8. #354 * Support for signed and encrypted UserInfo response. #305 * Support for signed and encrypted ID Token. #305 +* Update construct typehint in docblock. #364 ### Added - Support for signed and encrypted UserInfo response. #305 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 34af3753..71667a33 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -272,11 +272,10 @@ class OpenIDConnectClient private $token_endpoint_auth_methods_supported = ['client_secret_basic']; /** - * @param $provider_url string optional - * - * @param $client_id string optional - * @param $client_secret string optional - * @param null $issuer + * @param string|null $provider_url optional + * @param string|null $client_id optional + * @param string|null $client_secret optional + * @param string|null $issuer */ public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null) { $this->setProviderURL($provider_url); From 20b51cb3c7334b5575777cc69cfeb704a10b1721 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Wed, 29 Mar 2023 10:26:32 +0200 Subject: [PATCH 197/223] chore: Update visibility of getWellKnownConfigValue to protected (#363) * Update visibility of getWellKnownConfigValue to protected * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 924e5889..af8da92b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ 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). ## [unreleased] +* Update visibility of getWellKnownConfigValue to protected. #363 * Fixed issue on authentication for php8. #354 * Support for signed and encrypted UserInfo response. #305 * Support for signed and encrypted ID Token. #305 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 71667a33..b9f969f4 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -639,7 +639,7 @@ protected function getProviderConfigValue($param, $default = null) { * @return string * */ - private function getWellKnownConfigValue($param, $default = null) { + protected 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" From e6eab93a5f15f0edf44d30f6b997407fedfec00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 3 May 2023 15:42:43 +0200 Subject: [PATCH 198/223] feat: php7.0 minimum requirement (#327) --- .github/workflows/build.yml | 2 +- composer.json | 7 +- src/OpenIDConnectClient.php | 662 ++++++++++-------------------- tests/OpenIDConnectClientTest.php | 16 +- tests/TokenVerificationTest.php | 7 +- 5 files changed, 237 insertions(+), 457 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2033968d..515cd64c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] steps: - name: Checkout diff --git a/composer.json b/composer.json index 6d218ccf..3fa6d231 100644 --- a/composer.json +++ b/composer.json @@ -3,14 +3,13 @@ "description": "Bare-bones OpenID Connect client", "license": "Apache-2.0", "require": { - "php": ">=5.4", - "phpseclib/phpseclib" : "~2.0 || ^3.0", + "php": ">=7.0", "ext-json": "*", "ext-curl": "*", - "paragonie/random_compat": ">=2" + "phpseclib/phpseclib": "~3.0" }, "require-dev": { - "roave/security-advisories": "dev-master", + "roave/security-advisories": "dev-latest", "yoast/phpunit-polyfills": "^1.0" }, "archive" : { diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b83d9d3a..ba4fddef 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -22,18 +22,16 @@ 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'); -} +use Error; +use Exception; +use phpseclib3\Crypt\PublicKeyLoader; +use phpseclib3\Crypt\RSA; +use phpseclib3\Math\BigInteger; +use stdClass; +use function bin2hex; +use function is_object; +use function random_bytes; /** * A wrapper around base64_decode which decodes Base64URL-encoded data, @@ -41,7 +39,7 @@ * @param string $base64url * @return bool|string */ -function base64url_decode($base64url) { +function base64url_decode(string $base64url) { return base64_decode(b64url2b64($base64url)); } @@ -53,7 +51,8 @@ function base64url_decode($base64url) { * @param string $base64url * @return string */ -function b64url2b64($base64url) { +function b64url2b64(string $base64url): string +{ // "Shouldn't" be necessary, but why not $padding = strlen($base64url) % 4; if ($padding > 0) { @@ -66,19 +65,8 @@ function b64url2b64($base64url) { /** * OpenIDConnect Exception Class */ -class OpenIDConnectClientException extends \Exception +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.'); } /** @@ -169,11 +157,6 @@ class OpenIDConnectClient */ private $responseTypes = []; - /** - * @var array holds a cache of info returned from the user info endpoint - */ - private $userInfo = []; - /** * @var array holds authentication parameters */ @@ -190,7 +173,7 @@ class OpenIDConnectClient private $wellKnown = false; /** - * @var mixed holds well-known opendid configuration parameters, like policy for MS Azure AD B2C User Flow + * @var mixed holds well-known openid 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 = []; @@ -211,7 +194,7 @@ class OpenIDConnectClient private $additionalJwks = []; /** - * @var array holds verified jwt claims + * @var object holds verified jwt claims */ protected $verifiedClaims = []; @@ -277,7 +260,7 @@ class OpenIDConnectClient * @param string|null $client_secret optional * @param string|null $issuer */ - public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null) { + public function __construct(string $provider_url = null, string $client_id = null, string $client_secret = null, string $issuer = null) { $this->setProviderURL($provider_url); if ($issuer === null) { $this->setIssuer($provider_url); @@ -314,8 +297,8 @@ public function setResponseTypes($response_types) { * @return bool * @throws OpenIDConnectClientException */ - public function authenticate() { - + public function authenticate(): bool + { // 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'] : ''; @@ -367,7 +350,7 @@ public function authenticate() { $this->accessToken = $token_json->access_token; // If this is a valid claim - if ($this->verifyJWTclaims($claims, $token_json->access_token)) { + if ($this->verifyJWTClaims($claims, $token_json->access_token)) { // Clean up the session a little $this->unsetNonce(); @@ -394,10 +377,7 @@ public function authenticate() { // 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']; - } + $accessToken = $_REQUEST['access_token'] ?? null; // Do an OpenID Connect session check if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { @@ -416,7 +396,7 @@ public function authenticate() { $this->idToken = $id_token; // If this is a valid claim - if ($this->verifyJWTclaims($claims, $accessToken)) { + if ($this->verifyJWTClaims($claims, $accessToken)) { // Clean up the session a little $this->unsetNonce(); @@ -453,10 +433,9 @@ public function authenticate() { * * @throws OpenIDConnectClientException */ - public function signOut($idToken, $redirect) { - $signout_endpoint = $this->getProviderConfigValue('end_session_endpoint'); + public function signOut(string $idToken, $redirect) { + $sign_out_endpoint = $this->getProviderConfigValue('end_session_endpoint'); - $signout_params = null; if($redirect === null){ $signout_params = ['id_token_hint' => $idToken]; } @@ -466,8 +445,8 @@ public function signOut($idToken, $redirect) { 'post_logout_redirect_uri' => $redirect]; } - $signout_endpoint .= (strpos($signout_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->encType); - $this->redirect($signout_endpoint); + $sign_out_endpoint .= (strpos($sign_out_endpoint, '?') === false ? '?' : '&') . http_build_query( $signout_params, '', '&', $this->encType); + $this->redirect($sign_out_endpoint); } @@ -482,7 +461,7 @@ public function signOut($idToken, $redirect) { * @return bool * @throws OpenIDConnectClientException */ - public function verifyLogoutToken() + public function verifyLogoutToken(): bool { if (isset($_REQUEST['logout_token'])) { $logout_token = $_REQUEST['logout_token']; @@ -490,16 +469,11 @@ public function verifyLogoutToken() $claims = $this->decodeJWT($logout_token, 1); // Verify the signature - if ($this->canVerifySignatures()) { - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); - } - if (!$this->verifyJWTsignature($logout_token)) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); - } + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); } - else { - user_error('Warning: JWT signature verification unavailable'); + if (!$this->verifyJWTSignature($logout_token)) { + throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); } // Verify Logout Token Claims @@ -522,7 +496,7 @@ public function verifyLogoutToken() * @return bool * @throws OpenIDConnectClientException */ - public function verifyLogoutTokenClaims($claims) + public function verifyLogoutTokenClaims($claims): bool { // Verify that the Logout Token doesn't contain a nonce Claim. if (isset($claims->nonce)) { @@ -579,28 +553,25 @@ public function verifyLogoutTokenClaims($claims) /** * @param array $scope - example: openid, given_name, etc... */ - public function addScope($scope) { - $this->scopes = array_merge($this->scopes, (array)$scope); + public function addScope(array $scope) { + $this->scopes = array_merge($this->scopes, $scope); } /** * @param array $param - example: prompt=login */ - public function addAuthParam($param) { - $this->authParams = array_merge($this->authParams, (array)$param); + public function addAuthParam(array $param) { + $this->authParams = array_merge($this->authParams, $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); + public function addRegistrationParam(array $param) { + $this->registrationParams = array_merge($this->registrationParams, $param); } - /** - * @param array $token_endpoint_auth_methods_supported - */ - public function setTokenEndpointAuthMethodsSupported($token_endpoint_auth_methods_supported) + public function setTokenEndpointAuthMethodsSupported(array $token_endpoint_auth_methods_supported) { $this->token_endpoint_auth_methods_supported = $token_endpoint_auth_methods_supported; } @@ -613,15 +584,15 @@ protected function addAdditionalJwk($jwk) { } /** - * Get's anything that we need configuration wise including endpoints, and other values + * Gets anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string $default optional - * @throws OpenIDConnectClientException + * @param string|array|null $default optional * @return string|array * + *@throws OpenIDConnectClientException */ - protected function getProviderConfigValue($param, $default = null) { + protected function getProviderConfigValue(string $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" @@ -633,15 +604,16 @@ protected function getProviderConfigValue($param, $default = null) { } /** - * Get's anything that we need configuration wise including endpoints, and other values + * Gets anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string $default optional - * @throws OpenIDConnectClientException + * @param string|null $default optional * @return string * + *@throws OpenIDConnectClientException */ - protected function getWellKnownConfigValue($param, $default = null) { + protected function getWellKnownConfigValue(string $param, string $default = null): string + { // If the configuration value is not available, attempt to fetch it from a well known config endpoint // This is also known as auto "discovery" @@ -650,13 +622,10 @@ protected function getWellKnownConfigValue($param, $default = null) { if (count($this->wellKnownConfigParameters) > 0){ $well_known_config_url .= '?' . http_build_query($this->wellKnownConfigParameters) ; } - $this->wellKnown = json_decode($this->fetchURL($well_known_config_url)); + $this->wellKnown = json_decode($this->fetchURL($well_known_config_url), false); } - $value = false; - if(isset($this->wellKnown->{$param})){ - $value = $this->wellKnown->{$param}; - } + $value = $this->wellKnown->{$param} ?? false; if ($value) { return $value; @@ -667,14 +636,11 @@ protected function getWellKnownConfigValue($param, $default = null) { return $default; } - throw new OpenIDConnectClientException("The provider {$param} could not be fetched. Make sure your provider has a well known configuration available."); + 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 - * + * Set optional parameters for .well-known/openid-configuration */ public function setWellKnownConfigParameters(array $params = []){ $this->wellKnownConfigParameters=$params; @@ -684,7 +650,7 @@ public function setWellKnownConfigParameters(array $params = []){ /** * @param string $url Sets redirect URL for auth flow */ - public function setRedirectURL($url) { + public function setRedirectURL(string $url) { if (parse_url($url,PHP_URL_HOST) !== false) { $this->redirectURL = $url; } @@ -695,8 +661,8 @@ public function setRedirectURL($url) { * * @return string */ - public function getRedirectURL() { - + public function getRedirectURL(): string + { // If the redirect URL has been set then return it. if (property_exists($this, 'redirectURL') && $this->redirectURL) { return $this->redirectURL; @@ -728,9 +694,9 @@ public function getRedirectURL() { } if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $port = intval($_SERVER['HTTP_X_FORWARDED_PORT']); + $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = intval($_SERVER['SERVER_PORT']); + $port = $_SERVER['SERVER_PORT']; } elseif ($protocol === 'https') { $port = 443; } else { @@ -759,11 +725,10 @@ public function getRedirectURL() { * @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 + protected function generateRandString(): string + { try { - return \bin2hex(\random_bytes(16)); + return bin2hex(random_bytes(16)); } catch (Error $e) { throw new OpenIDConnectClientException('Random token generation failed.'); } catch (Exception $e) { @@ -775,7 +740,7 @@ protected function generateRandString() { * Start Here * @return void * @throws OpenIDConnectClientException - * @throws \Exception + * @throws Exception */ private function requestAuthorization() { @@ -852,7 +817,7 @@ public function requestClientCredentialsToken() { // 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)); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); } /** @@ -863,7 +828,7 @@ public function requestClientCredentialsToken() { * @return mixed * @throws OpenIDConnectClientException */ - public function requestResourceOwnerToken($bClientAuth = FALSE) { + public function requestResourceOwnerToken(bool $bClientAuth = false) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $headers = []; @@ -891,7 +856,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { // 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)); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); } @@ -903,7 +868,7 @@ public function requestResourceOwnerToken($bClientAuth = FALSE) { * @return mixed * @throws OpenIDConnectClientException */ - protected function requestTokens($code, $headers = array()) { + protected function requestTokens(string $code, array $headers = []) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -921,11 +886,10 @@ protected function requestTokens($code, $headers = array()) { # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $authorizationHeader = 'Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret)); - unset($token_params['client_secret']); - unset($token_params['client_id']); + unset($token_params['client_secret'], $token_params['client_id']); } - // When there is a private key jwt generator and it is supported then use it as client authentication + // When there is a private key jwt generator, and it is supported then use it as client authentication if ($this->privateKeyJwtGenerator !== null && $this->supportsAuthMethod('private_key_jwt', $token_endpoint_auth_methods_supported)) { $token_params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; $token_params['client_assertion'] = $this->privateKeyJwtGenerator->__invoke($token_endpoint); @@ -967,7 +931,7 @@ protected function requestTokens($code, $headers = array()) { $headers[] = $authorizationHeader; } - $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers)); + $this->tokenResponse = json_decode($this->fetchURL($token_endpoint, $token_params, $headers), false); return $this->tokenResponse; } @@ -982,7 +946,7 @@ protected function requestTokens($code, $headers = array()) { * @return mixed * @throws OpenIDConnectClientException */ - public function requestTokenExchange($subjectToken, $subjectTokenType, $audience = '') { + public function requestTokenExchange(string $subjectToken, string $subjectTokenType, string $audience = '') { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); $headers = []; @@ -1004,14 +968,13 @@ public function requestTokenExchange($subjectToken, $subjectTokenType, $audience # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($post_data['client_secret']); - unset($post_data['client_id']); + unset($post_data['client_secret'], $post_data['client_id']); } // Convert token params to string format $post_params = http_build_query($post_data, null, '&', $this->encType); - return json_decode($this->fetchURL($token_endpoint, $post_params, $headers)); + return json_decode($this->fetchURL($token_endpoint, $post_params, $headers), false); } @@ -1022,7 +985,7 @@ public function requestTokenExchange($subjectToken, $subjectTokenType, $audience * @return mixed * @throws OpenIDConnectClientException */ - public function refreshToken($refresh_token) { + public function refreshToken(string $refresh_token) { $token_endpoint = $this->getProviderConfigValue('token_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -1041,8 +1004,7 @@ public function refreshToken($refresh_token) { # Consider Basic authentication if provider config is set this way if ($this->supportsAuthMethod('client_secret_basic', $token_endpoint_auth_methods_supported)) { $headers = ['Authorization: Basic ' . base64_encode(urlencode($this->clientID) . ':' . urlencode($this->clientSecret))]; - unset($token_params['client_secret']); - unset($token_params['client_id']); + unset($token_params['client_secret'], $token_params['client_id']); } if ($this->supportsAuthMethod('client_secret_jwt', $token_endpoint_auth_methods_supported)) { @@ -1057,13 +1019,12 @@ public function refreshToken($refresh_token) { $token_params['client_assertion_type']=$client_assertion_type; $token_params['client_assertion'] = $client_assertion; - unset($token_params['client_secret']); - unset($token_params['client_id']); + unset($token_params['client_secret'], $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)); + $json = json_decode($this->fetchURL($token_endpoint, $token_params, $headers), false); if (isset($json->access_token)) { $this->accessToken = $json->access_token; @@ -1077,21 +1038,16 @@ public function refreshToken($refresh_token) { } /** - * @param array $keys - * @param array $header * @throws OpenIDConnectClientException - * @return object */ - private function getKeyForHeader($keys, $header) { + private function getKeyForHeader(array $keys, stdClass $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; - } + } else if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { + return $key; } } if ($this->additionalJwks) { @@ -1100,10 +1056,8 @@ private function getKeyForHeader($keys, $header) { 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; - } + } else if (isset($key->alg) && $key->alg === $header->alg && $key->kid === $header->kid) { + return $key; } } } @@ -1114,94 +1068,43 @@ private function getKeyForHeader($keys, $header) { 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.'); - } + private function verifyRSAJWTSignature(string $hashType, stdClass $key, $payload, $signature, $signatureType): bool + { 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); + $key = RSA::load([ + 'publicExponent' => new BigInteger(base64_decode(b64url2b64($key->e)), 256), + 'modulus' => new BigInteger(base64_decode(b64url2b64($key->n)), 256), + 'isPublicKey' => true, + ]) + ->withHash($hashType); + if ($signatureType === 'PSS') { + $key = $key->withMGFHash($hashType) + ->withPadding(RSA::SIGNATURE_PSS); } 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); + $key = $key->withPadding(RSA::SIGNATURE_PKCS1); } + return $key->verify($payload, $signature); } - /** - * @param string $hashtype - * @param string $key - * @param $payload - * @param $signature - * @return bool - * @throws OpenIDConnectClientException - */ - private function verifyHMACJWTsignature($hashtype, $key, $payload, $signature) + private function verifyHMACJWTSignature(string $hashType, string $key, string $payload, string $signature): bool { - 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); + $expected = hash_hmac($hashType, $payload, $key, true); + return hash_equals($signature, $expected); } /** * @param string $jwt encoded JWT - * @throws OpenIDConnectClientException * @return bool + * @throws OpenIDConnectClientException */ - public function verifyJWTsignature($jwt) { - if (!\is_string($jwt)) { - throw new OpenIDConnectClientException('Error token is not a string'); - } + public function verifyJWTSignature(string $jwt): bool + { $parts = explode('.', $jwt); if (!isset($parts[0])) { throw new OpenIDConnectClientException('Error missing part 0 in token'); @@ -1210,8 +1113,8 @@ public function verifyJWTsignature($jwt) { 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)) { + $header = json_decode(base64url_decode($parts[0]), false); + if (!is_object($header)) { throw new OpenIDConnectClientException('Error decoding JSON from token header'); } if (!isset($header->alg)) { @@ -1225,7 +1128,7 @@ public function verifyJWTsignature($jwt) { case 'PS512': case 'RS384': case 'RS512': - $hashtype = 'sha' . substr($header->alg, 2); + $hashType = 'sha' . substr($header->alg, 2); $signatureType = $header->alg === 'PS256' || $header->alg === 'PS512' ? 'PSS' : ''; if (isset($header->jwk)) { $jwk = $header->jwk; @@ -1238,15 +1141,15 @@ public function verifyJWTsignature($jwt) { $jwk = $this->getKeyForHeader($jwks->keys, $header); } - $verified = $this->verifyRSAJWTsignature($hashtype, + $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); + $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); @@ -1259,17 +1162,13 @@ public function verifyJWTsignature($jwt) { * @return void * @throws OpenIDConnectClientException */ - public function verifySignatures($jwt) + public function verifySignatures(string $jwt) { - 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($jwt)) { - throw new OpenIDConnectClientException ('Unable to verify signature'); - } - } else { - user_error('Warning: JWT signature verification unavailable.'); + if (!$this->getProviderConfigValue('jwks_uri')) { + throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); + } + if (!$this->verifyJWTSignature($jwt)) { + throw new OpenIDConnectClientException ('Unable to verify signature'); } } @@ -1278,7 +1177,8 @@ public function verifySignatures($jwt) * @return bool * @throws OpenIDConnectClientException */ - protected function validateIssuer($iss) { + protected function validateIssuer(string $iss): bool + { if ($this->issuerValidator !== null) { return $this->issuerValidator->__invoke($iss); } @@ -1290,9 +1190,11 @@ protected function validateIssuer($iss) { * @param object $claims * @param string|null $accessToken * @return bool + * @throws OpenIDConnectClientException */ - protected function verifyJWTclaims($claims, $accessToken = null) { - if(isset($claims->at_hash) && isset($accessToken)) { + protected function verifyJWTClaims($claims, string $accessToken = null): bool + { + if(isset($claims->at_hash, $accessToken)) { if(isset($this->getIdTokenHeader()->alg) && $this->getIdTokenHeader()->alg !== 'none') { $bit = substr($this->getIdTokenHeader()->alg, 2, 3); } else { @@ -1305,21 +1207,17 @@ protected function verifyJWTclaims($claims, $accessToken = null) { 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->exp) || ((is_int($claims->exp)) && ($claims->exp >= time() - $this->leeway))) + && ( !isset($claims->nbf) || ((is_int($claims->nbf)) && ($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) { + protected function urlEncode(string $str): string + { $enc = base64_encode($str); $enc = rtrim($enc, '='); - $enc = strtr($enc, '+/', '-_'); - return $enc; + return strtr($enc, '+/', '-_'); } /** @@ -1327,10 +1225,10 @@ protected function urlEncode($str) { * @param int $section the section we would like to decode * @return object */ - protected function decodeJWT($jwt, $section = 0) { + protected function decodeJWT(string $jwt, int $section = 0): stdClass { $parts = explode('.', $jwt); - return json_decode(base64url_decode($parts[$section])); + return json_decode(base64url_decode($parts[$section]), false); } /** @@ -1355,13 +1253,13 @@ protected function decodeJWT($jwt, $section = 0) { * 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. + * updated_time string Time the End-User's information was last updated, represented as an RFC 3339 [RFC3339] datetime. For example, 2011-01-03T23:58:42+0000. * * @return mixed * * @throws OpenIDConnectClientException */ - public function requestUserInfo($attribute = null) { + public function requestUserInfo(string $attribute = null) { $user_info_endpoint = $this->getProviderConfigValue('userinfo_endpoint'); $schema = 'openid'; @@ -1370,11 +1268,11 @@ public function requestUserInfo($attribute = null) { //The accessToken has to be sent in the Authorization header. // Accept json to indicate response type - $headers = ["Authorization: Bearer {$this->accessToken}", + $headers = ["Authorization: Bearer $this->accessToken", 'Accept: application/json']; $response = $this->fetchURL($user_info_endpoint,null,$headers); - if ($this->getResponseCode() <> 200) { + if ($this->getResponseCode() !== 200) { throw new OpenIDConnectClientException('The communication to retrieve user data has failed with status code '.$this->getResponseCode()); } @@ -1397,23 +1295,23 @@ public function requestUserInfo($attribute = null) { $claims = $this->decodeJWT($jwt, 1); // Verify the JWT claims - if (!$this->verifyJWTclaims($claims)) { + if (!$this->verifyJWTClaims($claims)) { throw new OpenIDConnectClientException('Invalid JWT signature'); } $user_json = $claims; } else { - $user_json = json_decode($response); + $user_json = json_decode($response, false); } - $this->userInfo = $user_json; + $userInfo = $user_json; if($attribute === null) { - return $this->userInfo; + return $userInfo; } - if (property_exists($this->userInfo, $attribute)) { - return $this->userInfo->$attribute; + if (property_exists($userInfo, $attribute)) { + return $userInfo->$attribute; } return null; @@ -1432,13 +1330,13 @@ public function requestUserInfo($attribute = null) { * aud string Audience * nonce string nonce * iat int Issued At - * auth_time int Authenatication time + * auth_time int Authentication time * oid string Object id * * @return mixed * */ - public function getVerifiedClaims($attribute = null) { + public function getVerifiedClaims(string $attribute = null) { if($attribute === null) { return $this->verifiedClaims; @@ -1454,11 +1352,11 @@ public function getVerifiedClaims($attribute = 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' + * @param array $headers Extra headers to be sent with the request. Format as 'NameHeader: ValueHeader' + * @return bool|string * @throws OpenIDConnectClientException - * @return mixed */ - protected function fetchURL($url, $post_body = null, $headers = []) { + protected function fetchURL(string $url, string $post_body = null, array $headers = []) { // OK cool - then let's create a new cURL resource handle $ch = curl_init(); @@ -1466,7 +1364,7 @@ protected function fetchURL($url, $post_body = null, $headers = []) { // 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 + // Allows to keep the POST method even after redirect curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); @@ -1474,13 +1372,12 @@ protected function fetchURL($url, $post_body = null, $headers = []) { $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))) { + if (is_object(json_decode($post_body, false))) { $content_type = 'application/json'; } // Add POST-specific headers - $headers[] = "Content-Type: {$content_type}"; - + $headers[] = "Content-Type: $content_type"; } // If we set some headers include them @@ -1546,20 +1443,18 @@ protected function fetchURL($url, $post_body = null, $headers = []) { } /** - * @param bool $appendSlash - * @return string * @throws OpenIDConnectClientException */ - public function getWellKnownIssuer($appendSlash = false) { - + public function getWellKnownIssuer(bool $appendSlash = false): string + { return $this->getWellKnownConfigValue('issuer') . ($appendSlash ? '/' : ''); } /** - * @return string * @throws OpenIDConnectClientException */ - public function getIssuer() { + public function getIssuer(): string + { if (!isset($this->providerConfig['issuer'])) { throw new OpenIDConnectClientException('The issuer has not been set'); @@ -1580,25 +1475,16 @@ public function getProviderURL() { return $this->providerConfig['providerUrl']; } - /** - * @param string $url - */ - public function redirect($url) { + public function redirect(string $url) { header('Location: ' . $url); exit; } - /** - * @param string $httpProxy - */ - public function setHttpProxy($httpProxy) { + public function setHttpProxy(string $httpProxy) { $this->httpProxy = $httpProxy; } - /** - * @param string $certPath - */ - public function setCertPath($certPath) { + public function setCertPath(string $certPath) { $this->certPath = $certPath; } @@ -1609,48 +1495,33 @@ public function getCertPath() { return $this->certPath; } - /** - * @param bool $verifyPeer - */ - public function setVerifyPeer($verifyPeer) { + public function setVerifyPeer(bool $verifyPeer) { $this->verifyPeer = $verifyPeer; } - /** - * @param bool $verifyHost - */ - public function setVerifyHost($verifyHost) { + public function setVerifyHost(bool $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) { + public function setHttpUpgradeInsecureRequests(bool $httpUpgradeInsecureRequests) { $this->httpUpgradeInsecureRequests = $httpUpgradeInsecureRequests; } - /** - * @return bool - */ - public function getVerifyHost() { + public function getVerifyHost(): bool + { return $this->verifyHost; } - /** - * @return bool - */ - public function getVerifyPeer() { + public function getVerifyPeer(): bool + { return $this->verifyPeer; } - /** - * @return bool - */ - public function getHttpUpgradeInsecureRequests() + public function getHttpUpgradeInsecureRequests(): bool { return $this->httpUpgradeInsecureRequests; } @@ -1659,10 +1530,8 @@ public function getHttpUpgradeInsecureRequests() * 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) { + public function setIssuerValidator(callable $issuerValidator) { $this->issuerValidator = $issuerValidator; } @@ -1671,24 +1540,20 @@ public function setIssuerValidator($issuerValidator) { * The given function should accept the token_endpoint string as the only argument * and return a jwt signed with your private key according to: * https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication - * - * @param callable $privateKeyJwtGenerator */ - public function setPrivateKeyJwtGenerator($privateKeyJwtGenerator) { + public function setPrivateKeyJwtGenerator(callable $privateKeyJwtGenerator) { $this->privateKeyJwtGenerator = $privateKeyJwtGenerator; } - /** - * @param bool $allowImplicitFlow - */ - public function setAllowImplicitFlow($allowImplicitFlow) { + public function setAllowImplicitFlow(bool $allowImplicitFlow) { $this->allowImplicitFlow = $allowImplicitFlow; } /** * @return bool */ - public function getAllowImplicitFlow() { + public function getAllowImplicitFlow(): bool + { return $this->allowImplicitFlow; } @@ -1699,21 +1564,15 @@ public function getAllowImplicitFlow() { * @param array $array * simple key => value */ - public function providerConfigParam($array) { + public function providerConfigParam(array $array) { $this->providerConfig = array_merge($this->providerConfig, $array); } - /** - * @param string $clientSecret - */ - public function setClientSecret($clientSecret) { + public function setClientSecret(string $clientSecret) { $this->clientSecret = $clientSecret; } - /** - * @param string $clientID - */ - public function setClientID($clientID) { + public function setClientID(string $clientID) { $this->clientID = $clientID; } @@ -1734,7 +1593,7 @@ public function register() { $response = $this->fetchURL($registration_endpoint, json_encode($send_object)); - $json_response = json_decode($response); + $json_response = json_decode($response, false); // Throw some errors if we encounter them if ($json_response === false) { @@ -1752,10 +1611,8 @@ public function register() { 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'); + throw new OpenIDConnectClientException('Error registering: Please contact the OpenID Connect provider and obtain a Client ID and Secret directly from them'); } - } /** @@ -1768,8 +1625,9 @@ public function register() { * @param string|null $clientSecret * @return mixed * @throws OpenIDConnectClientException + * @throws Exception */ - public function introspectToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { + public function introspectToken(string $token, string $token_type_hint = '', string $clientId = null, string $clientSecret = null) { $introspection_endpoint = $this->getProviderConfigValue('introspection_endpoint'); $token_endpoint_auth_methods_supported = $this->getProviderConfigValue('token_endpoint_auth_methods_supported', ['client_secret_basic']); @@ -1778,8 +1636,8 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } - $clientId = $clientId !== null ? $clientId : $this->clientID; - $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; + $clientId = $clientId ?? $this->clientID; + $clientSecret = $clientSecret ?? $this->clientSecret; // Convert token params to string format $headers = ['Authorization: Basic ' . base64_encode(urlencode($clientId) . ':' . urlencode($clientSecret)), @@ -1796,7 +1654,7 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, $post_params = http_build_query($post_data, '', '&'); - return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers)); + return json_decode($this->fetchURL($introspection_endpoint, $post_params, $headers), false); } /** @@ -1810,7 +1668,7 @@ public function introspectToken($token, $token_type_hint = '', $clientId = null, * @return mixed * @throws OpenIDConnectClientException */ - public function revokeToken($token, $token_type_hint = '', $clientId = null, $clientSecret = null) { + public function revokeToken(string $token, string $token_type_hint = '', string $clientId = null, string $clientSecret = null) { $revocation_endpoint = $this->getProviderConfigValue('revocation_endpoint'); $post_data = ['token' => $token]; @@ -1818,28 +1676,23 @@ public function revokeToken($token, $token_type_hint = '', $clientId = null, $cl if ($token_type_hint) { $post_data['token_type_hint'] = $token_type_hint; } - $clientId = $clientId !== null ? $clientId : $this->clientID; - $clientSecret = $clientSecret !== null ? $clientSecret : $this->clientSecret; + $clientId = $clientId ?? $this->clientID; + $clientSecret = $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 json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers), false); } - /** - * @return string - */ - public function getClientName() { + public function getClientName(): string + { return $this->clientName; } - /** - * @param string $clientName - */ - public function setClientName($clientName) { + public function setClientName(string $clientName) { $this->clientName = $clientName; } @@ -1857,43 +1710,27 @@ 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) { + public function setAccessToken(string $accessToken) { $this->accessToken = $accessToken; } - /** - * @return string - */ - public function getAccessToken() { + public function getAccessToken(): string + { return $this->accessToken; } - /** - * @return string - */ - public function getRefreshToken() { + public function getRefreshToken(): string + { return $this->refreshToken; } - /** - * @return string - */ - public function getIdToken() { + public function getIdToken(): string + { return $this->idToken; } @@ -1934,11 +1771,9 @@ public function getTokenResponse() { /** * Stores nonce - * - * @param string $nonce - * @return string */ - protected function setNonce($nonce) { + protected function setNonce(string $nonce): string + { $this->setSessionKey('openid_connect_nonce', $nonce); return $nonce; } @@ -1963,11 +1798,9 @@ protected function unsetNonce() { /** * Stores $state - * - * @param string $state - * @return string */ - protected function setState($state) { + protected function setState(string $state): string + { $this->setSessionKey('openid_connect_state', $state); return $state; } @@ -1992,11 +1825,9 @@ protected function unsetState() { /** * Stores $codeVerifier - * - * @param string $codeVerifier - * @return string */ - protected function setCodeVerifier($codeVerifier) { + protected function setCodeVerifier(string $codeVerifier): string + { $this->setSessionKey('openid_connect_code_verifier', $codeVerifier); return $codeVerifier; } @@ -2024,7 +1855,8 @@ protected function unsetCodeVerifier() { * * @return int */ - public function getResponseCode() { + public function getResponseCode(): int + { return $this->responseCode; } @@ -2043,50 +1875,15 @@ public function getResponseContentType() * * @param int $timeout */ - public function setTimeout($timeout) { + public function setTimeout(int $timeout) { $this->timeOut = $timeout; } - /** - * @return int - */ - public function getTimeout() { + public function getTimeout(): int + { 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 */ @@ -2102,7 +1899,7 @@ protected function commitSession() { session_write_close(); } - protected function getSessionKey($key) { + protected function getSessionKey(string $key) { $this->startSession(); if (array_key_exists($key, $_SESSION)) { @@ -2111,19 +1908,23 @@ protected function getSessionKey($key) { return false; } - protected function setSessionKey($key, $value) { + protected function setSessionKey(string $key, $value) { $this->startSession(); $_SESSION[$key] = $value; } - protected function unsetSessionKey($key) { + protected function unsetSessionKey(string $key) { $this->startSession(); unset($_SESSION[$key]); } - protected function getJWTClientAssertion($aud) { + /** + * @throws Exception + */ + protected function getJWTClientAssertion($aud): string + { $jti = hash('sha256',bin2hex(random_bytes(64))); $now = time(); @@ -2155,9 +1956,7 @@ protected function getJWTClientAssertion($aud) { // Encode Signature to Base64Url String $base64UrlSignature = $this->urlEncode($signature); - $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature; - - return $jwt; + return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature; } public function setUrlEncoding($curEncoding) { @@ -2174,27 +1973,20 @@ public function setUrlEncoding($curEncoding) { default: break; } - } - /** - * @return array - */ - public function getScopes() { + public function getScopes(): array + { return $this->scopes; } - /** - * @return array - */ - public function getResponseTypes() { + public function getResponseTypes(): array + { return $this->responseTypes; } - /** - * @return array - */ - public function getAuthParams() { + public function getAuthParams(): array + { return $this->authParams; } @@ -2213,10 +2005,8 @@ public function getPrivateKeyJwtGenerator() { return $this->privateKeyJwtGenerator; } - /** - * @return int - */ - public function getLeeway() { + public function getLeeway(): int + { return $this->leeway; } @@ -2227,10 +2017,7 @@ public function getCodeChallengeMethod() { return $this->codeChallengeMethod; } - /** - * @param string $codeChallengeMethod - */ - public function setCodeChallengeMethod($codeChallengeMethod) { + public function setCodeChallengeMethod(string $codeChallengeMethod) { $this->codeChallengeMethod = $codeChallengeMethod; } @@ -2247,31 +2034,22 @@ protected function verifyJWKHeader($jwk) * @return string the JWT payload * @throws OpenIDConnectClientException */ - protected function handleJweResponse($jwe) + protected function handleJweResponse(string $jwe): string { throw new OpenIDConnectClientException('JWE response is not supported, please extend the class and implement this method'); } - /* - * @return string - */ - public function getSidFromBackChannel() { + public function getSidFromBackChannel(): string + { return $this->backChannelSid; } - /** - * @return string - */ - public function getSubjectFromBackChannel() { + public function getSubjectFromBackChannel(): string + { return $this->backChannelSubject; } - /** - * @param string $auth_method - * @param array $token_endpoint_auth_methods_supported - * @return bool - */ - public function supportsAuthMethod($auth_method, $token_endpoint_auth_methods_supported) + public function supportsAuthMethod(string $auth_method, array $token_endpoint_auth_methods_supported): bool { # client_secret_jwt has to explicitly be enabled if (!in_array($auth_method, $this->token_endpoint_auth_methods_supported, true)) { diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index d82234ed..a16be71b 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -23,7 +23,7 @@ public function testGetRedirectURL() public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() { - $fakeClaims = new \StdClass(); + $fakeClaims = new StdClass(); $fakeClaims->iss = 'fake-issuer'; $fakeClaims->aud = 'fake-client-id'; $fakeClaims->nonce = null; @@ -33,10 +33,10 @@ public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() $_SESSION['openid_connect_state'] = false; /** @var OpenIDConnectClient | MockObject $client */ - $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTsignature'])->getMock(); + $client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTSignature'])->getMock(); $client->method('decodeJWT')->willReturn($fakeClaims); $client->method('getProviderConfigValue')->with('jwks_uri')->willReturn(true); - $client->method('verifyJWTsignature')->willReturn(true); + $client->method('verifyJWTSignature')->willReturn(true); $client->setClientID('fake-client-id'); $client->setIssuer('fake-issuer'); @@ -60,7 +60,7 @@ public function testSerialize() { $client = new OpenIDConnectClient('https://example.com', 'foo', 'bar', 'baz'); $serialized = serialize($client); - $this->assertInstanceOf('Jumbojett\OpenIDConnectClient', unserialize($serialized)); + $this->assertInstanceOf(OpenIDConnectClient::class, unserialize($serialized)); } /** @@ -75,7 +75,7 @@ public function testAuthMethodSupport($expected, $authMethod, $clientAuthMethods $this->assertEquals($expected, $client->supportsAuthMethod($authMethod, $idpAuthMethods)); } - public function provider() + public function provider(): array { return [ 'client_secret_basic - default config' => [true, 'client_secret_basic', null, ['client_secret_basic']], @@ -90,8 +90,9 @@ public function provider() } /** - * @covers Jumbojett\\OpenIDConnectClient::verifyLogoutTokenClaims + * @covers Jumbojett\\OpenIDConnectClient::verifyLogoutTokenClaims * @dataProvider provideTestVerifyLogoutTokenClaimsData + * @throws OpenIDConnectClientException */ public function testVerifyLogoutTokenClaims( $claims, $expectedResult ) { @@ -113,7 +114,8 @@ public function testVerifyLogoutTokenClaims( $claims, $expectedResult ) /** * @return array */ - public function provideTestVerifyLogoutTokenClaimsData() { + public function provideTestVerifyLogoutTokenClaimsData(): array + { return [ 'valid-single-aud' => [ (object)[ diff --git a/tests/TokenVerificationTest.php b/tests/TokenVerificationTest.php index 58449924..0715911e 100644 --- a/tests/TokenVerificationTest.php +++ b/tests/TokenVerificationTest.php @@ -2,6 +2,7 @@ use Jumbojett\OpenIDConnectClient; +use Jumbojett\OpenIDConnectClientException; use PHPUnit\Framework\MockObject\MockObject; use Yoast\PHPUnitPolyfills\TestCases\TestCase; @@ -10,7 +11,7 @@ class TokenVerificationTest extends TestCase /** * @param $alg * @param $jwt - * @throws \Jumbojett\OpenIDConnectClientException + * @throws OpenIDConnectClientException * @dataProvider providesTokens */ public function testTokenVerification($alg, $jwt) @@ -20,12 +21,12 @@ public function testTokenVerification($alg, $jwt) $client->method('fetchUrl')->willReturn(file_get_contents(__DIR__ . "/data/jwks-$alg.json")); $client->setProviderURL('https://jwt.io/'); $client->providerConfigParam(['jwks_uri' => 'https://jwt.io/.well-known/jwks.json']); - $verified = $client->verifyJWTsignature($jwt); + $verified = $client->verifyJWTSignature($jwt); self::assertTrue($verified); $client->setAccessToken($jwt); } - public function providesTokens() + public function providesTokens(): array { return [ 'PS256' => ['ps256', 'eyJhbGciOiJQUzI1NiIsImtpZCI6Imtvbm5lY3RkLXRva2Vucy1zaWduaW5nLWtleSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJrcG9wLWh0dHBzOi8va29wYW5vLmRlbW8vbWVldC8iLCJleHAiOjE1NjgzNzE0NjEsImp0aSI6IkpkR0tDbEdOTXl2VXJpcmlRRUlWUXZCVmttT2FfQkRjIiwiaWF0IjoxNTY4MzcxMjIxLCJpc3MiOiJodHRwczovL2tvcGFuby5kZW1vIiwic3ViIjoiUHpUVWp3NHBlXzctWE5rWlBILXJxVHE0MTQ1Z3lDdlRvQmk4V1E5bFBrcW5rbEc1aktvRU5LM21Qb0I1WGY1ZTM5dFRMR2RKWXBMNEJubXFnelpaX0FAa29ubmVjdCIsImtjLmlzQWNjZXNzVG9rZW4iOnRydWUsImtjLmF1dGhvcml6ZWRTY29wZXMiOlsicHJvZmlsZSIsImVtYWlsIiwia29wYW5vL2t3bSIsImtvcGFuby9nYyIsImtvcGFuby9rdnMiLCJvcGVuaWQiXSwia2MuYXV0aG9yaXplZENsYWltcyI6eyJpZF90b2tlbiI6eyJuYW1lIjpudWxsfX0sImtjLmlkZW50aXR5Ijp7ImtjLmkuZG4iOiJKb25hcyBCcmVra2UiLCJrYy5pLmlkIjoiQUFBQUFLd2hxVkJBMCs1SXN4bjdwMU13UkNVQkFBQUFCZ0FBQUJzQUFBQk5VVDA5QUFBQUFBPT0iLCJrYy5pLnVuIjoidXNlcjEiLCJrYy5pLnVzIjoiTVEifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWtjIn0.hGRuXvul2kOiALHexwYp5MBEJVwz1YV3ehyM3AOuwCoK2w5sJxdciqqY_TfXCKyO6nAEbYLK3J0CBOjfup_IG0aCZcwzjto8khYlc4ezXkGnFsbJBNQdDGkpHtWnioWx-OJ3cXvY9F8aOvjaq0gw11ZDAcqQl0g7LTbJ9-J_yx0pmy3NGai2JB30Fh1OgSDzYfxWnE0RRgZG-x68e65RXfSBaEGW85OUh4wihxO2zdTGAHJ3Iq_-QAG4yRbXZtLx3ZspG7LNmqG-YE3huy3Rd8u3xrJNhmUOfEnz3x07q7VW0cj9NedX98BAbj3iNvksQsE0oG0J_f_Tu8Ai8VbWB72sJuXZWxANDKdz0BBYLzXhsjXkNByRq9x3zqDVsX-cVHei_XudxEOVRBjhkvW2MmIjcAHNKCKsdar865-gFG9McP4PCcBlY28tC0Cvnzyi83LBfpGRXdl6MJunnUsKQ1C79iCoVI1doK1erFN959Q-TGJfJA3Tr5LNpuGawB5rpe1nDGWvmYhg3uYfNl8uTTyvNgvvejcflEb2DURuXdqABuSiP7RkDWYtzx6mq49G0tRxelBbvyjQ2id2QjmRRdQ6dHEZ2NCJ51b8OFoDJBtxN1CD62TTxa3FUqCdZAPAUR3hHn_69vYq82MR514s-Gb67A6j2PbMPFATQP2UdK8'] From 8ec206b60f87b4a1886c9c3eefa5f5d81d1ccc35 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Thu, 4 May 2023 11:32:06 +0200 Subject: [PATCH 199/223] feat: set useragent (#370) * Set useragent * Update CHANGELOG.md * Set default useragent --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbfd038..106f5dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Support for signed and encrypted ID Token. #305 * Update construct typehint in docblock. #364 * Fixed LogoutToken verification for single value aud claims #334 +* Added function to set useragent #370 ### Added - Support for signed and encrypted UserInfo response. #305 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index ba4fddef..8081134f 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1367,6 +1367,7 @@ protected function fetchURL(string $url, string $post_body = null, array $header // Allows to keep the POST method even after redirect curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); + curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); // Default content type is form encoded $content_type = 'application/x-www-form-urlencoded'; @@ -2058,4 +2059,9 @@ public function supportsAuthMethod(string $auth_method, array $token_endpoint_au return in_array($auth_method, $token_endpoint_auth_methods_supported, true); } + + protected function getUserAgent(): string + { + return "jumbojett/OpenID-Connect-PHP"; + } } From c146b716a7ec0955175377b7675a457d4402fb95 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Wed, 26 Jul 2023 13:58:05 +0200 Subject: [PATCH 200/223] fix: Update well known config value function response types (#376) * Fix: Update well known config value function response types * Update CHANGELOG * Update wellknown typing array to be string[] * Update wellknown typing null can be default but would never be returned --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 106f5dba..68264e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update construct typehint in docblock. #364 * Fixed LogoutToken verification for single value aud claims #334 * Added function to set useragent #370 +* Update well known config value function response types #376 ### Added - Support for signed and encrypted UserInfo response. #305 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 8081134f..c0015c83 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -587,13 +587,13 @@ protected function addAdditionalJwk($jwk) { * Gets anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string|array|null $default optional - * @return string|array + * @param string|string[]|bool|null $default optional + * @return string|string[]|bool * - *@throws OpenIDConnectClientException + * @throws OpenIDConnectClientException */ - protected function getProviderConfigValue(string $param, $default = null) { - + protected function getProviderConfigValue(string $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])) { @@ -607,12 +607,12 @@ protected function getProviderConfigValue(string $param, $default = null) { * Gets anything that we need configuration wise including endpoints, and other values * * @param string $param - * @param string|null $default optional - * @return string + * @param string|string[]|bool|null $default optional + * @return string|string[]|bool * - *@throws OpenIDConnectClientException + * @throws OpenIDConnectClientException */ - protected function getWellKnownConfigValue(string $param, string $default = null): string + protected function getWellKnownConfigValue(string $param, $default = null) { // If the configuration value is not available, attempt to fetch it from a well known config endpoint From 5d69bcf15478bf11f32f7344afaa2f2640b9bd2a Mon Sep 17 00:00:00 2001 From: mig5 Date: Tue, 1 Aug 2023 17:57:54 +1000 Subject: [PATCH 201/223] Set the User-Agent regardless of GET or POST (#382) --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68264e32..81f127d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ 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). ## [unreleased] +* User-Agent is set for any HTTP method in fetchURL() (not just POST). #382 * Update visibility of getWellKnownConfigValue to protected. #363 * Fixed issue on authentication for php8. #354 * Support for signed and encrypted UserInfo response. #305 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index c0015c83..6aa80b17 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1367,7 +1367,6 @@ protected function fetchURL(string $url, string $post_body = null, array $header // Allows to keep the POST method even after redirect curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_body); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); // Default content type is form encoded $content_type = 'application/x-www-form-urlencoded'; @@ -1381,6 +1380,9 @@ protected function fetchURL(string $url, string $post_body = null, array $header $headers[] = "Content-Type: $content_type"; } + // Set the User-Agent + curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); + // If we set some headers include them if(count($headers) > 0) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); From 7be38be967dd7845d0700924a69af34ef1c4077f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:03:27 +0100 Subject: [PATCH 202/223] release: 1.0.0 (#402) --- CHANGELOG.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f127d2..d4be9ff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,20 @@ 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). -## [unreleased] -* User-Agent is set for any HTTP method in fetchURL() (not just POST). #382 -* Update visibility of getWellKnownConfigValue to protected. #363 -* Fixed issue on authentication for php8. #354 -* Support for signed and encrypted UserInfo response. #305 -* Support for signed and encrypted ID Token. #305 -* Update construct typehint in docblock. #364 -* Fixed LogoutToken verification for single value aud claims #334 -* Added function to set useragent #370 -* Update well known config value function response types #376 +## [1.0.0] - 2023-12-13 ### Added -- Support for signed and encrypted UserInfo response. #305 -- Support for signed and encrypted ID Token. #305 +- PHP 7.0 is required. #327 +- Support for signed and encrypted UserInfo response and ID Token. #305 +- Allow to set User-Agent header. #370 + +### Fixed +- User-Agent is set for any HTTP method in fetchURL() (not just POST). #382 +- Update visibility of getWellKnownConfigValue to protected. #363 +- Fixed issue on authentication for php8. #354 +- Update construct typehint in docblock. #364 +- Fixed LogoutToken verification for single value aud claims. #334 +- Update well known config value function response types. #376 ## [0.9.10] - 2022-09-30 From 4e325951c3e9edf7bcb3e12dfa9f9548513bd1d5 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Mon, 22 Apr 2024 11:25:52 +0200 Subject: [PATCH 203/223] chore: Update ci to support php 8.3 and add dependabot (#407) * Add php 8.3 to test matrix in github actions and updated actions * Updated readme PHP requirement to PHP 7.0+ * Added dependabot for GitHub Actions --- .github/dependabot.yml | 13 +++++++++++++ .github/workflows/build.yml | 6 +++--- CHANGELOG.md | 5 +++++ README.md | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0f11f4a3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 + +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 515cd64c..a3081708 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] steps: - name: Checkout @@ -25,9 +25,9 @@ jobs: php-version: ${{ matrix.php }} - name: Get composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d4be9ff7..dd05c792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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). +[unreleased] +- Updated CI to also test on PHP 8.3 #407 +- Updated readme PHP requirement to PHP 7.0+ #407 +- Added dependabot for GitHub Actions #407 + ## [1.0.0] - 2023-12-13 ### Added diff --git a/README.md b/README.md index 79318e50..173c2e4b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ the OpenID Connect protocol to set up authentication. A special thanks goes to Justin Richer and Amanda Anganes for their help and support of the protocol. # Requirements # - 1. PHP 5.4 or greater + 1. PHP 7.0 or greater 2. CURL extension 3. JSON extension From 73af840733b418fd03d6f3697d5074a20e81f0c3 Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Mon, 22 Apr 2024 05:27:03 -0400 Subject: [PATCH 204/223] docs: Update README.md to correct addScope parameter type in 1.0.0 (#405) * Update README.md Correct the calls to addScope which now requires an array, not a string * Replaced usage of array() with [] * remove redundant addScope call from documentation --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 173c2e4b..904b83ec 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ use Jumbojett\OpenIDConnectClient; $oidc = new OpenIDConnectClient('https://id.provider.com', 'ClientIDHere', 'ClientSecretHere'); -$oidc->providerConfigParam(array('token_endpoint'=>'https://id.provider.com/connect/token')); -$oidc->addScope('my_scope'); +$oidc->providerConfigParam(['token_endpoint'=>'https://id.provider.com/connect/token']); +$oidc->addScope(['my_scope']); // this assumes success (to validate check if the access_token property is there and a valid JWT) : $clientCredentialsToken = $oidc->requestClientCredentialsToken()->access_token; @@ -85,12 +85,12 @@ use Jumbojett\OpenIDConnectClient; $oidc = new OpenIDConnectClient('https://id.provider.com', 'ClientIDHere', 'ClientSecretHere'); -$oidc->providerConfigParam(array('token_endpoint'=>'https://id.provider.com/connect/token')); -$oidc->addScope('my_scope'); +$oidc->providerConfigParam(['token_endpoint'=>'https://id.provider.com/connect/token']); +$oidc->addScope(['my_scope']); //Add username and password -$oidc->addAuthParam(array('username'=>'')); -$oidc->addAuthParam(array('password'=>'')); +$oidc->addAuthParam(['username'=>'']); +$oidc->addAuthParam(['password'=>'']); //Perform the auth and return the token (to validate check if the access_token property is there and a valid JWT) : $token = $oidc->requestResourceOwnerToken(TRUE)->access_token; @@ -105,10 +105,9 @@ use Jumbojett\OpenIDConnectClient; $oidc = new OpenIDConnectClient('https://id.provider.com', 'ClientIDHere', 'ClientSecretHere'); -$oidc->setResponseTypes(array('id_token')); -$oidc->addScope(array('openid')); +$oidc->setResponseTypes(['id_token']); $oidc->setAllowImplicitFlow(true); -$oidc->addAuthParam(array('response_mode' => 'form_post')); +$oidc->addAuthParam(['response_mode' => 'form_post']); $oidc->setCertPath('/path/to/my.cert'); $oidc->authenticate(); $sub = $oidc->getVerifiedClaims('sub'); @@ -184,7 +183,7 @@ function handleLogout() { session_commit(); session_id($session_id_to_destroy); // switches to that session session_start(); - $_SESSION = array(); // effectively ends the session + $_SESSION = []; // effectively ends the session } } } From 6ac3ed427f9386a433b7d7506dc7bc7d0a59ea74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:30:43 +0200 Subject: [PATCH 205/223] chore(deps): bump actions/checkout from 2 to 4 (#416) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3081708..52f9e1d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 with: From f5fadf1436c33777fdded7e1226da9baaf87f76e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:31:03 +0200 Subject: [PATCH 206/223] chore(deps): bump actions/cache from 3 to 4 (#417) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 52f9e1d3..c27126e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} From e31ec338d36c7c942df6dec4a0903e1563fa2074 Mon Sep 17 00:00:00 2001 From: Tim Smid Date: Mon, 22 Apr 2024 11:37:47 +0200 Subject: [PATCH 207/223] fix: Cast SERVER_PORT to integer (#404) --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd05c792..05283d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated CI to also test on PHP 8.3 #407 - Updated readme PHP requirement to PHP 7.0+ #407 - Added dependabot for GitHub Actions #407 +- Cast `$_SERVER['SERVER_PORT']` to integer to prevent adding 80 or 443 port to redirect URL. #403 ## [1.0.0] - 2023-12-13 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 6aa80b17..aea060fc 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -696,7 +696,7 @@ public function getRedirectURL(): string if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = $_SERVER['SERVER_PORT']; + $port = (int)$_SERVER['SERVER_PORT']; } elseif ($protocol === 'https') { $port = 443; } else { From 0c8f54dd5f05bacee87f7abd10282a8c881affc3 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Mon, 22 Apr 2024 15:18:47 +0200 Subject: [PATCH 208/223] fix: Check if subject is equal to subject of id token when verifying JWT claims (#406) * Check if subject is equal to subject of id token when verifying JWT claims * Add fake sub in test claims --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 1 + tests/OpenIDConnectClientTest.php | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05283d97..93149158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated readme PHP requirement to PHP 7.0+ #407 - Added dependabot for GitHub Actions #407 - Cast `$_SERVER['SERVER_PORT']` to integer to prevent adding 80 or 443 port to redirect URL. #403 +- Check subject when verifying JWT #406 ## [1.0.0] - 2023-12-13 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index aea060fc..abb37d8e 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1206,6 +1206,7 @@ protected function verifyJWTClaims($claims, string $accessToken = null): bool } return (($this->validateIssuer($claims->iss)) && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) + && ($claims->sub === $this->getIdTokenPayload()->sub) && (!isset($claims->nonce) || $claims->nonce === $this->getNonce()) && ( !isset($claims->exp) || ((is_int($claims->exp)) && ($claims->exp >= time() - $this->leeway))) && ( !isset($claims->nbf) || ((is_int($claims->nbf)) && ($claims->nbf <= time() + $this->leeway))) diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index a16be71b..f895879c 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -26,6 +26,7 @@ public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() $fakeClaims = new StdClass(); $fakeClaims->iss = 'fake-issuer'; $fakeClaims->aud = 'fake-client-id'; + $fakeClaims->sub = 'fake-sub'; $fakeClaims->nonce = null; $_REQUEST['id_token'] = 'abc.123.xyz'; From 1a468a40175e6d3366328678309623985ca2b150 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Tue, 23 Apr 2024 11:16:21 +0200 Subject: [PATCH 209/223] fix: Removed duplicate check on jwks_uri and only check if jwks_uri exists when needed (#373) * Removed duplicate check on jwks_uri * Update CHANGELOG * Only check jwks_uri when needed * Update changelog --- CHANGELOG.md | 1 + src/OpenIDConnectClient.php | 17 +++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93149158..4055711f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added dependabot for GitHub Actions #407 - Cast `$_SERVER['SERVER_PORT']` to integer to prevent adding 80 or 443 port to redirect URL. #403 - Check subject when verifying JWT #406 +- Removed duplicate check on jwks_uri and only check if jwks_uri exists when needed #373 ## [1.0.0] - 2023-12-13 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index abb37d8e..adabf80c 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -469,12 +469,7 @@ public function verifyLogoutToken(): bool $claims = $this->decodeJWT($logout_token, 1); // Verify the signature - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify signature due to no jwks_uri being defined'); - } - if (!$this->verifyJWTSignature($logout_token)) { - throw new OpenIDConnectClientException('Back-channel logout: Unable to verify JWT signature'); - } + $this->verifySignatures($logout_token); // Verify Logout Token Claims if ($this->verifyLogoutTokenClaims($claims)) { @@ -1134,7 +1129,12 @@ public function verifyJWTSignature(string $jwt): bool $jwk = $header->jwk; $this->verifyJWKHeader($jwk); } else { - $jwks = json_decode($this->fetchURL($this->getProviderConfigValue('jwks_uri')), false); + $jwksUri = $this->getProviderConfigValue('jwks_uri'); + if (!$jwksUri) { + throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); + } + + $jwks = json_decode($this->fetchURL($jwksUri), false); if ($jwks === NULL) { throw new OpenIDConnectClientException('Error decoding JSON from jwks_uri'); } @@ -1164,9 +1164,6 @@ public function verifyJWTSignature(string $jwt): bool */ public function verifySignatures(string $jwt) { - if (!$this->getProviderConfigValue('jwks_uri')) { - throw new OpenIDConnectClientException ('Unable to verify signature due to no jwks_uri being defined'); - } if (!$this->verifyJWTSignature($jwt)) { throw new OpenIDConnectClientException ('Unable to verify signature'); } From 1e854438f32075f0670b7e48c9ab3587c9913a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:53:08 +0200 Subject: [PATCH 210/223] fix: method signatures after 1.0 release (#427) --- src/OpenIDConnectClient.php | 36 ++++++++++++++++++++----------- tests/OpenIDConnectClientTest.php | 35 +++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index adabf80c..60f89bd8 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -3,7 +3,7 @@ * * Copyright MITRE 2020 * - * OpenIDConnectClient for PHP5 + * OpenIDConnectClient for PHP7+ * Author: Michael Jett * * Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,7 +25,6 @@ use Error; use Exception; -use phpseclib3\Crypt\PublicKeyLoader; use phpseclib3\Crypt\RSA; use phpseclib3\Math\BigInteger; use stdClass; @@ -380,7 +379,7 @@ public function authenticate(): bool $accessToken = $_REQUEST['access_token'] ?? null; // Do an OpenID Connect session check - if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { + if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { throw new OpenIDConnectClientException('Unable to determine state'); } @@ -691,7 +690,7 @@ public function getRedirectURL(): string if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = (int)$_SERVER['SERVER_PORT']; + $port = $_SERVER['SERVER_PORT']; } elseif ($protocol === 'https') { $port = 443; } else { @@ -1221,10 +1220,9 @@ protected function urlEncode(string $str): string /** * @param string $jwt encoded JWT * @param int $section the section we would like to decode - * @return object + * @return object|null */ - protected function decodeJWT(string $jwt, int $section = 0): stdClass { - + protected function decodeJWT(string $jwt, int $section = 0) { $parts = explode('.', $jwt); return json_decode(base64url_decode($parts[$section]), false); } @@ -1688,7 +1686,10 @@ public function revokeToken(string $token, string $token_type_hint = '', string return json_decode($this->fetchURL($revocation_endpoint, $post_params, $headers), false); } - public function getClientName(): string + /** + * @return string|null + */ + public function getClientName() { return $this->clientName; } @@ -1698,14 +1699,14 @@ public function setClientName(string $clientName) { } /** - * @return string + * @return string|null */ public function getClientID() { return $this->clientID; } /** - * @return string + * @return string|null */ public function getClientSecret() { return $this->clientSecret; @@ -1720,17 +1721,26 @@ public function setAccessToken(string $accessToken) { $this->accessToken = $accessToken; } - public function getAccessToken(): string + /** + * @return string|null + */ + public function getAccessToken() { return $this->accessToken; } - public function getRefreshToken(): string + /** + * @return string|null + */ + public function getRefreshToken() { return $this->refreshToken; } - public function getIdToken(): string + /** + * @return string|null + */ + public function getIdToken() { return $this->idToken; } diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index f895879c..88d98989 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -7,9 +7,38 @@ class OpenIDConnectClientTest extends TestCase { - /** - * @return void - */ + public function testJWTDecode() + { + $client = new OpenIDConnectClient(); + $client->setAccessToken(''); + $header = $client->getAccessTokenHeader(); + self::assertEquals('', $header); + } + + public function testGetNull() + { + $client = new OpenIDConnectClient(); + self::assertNull($client->getAccessToken()); + self::assertNull($client->getRefreshToken()); + self::assertNull($client->getIdToken()); + self::assertNull($client->getClientName()); + self::assertNull($client->getClientID()); + self::assertNull($client->getClientSecret()); + self::assertNull($client->getCertPath()); + } + + public function testResponseTypes() + { + $client = new OpenIDConnectClient(); + self::assertEquals([], $client->getResponseTypes()); + + $client->setResponseTypes('foo'); + self::assertEquals(['foo'], $client->getResponseTypes()); + + $client->setResponseTypes(['bar', 'ipsum']); + self::assertEquals(['foo', 'bar', 'ipsum'], $client->getResponseTypes()); + } + public function testGetRedirectURL() { $client = new OpenIDConnectClient(); From 0509be83cae7984e011ad3ee26f2dae4af67edf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:29:25 +0200 Subject: [PATCH 211/223] fix: handle JWT decode of non JWT tokens (#428) --- CHANGELOG.md | 2 ++ src/OpenIDConnectClient.php | 14 +++++++++----- tests/OpenIDConnectClientTest.php | 11 +++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4055711f..fa7fddf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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). [unreleased] +- Fix JWT decode of non JWT tokens #428 +- Fix method signatures #427 - Updated CI to also test on PHP 8.3 #407 - Updated readme PHP requirement to PHP 7.0+ #407 - Added dependabot for GitHub Actions #407 diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 60f89bd8..16c3d656 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1220,11 +1220,11 @@ protected function urlEncode(string $str): string /** * @param string $jwt encoded JWT * @param int $section the section we would like to decode - * @return object|null + * @return object|string|null */ protected function decodeJWT(string $jwt, int $section = 0) { $parts = explode('.', $jwt); - return json_decode(base64url_decode($parts[$section]), false); + return json_decode(base64url_decode($parts[$section] ?? ''), false); } /** @@ -1737,6 +1737,10 @@ public function getRefreshToken() return $this->refreshToken; } + public function setIdToken(string $idToken) { + $this->idToken = $idToken; + } + /** * @return string|null */ @@ -1753,21 +1757,21 @@ public function getAccessTokenHeader() { } /** - * @return object + * @return object|string|null */ public function getAccessTokenPayload() { return $this->decodeJWT($this->accessToken, 1); } /** - * @return object + * @return object|string|null */ public function getIdTokenHeader() { return $this->decodeJWT($this->idToken); } /** - * @return object + * @return object|string|null */ public function getIdTokenPayload() { return $this->decodeJWT($this->idToken, 1); diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 88d98989..3dc4709f 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -10,9 +10,20 @@ class OpenIDConnectClientTest extends TestCase public function testJWTDecode() { $client = new OpenIDConnectClient(); + # access token $client->setAccessToken(''); $header = $client->getAccessTokenHeader(); self::assertEquals('', $header); + $payload = $client->getAccessTokenPayload(); + self::assertEquals('', $payload); + + # id token + $client->setIdToken(''); + $header = $client->getIdTokenHeader(); + self::assertEquals('', $header); + $payload = $client->getIdTokenPayload(); + self::assertEquals('', $payload); + } public function testGetNull() From 0fbf8f2533b4529c9fd14b1bf3cb2d29cb1ef061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:33:44 +0200 Subject: [PATCH 212/223] chore: enable dependabot for composer (#429) --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0f11f4a3..a54473b4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,3 +11,9 @@ updates: directory: "/" schedule: interval: "weekly" + + # Maintain dependencies for composer + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "weekly" From 036530bb69c32849478a6bde2fe6b4c89e60b3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:39:11 +0200 Subject: [PATCH 213/223] ci: run GitHub workflows on pull requests and pushes to master (#431) --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c27126e2..31d0ed11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,13 @@ --- name: build -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + branches: + - master env: DEFAULT_COMPOSER_FLAGS: "--prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi" From e316397c876a602ff87d62bee2adb96cdb497f9a Mon Sep 17 00:00:00 2001 From: Artem Boyko Date: Thu, 5 Sep 2024 17:43:09 +0300 Subject: [PATCH 214/223] chore(deps): update phpseclib/phpseclib requirement from ~3.0 to ^3.0.7 * Update phpseclib/phpseclib to minimum 2.0.31 or 3.0.7 * Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3fa6d231..3cd6fc38 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "php": ">=7.0", "ext-json": "*", "ext-curl": "*", - "phpseclib/phpseclib": "~3.0" + "phpseclib/phpseclib": "^3.0.7" }, "require-dev": { "roave/security-advisories": "dev-latest", From 22560300d349b32c17e073198ec3b2f6dd2c0230 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:55:24 +0200 Subject: [PATCH 215/223] chore(deps-dev): update yoast/phpunit-polyfills requirement from ^1.0 to ^2.0 (#430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps-dev): update yoast/phpunit-polyfills requirement Updates the requirements on [yoast/phpunit-polyfills](https://github.com/Yoast/PHPUnit-Polyfills) to permit the latest version. - [Release notes](https://github.com/Yoast/PHPUnit-Polyfills/releases) - [Changelog](https://github.com/Yoast/PHPUnit-Polyfills/blob/2.x/CHANGELOG.md) - [Commits](https://github.com/Yoast/PHPUnit-Polyfills/compare/1.0.0...2.0.1) --- updated-dependencies: - dependency-name: yoast/phpunit-polyfills dependency-type: direct:development ... Signed-off-by: dependabot[bot] * fix: remove --verbose from phpunit * fix: force usage of phpunit < 10 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- composer.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31d0ed11..38080af1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,4 +41,4 @@ jobs: - name: Install dependencies run: composer update $DEFAULT_COMPOSER_FLAGS - name: Run unit tests - run: vendor/bin/phpunit --verbose --colors=always tests + run: vendor/bin/phpunit --colors=always tests diff --git a/composer.json b/composer.json index 3cd6fc38..64825884 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,9 @@ "phpseclib/phpseclib": "^3.0.7" }, "require-dev": { + "phpunit/phpunit": "<10", "roave/security-advisories": "dev-latest", - "yoast/phpunit-polyfills": "^1.0" + "yoast/phpunit-polyfills": "^2.0" }, "archive" : { "exclude" : [ From 765ddbd65643d69edce461617ea3c620d582714e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:16:24 +0200 Subject: [PATCH 216/223] fix: protected $responseCode to allow proper overloading of fetchURL() (#433) --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index 16c3d656..e12ad9c6 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -144,7 +144,7 @@ class OpenIDConnectClient /** * @var int|null Response code from the server */ - private $responseCode; + protected $responseCode; /** * @var string|null Content type from the server From 75693117e99c209b9aef70fc7b279fe561304cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:22:32 +0200 Subject: [PATCH 217/223] release: v1.0.1 (#432) --- CHANGELOG.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa7fddf6..1b39eead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,11 @@ 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). -[unreleased] +## [1.0.1] - 2024-09-05 + +### Fixed - Fix JWT decode of non JWT tokens #428 - Fix method signatures #427 -- Updated CI to also test on PHP 8.3 #407 -- Updated readme PHP requirement to PHP 7.0+ #407 -- Added dependabot for GitHub Actions #407 - Cast `$_SERVER['SERVER_PORT']` to integer to prevent adding 80 or 443 port to redirect URL. #403 - Check subject when verifying JWT #406 - Removed duplicate check on jwks_uri and only check if jwks_uri exists when needed #373 From db1ed8b5f1664db3af99feba114de34542a10a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 13 Sep 2024 08:52:32 +0200 Subject: [PATCH 218/223] fix: bring back #404 (#437) --- src/OpenIDConnectClient.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e12ad9c6..e3f9d3f2 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -690,7 +690,8 @@ public function getRedirectURL(): string if (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { $port = (int)$_SERVER['HTTP_X_FORWARDED_PORT']; } elseif (isset($_SERVER['SERVER_PORT'])) { - $port = $_SERVER['SERVER_PORT']; + # keep this case - even if some tool claim it is unnecessary + $port = (int)$_SERVER['SERVER_PORT']; } elseif ($protocol === 'https') { $port = 443; } else { From a5994e793a72b19747da880b6428b55638a3ebcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:07:45 +0200 Subject: [PATCH 219/223] test: add unit test for SERVER_PORT type cast (#438) --- tests/OpenIDConnectClientTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 3dc4709f..45adc7b3 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -58,7 +58,11 @@ public function testGetRedirectURL() $_SERVER['SERVER_NAME'] = 'domain.test'; $_SERVER['REQUEST_URI'] = '/path/index.php?foo=bar&baz#fragment'; + $_SERVER['SERVER_PORT'] = '443'; self::assertSame('http://domain.test/path/index.php', $client->getRedirectURL()); + + $_SERVER['SERVER_PORT'] = '8888'; + self::assertSame('http://domain.test:8888/path/index.php', $client->getRedirectURL()); } public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce() From 9af21bd04f5b564bdd18993a2b15d88b7594f152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:09:33 +0200 Subject: [PATCH 220/223] release: v1.0.2 (#439) --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b39eead..0e1c7105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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). +## [1.0.1] - 2024-09-13 + +### Fixed +- Cast `$_SERVER['SERVER_PORT']` to integer to prevent adding 80 or 443 port to redirect URL. #437 + ## [1.0.1] - 2024-09-05 ### Fixed From 60919af91a29b61d728d8b5a02d8af84271d0eaa Mon Sep 17 00:00:00 2001 From: Robert Vogel <1201528+osnard@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:48:09 +0200 Subject: [PATCH 221/223] Fix TypeError in `verifyJWTClaims` (#442) ... when ClientID does not match Co-authored-by: Robert Vogel --- src/OpenIDConnectClient.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e3f9d3f2..b38a81cd 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -1201,8 +1201,10 @@ protected function verifyJWTClaims($claims, string $accessToken = null): bool $len = ((int)$bit)/16; $expected_at_hash = $this->urlEncode(substr(hash('sha'.$bit, $accessToken, true), 0, $len)); } + $auds = $claims->aud; + $auds = is_array( $auds ) ? $auds : [ $auds ]; return (($this->validateIssuer($claims->iss)) - && (($claims->aud === $this->clientID) || in_array($this->clientID, $claims->aud, true)) + && (in_array($this->clientID, $auds, true)) && ($claims->sub === $this->getIdTokenPayload()->sub) && (!isset($claims->nonce) || $claims->nonce === $this->getNonce()) && ( !isset($claims->exp) || ((is_int($claims->exp)) && ($claims->exp >= time() - $this->leeway))) From 97adbcee4b519700ad14abc9f73962077a4c6b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:06:55 +0200 Subject: [PATCH 222/223] test: unit tests for verifyJWTClaims and different aud claims (#443) --- tests/OpenIDConnectClientTest.php | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/OpenIDConnectClientTest.php b/tests/OpenIDConnectClientTest.php index 45adc7b3..4b46923d 100644 --- a/tests/OpenIDConnectClientTest.php +++ b/tests/OpenIDConnectClientTest.php @@ -7,6 +7,48 @@ class OpenIDConnectClientTest extends TestCase { + public function testValidateClaims() + { + $client = new class extends OpenIDConnectClient { + public function testVerifyJWTClaims($claims): bool + { + return $this->verifyJWTClaims($claims); + } + public function getIdTokenPayload() + { + return (object)[ + 'sub' => 'sub' + ]; + } + }; + $client->setClientID('client-id'); + $client->setIssuer('issuer'); + $client->setIdToken(''); + + # simple aud + $valid = $client->testVerifyJWTClaims((object)[ + 'aud' => 'client-id', + 'iss' => 'issuer', + 'sub' => 'sub', + ]); + self::assertTrue($valid); + + # array aud + $valid = $client->testVerifyJWTClaims((object)[ + 'aud' => ['client-id'], + 'iss' => 'issuer', + 'sub' => 'sub', + ]); + self::assertTrue($valid); + + # aud not matching + $valid = $client->testVerifyJWTClaims((object)[ + 'aud' => ['ipsum'], + 'iss' => 'issuer', + 'sub' => 'sub', + ]); + self::assertFalse($valid); + } public function testJWTDecode() { $client = new OpenIDConnectClient(); @@ -23,7 +65,6 @@ public function testJWTDecode() self::assertEquals('', $header); $payload = $client->getIdTokenPayload(); self::assertEquals('', $payload); - } public function testGetNull() From f7c91b9079bf96323b5eb3ab4383f297f21d98d1 Mon Sep 17 00:00:00 2001 From: Rick Lambrechts Date: Fri, 27 Sep 2024 13:01:32 +0200 Subject: [PATCH 223/223] fix: protected responseContentType to allow overloading of fetchUrl function (#446) --- src/OpenIDConnectClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index b38a81cd..07c00540 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -149,7 +149,7 @@ class OpenIDConnectClient /** * @var string|null Content type from the server */ - private $responseContentType; + protected $responseContentType; /** * @var array holds response types