From 5c2f16f95b0406060a8d4248efa155c3f9b0d4f0 Mon Sep 17 00:00:00 2001 From: lyceus Date: Fri, 16 Nov 2018 22:14:35 +0000 Subject: [PATCH] restructured Azure to not require custom AccessToken and still be able to create AzureResourceOwner --- src/Provider/Azure.php | 70 ++++++++++++++++++++++++++++++++++---- src/Token/AccessToken.php | 71 --------------------------------------- 2 files changed, 64 insertions(+), 77 deletions(-) delete mode 100644 src/Token/AccessToken.php diff --git a/src/Provider/Azure.php b/src/Provider/Azure.php index 9fcf1d2..69ee6ff 100644 --- a/src/Provider/Azure.php +++ b/src/Provider/Azure.php @@ -9,7 +9,8 @@ use League\OAuth2\Client\Tool\BearerAuthorizationTrait; use Psr\Http\Message\ResponseInterface; use TheNetworg\OAuth2\Client\Grant\JwtBearer; -use TheNetworg\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessToken; +use RuntimeException; class Azure extends AbstractProvider { @@ -59,13 +60,70 @@ public function getAccessToken($grant, array $options = []) return parent::getAccessToken($grant, $options); } - public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token) + /** + * @param string $data + * @return array + * @throws RuntimeException + */ + public function readIdToken($data) { - $data = $token->getIdTokenClaims(); - return $this->createResourceOwner($data, $token); + $idTokenClaims = null; + try { + $tks = explode('.', $data); + // Check if the id_token contains signature + if (count($tks) < 2) { + throw new RuntimeException('Invalid id_token'); + } + if (3 == count($tks) && !empty($tks[2])) { + $keys = $this->getJwtVerificationKeys(); + $idTokenClaims = (array)JWT::decode($data, $keys, ['RS256']); + } else { + // The id_token is unsigned (coming from v1.0 endpoint) - https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx + + // Since idToken is not signed, we just do OAuth2 flow without validating the id_token + // // Validate the access_token signature first by parsing it as JWT into claims + // $accessTokenClaims = (array)JWT::decode($options['access_token'], $keys, ['RS256']); + // Then parse the idToken claims only without validating the signature + $idTokenClaims = (array)JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); + } + } catch (JWT_Exception $e) { + throw new RuntimeException('Unable to parse the id_token!'); + } + if ($this->getClientId() != $idTokenClaims['aud']) { + throw new RuntimeException('The audience is invalid!'); + } + if ($idTokenClaims['nbf'] > time() || $idTokenClaims['exp'] < time()) { + // Additional validation is being performed in firebase/JWT itself + throw new RuntimeException('The id_token is invalid!'); + } + + if ('common' == $this->tenant) { + $this->tenant = $idTokenClaims['tid']; + } + $tenant = $this->getTenantDetails($this->tenant); + if ($idTokenClaims['iss'] != $tenant['issuer']) { + throw new RuntimeException('Invalid token issuer!'); + } + + return $idTokenClaims; + } + + /** + * @param AccessToken $token + * @return null|AzureResourceOwner + */ + public function getResourceOwner(AccessToken $token) + { + $tokenValues = $token->getValues(); + if(empty($tokenValues['id_token'])) { + return NULL; + } + $id = $this->readIdToken($tokenValues['id_token']); + + return $this->createResourceOwner($id, $token); } - public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) + public function getResourceOwnerDetailsUrl(AccessToken $token) { } @@ -313,7 +371,7 @@ protected function createAccessToken(array $response, AbstractGrant $grant) return new AccessToken($response, $this); } - protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + protected function createResourceOwner(array $response, AccessToken $token) { return new AzureResourceOwner($response); } diff --git a/src/Token/AccessToken.php b/src/Token/AccessToken.php deleted file mode 100644 index 4033eb5..0000000 --- a/src/Token/AccessToken.php +++ /dev/null @@ -1,71 +0,0 @@ -idToken = $options['id_token']; - - $keys = $provider->getJwtVerificationKeys(); - $idTokenClaims = null; - try { - $tks = explode('.', $this->idToken); - // Check if the id_token contains signature - if (3 == count($tks) && !empty($tks[2])) { - $idTokenClaims = (array)JWT::decode($this->idToken, $keys, ['RS256']); - } else { - // The id_token is unsigned (coming from v1.0 endpoint) - https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx - - // Since idToken is not signed, we just do OAuth2 flow without validating the id_token - // // Validate the access_token signature first by parsing it as JWT into claims - // $accessTokenClaims = (array)JWT::decode($options['access_token'], $keys, ['RS256']); - // Then parse the idToken claims only without validating the signature - $idTokenClaims = (array)JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); - } - } catch (JWT_Exception $e) { - throw new RuntimeException('Unable to parse the id_token!'); - } - if ($provider->getClientId() != $idTokenClaims['aud']) { - throw new RuntimeException('The audience is invalid!'); - } - if ($idTokenClaims['nbf'] > time() || $idTokenClaims['exp'] < time()) { - // Additional validation is being performed in firebase/JWT itself - throw new RuntimeException('The id_token is invalid!'); - } - - if ('common' == $provider->tenant) { - $provider->tenant = $idTokenClaims['tid']; - - $tenant = $provider->getTenantDetails($provider->tenant); - if ($idTokenClaims['iss'] != $tenant['issuer']) { - throw new RuntimeException('Invalid token issuer!'); - } - } else { - $tenant = $provider->getTenantDetails($provider->tenant); - if ($idTokenClaims['iss'] != $tenant['issuer']) { - throw new RuntimeException('Invalid token issuer!'); - } - } - - $this->idTokenClaims = $idTokenClaims; - } - } - - public function getIdTokenClaims() - { - return $this->idTokenClaims; - } -}