From 68e0e73a0cae057e94a4396e6cc069319a03defc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andi=20R=C3=BCckauer?= Date: Thu, 25 Oct 2018 11:01:41 +0200 Subject: [PATCH 1/2] Added PHP-CS-Fixer configuration. #71 --- .php_cs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..47be3df --- /dev/null +++ b/.php_cs @@ -0,0 +1,70 @@ +in(__DIR__ . '/src'); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setUsingCache(false) + ->setRules([ + '@PSR2' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['default' => 'align_single_space_minimal'], + 'blank_line_after_opening_tag' => true, + 'class_attributes_separation' => true, + 'combine_consecutive_issets' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package', 'subpackage']], + 'declare_equal_normalize' => ['space' => 'single'], + 'dir_constant' => true, + 'fully_qualified_strict_types' => true, + 'function_typehint_space' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'is_null' => ['use_yoda_style' => true], + 'linebreak_after_opening_tag' => true, + 'lowercase_cast' => true, + 'modernize_types_casting' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_before_semicolons' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_echo_tag' => false, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], + 'phpdoc_var_without_name' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_before_namespace' => true, + 'single_line_comment_style' => true, + 'single_quote' => ['strings_containing_single_quote_chars' => true], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'trailing_comma_in_multiline_array' => true, + 'trim_array_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder($finder); From ff7abb24fcf05710f27eb92ef60f2899d88be5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andi=20R=C3=BCckauer?= Date: Thu, 25 Oct 2018 11:07:07 +0200 Subject: [PATCH 2/2] Fixed coding standard issues via php-cs-fixer. #71 --- src/Grant/JwtBearer.php | 5 +- src/Provider/Azure.php | 258 ++++++++++++++-------------- src/Provider/AzureResourceOwner.php | 14 +- src/Token/AccessToken.php | 55 +++--- 4 files changed, 169 insertions(+), 163 deletions(-) diff --git a/src/Grant/JwtBearer.php b/src/Grant/JwtBearer.php index 9c28ac3..c237728 100644 --- a/src/Grant/JwtBearer.php +++ b/src/Grant/JwtBearer.php @@ -1,4 +1,5 @@ grantFactory->setGrant('jwt_bearer', new JwtBearer); + $this->grantFactory->setGrant('jwt_bearer', new JwtBearer()); } public function getBaseAuthorizationUrl() { - return $this->urlLogin.$this->tenant.$this->pathAuthorize; + return $this->urlLogin . $this->tenant . $this->pathAuthorize; } public function getBaseAccessTokenUrl(array $params) { - return $this->urlLogin.$this->tenant.$this->pathToken; + return $this->urlLogin . $this->tenant . $this->pathToken; } - + public function getAccessToken($grant, array $options = []) { - if($this->authWithResource) { + if ($this->authWithResource) { $options['resource'] = $this->resource ? $this->resource : $this->urlAPI; } return parent::getAccessToken($grant, $options); } - protected function checkResponse(ResponseInterface $response, $data) - { - 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(); - } - - throw new IdentityProviderException( - $message, - $response->getStatusCode(), - $response - ); - } - } - - protected function getDefaultScopes() - { - return $this->scope; - } - - protected function getScopeSeparator() - { - return $this->scopeSeparator; - } - - protected function createAccessToken(array $response, AbstractGrant $grant) - { - return new AccessToken($response, $this); - } - public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token) { $data = $token->getIdTokenClaims(); return $this->createResourceOwner($data, $token); } - - public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) - { - return null; - } - protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token) { - return new AzureResourceOwner($response); } public function getObjects($tenant, $ref, &$accessToken, $headers = []) { $objects = []; - + $response = null; - do { - if (filter_var($ref, FILTER_VALIDATE_URL) === FALSE) { - $ref = $tenant."/".$ref; + 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']; } - - $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'])) { + if (isset($response['odata.nextLink'])) { $ref = $response['odata.nextLink']; } elseif (isset($response['@odata.nextLink'])) { $ref = $response['@odata.nextLink']; + } else { + $ref = null; } - else { - $ref = null; - } - } while ($ref != null); - + } while (null != $ref); + return $objects; } @@ -175,110 +138,97 @@ public function request($method, $ref, &$accessToken, $options = []) { if ($accessToken->hasExpired()) { $accessToken = $this->getAccessToken('refresh_token', [ - 'refresh_token' => $accessToken->getRefreshToken() + 'refresh_token' => $accessToken->getRefreshToken(), ]); } $url = null; - if (filter_var($ref, FILTER_VALIDATE_URL) !== FALSE) { + if (false !== filter_var($ref, FILTER_VALIDATE_URL)) { $url = $ref; } else { - if (strpos($this->urlAPI, "graph.windows.net") !== FALSE) { + 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 = $this->urlAPI . $ref; - $url .= (strrpos($url, "?") === false) ? "?" : "&"; - $url .= "api-version=".$this->API_VERSION; - } - else { - $url = $this->urlAPI.$ref; + $url .= (false === strrpos($url, '?')) ? '?' : '&'; + $url .= 'api-version=' . $this->API_VERSION; + } else { + $url = $this->urlAPI . $ref; } } - if(isset($options['body']) && (gettype($options['body']) == 'array' || gettype($options['body']) == 'object')) { + 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'])) { + if (!isset($options['headers']['Content-Type']) && isset($options['body'])) { $options['headers']['Content-Type'] = 'application/json'; } - $request = $this->getAuthenticatedRequest($method, $url, $accessToken, $options); + $request = $this->getAuthenticatedRequest($method, $url, $accessToken, $options); $response = $this->getParsedResponse($request); return $response; } - private function wrapResponse($response) - { - if (empty($response)) { - return null; - } elseif (isset($response['value'])) { - return $response['value']; - } - - return $response; - } - public function getClientId() { return $this->clientId; } - + /** * Obtain URL for logging out the user. * - * @input $post_logout_redirect_uri string The URL which the user should be redirected to after logout + * @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) { - return 'https://login.microsoftonline.com/'.$this->tenant.'/oauth2/logout?post_logout_redirect_uri='.rawurlencode($post_logout_redirect_uri); + return 'https://login.microsoftonline.com/' . $this->tenant . '/oauth2/logout?post_logout_redirect_uri=' . rawurlencode($post_logout_redirect_uri); } - + /** * Validate the access token you received in your application. * - * @input $accessToken string The access token you received in the authorization header. + * @param $accessToken string The access token you received in the authorization header. * * @return array */ public function validateAccessToken($accessToken) { - $keys = $this->getJwtVerificationKeys(); + $keys = $this->getJwtVerificationKeys(); $tokenClaims = (array)JWT::decode($accessToken, $keys, ['RS256']); - + if ($this->getClientId() != $tokenClaims['aud'] && $this->getClientId() != $tokenClaims['appid']) { - throw new \RuntimeException("The client_id / audience is invalid!"); + throw new \RuntimeException('The client_id / audience is invalid!'); } - if($tokenClaims['nbf'] > time() || $tokenClaims['exp'] < time()) { + if ($tokenClaims['nbf'] > time() || $tokenClaims['exp'] < time()) { // Additional validation is being performed in firebase/JWT itself - throw new \RuntimeException("The id_token is invalid!"); + throw new \RuntimeException('The id_token is invalid!'); } - - if($this->tenant == "common") { + + if ('common' == $this->tenant) { $this->tenant = $tokenClaims['tid']; - + $tenant = $this->getTenantDetails($this->tenant); - if($tokenClaims['iss'] != $tenant['issuer']) { - throw new \RuntimeException("Invalid token issuer!"); + if ($tokenClaims['iss'] != $tenant['issuer']) { + throw new \RuntimeException('Invalid token issuer!'); } - } - else { + } else { $tenant = $this->getTenantDetails($this->tenant); - if($tokenClaims['iss'] != $tenant['issuer']) { - throw new \RuntimeException("Invalid token issuer!"); + if ($tokenClaims['iss'] != $tenant['issuer']) { + throw new \RuntimeException('Invalid token issuer!'); } } - + return $tokenClaims; } - + /** * Get JWT verification keys from Azure Active Directory. * @@ -288,9 +238,9 @@ public function getJwtVerificationKeys() { $factory = $this->getRequestFactory(); $request = $factory->getRequestWithOptions('get', 'https://login.windows.net/common/discovery/keys', []); - + $response = $this->getParsedResponse($request); - + $keys = []; foreach ($response['keys'] as $i => $keyinfo) { if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) { @@ -302,10 +252,10 @@ public function getJwtVerificationKeys() } } } - + return $keys; } - + /** * Get the specified tenant's details. * @@ -316,10 +266,66 @@ public function getJwtVerificationKeys() public function getTenantDetails($tenant) { $factory = $this->getRequestFactory(); - $request = $factory->getRequestWithOptions('get', 'https://login.windows.net/'.$tenant.'/.well-known/openid-configuration', []); - + $request = $factory->getRequestWithOptions( + 'get', + 'https://login.windows.net/' . $tenant . '/.well-known/openid-configuration', + [] + ); + $response = $this->getParsedResponse($request); - + + return $response; + } + + protected function checkResponse(ResponseInterface $response, $data) + { + 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(); + } + + throw new IdentityProviderException( + $message, + $response->getStatusCode(), + $response + ); + } + } + + protected function getDefaultScopes() + { + return $this->scope; + } + + protected function getScopeSeparator() + { + return $this->scopeSeparator; + } + + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response, $this); + } + + protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token) + { + return new AzureResourceOwner($response); + } + + private function wrapResponse($response) + { + if (empty($response)) { + return; + } elseif (isset($response['value'])) { + return $response['value']; + } + return $response; } } diff --git a/src/Provider/AzureResourceOwner.php b/src/Provider/AzureResourceOwner.php index 259ef70..337ae51 100644 --- a/src/Provider/AzureResourceOwner.php +++ b/src/Provider/AzureResourceOwner.php @@ -16,7 +16,7 @@ class AzureResourceOwner implements ResourceOwnerInterface /** * Creates new azure resource owner. * - * @param array $data + * @param array $data */ public function __construct($data = []) { @@ -32,7 +32,7 @@ public function getId() { return $this->claim('oid'); } - + /** * Retrieves first name of resource owner. * @@ -42,7 +42,7 @@ public function getFirstName() { return $this->claim('given_name'); } - + /** * Retrieves last name of resource owner. * @@ -52,7 +52,7 @@ public function getLastName() { return $this->claim('family_name'); } - + /** * Retrieves user principal name of resource owner. * @@ -62,7 +62,7 @@ public function getUpn() { return $this->claim('upn'); } - + /** * Retrieves tenant id of resource owner. * @@ -72,7 +72,7 @@ public function getTenantId() { return $this->claim('tid'); } - + /** * Returns a field from the parsed JWT data. * @@ -84,7 +84,7 @@ public function claim($name) { return isset($this->data[$name]) ? $this->data[$name] : null; } - + /** * Returns all the data obtained about the user. * diff --git a/src/Token/AccessToken.php b/src/Token/AccessToken.php index ab62d65..4033eb5 100644 --- a/src/Token/AccessToken.php +++ b/src/Token/AccessToken.php @@ -2,71 +2,70 @@ namespace TheNetworg\OAuth2\Client\Token; +use Firebase\JWT\JWT; use InvalidArgumentException; -use RuntimeException; use League\OAuth2\Client\Tool\RequestFactory; -use \Firebase\JWT\JWT; +use RuntimeException; class AccessToken extends \League\OAuth2\Client\Token\AccessToken { protected $idToken; + protected $idTokenClaims; - - public function __construct(array $options = [], $provider) + + public function __construct(array $options, $provider) { parent::__construct($options); if (!empty($options['id_token'])) { $this->idToken = $options['id_token']; - - $keys = $provider->getJwtVerificationKeys(); + + $keys = $provider->getJwtVerificationKeys(); $idTokenClaims = null; try { $tks = explode('.', $this->idToken); // Check if the id_token contains signature - if(count($tks) == 3 && !empty($tks[2])) { + if (3 == count($tks) && !empty($tks[2])) { $idTokenClaims = (array)JWT::decode($this->idToken, $keys, ['RS256']); - } - else { + } 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!"); + } 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 ($provider->getClientId() != $idTokenClaims['aud']) { + throw new RuntimeException('The audience is invalid!'); } - if($idTokenClaims['nbf'] > time() || $idTokenClaims['exp'] < time()) { + if ($idTokenClaims['nbf'] > time() || $idTokenClaims['exp'] < time()) { // Additional validation is being performed in firebase/JWT itself - throw new RuntimeException("The id_token is invalid!"); + throw new RuntimeException('The id_token is invalid!'); } - - if($provider->tenant == "common") { + + if ('common' == $provider->tenant) { $provider->tenant = $idTokenClaims['tid']; - + $tenant = $provider->getTenantDetails($provider->tenant); - if($idTokenClaims['iss'] != $tenant['issuer']) { - throw new RuntimeException("Invalid token issuer!"); + if ($idTokenClaims['iss'] != $tenant['issuer']) { + throw new RuntimeException('Invalid token issuer!'); } - } - else { + } else { $tenant = $provider->getTenantDetails($provider->tenant); - if($idTokenClaims['iss'] != $tenant['issuer']) { - throw new RuntimeException("Invalid token issuer!"); + if ($idTokenClaims['iss'] != $tenant['issuer']) { + throw new RuntimeException('Invalid token issuer!'); } } - + $this->idTokenClaims = $idTokenClaims; } } - + public function getIdTokenClaims() { return $this->idTokenClaims; } -} \ No newline at end of file +}