Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
/build
/vendor
composer.phar
composer.lock
.DS_Store

70
vendor/thenetworg/oauth2-azure/.php_cs vendored Normal file
View File

@@ -0,0 +1,70 @@
<?php
$finder = PhpCsFixer\Finder::create()
->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);

View File

@@ -0,0 +1,5 @@
# Changelog
All Notable changes to `oauth2-azure` will be documented in this file
## v1.0.0 - 16NOV2015
- Initial release

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 TheNetw.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

232
vendor/thenetworg/oauth2-azure/README.md vendored Normal file
View File

@@ -0,0 +1,232 @@
# Azure Active Directory Provider for OAuth 2.0 Client
[![Latest Version](https://img.shields.io/github/release/thenetworg/oauth2-azure.svg?style=flat-square)](https://github.com/thenetworg/oauth2-azure/releases)
[![Total Downloads](https://img.shields.io/packagist/dt/thenetworg/oauth2-azure.svg?style=flat-square)](https://packagist.org/packages/thenetworg/oauth2-azure)
[![Software License](https://img.shields.io/packagist/l/thenetworg/oauth2-azure.svg?style=flat-square)](LICENSE.md)
This package provides [Azure Active Directory](https://azure.microsoft.com/en-us/services/active-directory/) OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Authorization Code Flow](#authorization-code-flow)
- [Advanced flow](#advanced-flow)
- [Using custom parameters](#using-custom-parameters)
- [**NEW** - Logging out](#logging-out)
- [Making API Requests](#making-api-requests)
- [Variables](#variables)
- [Resource Owner](#resource-owner)
- [Microsoft Graph](#microsoft-graph)
- [**NEW** - Protecting your API - *experimental*](#protecting-your-api---experimental)
- [Azure Active Directory B2C - *experimental*](#azure-active-directory-b2c---experimental)
- [Multipurpose refresh tokens - *experimental*](#multipurpose-refresh-tokens---experimental)
- [Known users](#known-users)
- [Contributing](#contributing)
- [Credits](#credits)
- [Support](#support)
- [License](#license)
## Installation
To install, use composer:
```
composer require thenetworg/oauth2-azure
```
## Usage
Usage is the same as The League's OAuth client, using `\TheNetworg\OAuth2\Client\Provider\Azure` as the provider.
### Authorization Code Flow
```php
$provider = new TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => '{azure-client-id}',
'clientSecret' => '{azure-client-secret}',
'redirectUri' => 'https://example.com/callback-url'
]);
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: '.$authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code'],
'resource' => 'https://graph.windows.net',
]);
// Optional: Now you have a token you can look up a users profile data
try {
// We got an access token, let's now get the user's details
$me = $provider->get("me", $token);
// Use these details to create a new profile
printf('Hello %s!', $me['givenName']);
} catch (Exception $e) {
// Failed to get user details
exit('Oh dear...');
}
// Use this to interact with an API on the users behalf
echo $token->getToken();
}
```
#### Advanced flow
The [Authorization Code Grant Flow](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx) is a little bit different for Azure Active Directory. Instead of scopes, you specify the resource which you would like to access - there is a param `$provider->authWithResource` which will automatically populate the `resource` param of request with the value of either `$provider->resource` or `$provider->urlAPI`. This feature is mostly intended for v2.0 endpoint of Azure AD (see more [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/azure-ad-endpoint-comparison#scopes-not-resources)).
#### Using custom parameters
With [oauth2-client](https://github.com/thephpleague/oauth2-client) of version 1.3.0 and higher, it is now possible to specify custom parameters for the authorization URL, so you can now make use of options like `prompt`, `login_hint` and similar. See the following example of obtaining an authorization URL which will force the user to reauthenticate:
```php
$authUrl = $provider->getAuthorizationUrl([
'prompt' => 'login'
]);
```
You can find additional parameters [here](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx).
### Logging out
If you need to quickly generate a logout URL for the user, you can do following:
```php
// Assuming you have provider properly initialized.
$post_logout_redirect_uri = 'https://www.msn.com'; // The logout destination after the user is logged out from their account.
$logoutUrl = $provider->getLogoutUrl($post_logout_redirect_uri);
header('Location: '.$logoutUrl); // Redirect the user to the generated URL
```
## Making API Requests
This library also provides easy interface to make it easier to interact with [Azure Graph API](https://msdn.microsoft.com/en-us/library/azure/hh974476.aspx) and [Microsoft Graph](http://graph.microsoft.io), the following methods are available on `provider` object (it also handles automatic token refresh flow should it be needed during making the request):
- `get($ref, $accessToken, $headers = [])`
- `post($ref, $body, $accessToken, $headers = [])`
- `put($ref, $body, $accessToken, $headers = [])`
- `delete($ref, $body, $accessToken, $headers = [])`
- `patch($ref, $body, $accessToken, $headers = [])`
- `getObjects($tenant, $ref, $accessToken, $headers = [])` This is used for example for listing large amount of data - where you need to list all users for example - it automatically follows `odata.nextLink` until the end.
- `$tenant` tenant has to be provided since the `odata.nextLink` doesn't contain it.
- `request($method, $ref, $accessToken, $options = [])` See [#36](https://github.com/TheNetworg/oauth2-azure/issues/36) for use case.
*Please note that if you need to create a custom request, the method getAuthenticatedRequest and getResponse can still be used.*
### Variables
- `$ref` The URL reference without the leading `/`, for example `myOrganization/groups`
- `$body` The contents of the request, make has to be either string (so make sure to use `json_encode` to encode the request)s or stream (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#body))
- `$accessToken` The access token object obtained by using `getAccessToken` method
- `$headers` Ability to set custom headers for the request (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#headers))
## Resource Owner
With version 1.1.0 and onward, the Resource Owner information is parsed from the JWT passed in `access_token` by Azure Active Directory. It exposes few attributes and one function.
**Example:**
```php
$resourceOwner = $provider->getResourceOwner($token);
echo 'Hello, '.$resourceOwner->getFirstName().'!';
```
The exposed attributes and function are:
- `getId()` - Gets user's object id - unique for each user
- `getFirstName()` - Gets user's first name
- `getLastName()` - Gets user's family name/surname
- `getTenantId()` - Gets id of tenant which the user is member of
- `getUpn()` - Gets user's User Principal Name, which can be also used as user's e-mail address
- `claim($name)` - Gets any other claim (specified as `$name`) from the JWT, full list can be found [here](https://azure.microsoft.com/en-us/documentation/articles/active-directory-token-and-claims/)
## Microsoft Graph
Calling [Microsoft Graph](http://graph.microsoft.io/) is very simple with this library. After provider initialization simply change the API URL followingly (replace `v1.0` with your desired version):
```php
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
$provider->resource = "https://graph.microsoft.com/";
```
After that, when requesting access token, refresh token or so, provide the `resource` with value `https://graph.microsoft.com/` in order to be able to make calls to the Graph (see more about `resource` [here](#advanced-flow)).
## Protecting your API - *experimental*
With version 1.2.0 you can now use this library to protect your API with Azure Active Directory authentication very easily. The Provider now also exposes `validateAccessToken(string $token)` which lets you pass an access token inside which you for example received in the `Authorization` header of the request on your API. You can use the function followingly (in vanilla PHP):
```php
// Assuming you have already initialized the $provider
// Obtain the accessToken - in this case, we are getting it from Authorization header
$headers = getallheaders();
// Assuming you got the value of Authorization header as "Bearer [the_access_token]" we parse it
$authorization = explode(' ', $headers['Authorization']);
$accessToken = $authorization[1];
try {
$claims = $provider->validateAccessToken($accessToken);
} catch (Exception $e) {
// Something happened, handle the error
}
// The access token is valid, you can now proceed with your code. You can also access the $claims as defined in JWT - for example roles, group memberships etc.
```
You may also need to access some other resource from the API like the Microsoft Graph to get some additional information. In order to do that, there is `urn:ietf:params:oauth:grant-type:jwt-bearer` grant available ([RFC](https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03)). An example (assuming you have the code above working and you have the required permissions configured correctly in the Azure AD application):
```php
$graphAccessToken = $provider->getAccessToken('jwt_bearer', [
'resource' => 'https://graph.microsoft.com/v1.0/',
'assertion' => $accessToken,
'requested_token_use' => 'on_behalf_of'
]);
$me = $provider->get('https://graph.microsoft.com/v1.0/me', $graphAccessToken);
print_r($me);
```
Just to make it easier so you don't have to remember entire name for `grant_type` (`urn:ietf:params:oauth:grant-type:jwt-bearer`), you just use short `jwt_bearer` instead.
## Azure Active Directory B2C - *experimental*
You can also now very simply make use of [Azure Active Directory B2C](https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-reference-oauth-code/). Before authentication, change the endpoints using `pathAuthorize`, `pathToken` and `scope` and additionally specify your [login policy](https://azure.microsoft.com/en-gb/documentation/articles/active-directory-b2c-reference-policies/). **Please note that the B2C support is still experimental and wasn't fully tested.**
```php
$provider->pathAuthorize = "/oauth2/v2.0/authorize";
$provider->pathToken = "/oauth2/v2.0/token";
$provider->scope = ["idtoken"];
// Specify custom policy in our authorization URL
$authUrl = $provider->getAuthorizationUrl([
'p' => 'b2c_1_siup'
]);
```
## Multipurpose refresh tokens - *experimental*
In cause that you need to access multiple resources (like your API and Microsoft Graph), you can use multipurpose [refresh tokens](https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx). Once obtaining a token for first resource, you can simply request another token for different resource like so:
```php
$accessToken2 = $provider->getAccessToken('refresh_token', [
'refresh_token' => $accessToken1->getRefreshToken(),
'resource' => 'http://urlOfYourSecondResource'
]);
```
At the moment, there is one issue: When you make a call to your API and the token has expired, it will have the value of `$provider->urlAPI` which is obviously wrong for `$accessToken2`. The solution is very simple - set the `$provider->urlAPI` to the resource which you want to call. This issue will be addressed in future release. **Please note that this is experimental and wasn't fully tested.**
## Known users
If you are using this library and would like to be listed here, please let us know!
- [TheNetworg/DreamSpark-SSO](https://github.com/thenetworg/dreamspark-sso)
## Contributing
We accept contributions via [Pull Requests on Github](https://github.com/thenetworg/oauth2-azure).
## Credits
- [Jan Hajek](https://github.com/hajekj) ([TheNetw.org](https://thenetw.org))
- [Vittorio Bertocci](https://github.com/vibronet) (Microsoft)
- Thanks for the splendid support while implementing #16
- [All Contributors](https://github.com/thenetworg/oauth2-azure/contributors)
## Support
If you find a bug or encounter any issue or have a problem/question with this library please create a [new issue](https://github.com/TheNetworg/oauth2-azure/issues).
## License
The MIT License (MIT). Please see [License File](https://github.com/thenetworg/oauth2-azure/blob/master/LICENSE) for more information.

View File

@@ -0,0 +1,34 @@
{
"name": "thenetworg/oauth2-azure",
"description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"license": "MIT",
"authors": [
{
"name": "Jan Hajek",
"email": "jan.hajek@thenetw.org",
"homepage": "https://thenetw.org"
}
],
"keywords": [
"oauth",
"oauth2",
"client",
"authorization",
"microsoft",
"windows azure",
"azure",
"azure active directory",
"aad",
"sso"
],
"require": {
"php": "^5.6|^7.0",
"league/oauth2-client": "~2.0",
"firebase/php-jwt": "~3.0||~4.0||~5.0"
},
"autoload": {
"psr-4": {
"TheNetworg\\OAuth2\\Client\\": "src/"
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace TheNetworg\OAuth2\Client\Grant;
class JwtBearer extends \League\OAuth2\Client\Grant\AbstractGrant
{
protected function getName()
{
return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
}
protected function getRequiredRequestParameters()
{
return [
'requested_token_use',
'assertion',
];
}
}

View File

@@ -0,0 +1,353 @@
<?php
namespace TheNetworg\OAuth2\Client\Provider;
use Firebase\JWT\JWT;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
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
{
use BearerAuthorizationTrait;
public $urlLogin = 'https://login.microsoftonline.com/';
public $pathAuthorize = '/oauth2/authorize';
public $pathToken = '/oauth2/token';
public $scope = [];
public $scopeSeparator = ' ';
public $tenant = 'common';
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);
$this->grantFactory->setGrant('jwt_bearer', new JwtBearer());
}
public function getBaseAuthorizationUrl()
{
return $this->urlLogin . $this->tenant . $this->pathAuthorize;
}
public function getBaseAccessTokenUrl(array $params)
{
return $this->urlLogin . $this->tenant . $this->pathToken;
}
public function getAccessToken($grant, array $options = [])
{
if ($this->authWithResource) {
$options['resource'] = $this->resource ? $this->resource : $this->urlAPI;
}
return parent::getAccessToken($grant, $options);
}
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)
{
}
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;
}
public function get($ref, &$accessToken, $headers = [])
{
$response = $this->request('get', $ref, $accessToken, ['headers' => $headers]);
return $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)
{
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.
*
* @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']);
if ($this->getClientId() != $tokenClaims['aud'] && $this->getClientId() != $tokenClaims['appid']) {
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'];
$tenant = $this->getTenantDetails($this->tenant);
if ($tokenClaims['iss'] != $tenant['issuer']) {
throw new \RuntimeException('Invalid token issuer!');
}
} else {
$tenant = $this->getTenantDetails($this->tenant);
if ($tokenClaims['iss'] != $tenant['issuer']) {
throw new \RuntimeException('Invalid token issuer!');
}
}
return $tokenClaims;
}
/**
* Get JWT verification keys from Azure Active Directory.
*
* @return array
*/
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'])) {
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']] = $publicKey;
}
}
}
return $keys;
}
/**
* Get the specified tenant's details.
*
* @param string $tenant
*
* @return array
*/
public function getTenantDetails($tenant)
{
$factory = $this->getRequestFactory();
$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;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace TheNetworg\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
class AzureResourceOwner implements ResourceOwnerInterface
{
/**
* Response payload
*
* @var array
*/
protected $data;
/**
* Creates new azure resource owner.
*
* @param array $data
*/
public function __construct($data = [])
{
$this->data = $data;
}
/**
* Retrieves id of resource owner.
*
* @return string|null
*/
public function getId()
{
return $this->claim('oid');
}
/**
* Retrieves first name of resource owner.
*
* @return string|null
*/
public function getFirstName()
{
return $this->claim('given_name');
}
/**
* Retrieves last name of resource owner.
*
* @return string|null
*/
public function getLastName()
{
return $this->claim('family_name');
}
/**
* Retrieves user principal name of resource owner.
*
* @return string|null
*/
public function getUpn()
{
return $this->claim('upn');
}
/**
* Retrieves tenant id of resource owner.
*
* @return string|null
*/
public function getTenantId()
{
return $this->claim('tid');
}
/**
* Returns a field from the parsed JWT data.
*
* @param string $name
*
* @return mixed|null
*/
public function claim($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
}
/**
* Returns all the data obtained about the user.
*
* @return array
*/
public function toArray()
{
return $this->data;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace TheNetworg\OAuth2\Client\Token;
use Firebase\JWT\JWT;
use InvalidArgumentException;
use League\OAuth2\Client\Tool\RequestFactory;
use RuntimeException;
class AccessToken extends \League\OAuth2\Client\Token\AccessToken
{
protected $idToken;
protected $idTokenClaims;
public function __construct(array $options, $provider)
{
parent::__construct($options);
if (!empty($options['id_token'])) {
$this->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;
}
}