diff --git a/lib/thenetworg/oauth2-azure/.gitignore b/lib/thenetworg/oauth2-azure/.gitignore index 9c9c8f271..57a23bd06 100644 --- a/lib/thenetworg/oauth2-azure/.gitignore +++ b/lib/thenetworg/oauth2-azure/.gitignore @@ -3,3 +3,7 @@ composer.phar composer.lock .DS_Store + +# IDE +/.idea +/.vscode diff --git a/lib/thenetworg/oauth2-azure/README.md b/lib/thenetworg/oauth2-azure/README.md index fbf296595..ee6f06d3f 100644 --- a/lib/thenetworg/oauth2-azure/README.md +++ b/lib/thenetworg/oauth2-azure/README.md @@ -46,7 +46,7 @@ $provider = new TheNetworg\OAuth2\Client\Provider\Azure([ 'clientSecret' => '{azure-client-secret}', 'redirectUri' => 'https://example.com/callback-url', //Optional - 'scopes' => 'openid', + 'scopes' => ['openid'], //Optional 'defaultEndPointVersion' => '2.0' ]); diff --git a/lib/thenetworg/oauth2-azure/composer.json b/lib/thenetworg/oauth2-azure/composer.json index e2e3b922d..4fb348463 100644 --- a/lib/thenetworg/oauth2-azure/composer.json +++ b/lib/thenetworg/oauth2-azure/composer.json @@ -22,9 +22,11 @@ "sso" ], "require": { - "php": "^5.6|^7.0|^8.0", + "ext-json": "*", + "ext-openssl": "*", + "php": "^7.1|^8.0", "league/oauth2-client": "~2.0", - "firebase/php-jwt": "~3.0||~4.0||~5.0" + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0" }, "autoload": { "psr-4": { diff --git a/lib/thenetworg/oauth2-azure/src/Provider/Azure.php b/lib/thenetworg/oauth2-azure/src/Provider/Azure.php index d07fdef34..50aec8235 100644 --- a/lib/thenetworg/oauth2-azure/src/Provider/Azure.php +++ b/lib/thenetworg/oauth2-azure/src/Provider/Azure.php @@ -3,9 +3,13 @@ namespace TheNetworg\OAuth2\Client\Provider; use Firebase\JWT\JWT; +use Firebase\JWT\JWK; +use Firebase\JWT\Key; use League\OAuth2\Client\Grant\AbstractGrant; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Provider\ResourceOwnerInterface; +use League\OAuth2\Client\Token\AccessTokenInterface; use League\OAuth2\Client\Tool\BearerAuthorizationTrait; use Psr\Http\Message\ResponseInterface; use TheNetworg\OAuth2\Client\Grant\JwtBearer; @@ -66,7 +70,8 @@ class Azure extends AbstractProvider } if (!array_key_exists($version, $this->openIdConfiguration[$tenant])) { $versionInfix = $this->getVersionUriInfix($version); - $openIdConfigurationUri = 'https://login.microsoftonline.com/' . $tenant . $versionInfix . '/.well-known/openid-configuration'; + $openIdConfigurationUri = $this->urlLogin . $tenant . $versionInfix . '/.well-known/openid-configuration?appid=' . $this->clientId; + $factory = $this->getRequestFactory(); $request = $factory->getRequestWithOptions( 'get', @@ -80,19 +85,28 @@ class Azure extends AbstractProvider return $this->openIdConfiguration[$tenant][$version]; } - public function getBaseAuthorizationUrl() + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl(): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['authorization_endpoint']; } - public function getBaseAccessTokenUrl(array $params) + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['token_endpoint']; } - public function getAccessToken($grant, array $options = []) + /** + * @inheritdoc + */ + public function getAccessToken($grant, array $options = []): AccessTokenInterface { if ($this->defaultEndPointVersion != self::ENDPOINT_VERSION_2_0) { // Version 2.0 does not support the resources parameter @@ -106,14 +120,21 @@ class Azure extends AbstractProvider return parent::getAccessToken($grant, $options); } - public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token): ResourceOwnerInterface { $data = $token->getIdTokenClaims(); return $this->createResourceOwner($data, $token); } - public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token): string { + return ''; // shouldn't that return such a URL? } public function getObjects($tenant, $ref, &$accessToken, $headers = []) @@ -164,11 +185,11 @@ class Azure extends AbstractProvider return 'https://' . $openIdConfiguration['msgraph_host']; } - public function get($ref, &$accessToken, $headers = []) + public function get($ref, &$accessToken, $headers = [], $doNotWrap = false) { $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); - return $this->wrapResponse($response); + return $doNotWrap ? $response : $this->wrapResponse($response); } public function post($ref, $body, &$accessToken, $headers = []) @@ -352,8 +373,24 @@ class Azure extends AbstractProvider $publicKey = $pkey_array ['key']; - $keys[$keyinfo['kid']] = $publicKey; + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256'); } + } else if (isset($keyinfo['n']) && isset($keyinfo['e'])) { + $pkey_object = JWK::parseKey($keyinfo); + + if ($pkey_object === false) { + throw new \RuntimeException('An attempt to read a public key from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $pkey_array = openssl_pkey_get_details($pkey_object); + + if ($pkey_array === false) { + throw new \RuntimeException('An attempt to get a public key as an array from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $publicKey = $pkey_array ['key']; + + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256');; } } @@ -382,7 +419,10 @@ class Azure extends AbstractProvider return $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); } - protected function checkResponse(ResponseInterface $response, $data) + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data): void { if (isset($data['odata.error']) || isset($data['error'])) { if (isset($data['odata.error']['message']['value'])) { @@ -402,27 +442,39 @@ class Azure extends AbstractProvider throw new IdentityProviderException( $message, $response->getStatusCode(), - $response + $response->getBody() ); } } - protected function getDefaultScopes() + /** + * @inheritdoc + */ + protected function getDefaultScopes(): array { return $this->scope; } - protected function getScopeSeparator() + /** + * @inheritdoc + */ + protected function getScopeSeparator(): string { return $this->scopeSeparator; } - protected function createAccessToken(array $response, AbstractGrant $grant) + /** + * @inheritdoc + */ + protected function createAccessToken(array $response, AbstractGrant $grant): AccessToken { return new AccessToken($response, $this); } - protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token): AzureResourceOwner { return new AzureResourceOwner($response); } diff --git a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php index 3899f8377..7de80f9e5 100644 --- a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php +++ b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php @@ -19,6 +19,8 @@ class AccessToken extends \League\OAuth2\Client\Token\AccessToken if (!empty($options['id_token'])) { $this->idToken = $options['id_token']; + unset($this->values['id_token']); + $keys = $provider->getJwtVerificationKeys(); $idTokenClaims = null; try { @@ -45,8 +47,27 @@ class AccessToken extends \League\OAuth2\Client\Token\AccessToken } } + public function getIdToken() + { + return $this->idToken; + } + public function getIdTokenClaims() { return $this->idTokenClaims; } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = parent::jsonSerialize(); + + if ($this->idToken) { + $parameters['id_token'] = $this->idToken; + } + + return $parameters; + } }