README.PROVIDER-GUIDE.md 0000666 00000010215 13436754462 0010130 0 ustar 00 # OAuth 2.0 Client
## Provider Guide
New providers may be created by copying the layout of an existing package. See
the [list of providers](docs/providers/thirdparty.md) for good examples.
When choosing a name for your package, please don’t use the `league` vendor
prefix, as this implies that it is officially supported. You should use your own
username as the vendor prefix, and prepend `oauth2-` to the package name to make
it clear that your package works with OAuth2 Client. For example, if your GitHub
username was "santa," and you were implementing the "giftpay" OAuth2 library, a
good name for your composer package would be `santa/oauth2-giftpay`.
### Implementing your own provider
If you are working with an oauth2 service not supported out-of-the-box or by an
existing package, it is quite simple to implement your own. Simply extend
[`League\OAuth2\Client\Provider\AbstractProvider`](src/Provider/AbstractProvider.php)
and implement the required abstract methods:
```php
abstract public function getBaseAuthorizationUrl();
abstract public function getBaseAccessTokenUrl(array $params);
abstract public function getResourceOwnerDetailsUrl(AccessToken $token);
abstract protected function getDefaultScopes();
abstract protected function checkResponse(ResponseInterface $response, $data);
abstract protected function createResourceOwner(array $response, AccessToken $token);
```
Each of these abstract methods contain a docblock defining their expectations
and typical behavior. Once you have extended this class, you can simply follow
the [usage example in the README](README.md#usage) using your new `Provider`.
If you wish to use the `Provider` to make authenticated requests to the
service, you will also need to define how you provide the token to the
service. If this is done via headers, you should override this method:
```php
protected function getAuthorizationHeaders($token = null);
```
This package comes with a trait for implementing `Bearer` authorization.
To use this, you just need to include the trait in your `Provider` class:
```php
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.
README.md 0000666 00000034665 13436754462 0006063 0 ustar 00 # OAuth 2.0 Client
This package makes it simple to integrate your application with [OAuth 2.0](http://oauth.net/2/) service providers.
[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client)
[![Source Code](http://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client)
[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE)
[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-client/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/oauth2-client)
[![Scrutinizer](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-client/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-client/)
[![Coverage Status](https://img.shields.io/coveralls/thephpleague/oauth2-client/master.svg?style=flat-square)](https://coveralls.io/r/thephpleague/oauth2-client?branch=master)
[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client)
---
We are all used to seeing those "Connect with Facebook/Google/etc." buttons around the internet, and social network integration is an important feature of most web applications these days. Many of these sites use an authentication and authorization standard called OAuth 2.0 ([RFC 6749](http://tools.ietf.org/html/rfc6749)).
This OAuth 2.0 client library will work with any OAuth provider that conforms to the OAuth 2.0 standard. Out-of-the-box, we provide a `GenericProvider` that may be used to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750) (see example below).
Many service providers provide additional functionality above and beyond the OAuth 2.0 standard. For this reason, this library may be easily extended and wrapped to support this additional behavior. We provide links to [all known provider clients extending this library](docs/providers/thirdparty.md) (i.e. Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it.
This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interesting in contributing to this library, please take a look at our [contributing guidelines](CONTRIBUTING.md).
## Requirements
The following versions of PHP are supported.
* PHP 5.6
* PHP 7.0
* PHP 7.1
* PHP 7.2
## Providers
A list of official PHP League providers, as well as third-party providers, may be found in the [providers list README](docs/providers/thirdparty.md).
To build your own provider, please refer to the [provider guide README](README.PROVIDER-GUIDE.md).
## Usage
**In most cases, you'll want to use a specific provider client library rather than this base library.**
Take a look at [providers list README](docs/providers/thirdparty.md) to see a list of provider client libraries.
If using Composer to require a specific provider client library, you **do not need to also require this library**. Composer will handle the dependencies for you.
### Authorization Code Grant
The following example uses the out-of-the-box `GenericProvider` provided by this library. If you're looking for a specific provider (i.e. Facebook, Google, GitHub, etc.), take a look at our [list of provider client libraries](docs/providers/thirdparty.md). **HINT: You're probably looking for a specific provider.**
The authorization code grant type is the most common grant type used when authenticating users with a third-party service. This grant type utilizes a client (this library), a server (the service provider), and a resource owner (the user with credentials to a protected—or owned—resource) to request access to resources owned by the user. This is often referred to as _3-legged OAuth_, since there are three parties involved.
The following example illustrates this using [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. When running this code, you will be redirected to Lock'd In, where you'll be prompted to authorize the client to make requests to a resource on your behalf.
Now, you don't really have an account on Lock'd In, but for the sake of this example, imagine that you are already logged in on Lock'd In when you are redirected there.
```php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'demoapp', // The client ID assigned to you by the provider
'clientSecret' => 'demopass', // The client password assigned to you by the provider
'redirectUri' => 'http://example.com/your-redirect-url/',
'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize',
'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token',
'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);
// If we don't have an authorization code then get one
if (!isset($_GET['code'])) {
// Fetch the authorization URL from the provider; this returns the
// urlAuthorize option and generates and applies any necessary parameters
// (e.g. state).
$authorizationUrl = $provider->getAuthorizationUrl();
// Get the state generated for you and store it to the session.
$_SESSION['oauth2state'] = $provider->getState();
// Redirect the user to the authorization URL.
header('Location: ' . $authorizationUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {
if (isset($_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
}
exit('Invalid state');
} else {
try {
// Try to get an access token using the authorization code grant.
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// We have an access token, which we may use in authenticated
// requests against the service provider's API.
echo 'Access Token: ' . $accessToken->getToken() . "
";
echo 'Refresh Token: ' . $accessToken->getRefreshToken() . "
";
echo 'Expired in: ' . $accessToken->getExpires() . "
";
echo 'Already expired? ' . ($accessToken->hasExpired() ? 'expired' : 'not expired') . "
";
// Using the access token, we may look up details about the
// resource owner.
$resourceOwner = $provider->getResourceOwner($accessToken);
var_export($resourceOwner->toArray());
// The provider provides a way to get an authenticated API request for
// the service, using the access token; it returns an object conforming
// to Psr\Http\Message\RequestInterface.
$request = $provider->getAuthenticatedRequest(
'GET',
'http://brentertainment.com/oauth2/lockdin/resource',
$accessToken
);
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
// Failed to get the access token or user details.
exit($e->getMessage());
}
}
```
### Refreshing a Token
Once your application is authorized, you can refresh an expired token using a refresh token rather than going through the entire process of obtaining a brand new token. To do so, simply reuse this refresh token from your data store to request a refresh.
_This example uses [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details._
```php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'demoapp', // The client ID assigned to you by the provider
'clientSecret' => 'demopass', // The client password assigned to you by the provider
'redirectUri' => 'http://example.com/your-redirect-url/',
'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize',
'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token',
'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);
$existingAccessToken = getAccessTokenFromYourDataStore();
if ($existingAccessToken->hasExpired()) {
$newAccessToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $existingAccessToken->getRefreshToken()
]);
// Purge old access token and store new access token to your data store.
}
```
### Resource Owner Password Credentials Grant
Some service providers allow you to skip the authorization code step to exchange a user's credentials (username and password) for an access token. This is referred to as the "resource owner password credentials" grant type.
According to [section 1.3.3](http://tools.ietf.org/html/rfc6749#section-1.3.3) of the OAuth 2.0 standard (emphasis added):
> The credentials **should only be used when there is a high degree of trust**
> between the resource owner and the client (e.g., the client is part of the
> device operating system or a highly privileged application), and when other
> authorization grant types are not available (such as an authorization code).
**We do not advise using this grant type if the service provider supports the authorization code grant type (see above), as this reinforces the [password anti-pattern](https://agentile.com/the-password-anti-pattern) by allowing users to think it's okay to trust third-party applications with their usernames and passwords.**
That said, there are use-cases where the resource owner password credentials grant is acceptable and useful. Here's an example using it with [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details about the Lock'd In demo application.
``` php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'demoapp', // The client ID assigned to you by the provider
'clientSecret' => 'demopass', // The client password assigned to you by the provider
'redirectUri' => 'http://example.com/your-redirect-url/',
'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize',
'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token',
'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);
try {
// Try to get an access token using the resource owner password credentials grant.
$accessToken = $provider->getAccessToken('password', [
'username' => 'demouser',
'password' => 'testpass'
]);
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
// Failed to get the access token
exit($e->getMessage());
}
```
### Client Credentials Grant
When your application is acting on its own behalf to access resources it controls/owns in a service provider, it may use the client credentials grant type. This is best used when the credentials for your application are stored privately and never exposed (e.g. through the web browser, etc.) to end-users. This grant type functions similarly to the resource owner password credentials grant type, but it does not request a user's username or password. It uses only the client ID and secret issued to your client by the service provider.
Unlike earlier examples, the following does not work against a functioning demo service provider. It is provided for the sake of example only.
``` php
// Note: the GenericProvider requires the `urlAuthorize` option, even though
// it's not used in the OAuth 2.0 client credentials grant type.
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'XXXXXX', // The client ID assigned to you by the provider
'clientSecret' => 'XXXXXX', // The client password assigned to you by the provider
'redirectUri' => 'http://my.example.com/your-redirect-url/',
'urlAuthorize' => 'http://service.example.com/authorize',
'urlAccessToken' => 'http://service.example.com/token',
'urlResourceOwnerDetails' => 'http://service.example.com/resource'
]);
try {
// Try to get an access token using the client credentials grant.
$accessToken = $provider->getAccessToken('client_credentials');
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
// Failed to get the access token
exit($e->getMessage());
}
```
### Using a proxy
It is possible to use a proxy to debug HTTP calls made to a provider. All you need to do is set the `proxy` and `verify` options when creating your Provider instance. Make sure you enable SSL proxying in your proxy.
``` php
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'XXXXXX', // The client ID assigned to you by the provider
'clientSecret' => 'XXXXXX', // The client password assigned to you by the provider
'redirectUri' => 'http://my.example.com/your-redirect-url/',
'urlAuthorize' => 'http://service.example.com/authorize',
'urlAccessToken' => 'http://service.example.com/token',
'urlResourceOwnerDetails' => 'http://service.example.com/resource',
'proxy' => '192.168.0.1:8888',
'verify' => false
]);
```
## Install
Via Composer
``` bash
$ composer require league/oauth2-client
```
## Contributing
Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details.
## License
The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information.
[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md
src/Provider/GenericResourceOwner.php 0000666 00000002655 13436754462 0013767 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Provider;
/**
* Represents a generic resource owner for use with the GenericProvider.
*/
class GenericResourceOwner implements ResourceOwnerInterface
{
/**
* @var array
*/
protected $response;
/**
* @var string
*/
protected $resourceOwnerId;
/**
* @param array $response
* @param string $resourceOwnerId
*/
public function __construct(array $response, $resourceOwnerId)
{
$this->response = $response;
$this->resourceOwnerId = $resourceOwnerId;
}
/**
* Returns the identifier of the authorized resource owner.
*
* @return mixed
*/
public function getId()
{
return $this->response[$this->resourceOwnerId];
}
/**
* Returns the raw resource owner response.
*
* @return array
*/
public function toArray()
{
return $this->response;
}
}
src/Provider/AbstractProvider.php 0000666 00000055451 13436754462 0013150 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Provider;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\ClientInterface as HttpClientInterface;
use GuzzleHttp\Exception\BadResponseException;
use League\OAuth2\Client\Grant\AbstractGrant;
use League\OAuth2\Client\Grant\GrantFactory;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\ArrayAccessorTrait;
use League\OAuth2\Client\Tool\QueryBuilderTrait;
use League\OAuth2\Client\Tool\RequestFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;
/**
* Represents a service provider (authorization server).
*
* @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1)
*/
abstract class AbstractProvider
{
use ArrayAccessorTrait;
use QueryBuilderTrait;
/**
* @var string Key used in a token response to identify the resource owner.
*/
const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;
/**
* @var string HTTP method used to fetch access tokens.
*/
const METHOD_GET = 'GET';
/**
* @var string HTTP method used to fetch access tokens.
*/
const METHOD_POST = 'POST';
/**
* @var string
*/
protected $clientId;
/**
* @var string
*/
protected $clientSecret;
/**
* @var string
*/
protected $redirectUri;
/**
* @var string
*/
protected $state;
/**
* @var GrantFactory
*/
protected $grantFactory;
/**
* @var RequestFactory
*/
protected $requestFactory;
/**
* @var HttpClientInterface
*/
protected $httpClient;
/**
* Constructs an OAuth 2.0 service provider.
*
* @param array $options An array of options to set on this provider.
* Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
* Individual providers may introduce more options, as needed.
* @param array $collaborators An array of collaborators that may be used to
* override this provider's default behavior. Collaborators include
* `grantFactory`, `requestFactory`, and `httpClient`.
* Individual providers may introduce more collaborators, as needed.
*/
public function __construct(array $options = [], array $collaborators = [])
{
foreach ($options as $option => $value) {
if (property_exists($this, $option)) {
$this->{$option} = $value;
}
}
if (empty($collaborators['grantFactory'])) {
$collaborators['grantFactory'] = new GrantFactory();
}
$this->setGrantFactory($collaborators['grantFactory']);
if (empty($collaborators['requestFactory'])) {
$collaborators['requestFactory'] = new RequestFactory();
}
$this->setRequestFactory($collaborators['requestFactory']);
if (empty($collaborators['httpClient'])) {
$client_options = $this->getAllowedClientOptions($options);
$collaborators['httpClient'] = new HttpClient(
array_intersect_key($options, array_flip($client_options))
);
}
$this->setHttpClient($collaborators['httpClient']);
}
/**
* Returns the list of options that can be passed to the HttpClient
*
* @param array $options An array of options to set on this provider.
* Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
* Individual providers may introduce more options, as needed.
* @return array The options to pass to the HttpClient constructor
*/
protected function getAllowedClientOptions(array $options)
{
$client_options = ['timeout', 'proxy'];
// Only allow turning off ssl verification if it's for a proxy
if (!empty($options['proxy'])) {
$client_options[] = 'verify';
}
return $client_options;
}
/**
* Sets the grant factory instance.
*
* @param GrantFactory $factory
* @return self
*/
public function setGrantFactory(GrantFactory $factory)
{
$this->grantFactory = $factory;
return $this;
}
/**
* Returns the current grant factory instance.
*
* @return GrantFactory
*/
public function getGrantFactory()
{
return $this->grantFactory;
}
/**
* Sets the request factory instance.
*
* @param RequestFactory $factory
* @return self
*/
public function setRequestFactory(RequestFactory $factory)
{
$this->requestFactory = $factory;
return $this;
}
/**
* Returns the request factory instance.
*
* @return RequestFactory
*/
public function getRequestFactory()
{
return $this->requestFactory;
}
/**
* Sets the HTTP client instance.
*
* @param HttpClientInterface $client
* @return self
*/
public function setHttpClient(HttpClientInterface $client)
{
$this->httpClient = $client;
return $this;
}
/**
* Returns the HTTP client instance.
*
* @return HttpClientInterface
*/
public function getHttpClient()
{
return $this->httpClient;
}
/**
* Returns the current value of the state parameter.
*
* This can be accessed by the redirect handler during authorization.
*
* @return string
*/
public function getState()
{
return $this->state;
}
/**
* Returns the base URL for authorizing a client.
*
* Eg. https://oauth.service.com/authorize
*
* @return string
*/
abstract public function getBaseAuthorizationUrl();
/**
* Returns the base URL for requesting an access token.
*
* Eg. https://oauth.service.com/token
*
* @param array $params
* @return string
*/
abstract public function getBaseAccessTokenUrl(array $params);
/**
* Returns the URL for requesting the resource owner's details.
*
* @param AccessToken $token
* @return string
*/
abstract public function getResourceOwnerDetailsUrl(AccessToken $token);
/**
* Returns a new random string to use as the state parameter in an
* authorization flow.
*
* @param int $length Length of the random string to be generated.
* @return string
*/
protected function getRandomState($length = 32)
{
// Converting bytes to hex will always double length. Hence, we can reduce
// the amount of bytes by half to produce the correct length.
return bin2hex(random_bytes($length / 2));
}
/**
* Returns the default scopes used by this provider.
*
* This should only be the scopes that are required to request the details
* of the resource owner, rather than all the available scopes.
*
* @return array
*/
abstract protected function getDefaultScopes();
/**
* Returns the string that should be used to separate scopes when building
* the URL for requesting an access token.
*
* @return string Scope separator, defaults to ','
*/
protected function getScopeSeparator()
{
return ',';
}
/**
* Returns authorization parameters based on provided options.
*
* @param array $options
* @return array Authorization parameters
*/
protected function getAuthorizationParameters(array $options)
{
if (empty($options['state'])) {
$options['state'] = $this->getRandomState();
}
if (empty($options['scope'])) {
$options['scope'] = $this->getDefaultScopes();
}
$options += [
'response_type' => 'code',
'approval_prompt' => 'auto'
];
if (is_array($options['scope'])) {
$separator = $this->getScopeSeparator();
$options['scope'] = implode($separator, $options['scope']);
}
// Store the state as it may need to be accessed later on.
$this->state = $options['state'];
// Business code layer might set a different redirect_uri parameter
// depending on the context, leave it as-is
if (!isset($options['redirect_uri'])) {
$options['redirect_uri'] = $this->redirectUri;
}
$options['client_id'] = $this->clientId;
return $options;
}
/**
* Builds the authorization URL's query string.
*
* @param array $params Query parameters
* @return string Query string
*/
protected function getAuthorizationQuery(array $params)
{
return $this->buildQueryString($params);
}
/**
* Builds the authorization URL.
*
* @param array $options
* @return string Authorization URL
*/
public function getAuthorizationUrl(array $options = [])
{
$base = $this->getBaseAuthorizationUrl();
$params = $this->getAuthorizationParameters($options);
$query = $this->getAuthorizationQuery($params);
return $this->appendQuery($base, $query);
}
/**
* Redirects the client for authorization.
*
* @param array $options
* @param callable|null $redirectHandler
* @return mixed
*/
public function authorize(
array $options = [],
callable $redirectHandler = null
) {
$url = $this->getAuthorizationUrl($options);
if ($redirectHandler) {
return $redirectHandler($url, $this);
}
// @codeCoverageIgnoreStart
header('Location: ' . $url);
exit;
// @codeCoverageIgnoreEnd
}
/**
* Appends a query string to a URL.
*
* @param string $url The URL to append the query to
* @param string $query The HTTP query string
* @return string The resulting URL
*/
protected function appendQuery($url, $query)
{
$query = trim($query, '?&');
if ($query) {
$glue = strstr($url, '?') === false ? '?' : '&';
return $url . $glue . $query;
}
return $url;
}
/**
* Returns the method to use when requesting an access token.
*
* @return string HTTP method
*/
protected function getAccessTokenMethod()
{
return self::METHOD_POST;
}
/**
* Returns the key used in the access token response to identify the resource owner.
*
* @return string|null Resource owner identifier key
*/
protected function getAccessTokenResourceOwnerId()
{
return static::ACCESS_TOKEN_RESOURCE_OWNER_ID;
}
/**
* Builds the access token URL's query string.
*
* @param array $params Query parameters
* @return string Query string
*/
protected function getAccessTokenQuery(array $params)
{
return $this->buildQueryString($params);
}
/**
* Checks that a provided grant is valid, or attempts to produce one if the
* provided grant is a string.
*
* @param AbstractGrant|string $grant
* @return AbstractGrant
*/
protected function verifyGrant($grant)
{
if (is_string($grant)) {
return $this->grantFactory->getGrant($grant);
}
$this->grantFactory->checkGrant($grant);
return $grant;
}
/**
* Returns the full URL to use when requesting an access token.
*
* @param array $params Query parameters
* @return string
*/
protected function getAccessTokenUrl(array $params)
{
$url = $this->getBaseAccessTokenUrl($params);
if ($this->getAccessTokenMethod() === self::METHOD_GET) {
$query = $this->getAccessTokenQuery($params);
return $this->appendQuery($url, $query);
}
return $url;
}
/**
* Returns the request body for requesting an access token.
*
* @param array $params
* @return string
*/
protected function getAccessTokenBody(array $params)
{
return $this->buildQueryString($params);
}
/**
* Builds request options used for requesting an access token.
*
* @param array $params
* @return array
*/
protected function getAccessTokenOptions(array $params)
{
$options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']];
if ($this->getAccessTokenMethod() === self::METHOD_POST) {
$options['body'] = $this->getAccessTokenBody($params);
}
return $options;
}
/**
* Returns a prepared request for requesting an access token.
*
* @param array $params Query string parameters
* @return RequestInterface
*/
protected function getAccessTokenRequest(array $params)
{
$method = $this->getAccessTokenMethod();
$url = $this->getAccessTokenUrl($params);
$options = $this->getAccessTokenOptions($params);
return $this->getRequest($method, $url, $options);
}
/**
* Requests an access token using a specified grant and option set.
*
* @param mixed $grant
* @param array $options
* @throws IdentityProviderException
* @return AccessToken
*/
public function getAccessToken($grant, array $options = [])
{
$grant = $this->verifyGrant($grant);
$params = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
];
$params = $grant->prepareRequestParameters($params, $options);
$request = $this->getAccessTokenRequest($params);
$response = $this->getParsedResponse($request);
if (false === is_array($response)) {
throw new UnexpectedValueException(
'Invalid response received from Authorization Server. Expected JSON.'
);
}
$prepared = $this->prepareAccessTokenResponse($response);
$token = $this->createAccessToken($prepared, $grant);
return $token;
}
/**
* Returns a PSR-7 request instance that is not authenticated.
*
* @param string $method
* @param string $url
* @param array $options
* @return RequestInterface
*/
public function getRequest($method, $url, array $options = [])
{
return $this->createRequest($method, $url, null, $options);
}
/**
* Returns an authenticated PSR-7 request instance.
*
* @param string $method
* @param string $url
* @param AccessToken|string $token
* @param array $options Any of "headers", "body", and "protocolVersion".
* @return RequestInterface
*/
public function getAuthenticatedRequest($method, $url, $token, array $options = [])
{
return $this->createRequest($method, $url, $token, $options);
}
/**
* Creates a PSR-7 request instance.
*
* @param string $method
* @param string $url
* @param AccessToken|string|null $token
* @param array $options
* @return RequestInterface
*/
protected function createRequest($method, $url, $token, array $options)
{
$defaults = [
'headers' => $this->getHeaders($token),
];
$options = array_merge_recursive($defaults, $options);
$factory = $this->getRequestFactory();
return $factory->getRequestWithOptions($method, $url, $options);
}
/**
* Sends a request instance and returns a response instance.
*
* WARNING: This method does not attempt to catch exceptions caused by HTTP
* errors! It is recommended to wrap this method in a try/catch block.
*
* @param RequestInterface $request
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request)
{
return $this->getHttpClient()->send($request);
}
/**
* Sends a request and returns the parsed response.
*
* @param RequestInterface $request
* @throws IdentityProviderException
* @return mixed
*/
public function getParsedResponse(RequestInterface $request)
{
try {
$response = $this->getResponse($request);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
$parsed = $this->parseResponse($response);
$this->checkResponse($response, $parsed);
return $parsed;
}
/**
* Attempts to parse a JSON response.
*
* @param string $content JSON content from response body
* @return array Parsed JSON data
* @throws UnexpectedValueException if the content could not be parsed
*/
protected function parseJson($content)
{
$content = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new UnexpectedValueException(sprintf(
"Failed to parse JSON response: %s",
json_last_error_msg()
));
}
return $content;
}
/**
* Returns the content type header of a response.
*
* @param ResponseInterface $response
* @return string Semi-colon separated join of content-type headers.
*/
protected function getContentType(ResponseInterface $response)
{
return join(';', (array) $response->getHeader('content-type'));
}
/**
* Parses the response according to its content-type header.
*
* @throws UnexpectedValueException
* @param ResponseInterface $response
* @return array
*/
protected function parseResponse(ResponseInterface $response)
{
$content = (string) $response->getBody();
$type = $this->getContentType($response);
if (strpos($type, 'urlencoded') !== false) {
parse_str($content, $parsed);
return $parsed;
}
// Attempt to parse the string as JSON regardless of content type,
// since some providers use non-standard content types. Only throw an
// exception if the JSON could not be parsed when it was expected to.
try {
return $this->parseJson($content);
} catch (UnexpectedValueException $e) {
if (strpos($type, 'json') !== false) {
throw $e;
}
if ($response->getStatusCode() == 500) {
throw new UnexpectedValueException(
'An OAuth server error was encountered that did not contain a JSON body',
0,
$e
);
}
return $content;
}
}
/**
* Checks a provider response for errors.
*
* @throws IdentityProviderException
* @param ResponseInterface $response
* @param array|string $data Parsed response data
* @return void
*/
abstract protected function checkResponse(ResponseInterface $response, $data);
/**
* Prepares an parsed access token response for a grant.
*
* Custom mapping of expiration, etc should be done here. Always call the
* parent method when overloading this method.
*
* @param mixed $result
* @return array
*/
protected function prepareAccessTokenResponse(array $result)
{
if ($this->getAccessTokenResourceOwnerId() !== null) {
$result['resource_owner_id'] = $this->getValueByKey(
$result,
$this->getAccessTokenResourceOwnerId()
);
}
return $result;
}
/**
* Creates an access token from a response.
*
* The grant that was used to fetch the response can be used to provide
* additional context.
*
* @param array $response
* @param AbstractGrant $grant
* @return AccessToken
*/
protected function createAccessToken(array $response, AbstractGrant $grant)
{
return new AccessToken($response);
}
/**
* Generates a resource owner object from a successful resource owner
* details request.
*
* @param array $response
* @param AccessToken $token
* @return ResourceOwnerInterface
*/
abstract protected function createResourceOwner(array $response, AccessToken $token);
/**
* Requests and returns the resource owner of given access token.
*
* @param AccessToken $token
* @return ResourceOwnerInterface
*/
public function getResourceOwner(AccessToken $token)
{
$response = $this->fetchResourceOwnerDetails($token);
return $this->createResourceOwner($response, $token);
}
/**
* Requests resource owner details.
*
* @param AccessToken $token
* @return mixed
*/
protected function fetchResourceOwnerDetails(AccessToken $token)
{
$url = $this->getResourceOwnerDetailsUrl($token);
$request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token);
$response = $this->getParsedResponse($request);
if (false === is_array($response)) {
throw new UnexpectedValueException(
'Invalid response received from Authorization Server. Expected JSON.'
);
}
return $response;
}
/**
* Returns the default headers used by this provider.
*
* Typically this is used to set 'Accept' or 'Content-Type' headers.
*
* @return array
*/
protected function getDefaultHeaders()
{
return [];
}
/**
* Returns the authorization headers used by this provider.
*
* Typically this is "Bearer" or "MAC". For more information see:
* http://tools.ietf.org/html/rfc6749#section-7.1
*
* No default is provided, providers must overload this method to activate
* authorization headers.
*
* @param mixed|null $token Either a string or an access token instance
* @return array
*/
protected function getAuthorizationHeaders($token = null)
{
return [];
}
/**
* Returns all headers used by this provider for a request.
*
* The request will be authenticated if an access token is provided.
*
* @param mixed|null $token object or string
* @return array
*/
public function getHeaders($token = null)
{
if ($token) {
return array_merge(
$this->getDefaultHeaders(),
$this->getAuthorizationHeaders($token)
);
}
return $this->getDefaultHeaders();
}
}
src/Provider/GenericProvider.php 0000666 00000012565 13436754462 0012760 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Provider;
use InvalidArgumentException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
/**
* Represents a generic service provider that may be used to interact with any
* OAuth 2.0 service provider, using Bearer token authentication.
*/
class GenericProvider extends AbstractProvider
{
use BearerAuthorizationTrait;
/**
* @var string
*/
private $urlAuthorize;
/**
* @var string
*/
private $urlAccessToken;
/**
* @var string
*/
private $urlResourceOwnerDetails;
/**
* @var string
*/
private $accessTokenMethod;
/**
* @var string
*/
private $accessTokenResourceOwnerId;
/**
* @var array|null
*/
private $scopes = null;
/**
* @var string
*/
private $scopeSeparator;
/**
* @var string
*/
private $responseError = 'error';
/**
* @var string
*/
private $responseCode;
/**
* @var string
*/
private $responseResourceOwnerId = 'id';
/**
* @param array $options
* @param array $collaborators
*/
public function __construct(array $options = [], array $collaborators = [])
{
$this->assertRequiredOptions($options);
$possible = $this->getConfigurableOptions();
$configured = array_intersect_key($options, array_flip($possible));
foreach ($configured as $key => $value) {
$this->$key = $value;
}
// Remove all options that are only used locally
$options = array_diff_key($options, $configured);
parent::__construct($options, $collaborators);
}
/**
* Returns all options that can be configured.
*
* @return array
*/
protected function getConfigurableOptions()
{
return array_merge($this->getRequiredOptions(), [
'accessTokenMethod',
'accessTokenResourceOwnerId',
'scopeSeparator',
'responseError',
'responseCode',
'responseResourceOwnerId',
'scopes',
]);
}
/**
* Returns all options that are required.
*
* @return array
*/
protected function getRequiredOptions()
{
return [
'urlAuthorize',
'urlAccessToken',
'urlResourceOwnerDetails',
];
}
/**
* Verifies that all required options have been passed.
*
* @param array $options
* @return void
* @throws InvalidArgumentException
*/
private function assertRequiredOptions(array $options)
{
$missing = array_diff_key(array_flip($this->getRequiredOptions()), $options);
if (!empty($missing)) {
throw new InvalidArgumentException(
'Required options not defined: ' . implode(', ', array_keys($missing))
);
}
}
/**
* @inheritdoc
*/
public function getBaseAuthorizationUrl()
{
return $this->urlAuthorize;
}
/**
* @inheritdoc
*/
public function getBaseAccessTokenUrl(array $params)
{
return $this->urlAccessToken;
}
/**
* @inheritdoc
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return $this->urlResourceOwnerDetails;
}
/**
* @inheritdoc
*/
public function getDefaultScopes()
{
return $this->scopes;
}
/**
* @inheritdoc
*/
protected function getAccessTokenMethod()
{
return $this->accessTokenMethod ?: parent::getAccessTokenMethod();
}
/**
* @inheritdoc
*/
protected function getAccessTokenResourceOwnerId()
{
return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId();
}
/**
* @inheritdoc
*/
protected function getScopeSeparator()
{
return $this->scopeSeparator ?: parent::getScopeSeparator();
}
/**
* @inheritdoc
*/
protected function checkResponse(ResponseInterface $response, $data)
{
if (!empty($data[$this->responseError])) {
$error = $data[$this->responseError];
if (!is_string($error)) {
$error = var_export($error, true);
}
$code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0;
if (!is_int($code)) {
$code = intval($code);
}
throw new IdentityProviderException($error, $code, $data);
}
}
/**
* @inheritdoc
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return new GenericResourceOwner($response, $this->responseResourceOwnerId);
}
}
src/Provider/ResourceOwnerInterface.php 0000666 00000001763 13436754462 0014312 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Provider;
/**
* Classes implementing `ResourceOwnerInterface` may be used to represent
* the resource owner authenticated with a service provider.
*/
interface ResourceOwnerInterface
{
/**
* Returns the identifier of the authorized resource owner.
*
* @return mixed
*/
public function getId();
/**
* Return all of the owner details available as an array.
*
* @return array
*/
public function toArray();
}
src/Provider/Exception/IdentityProviderException.php 0000666 00000002274 13436754462 0017006 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Provider\Exception;
/**
* Exception thrown if the provider response contains errors.
*/
class IdentityProviderException extends \Exception
{
/**
* @var mixed
*/
protected $response;
/**
* @param string $message
* @param int $code
* @param array|string $response The response body
*/
public function __construct($message, $code, $response)
{
$this->response = $response;
parent::__construct($message, $code);
}
/**
* Returns the exception's response body.
*
* @return array|string
*/
public function getResponseBody()
{
return $this->response;
}
}
src/Tool/ProviderRedirectTrait.php 0000666 00000006146 13436754462 0013272 0 ustar 00 redirectLimit) {
$attempts++;
$response = $this->getHttpClient()->send($request, [
'allow_redirects' => false
]);
if ($this->isRedirect($response)) {
$redirectUrl = new Uri($response->getHeader('Location')[0]);
$request = $request->withUri($redirectUrl);
} else {
break;
}
}
return $response;
}
/**
* Returns the HTTP client instance.
*
* @return GuzzleHttp\ClientInterface
*/
abstract public function getHttpClient();
/**
* Retrieves current redirect limit.
*
* @return integer
*/
public function getRedirectLimit()
{
return $this->redirectLimit;
}
/**
* Determines if a given response is a redirect.
*
* @param ResponseInterface $response
*
* @return boolean
*/
protected function isRedirect(ResponseInterface $response)
{
$statusCode = $response->getStatusCode();
return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location');
}
/**
* Sends a request instance and returns a response instance.
*
* WARNING: This method does not attempt to catch exceptions caused by HTTP
* errors! It is recommended to wrap this method in a try/catch block.
*
* @param RequestInterface $request
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request)
{
try {
$response = $this->followRequestRedirects($request);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
return $response;
}
/**
* Updates the redirect limit.
*
* @param integer $limit
* @return League\OAuth2\Client\Provider\AbstractProvider
* @throws InvalidArgumentException
*/
public function setRedirectLimit($limit)
{
if (!is_int($limit)) {
throw new InvalidArgumentException('redirectLimit must be an integer.');
}
if ($limit < 1) {
throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.');
}
$this->redirectLimit = $limit;
return $this;
}
}
src/Tool/RequiredParameterTrait.php 0000666 00000002776 13436754462 0013444 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
use BadMethodCallException;
/**
* Provides functionality to check for required parameters.
*/
trait RequiredParameterTrait
{
/**
* Checks for a required parameter in a hash.
*
* @throws BadMethodCallException
* @param string $name
* @param array $params
* @return void
*/
private function checkRequiredParameter($name, array $params)
{
if (!isset($params[$name])) {
throw new BadMethodCallException(sprintf(
'Required parameter not passed: "%s"',
$name
));
}
}
/**
* Checks for multiple required parameters in a hash.
*
* @throws InvalidArgumentException
* @param array $names
* @param array $params
* @return void
*/
private function checkRequiredParameters(array $names, array $params)
{
foreach ($names as $name) {
$this->checkRequiredParameter($name, $params);
}
}
}
src/Tool/RequestFactory.php 0000666 00000004427 13436754462 0011772 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
use GuzzleHttp\Psr7\Request;
/**
* Used to produce PSR-7 Request instances.
*
* @link https://github.com/guzzle/guzzle/pull/1101
*/
class RequestFactory
{
/**
* Creates a PSR-7 Request instance.
*
* @param null|string $method HTTP method for the request.
* @param null|string $uri URI for the request.
* @param array $headers Headers for the message.
* @param string|resource|StreamInterface $body Message body.
* @param string $version HTTP protocol version.
*
* @return Request
*/
public function getRequest(
$method,
$uri,
array $headers = [],
$body = null,
$version = '1.1'
) {
return new Request($method, $uri, $headers, $body, $version);
}
/**
* Parses simplified options.
*
* @param array $options Simplified options.
*
* @return array Extended options for use with getRequest.
*/
protected function parseOptions(array $options)
{
// Should match default values for getRequest
$defaults = [
'headers' => [],
'body' => null,
'version' => '1.1',
];
return array_merge($defaults, $options);
}
/**
* Creates a request using a simplified array of options.
*
* @param null|string $method
* @param null|string $uri
* @param array $options
*
* @return Request
*/
public function getRequestWithOptions($method, $uri, array $options = [])
{
$options = $this->parseOptions($options);
return $this->getRequest(
$method,
$uri,
$options['headers'],
$options['body'],
$options['version']
);
}
}
src/Tool/BearerAuthorizationTrait.php 0000666 00000002020 13436754462 0013762 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
/**
* Enables `Bearer` header authorization for providers.
*
* @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750)
*/
trait BearerAuthorizationTrait
{
/**
* Returns authorization headers for the 'bearer' grant.
*
* @param mixed|null $token Either a string or an access token instance
* @return array
*/
protected function getAuthorizationHeaders($token = null)
{
return ['Authorization' => 'Bearer ' . $token];
}
}
src/Tool/ArrayAccessorTrait.php 0000666 00000002637 13436754462 0012560 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
/**
* Provides generic array navigation tools.
*/
trait ArrayAccessorTrait
{
/**
* Returns a value by key using dot notation.
*
* @param array $data
* @param string $key
* @param mixed|null $default
* @return mixed
*/
private function getValueByKey(array $data, $key, $default = null)
{
if (!is_string($key) || empty($key) || !count($data)) {
return $default;
}
if (strpos($key, '.') !== false) {
$keys = explode('.', $key);
foreach ($keys as $innerKey) {
if (!is_array($data) || !array_key_exists($innerKey, $data)) {
return $default;
}
$data = $data[$innerKey];
}
return $data;
}
return array_key_exists($key, $data) ? $data[$key] : $default;
}
}
src/Tool/QueryBuilderTrait.php 0000666 00000001611 13436754462 0012422 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
/**
* Provides a standard way to generate query strings.
*/
trait QueryBuilderTrait
{
/**
* Build a query string from an array.
*
* @param array $params
*
* @return string
*/
protected function buildQueryString(array $params)
{
return http_build_query($params, null, '&', \PHP_QUERY_RFC3986);
}
}
src/Tool/MacAuthorizationTrait.php 0000666 00000004507 13436754462 0013276 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Tool;
use League\OAuth2\Client\Token\AccessToken;
/**
* Enables `MAC` header authorization for providers.
*
* @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens
*/
trait MacAuthorizationTrait
{
/**
* Returns the id of this token for MAC generation.
*
* @param AccessToken $token
* @return string
*/
abstract protected function getTokenId(AccessToken $token);
/**
* Returns the MAC signature for the current request.
*
* @param string $id
* @param integer $ts
* @param string $nonce
* @return string
*/
abstract protected function getMacSignature($id, $ts, $nonce);
/**
* Returns a new random string to use as the state parameter in an
* authorization flow.
*
* @param int $length Length of the random string to be generated.
* @return string
*/
abstract protected function getRandomState($length);
/**
* Returns the authorization headers for the 'mac' grant.
*
* @param AccessToken $token
* @return array
* @codeCoverageIgnore
*
* @todo This is currently untested and provided only as an example. If you
* complete the implementation, please create a pull request for
* https://github.com/thephpleague/oauth2-client
*/
protected function getAuthorizationHeaders($token)
{
$ts = time();
$id = $this->getTokenId($token);
$nonce = $this->getRandomState(16);
$mac = $this->getMacSignature($id, $ts, $nonce);
$parts = [];
foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) {
$parts[] = sprintf('%s="%s"', $key, $value);
}
return ['Authorization' => 'MAC ' . implode(', ', $parts)];
}
}
src/Token/AccessToken.php 0000666 00000013512 13436754462 0011352 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Token;
use InvalidArgumentException;
use JsonSerializable;
use RuntimeException;
/**
* Represents an access token.
*
* @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4)
*/
class AccessToken implements JsonSerializable
{
/**
* @var string
*/
protected $accessToken;
/**
* @var int
*/
protected $expires;
/**
* @var string
*/
protected $refreshToken;
/**
* @var string
*/
protected $resourceOwnerId;
/**
* @var array
*/
protected $values = [];
/**
* Constructs an access token.
*
* @param array $options An array of options returned by the service provider
* in the access token request. The `access_token` option is required.
* @throws InvalidArgumentException if `access_token` is not provided in `$options`.
*/
public function __construct(array $options = [])
{
if (empty($options['access_token'])) {
throw new InvalidArgumentException('Required option not passed: "access_token"');
}
$this->accessToken = $options['access_token'];
if (!empty($options['resource_owner_id'])) {
$this->resourceOwnerId = $options['resource_owner_id'];
}
if (!empty($options['refresh_token'])) {
$this->refreshToken = $options['refresh_token'];
}
// We need to know when the token expires. Show preference to
// 'expires_in' since it is defined in RFC6749 Section 5.1.
// Defer to 'expires' if it is provided instead.
if (isset($options['expires_in'])) {
if (!is_numeric($options['expires_in'])) {
throw new \InvalidArgumentException('expires_in value must be an integer');
}
$this->expires = $options['expires_in'] != 0 ? time() + $options['expires_in'] : 0;
} elseif (!empty($options['expires'])) {
// Some providers supply the seconds until expiration rather than
// the exact timestamp. Take a best guess at which we received.
$expires = $options['expires'];
if (!$this->isExpirationTimestamp($expires)) {
$expires += time();
}
$this->expires = $expires;
}
// Capture any additional values that might exist in the token but are
// not part of the standard response. Vendors will sometimes pass
// additional user data this way.
$this->values = array_diff_key($options, array_flip([
'access_token',
'resource_owner_id',
'refresh_token',
'expires_in',
'expires',
]));
}
/**
* Check if a value is an expiration timestamp or second value.
*
* @param integer $value
* @return bool
*/
protected function isExpirationTimestamp($value)
{
// If the given value is larger than the original OAuth 2 draft date,
// assume that it is meant to be a (possible expired) timestamp.
$oauth2InceptionDate = 1349067600; // 2012-10-01
return ($value > $oauth2InceptionDate);
}
/**
* Returns the access token string of this instance.
*
* @return string
*/
public function getToken()
{
return $this->accessToken;
}
/**
* Returns the refresh token, if defined.
*
* @return string|null
*/
public function getRefreshToken()
{
return $this->refreshToken;
}
/**
* Returns the expiration timestamp, if defined.
*
* @return integer|null
*/
public function getExpires()
{
return $this->expires;
}
/**
* Returns the resource owner identifier, if defined.
*
* @return string|null
*/
public function getResourceOwnerId()
{
return $this->resourceOwnerId;
}
/**
* Checks if this token has expired.
*
* @return boolean true if the token has expired, false otherwise.
* @throws RuntimeException if 'expires' is not set on the token.
*/
public function hasExpired()
{
$expires = $this->getExpires();
if (empty($expires)) {
throw new RuntimeException('"expires" is not set on the token');
}
return $expires < time();
}
/**
* Returns additional vendor values stored in the token.
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Returns the token key.
*
* @return string
*/
public function __toString()
{
return (string) $this->getToken();
}
/**
* Returns an array of parameters to serialize when this is serialized with
* json_encode().
*
* @return array
*/
public function jsonSerialize()
{
$parameters = $this->values;
if ($this->accessToken) {
$parameters['access_token'] = $this->accessToken;
}
if ($this->refreshToken) {
$parameters['refresh_token'] = $this->refreshToken;
}
if ($this->expires) {
$parameters['expires'] = $this->expires;
}
if ($this->resourceOwnerId) {
$parameters['resource_owner_id'] = $this->resourceOwnerId;
}
return $parameters;
}
}
src/Grant/Password.php 0000666 00000002047 13436754462 0010746 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
/**
* Represents a resource owner password credentials grant.
*
* @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3)
*/
class Password extends AbstractGrant
{
/**
* @inheritdoc
*/
protected function getName()
{
return 'password';
}
/**
* @inheritdoc
*/
protected function getRequiredRequestParameters()
{
return [
'username',
'password',
];
}
}
src/Grant/AbstractGrant.php 0000666 00000004645 13436754462 0011711 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Tool\RequiredParameterTrait;
/**
* Represents a type of authorization grant.
*
* An authorization grant is a credential representing the resource
* owner's authorization (to access its protected resources) used by the
* client to obtain an access token. OAuth 2.0 defines four
* grant types -- authorization code, implicit, resource owner password
* credentials, and client credentials -- as well as an extensibility
* mechanism for defining additional types.
*
* @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3)
*/
abstract class AbstractGrant
{
use RequiredParameterTrait;
/**
* Returns the name of this grant, eg. 'grant_name', which is used as the
* grant type when encoding URL query parameters.
*
* @return string
*/
abstract protected function getName();
/**
* Returns a list of all required request parameters.
*
* @return array
*/
abstract protected function getRequiredRequestParameters();
/**
* Returns this grant's name as its string representation. This allows for
* string interpolation when building URL query parameters.
*
* @return string
*/
public function __toString()
{
return $this->getName();
}
/**
* Prepares an access token request's parameters by checking that all
* required parameters are set, then merging with any given defaults.
*
* @param array $defaults
* @param array $options
* @return array
*/
public function prepareRequestParameters(array $defaults, array $options)
{
$defaults['grant_type'] = $this->getName();
$required = $this->getRequiredRequestParameters();
$provided = array_merge($defaults, $options);
$this->checkRequiredParameters($required, $provided);
return $provided;
}
}
src/Grant/AuthorizationCode.php 0000666 00000001775 13436754462 0012606 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
/**
* Represents an authorization code grant.
*
* @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1)
*/
class AuthorizationCode extends AbstractGrant
{
/**
* @inheritdoc
*/
protected function getName()
{
return 'authorization_code';
}
/**
* @inheritdoc
*/
protected function getRequiredRequestParameters()
{
return [
'code',
];
}
}
src/Grant/GrantFactory.php 0000666 00000005144 13436754462 0011550 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Grant\Exception\InvalidGrantException;
/**
* Represents a factory used when retrieving an authorization grant type.
*/
class GrantFactory
{
/**
* @var array
*/
protected $registry = [];
/**
* Defines a grant singleton in the registry.
*
* @param string $name
* @param AbstractGrant $grant
* @return self
*/
public function setGrant($name, AbstractGrant $grant)
{
$this->registry[$name] = $grant;
return $this;
}
/**
* Returns a grant singleton by name.
*
* If the grant has not be registered, a default grant will be loaded.
*
* @param string $name
* @return AbstractGrant
*/
public function getGrant($name)
{
if (empty($this->registry[$name])) {
$this->registerDefaultGrant($name);
}
return $this->registry[$name];
}
/**
* Registers a default grant singleton by name.
*
* @param string $name
* @return self
*/
protected function registerDefaultGrant($name)
{
// PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode'
$class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
$class = 'League\\OAuth2\\Client\\Grant\\' . $class;
$this->checkGrant($class);
return $this->setGrant($name, new $class);
}
/**
* Determines if a variable is a valid grant.
*
* @param mixed $class
* @return boolean
*/
public function isGrant($class)
{
return is_subclass_of($class, AbstractGrant::class);
}
/**
* Checks if a variable is a valid grant.
*
* @throws InvalidGrantException
* @param mixed $class
* @return void
*/
public function checkGrant($class)
{
if (!$this->isGrant($class)) {
throw new InvalidGrantException(sprintf(
'Grant "%s" must extend AbstractGrant',
is_object($class) ? get_class($class) : $class
));
}
}
}
src/Grant/RefreshToken.php 0000666 00000001766 13436754462 0011552 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
/**
* Represents a refresh token grant.
*
* @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6)
*/
class RefreshToken extends AbstractGrant
{
/**
* @inheritdoc
*/
protected function getName()
{
return 'refresh_token';
}
/**
* @inheritdoc
*/
protected function getRequiredRequestParameters()
{
return [
'refresh_token',
];
}
}
src/Grant/Exception/InvalidGrantException.php 0000666 00000001424 13436754462 0015341 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant\Exception;
use InvalidArgumentException;
/**
* Exception thrown if the grant does not extend from AbstractGrant.
*
* @see League\OAuth2\Client\Grant\AbstractGrant
*/
class InvalidGrantException extends InvalidArgumentException
{
}
src/Grant/ClientCredentials.php 0000666 00000001737 13436754462 0012545 0 ustar 00
* @license http://opensource.org/licenses/MIT MIT
* @link http://thephpleague.com/oauth2-client/ Documentation
* @link https://packagist.org/packages/league/oauth2-client Packagist
* @link https://github.com/thephpleague/oauth2-client GitHub
*/
namespace League\OAuth2\Client\Grant;
/**
* Represents a client credentials grant.
*
* @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4)
*/
class ClientCredentials extends AbstractGrant
{
/**
* @inheritdoc
*/
protected function getName()
{
return 'client_credentials';
}
/**
* @inheritdoc
*/
protected function getRequiredRequestParameters()
{
return [];
}
}
CREDITS.md 0000666 00000001073 13436754462 0006206 0 ustar 00 # OAuth 2.0 Client
## Authors
Also see .
### Current Maintainer
- [Ben Ramsey](https://github.com/ramsey)
### Contributors
- [Alex Bilbie](https://github.com/alexbilbie)
- [Ben Corlett](https://github.com/bencorlett)
- [Ben Ramsey](https://github.com/ramsey)
- [James Mills](https://github.com/jamesmills)
- [Phil Sturgeon](https://github.com/philsturgeon)
- [Rudi Theunissen](https://github.com/rtheunissen)
- [Tom Anderson](https://github.com/TomHAnderson)
- [Woody Gilk](https://github.com/shadowhand)
composer.json 0000666 00000002576 13436754462 0007322 0 ustar 00 {
"name": "league/oauth2-client",
"description": "OAuth 2.0 Client Library",
"license": "MIT",
"config": {
"sort-packages": true
},
"require": {
"php": "^5.6|^7.0",
"guzzlehttp/guzzle": "^6.0",
"paragonie/random_compat": "^1|^2|^9.99"
},
"require-dev": {
"eloquent/liberator": "^2.0",
"eloquent/phony-phpunit": "^1.0|^3.0",
"jakub-onderka/php-parallel-lint": "^0.9.2",
"phpunit/phpunit": "^5.7|^6.0",
"squizlabs/php_codesniffer": "^2.3|^3.0"
},
"keywords": [
"oauth",
"oauth2",
"authorization",
"authentication",
"idp",
"identity",
"sso",
"single sign on"
],
"authors": [
{
"name": "Alex Bilbie",
"email": "hello@alexbilbie.com",
"homepage": "http://www.alexbilbie.com",
"role": "Developer"
},
{
"name": "Woody Gilk",
"homepage": "https://github.com/shadowhand",
"role": "Contributor"
}
],
"autoload": {
"psr-4": {
"League\\OAuth2\\Client\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"League\\OAuth2\\Client\\Test\\": "test/src/"
}
},
"extra": {
"branch-alias": {
"dev-2.x": "2.0.x-dev"
}
}
}
CHANGELOG.md 0000666 00000024506 13436754462 0006406 0 ustar 00 # OAuth 2.0 Client Changelog
## 2.3.1
_Released: 2018-11-19_
* Allow paragonie/random_compat's empty 9.99.99 placeholder
* Throw an `UnexpectedValueException` on non-JSON responses from access token
request (when calling `AbstractProvider::getAccessToken()`)
## 2.3.0
_Released: 2018-01-13_
* Add `ProviderRedirectTrait` tool for 3rd-party provider libraries to use when
handling provider redirections
* Fix TypeError thrown because `getResourceOwner()` receives a non-JSON Response
* Gracefully handle non-standard errors received from providers
* Update README to reflect official support of PHP 7.2
## 2.2.1
_Released: 2017-04-25_
* Fix potential type error when HTTP 500 errors are encountered
* Allow broader range of `random_compat` versions
## 2.2.0
_Released: 2017-02-01_
* Allow base URLs to contain query parameters
* Protect against `+` being improperly encoded in URL parameters
* Remove misleading `state` option from authorization parameters
* Stop generating more random bytes than necessary
## 2.1.0
_Released: 2017-01-24_
* Allow `expires_in` with a value of `0`
## 2.0.0
_Released: 2017-01-12_
* Rename `getResponse()` to `getParsedResponse()`
* Add `getResponse()` method that returns the unparsed PSR-7 `Response` instance
* Removed `RandomFactory`, switched to native random functions
## 1.4.1
_Released: 2016-04-29_
* Add `QueryBuilderTrait` to standardize query string generation.
## 1.4.0
_Released: 2016-04-19_
* Add `AccessToken::getValues()` to access additional vendor data provided with tokens.
## 1.3.0
_Released: 2016-02-13_
* Enable dynamic parameters being passed into the authorization URL.
* Minor documentation updates.
## 1.2.0
_Released: 2016-01-23_
* Add `resource_owner_id` to the JSON-serialized representation of the access token.
* Minor documentation updates and improved test coverage.
## 1.1.0
_Released: 2015-11-13_
* Add `ArrayAccessorTrait`, update `AbstractProvider` to utilize.
* Use `expires` to serialize access tokens.
* Documentation updates.
## 1.0.2
_Released: 2015-09-22_
* Allow access tokens to be created from storage (see #431).
* Minor fixes and documentation updates.
## 1.0.1
_Released: 2015-08-26_
* Allow required parameters checked using the `RequiredParameterTrait` to be set as `false`, `null`, `"0"`, etc.
## 1.0.0
_Released: 2015-08-19_
* We are running code-quality builds through Scrutinizer, and we are running unit test builds on the new Travis CI container-based infrastructure.
* Cleaned up code, as recommended by Scrutinizer.
* Documentation updates.
## 1.0.0-beta2
_Released: 2015-08-12_
* BREAK: Add toArray() to ResourceOwnerInterface.
* Always attempt to parse responses as JSON and fallback on failure.
* Add dot notation support to access token resource owner ID.
* Use the Bearer authorization header for the generic provider.
* Documentation updates.
## 1.0.0-beta1
_Released: 2015-07-16_
* API for 1.0 is now frozen!
* BREAK: Convert all uses of "User" to "ResourceOwner" to more closely match the OAuth 2.0 specification.
* BREAK: Rename `StandardProvider` to `GenericProvider`.
* BREAK: Move access token creation to the `AbstractProvider`. It was previously handled in the `AbstractGrant`.
* FIX: Add `Content-Type` header with value of `application/x-www-form-urlencoded` to the request header when retrieving access tokens. This adheres to the OAuth 2.0 specification and fixes issues where certain OAuth servers expect this header.
* Enhanced `json_encode()` serialization of AccessToken; when using `json_encode()` on an AccessToken, it will return a JSON object with these properties: `access_token`, `refresh_token`, and `expires_in`.
## 1.0.0-alpha2
_Released: 2015-07-04_
* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_GET` to `AbstractProvider::METHOD_GET`.
* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_POST` to `AbstractProvider::METHOD_POST`.
* BREAK: Renamed `AbstractProvider::prepareUserDetails()` to `AbstractProvider::createUser()`.
* BREAK: Renamed `AbstractProvider::getUserDetails()` to `AbstractProvider::getUser()`.
* BREAK: Removed `$token` parameter from `AbstractProvider::getDefaultHeaders()`.
* BREAK: Modify `AbstractProvider::getBaseAccessTokenUrl()` to accept a required array of parameters, allowing providers the ability to vary the access token URL, based on the parameters.
* Removed newline characters from MAC Authorization header.
* Documentation updates, notably:
- Moved list of providers to `README.PROVIDERS.md`.
- Moved provider creation notes to `README.PROVIDER-GUIDE.md`.
## 1.0.0-alpha1
_Released: 2015-06-25_
This release contains numerous BC breaks from the 0.x series. Please note these breaks and refer to the [upgrade guide](GUIDE-UPGRADING.md).
* BREAK: Requires PHP 5.5.0 and greater.
* BREAK: All providers have been moved to separate repositories, one for each provider.
* BREAK: All `public` properties have been set as `protected` or `private` and getters/setters have been introduced for access to these properties.
* BREAK: The `Provider\ProviderInterface` has been removed. Please extend from and override `Provider\AbstractProvider`.
* BREAK: The `Entity\User` has been removed. Providers should implement the `Provider\UserInterface` and provide user functionality instead of expecting it in this base library.
* BREAK: The `Grant\GrantInterface` has been removed. Providers needing to provide a new grant type should extend from and override `Grant\AbstractGrant`.
* A generic `Provider\StandardProvider` has been introduced, which may be used as a client to integrate with most OAuth 2.0 compatible servers.
* A `Grant\GrantFactory` has been introduced as a means to register and retrieve singleton grants from a registry.
* Introduced traits for bearer and MAC authorization (`Tool\BearerAuthorizationTrait` and `Tool\MacAuthorizationTrait`), which providers may use to enable these header authorization types.
## 0.12.1
_Released: 2015-06-20_
* FIX: Scope separators for LinkedIn and Instagram are now correctly a single space
## 0.12.0
_Released: 2015-06-15_
* BREAK: LinkedIn Provider: Default scopes removed from LinkedIn Provider. See "[Managing LinkedIn Scopes](https://github.com/thephpleague/oauth2-client/blob/9cea9864c2e89bce1b922d1e37ba5378b3b0b264/README.md#managing-linkedin-scopes)" in the README for information on how to set scopes. See [#327](https://github.com/thephpleague/oauth2-client/pull/327) and [#307](https://github.com/thephpleague/oauth2-client/pull/307) for details on this change.
* FIX: LinkedIn Provider: A scenario existed in which `publicProfileUrl` was not set, generating a PHP notice; this has been fixed.
* FIX: Instagram Provider: Fixed scope separator.
* Documentation updates and corrections.
## 0.11.0
_Released: 2015-04-25_
* Identity Provider: Better handling of error responses
* Documentation updates
## 0.10.1
_Released: 2015-04-02_
* FIX: Invalid JSON triggering fatal error
* FIX: Sending headers along with auth `getAccessToken()` requests
* Now running Travis CI tests on PHP 7
* Documentation updates
## 0.10.0
_Released: 2015-03-10_
* Providers: Added `getHeaders()` to ProviderInterface and updated AbstractProvider to provide the method
* Providers: Updated all bundled providers to support new `$authorizationHeader` property
* Identity Provider: Update IDPException to account for empty strings
* Identity Provider: Added `getResponseBody()` method to IDPException
* Documentation updates, minor bug fixes, and coding standards fixes
## 0.9.0
_Released: 2015-02-24_
* Add `AbstractProvider::prepareAccessTokenResult()` to provide additional token response preparation to providers
* Remove custom provider code from AccessToken
* Add links to README for Dropbox and Square providers
## 0.8.1
_Released: 2015-02-12_
* Allow `approval_prompt` to be set by providers. This fixes an issue where some providers have problems if the `approval_prompt` is present in the query string.
## 0.8.0
_Released: 2015-02-10_
* Facebook Provider: Upgrade to Graph API v2.2
* Google Provider: Add `access_type` parameter for Google authorization URL
* Get a more reliable response body on errors
## 0.7.2
_Released: 2015-02-03_
* GitHub Provider: Fix regression
* Documentation updates
## 0.7.1
_Released: 2015-01-06_
* Google Provider: fixed issue where Google API was not returning the user ID
## 0.7.0
_Released: 2014-12-29_
* Improvements to Provider\AbstractProvider (addition of `userUid()`, `userEmail()`, and `userScreenName()`)
* GitHub Provider: Support for GitHub Enterprise
* GitHub Provider: Methods to allow fetching user email addresses
* Google Provider: Updated scopes and endpoints to remove deprecated values
* Documentation updates, minor bug fixes, and coding standards fixes
## 0.6.0
_Released: 2014-12-03_
* Added ability to specify a redirect handler for providers through use of a callback (see [Provider\AbstractProvider::setRedirectHandler()](https://github.com/thephpleague/oauth2-client/blob/55de45401eaa21f53c0b2414091da6f3b0f3fcb7/src/Provider/AbstractProvider.php#L314-L317))
* Updated authorize and token URLs for the Microsoft provider; the old URLs had been phased out and were no longer working (see #146)
* Increased test coverage
* Documentation updates, minor bug fixes, and coding standards fixes
## 0.5.0
_Released: 2014-11-28_
* Added `ClientCredentials` and `Password` grants
* Added support for providers to set their own `uid` parameter key name
* Added support for Google's `hd` (hosted domain) parameter
* Added support for providing a custom `state` parameter to the authorization URL
* LinkedIn `pictureUrl` is now an optional response element
* Added Battle.net provider package link to README
* Added Meetup provider package link to README
* Added `.gitattributes` file
* Increased test coverage
* A number of documentation fixes, minor bug fixes, and coding standards fixes
## 0.4.0
_Released: 2014-10-28_
* Added `ProviderInterface` and removed `IdentityProvider`.
* Expose generated state to allow for CSRF validation.
* Renamed `League\OAuth2\Client\Provider\User` to `League\OAuth2\Client\Entity\User`.
* Entity: User: added `gender` and `locale` properties
* Updating logic for populating the token expiration time.
## 0.3.0
_Released: 2014-04-26_
* This release made some huge leaps forward, including 100% unit-coverage and a bunch of new features.
## 0.2.0
_Released: 2013-05-28_
* No release notes available.
## 0.1.0
_Released: 2013-05-25_
* Initial release.
CONTRIBUTING.md 0000666 00000003556 13436754462 0007030 0 ustar 00 # Contributing
Contributions are **welcome** and will be fully **credited**.
We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-client).
## Pull Requests
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.
- **Create topic branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.
- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.
## Testing
The following tests must pass for a build to be considered successful. If contributing, please ensure these pass before submitting a pull request.
``` bash
$ ./vendor/bin/parallel-lint src test
$ ./vendor/bin/phpunit --coverage-text
$ ./vendor/bin/phpcs src --standard=psr2 -sp
```
**Happy coding**!