%PDF- %PDF-
Direktori : /var/www/projetos/suporte.iigd.com.br/vendor/thenetworg/oauth2-azure/src/Provider/ |
Current File : //var/www/projetos/suporte.iigd.com.br/vendor/thenetworg/oauth2-azure/src/Provider/Azure.php |
<?php 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; use TheNetworg\OAuth2\Client\Token\AccessToken; class Azure extends AbstractProvider { const ENDPOINT_VERSION_1_0 = '1.0'; const ENDPOINT_VERSION_2_0 = '2.0'; const ENDPOINT_VERSIONS = [self::ENDPOINT_VERSION_1_0, self::ENDPOINT_VERSION_2_0]; use BearerAuthorizationTrait; public $urlLogin = 'https://login.microsoftonline.com/'; /** @var array|null */ protected $openIdConfiguration; public $scope = []; public $scopeSeparator = ' '; public $tenant = 'common'; public $defaultEndPointVersion = self::ENDPOINT_VERSION_1_0; public $urlAPI = 'https://graph.windows.net/'; public $resource = ''; public $API_VERSION = '1.6'; public $authWithResource = true; public function __construct(array $options = [], array $collaborators = []) { parent::__construct($options, $collaborators); if (isset($options['scopes'])) { $this->scope = array_merge($options['scopes'], $this->scope); } if (isset($options['defaultEndPointVersion']) && in_array($options['defaultEndPointVersion'], self::ENDPOINT_VERSIONS, true)) { $this->defaultEndPointVersion = $options['defaultEndPointVersion']; } $this->grantFactory->setGrant('jwt_bearer', new JwtBearer()); } /** * @param string $tenant * @param string $version */ protected function getOpenIdConfiguration($tenant, $version) { if (!is_array($this->openIdConfiguration)) { $this->openIdConfiguration = []; } if (!array_key_exists($tenant, $this->openIdConfiguration)) { $this->openIdConfiguration[$tenant] = []; } if (!array_key_exists($version, $this->openIdConfiguration[$tenant])) { $versionInfix = $this->getVersionUriInfix($version); $openIdConfigurationUri = $this->urlLogin . $tenant . $versionInfix . '/.well-known/openid-configuration?appid=' . $this->clientId; $factory = $this->getRequestFactory(); $request = $factory->getRequestWithOptions( 'get', $openIdConfigurationUri, [] ); $response = $this->getParsedResponse($request); $this->openIdConfiguration[$tenant][$version] = $response; } return $this->openIdConfiguration[$tenant][$version]; } /** * @inheritdoc */ public function getBaseAuthorizationUrl(): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['authorization_endpoint']; } /** * @inheritdoc */ public function getBaseAccessTokenUrl(array $params): string { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); return $openIdConfiguration['token_endpoint']; } /** * @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 // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow // while version 1.0 does recommend it // https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code if ($this->authWithResource) { $options['resource'] = $this->resource ? $this->resource : $this->urlAPI; } } return parent::getAccessToken($grant, $options); } /** * @inheritdoc */ public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token): ResourceOwnerInterface { $data = $token->getIdTokenClaims(); return $this->createResourceOwner($data, $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 = []) { $objects = []; $response = null; do { if (false === filter_var($ref, FILTER_VALIDATE_URL)) { $ref = $tenant . '/' . $ref; } $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); $values = $response; if (isset($response['value'])) { $values = $response['value']; } foreach ($values as $value) { $objects[] = $value; } if (isset($response['odata.nextLink'])) { $ref = $response['odata.nextLink']; } elseif (isset($response['@odata.nextLink'])) { $ref = $response['@odata.nextLink']; } else { $ref = null; } } while (null != $ref); return $objects; } /** * @param $accessToken AccessToken|null * @return string */ public function getRootMicrosoftGraphUri($accessToken) { if (is_null($accessToken)) { $tenant = $this->tenant; $version = $this->defaultEndPointVersion; } else { $idTokenClaims = $accessToken->getIdTokenClaims(); $tenant = array_key_exists('tid', $idTokenClaims) ? $idTokenClaims['tid'] : $this->tenant; $version = array_key_exists('ver', $idTokenClaims) ? $idTokenClaims['ver'] : $this->defaultEndPointVersion; } $openIdConfiguration = $this->getOpenIdConfiguration($tenant, $version); return 'https://' . $openIdConfiguration['msgraph_host']; } public function get($ref, &$accessToken, $headers = [], $doNotWrap = false) { $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); return $doNotWrap ? $response : $this->wrapResponse($response); } public function post($ref, $body, &$accessToken, $headers = []) { $response = $this->request('post', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); return $this->wrapResponse($response); } public function put($ref, $body, &$accessToken, $headers = []) { $response = $this->request('put', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); return $this->wrapResponse($response); } public function delete($ref, &$accessToken, $headers = []) { $response = $this->request('delete', $ref, $accessToken, ['headers' => $headers]); return $this->wrapResponse($response); } public function patch($ref, $body, &$accessToken, $headers = []) { $response = $this->request('patch', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); return $this->wrapResponse($response); } public function request($method, $ref, &$accessToken, $options = []) { if ($accessToken->hasExpired()) { $accessToken = $this->getAccessToken('refresh_token', [ 'refresh_token' => $accessToken->getRefreshToken(), ]); } $url = null; if (false !== filter_var($ref, FILTER_VALIDATE_URL)) { $url = $ref; } else { if (false !== strpos($this->urlAPI, 'graph.windows.net')) { $tenant = 'common'; if (property_exists($this, 'tenant')) { $tenant = $this->tenant; } $ref = "$tenant/$ref"; $url = $this->urlAPI . $ref; $url .= (false === strrpos($url, '?')) ? '?' : '&'; $url .= 'api-version=' . $this->API_VERSION; } else { $url = $this->urlAPI . $ref; } } if (isset($options['body']) && ('array' == gettype($options['body']) || 'object' == gettype($options['body']))) { $options['body'] = json_encode($options['body']); } if (!isset($options['headers']['Content-Type']) && isset($options['body'])) { $options['headers']['Content-Type'] = 'application/json'; } $request = $this->getAuthenticatedRequest($method, $url, $accessToken, $options); $response = $this->getParsedResponse($request); return $response; } public function getClientId() { return $this->clientId; } /** * Obtain URL for logging out the user. * * @param $post_logout_redirect_uri string The URL which the user should be redirected to after logout * * @return string */ public function getLogoutUrl($post_logout_redirect_uri = "") { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); $logoutUri = $openIdConfiguration['end_session_endpoint']; if (!empty($post_logout_redirect_uri)) { $logoutUri .= '?post_logout_redirect_uri=' . rawurlencode($post_logout_redirect_uri); } return $logoutUri; } /** * Validate the access token you received in your application. * * @param $accessToken string The access token you received in the authorization header. * * @return array */ public function validateAccessToken($accessToken) { $keys = $this->getJwtVerificationKeys(); $tokenClaims = (array)JWT::decode($accessToken, $keys, ['RS256']); $this->validateTokenClaims($tokenClaims); return $tokenClaims; } /** * Validate the access token claims from an access token you received in your application. * * @param $tokenClaims array The token claims from an access token you received in the authorization header. * * @return void */ public function validateTokenClaims($tokenClaims) { if ($this->getClientId() != $tokenClaims['aud']) { throw new \RuntimeException('The client_id / audience is invalid!'); } if ($tokenClaims['nbf'] > time() || $tokenClaims['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 = $tokenClaims['tid']; } $version = array_key_exists('ver', $tokenClaims) ? $tokenClaims['ver'] : $this->defaultEndPointVersion; $tenant = $this->getTenantDetails($this->tenant, $version); if ($tokenClaims['iss'] != $tenant['issuer']) { throw new \RuntimeException('Invalid token issuer (tokenClaims[iss]' . $tokenClaims['iss'] . ', tenant[issuer] ' . $tenant['issuer'] . ')!'); } } /** * Get JWT verification keys from Azure Active Directory. * * @return array */ public function getJwtVerificationKeys() { $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); $keysUri = $openIdConfiguration['jwks_uri']; $factory = $this->getRequestFactory(); $request = $factory->getRequestWithOptions('get', $keysUri, []); $response = $this->getParsedResponse($request); $keys = []; foreach ($response['keys'] as $i => $keyinfo) { if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) { foreach ($keyinfo['x5c'] as $encodedkey) { $cert = '-----BEGIN CERTIFICATE-----' . PHP_EOL . chunk_split($encodedkey, 64, PHP_EOL) . '-----END CERTIFICATE-----' . PHP_EOL; $cert_object = openssl_x509_read($cert); if ($cert_object === false) { throw new \RuntimeException('An attempt to read ' . $encodedkey . ' as a certificate failed.'); } $pkey_object = openssl_pkey_get_public($cert_object); if ($pkey_object === false) { throw new \RuntimeException('An attempt to read a public key from a ' . $encodedkey . ' 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 ' . $encodedkey . ' certificate failed.'); } $publicKey = $pkey_array ['key']; $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');; } } return $keys; } protected function getVersionUriInfix($version) { return ($version == self::ENDPOINT_VERSION_2_0) ? '/v' . self::ENDPOINT_VERSION_2_0 : ''; } /** * Get the specified tenant's details. * * @param string $tenant * @param string|null $version * * @return array * @throws IdentityProviderException */ public function getTenantDetails($tenant, $version) { return $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); } /** * @inheritdoc */ protected function checkResponse(ResponseInterface $response, $data): void { if (isset($data['odata.error']) || isset($data['error'])) { if (isset($data['odata.error']['message']['value'])) { $message = $data['odata.error']['message']['value']; } elseif (isset($data['error']['message'])) { $message = $data['error']['message']; } elseif (isset($data['error']) && !is_array($data['error'])) { $message = $data['error']; } else { $message = $response->getReasonPhrase(); } if (isset($data['error_description']) && !is_array($data['error_description'])) { $message .= PHP_EOL . $data['error_description']; } throw new IdentityProviderException( $message, $response->getStatusCode(), $response->getBody() ); } } /** * @inheritdoc */ protected function getDefaultScopes(): array { return $this->scope; } /** * @inheritdoc */ protected function getScopeSeparator(): string { return $this->scopeSeparator; } /** * @inheritdoc */ protected function createAccessToken(array $response, AbstractGrant $grant): AccessToken { return new AccessToken($response, $this); } /** * @inheritdoc */ protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token): AzureResourceOwner { return new AzureResourceOwner($response); } private function wrapResponse($response) { if (empty($response)) { return; } elseif (isset($response['value'])) { return $response['value']; } return $response; } }