Browse Source

Simplify everything

* Remove useless wrapper code (IDPException, Entity\User, Grant\*, httpBuildQuery)
* Remove useless getUserUid(), getUserEmail(), getUserScreenName() methods that throw away remote-fetched data
* Do not depend on Guzzle, use just cURL instead
* Shorten namespace name and remove sub-namespaces
* Do not use Mockery in tests, use custom cURL mock instead (TESTS STILL PASS)
oauth1test
Vitaliy Filippov 7 years ago
parent
commit
b789b77c07
  1. 5
      .travis.yml
  2. 78
      README.md
  3. 21
      composer.json
  4. 298
      src/AbstractProvider.php
  5. 40
      src/AccessToken.php
  6. 117
      src/Entity/User.php
  7. 31
      src/Eventbrite.php
  8. 64
      src/Exception/IDPException.php
  9. 33
      src/Facebook.php
  10. 42
      src/Github.php
  11. 53
      src/Google.php
  12. 27
      src/Grant/AuthorizationCode.php
  13. 25
      src/Grant/ClientCredentials.php
  14. 12
      src/Grant/GrantInterface.php
  15. 33
      src/Grant/Password.php
  16. 29
      src/Grant/RefreshToken.php
  17. 36
      src/Instagram.php
  18. 32
      src/LinkedIn.php
  19. 51
      src/Microsoft.php
  20. 382
      src/Provider/AbstractProvider.php
  21. 51
      src/Provider/Eventbrite.php
  22. 58
      src/Provider/Instagram.php
  23. 69
      src/Provider/Microsoft.php
  24. 36
      src/Provider/ProviderInterface.php
  25. 42
      src/Vkontakte.php
  26. 2
      test/Bootstrap.php
  27. 63
      test/CurlMock.php
  28. 119
      test/src/AbstractProviderTest.php
  29. 6
      test/src/AccessTokenTest.php
  30. 18
      test/src/AuthorizationCodeTest.php
  31. 25
      test/src/ClientCredentialsTest.php
  32. 64
      test/src/Entity/UserTest.php
  33. 40
      test/src/EventbriteTest.php
  34. 81
      test/src/Exception/IDPExceptionTest.php
  35. 56
      test/src/FacebookTest.php
  36. 85
      test/src/GithubTest.php
  37. 42
      test/src/GoogleTest.php
  38. 42
      test/src/Grant/ClientCredentialsTest.php
  39. 64
      test/src/Grant/RefreshTokenTest.php
  40. 42
      test/src/InstagramTest.php
  41. 72
      test/src/LinkedInTest.php
  42. 48
      test/src/MicrosoftTest.php
  43. 25
      test/src/PasswordTest.php
  44. 195
      test/src/Provider/AbstractProviderTest.php
  45. 96
      test/src/Provider/LinkedInTest.php
  46. 95
      test/src/Provider/VkontakteTest.php
  47. 41
      test/src/RefreshTokenTest.php
  48. 73
      test/src/VkontakteTest.php

5
.travis.yml

@ -7,15 +7,10 @@ php:
- 7.0
- hhvm
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-source
script:
- mkdir -p build/logs
- ./vendor/bin/parallel-lint src test
- ./vendor/bin/phpunit --coverage-text
- ./vendor/bin/phpcs src --standard=psr2 -sp
after_script:
- php vendor/bin/coveralls

78
README.md

@ -1,9 +1,8 @@
# OAuth 2.0 Client
This is a STANDALONE SIMPLIFIED fork of https://github.com/thephpleague/oauth2-client/
cURL is used instead of Guzzle, some useless wrapper code is removed. Tests still pass.
[![Build Status](https://travis-ci.org/thephpleague/oauth2-client.svg?branch=master)](https://travis-ci.org/thephpleague/oauth2-client)
[![Coverage Status](https://coveralls.io/repos/thephpleague/oauth2-client/badge.svg?branch=master)](https://coveralls.io/r/thephpleague/oauth2-client?branch=master)
[![Latest Stable Version](https://poser.pugx.org/league/oauth2-client/version.svg)](https://packagist.org/packages/league/oauth2-client)
[![Total Downloads](https://poser.pugx.org/league/oauth2-client/downloads.svg)](https://packagist.org/packages/league/oauth2-client)
# OAuth 2.0 Client
This package makes it stupidly simple to integrate your application with OAuth 2.0 identity providers.
@ -13,14 +12,6 @@ integration is an important feature of most web-apps these days. Many of these s
It will work with any OAuth 2.0 provider (be it an OAuth 2.0 Server for your own API or Facebook) and provides support
for popular systems out of the box. This package abstracts out some of the subtle but important differences between various providers, handles access tokens and refresh tokens, and allows you easy access to profile information on these other sites.
This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send
a patch via pull request.
[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
## Requirements
The following versions of PHP are supported.
@ -36,45 +27,47 @@ The following versions of PHP are supported.
### Authorization Code Flow
```php
$provider = new League\OAuth2\Client\Provider\<ProviderName>([
$provider = new \OAuth2\<ProviderName>([
'clientId' => 'XXXXXXXX',
'clientSecret' => 'XXXXXXXX',
'redirectUri' => 'https://your-registered-redirect-uri/',
'scopes' => ['email', '...', '...'],
]);
if (!isset($_GET['code'])) {
if (!isset($_GET['code']))
{
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->state;
header('Location: '.$authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
}
elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state']))
{
// Check given state against previously stored one to mitigate CSRF attack
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
}
else
{
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// Optional: Now you have a token you can look up a users profile data
try {
try
{
// We got an access token, let's now get the user's details
$userDetails = $provider->getUserDetails($token);
// Use these details to create a new profile
printf('Hello %s!', $userDetails->firstName);
} catch (Exception $e) {
printf('Hello %s!', $userDetails['firstname']);
var_dump($userDetails);
}
catch (Exception $e)
{
// Failed to get user details
exit('Oh dear...');
}
@ -93,17 +86,15 @@ if (!isset($_GET['code'])) {
### Refreshing a Token
```php
$provider = new League\OAuth2\Client\Provider\<ProviderName>([
$provider = new \OAuth2\<ProviderName>([
'clientId' => 'XXXXXXXX',
'clientSecret' => 'XXXXXXXX',
'redirectUri' => 'https://your-registered-redirect-uri/',
]);
$grant = new \League\OAuth2\Client\Grant\RefreshToken();
$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]);
$token = $provider->getAccessToken('refresh_token', ['refresh_token' => $refreshToken]);
```
### Built-In Providers
This package currently has built-in support for:
@ -147,14 +138,14 @@ so please help them out with a pull request if you notice this.
### 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` and implement the required abstract
implement your own. Simply extend `\OAuth2\AbstractProvider` and implement the required abstract
methods:
```php
abstract public function urlAuthorize();
abstract public function urlAccessToken();
abstract public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token);
abstract public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token);
abstract public function urlUserDetails(\OAuth2\AccessToken $token);
abstract public function userDetails($response, \OAuth2\AccessToken $token);
```
Each of these abstract methods contain a docblock defining their expectations and typical behaviour. Once you have
@ -173,18 +164,12 @@ provider you would add a property:
public $uidKey = 'accountId';
```
### Client Packages
Some developers use this library as a base for their own PHP API wrappers, and that seems like a really great idea. It might make it slightly tricky to integrate their provider with an existing generic "OAuth 2.0 All the Things" login system, but it does make working with them easier.
- [Sniply](https://github.com/younes0/sniply)
## Install
Via Composer
``` bash
$ composer require league/oauth2-client
$ composer require vitalif/oauth2-client
```
## Testing
@ -193,13 +178,9 @@ $ composer require league/oauth2-client
$ ./vendor/bin/phpunit
```
## Contributing
Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details.
## Credits
- [Vitaliy Filippov](https://github.com/vitalif) - this simplified version
- [Alex Bilbie](https://github.com/alexbilbie)
- [Ben Corlett](https://github.com/bencorlett)
- [James Mills](https://github.com/jamesmills)
@ -207,7 +188,6 @@ Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/mas
- [Tom Anderson](https://github.com/TomHAnderson)
- [All Contributors](https://github.com/thephpleague/oauth2-client/contributors)
## License
The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information.
The MIT License (MIT). Please see [License File](https://github.com/vitalif/oauth2-client/blob/master/LICENSE) for more information.

21
composer.json

@ -1,17 +1,12 @@
{
"name": "league/oauth2-client",
"description": "OAuth 2.0 Client Library",
"name": "vitalif/oauth2-client",
"description": "OAuth 2.0 Client Library Simplified",
"license": "MIT",
"require": {
"php": ">=5.4.0",
"guzzle/guzzle": "~3.7"
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0",
"mockery/mockery": "~0.9",
"squizlabs/php_codesniffer": "~2.0",
"satooshi/php-coveralls": "0.6.*",
"jakub-onderka/php-parallel-lint": "0.8.*"
"phpunit/phpunit": "~4.0"
},
"keywords": [
"oauth",
@ -33,17 +28,17 @@
],
"autoload": {
"psr-4": {
"League\\OAuth2\\Client\\": "src/"
"OAuth2\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"League\\OAuth2\\Client\\Test\\": "test/src/"
}
"OAuth2\\Test\\": "test/src/"
},
"files": ["test/CurlMock.php"]
},
"extra": {
"branch-alias": {
"dev-master": "0.10.x-dev"
}
}
}

298
src/AbstractProvider.php

@ -0,0 +1,298 @@
<?php
namespace OAuth2;
interface ProviderInterface
{
public function urlAuthorize();
public function urlAccessToken();
public function urlUserDetails(AccessToken $token);
public function userDetails($response, AccessToken $token);
public function getScopes();
public function setScopes(array $scopes);
public function getAuthorizationUrl($options = []);
public function authorize($options = []);
public function getAccessToken($grant = 'authorization_code', $params = []);
public function getHeaders($token = null);
public function getUserDetails(AccessToken $token);
}
abstract class AbstractProvider implements ProviderInterface
{
public $clientId = '';
public $clientSecret = '';
public $redirectUri = '';
public $state;
public $name;
public $uidKey = 'uid';
public $scopes = [];
public $method = 'post';
public $scopeSeparator = ',';
public $responseType = 'json';
public $headers = [];
public $authorizationHeader;
protected $redirectHandler;
public function __construct($options = [])
{
foreach ($options as $option => $value)
{
if (property_exists($this, $option))
{
$this->{$option} = $value;
}
}
}
/**
* Get the URL that this provider uses to begin authorization.
*
* @return string
*/
abstract public function urlAuthorize();
/**
* Get the URL that this provider users to request an access token.
*
* @return string
*/
abstract public function urlAccessToken();
/**
* Get the URL that this provider uses to request user details.
*
* Since this URL is typically an authorized route, most providers will require you to pass the access_token as
* a parameter to the request. For example, the google url is:
*
* 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token='.$token
*
* @param AccessToken $token
* @return string
*/
abstract public function urlUserDetails(AccessToken $token);
/**
* Given an object response from the server, process the user details into a format expected by the user
* of the client.
*
* @param object $response
* @param AccessToken $token
* @return mixed
*/
abstract public function userDetails($response, AccessToken $token);
public function getScopes()
{
return $this->scopes;
}
public function setScopes(array $scopes)
{
$this->scopes = $scopes;
}
public function getAuthorizationUrl($options = [])
{
$this->state = isset($options['state']) ? $options['state'] : md5(uniqid(rand(), true));
$params = [
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->state,
'scope' => is_array($this->scopes) ? implode($this->scopeSeparator, $this->scopes) : $this->scopes,
'response_type' => isset($options['response_type']) ? $options['response_type'] : 'code',
'approval_prompt' => isset($options['approval_prompt']) ? $options['approval_prompt'] : 'auto',
];
return $this->urlAuthorize().'?'.http_build_query($params);
}
public function authorize($options = [])
{
$url = $this->getAuthorizationUrl($options);
if ($this->redirectHandler)
{
$handler = $this->redirectHandler;
return $handler($url);
}
header('Location: ' . $url);
exit;
}
/**
* @param string $grant: grant type, one of 'authorization_code' (default), 'client_credentials', 'refresh_token', 'password'
*/
public function getAccessToken($grant = 'authorization_code', $params = [])
{
if ($grant == 'password' && (empty($params['username']) || empty($params['password'])))
{
throw new \BadMethodCallException('Missing username or password');
}
elseif ($grant == 'authorization_code' && empty($params['code']))
{
throw new \BadMethodCallException('Missing authorization code');
}
elseif ($grant == 'refresh_token' && empty($params['refresh_token']))
{
throw new \BadMethodCallException('Missing refresh_token');
}
$requestParams = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => $grant,
] + $params;
$curl = curl_init();
if (strtoupper($this->method) == 'POST')
{
curl_setopt_array($curl, [
CURLOPT_URL => $this->urlAccessToken(),
CURLOPT_POST => 1,
CURLOPT_HTTPHEADER => $this->getHeaders(),
CURLOPT_POSTFIELDS => http_build_query($requestParams),
CURLOPT_RETURNTRANSFER => true,
]);
}
else
{
// No providers included with this library use get but 3rd parties may
curl_setopt_array($curl, [
CURLOPT_URL => $this->urlAccessToken() . '?' . http_build_query($requestParams),
CURLOPT_HTTPHEADER => $this->getHeaders(),
CURLOPT_RETURNTRANSFER => true,
]);
}
$response = curl_exec($curl);
curl_close($curl);
switch ($this->responseType)
{
case 'json':
$result = json_decode($response, true);
if (JSON_ERROR_NONE !== json_last_error())
{
$result = [];
}
break;
case 'string':
$result = [];
parse_str($response, $result);
break;
}
if (isset($result['error']) && !empty($result['error']))
{
// OAuth 2.0 Draft 10 style
throw new \Exception($result['error']);
}
$result = $this->prepareAccessTokenResult($result);
return new AccessToken($result);
}
/**
* Prepare the access token response for the grant. Custom mapping of
* expirations, etc should be done here.
*
* @param array $result
* @return array
*/
protected function prepareAccessTokenResult(array $result)
{
$this->setResultUid($result);
return $result;
}
/**
* Sets any result keys we've received matching our provider-defined uidKey to the key "uid".
*
* @param array $result
*/
protected function setResultUid(array &$result)
{
// If we're operating with the default uidKey there's nothing to do.
if ($this->uidKey === "uid")
{
return;
}
if (isset($result[$this->uidKey]))
{
// The AccessToken expects a "uid" to have the key "uid".
$result['uid'] = $result[$this->uidKey];
}
}
public function getUserDetails(AccessToken $token)
{
$response = $this->fetchUserDetails($token);
return $this->userDetails(json_decode($response), $token);
}
protected function fetchUserDetails(AccessToken $token)
{
$url = $this->urlUserDetails($token);
$headers = $this->getHeaders($token);
return $this->fetchProviderData($url, $headers);
}
protected function fetchProviderData($url, array $headers = [])
{
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status != 200)
{
throw new \Exception($response);
}
return $response;
}
protected function getAuthorizationHeaders($token)
{
$headers = [];
if ($this->authorizationHeader)
{
$headers[] = 'Authorization: ' . $this->authorizationHeader . ' ' . $token;
}
return $headers;
}
public function getHeaders($token = null)
{
$headers = $this->headers;
if ($token)
{
$headers = array_merge($headers, $this->getAuthorizationHeaders($token));
}
return $headers;
}
public function setRedirectHandler(callable $handler)
{
$this->redirectHandler = $handler;
}
}

40
src/Token/AccessToken.php → src/AccessToken.php

@ -1,62 +1,50 @@
<?php
namespace League\OAuth2\Client\Token;
use InvalidArgumentException;
namespace OAuth2;
class AccessToken
{
/**
* @var string accessToken
*/
public $accessToken;
/**
* @var int expires
*/
public $expires;
/**
* @var string refreshToken
*/
public $refreshToken;
/**
* @var string uid
*/
public $uid;
/**
* Sets the token, expiry, etc values.
*
* @param array $options token options
* @param array $options token options
* @return void
*/
public function __construct(array $options = null)
{
if (! isset($options['access_token'])) {
if (!isset($options['access_token']))
{
throw new \InvalidArgumentException(
'Required option not passed: access_token'.PHP_EOL
.print_r($options, true)
'Required option not passed: access_token in '.print_r($options, true)
);
}
$this->accessToken = $options['access_token'];
if (!empty($options['uid'])) {
if (!empty($options['uid']))
{
$this->uid = $options['uid'];
}
if (!empty($options['refresh_token'])) {
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 (!empty($options['expires_in'])) {
if (!empty($options['expires_in']))
{
$this->expires = time() + ((int) $options['expires_in']);
} elseif (!empty($options['expires'])) {
}
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'];

117
src/Entity/User.php

@ -1,117 +0,0 @@
<?php
namespace League\OAuth2\Client\Entity;
class User
{
protected $uid;
protected $nickname;
protected $name;
protected $firstName;
protected $lastName;
protected $email;
protected $location;
protected $description;
protected $imageUrl;
protected $urls;
protected $gender;
protected $locale;
public function __get($name)
{
if (!property_exists($this, $name)) {
throw new \OutOfRangeException(sprintf(
'%s does not contain a property by the name of "%s"',
__CLASS__,
$name
));
}
return $this->{$name};
}
public function __set($property, $value)
{
if (!property_exists($this, $property)) {
throw new \OutOfRangeException(sprintf(
'%s does not contain a property by the name of "%s"',
__CLASS__,
$property
));
}
$this->$property = $value;
return $this;
}
public function __isset($name)
{
return (property_exists($this, $name));
}
public function getArrayCopy()
{
return [
'uid' => $this->uid,
'nickname' => $this->nickname,
'name' => $this->name,
'firstName' => $this->firstName,
'lastName' => $this->lastName,
'email' => $this->email,
'location' => $this->location,
'description' => $this->description,
'imageUrl' => $this->imageUrl,
'urls' => $this->urls,
'gender' => $this->gender,
'locale' => $this->locale,
];
}
public function exchangeArray(array $data)
{
foreach ($data as $key => $value) {
$key = strtolower($key);
switch ($key) {
case 'uid':
$this->uid = $value;
break;
case 'nickname':
$this->nickname = $value;
break;
case 'name':
$this->name = $value;
break;
case 'firstname':
$this->firstName = $value;
break;
case 'lastname':
$this->lastName = $value;
break;
case 'email':
$this->email = $value;
break;
case 'location':
$this->location = $value;
break;
case 'description':
$this->description = $value;
break;
case 'imageurl':
$this->imageUrl = $value;
break;
case 'urls':
$this->urls = $value;
break;
case 'gender':
$this->gender = $value;
break;
case 'locale':
$this->locale = $value;
break;
}
}
return $this;
}
}

31
src/Eventbrite.php

@ -0,0 +1,31 @@
<?php
namespace OAuth2;
class Eventbrite extends AbstractProvider
{
public $authorizationHeader = 'Bearer';
public function urlAuthorize()
{
return 'https://www.eventbrite.com/oauth/authorize';
}
public function urlAccessToken()
{
return 'https://www.eventbrite.com/oauth/token';
}
public function urlUserDetails(AccessToken $token)
{
return 'https://www.eventbrite.com/json/user_get';
}
public function userDetails($response, AccessToken $token)
{
return [
'uid' => $response->user->user_id,
'email' => $response->user->email,
];
}
}

64
src/Exception/IDPException.php

@ -1,64 +0,0 @@
<?php
namespace League\OAuth2\Client\Exception;
class IDPException extends \Exception
{
protected $result;
public function __construct($result)
{
$this->result = $result;
$code = isset($result['code']) ? $result['code'] : 0;
if (isset($result['error']) && $result['error'] !== '') {
// OAuth 2.0 Draft 10 style
$message = $result['error'];
} elseif (isset($result['message']) && $result['message'] !== '') {
// cURL style
$message = $result['message'];
} else {
$message = 'Unknown Error.';
}
parent::__construct($message, $code);
}
public function getResponseBody()
{
return $this->result;
}
public function getType()
{
$result = 'Exception';
if (isset($this->result['error'])) {
$message = $this->result['error'];
if (is_string($message)) {
// OAuth 2.0 Draft 10 style
$result = $message;
}
}
return $result;
}
/**
* To make debugging easier.
*
* @return string The string representation of the error.
*/
public function __toString()
{
$str = $this->getType().': ';
if ($this->code != 0) {
$str .= $this->code.': ';
}
return $str.$this->message;
}
}

33
src/Provider/Facebook.php → src/Facebook.php

@ -1,8 +1,6 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
namespace OAuth2;
class Facebook extends AbstractProvider
{
@ -38,7 +36,7 @@ class Facebook extends AbstractProvider
return 'https://graph.facebook.com/'.$this->graphApiVersion.'/oauth/access_token';
}
public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
public function urlUserDetails(AccessToken $token)
{
$fields = implode(',', [
'id',
@ -57,10 +55,8 @@ class Facebook extends AbstractProvider
return 'https://graph.facebook.com/'.$this->graphApiVersion.'/me?fields='.$fields.'&access_token='.$token;
}
public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
public function userDetails($response, AccessToken $token)
{
$user = new User();
$email = (isset($response->email)) ? $response->email : null;
// The "hometown" field will only be returned if you ask for the `user_hometown` permission.
$location = (isset($response->hometown->name)) ? $response->hometown->name : null;
@ -69,7 +65,7 @@ class Facebook extends AbstractProvider
$gender = (isset($response->gender)) ? $response->gender : null;
$locale = (isset($response->locale)) ? $response->locale : null;
$user->exchangeArray([
return [
'uid' => $response->id,
'name' => $response->name,
'firstname' => $response->first_name,
@ -77,27 +73,10 @@ class Facebook extends AbstractProvider
'email' => $email,
'location' => $location,
'description' => $description,
'imageurl' => $imageUrl,
'image_url' => $imageUrl,
'gender' => $gender,
'locale' => $locale,
'urls' => [ 'Facebook' => $response->link ],
]);
return $user;
}
public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return $response->id;
}
public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return isset($response->email) && $response->email ? $response->email : null;
}
public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return [$response->first_name, $response->last_name];
];
}
}

42
src/Provider/Github.php → src/Github.php

@ -1,9 +1,6 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
use League\OAuth2\Client\Token\AccessToken;
namespace OAuth2;
class Github extends AbstractProvider
{
@ -27,7 +24,8 @@ class Github extends AbstractProvider
public function urlUserDetails(AccessToken $token)
{
if ($this->domain === 'https://github.com') {
if ($this->domain === 'https://github.com')
{
return $this->apiDomain.'/user';
}
return $this->domain.'/api/v3/user';
@ -35,7 +33,8 @@ class Github extends AbstractProvider
public function urlUserEmails(AccessToken $token)
{
if ($this->domain === 'https://github.com') {
if ($this->domain === 'https://github.com')
{
return $this->apiDomain.'/user/emails';
}
return $this->domain.'/api/v3/user/emails';
@ -43,12 +42,10 @@ class Github extends AbstractProvider
public function userDetails($response, AccessToken $token)
{
$user = new User();
$name = (isset($response->name)) ? $response->name : null;
$email = (isset($response->email)) ? $response->email : null;
$user->exchangeArray([
return [
'uid' => $response->id,
'nickname' => $response->login,
'name' => $name,
@ -56,36 +53,13 @@ class Github extends AbstractProvider
'urls' => [
'GitHub' => $this->domain.'/'.$response->login,
],
]);
return $user;
}
public function userUid($response, AccessToken $token)
{
return $response->id;
];
}
public function getUserEmails(AccessToken $token)
{
$response = $this->fetchUserEmails($token);
return $this->userEmails(json_decode($response), $token);
}
public function userEmail($response, AccessToken $token)
{
return isset($response->email) && $response->email ? $response->email : null;
}
public function userEmails($response, AccessToken $token)
{
return $response;
}
public function userScreenName($response, AccessToken $token)
{
return $response->name;
return json_decode($response);
}
protected function fetchUserEmails(AccessToken $token)

53
src/Provider/Google.php → src/Google.php

@ -1,8 +1,6 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
namespace OAuth2;
class Google extends AbstractProvider
{
@ -57,7 +55,7 @@ class Google extends AbstractProvider
return 'https://accounts.google.com/o/oauth2/token';
}
public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
public function urlUserDetails(AccessToken $token)
{
return
'https://www.googleapis.com/plus/v1/people/me?'.
@ -65,58 +63,37 @@ class Google extends AbstractProvider
'emails%2Fvalue%2Cimage%2Furl&alt=json';
}
public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
public function userDetails($response, AccessToken $token)
{
$response = (array) $response;
$user = new User();
$imageUrl = (isset($response['image']) &&
$response['image']->url) ? $response['image']->url : null;
$email =
(isset($response['emails']) &&
count($response['emails']) &&
$response['emails'][0]->value)? $response['emails'][0]->value : null;
$email = (isset($response['emails']) && count($response['emails']) &&
$response['emails'][0]->value) ? $response['emails'][0]->value : null;
$user->exchangeArray([
return [
'uid' => $response['id'],
'name' => $response['displayName'],
'firstname' => $response['name']->givenName,
'lastName' => $response['name']->familyName,
'lastname' => $response['name']->familyName,
'email' => $email,
'imageUrl' => $imageUrl,
]);
return $user;
}
public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return $response->id;
}
public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return ($response->emails &&
count($response->emails) &&
$response->emails[0]->value) ? $response->emails[0]->value : null;
}
public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return [$response->name->givenName, $response->name->familyName];
'image_url' => $imageUrl,
];
}
public function getAuthorizationUrl($options = array())
{
$url = parent::getAuthorizationUrl($options);
if (!empty($this->hostedDomain)) {
$url .= '&' . $this->httpBuildQuery(['hd' => $this->hostedDomain]);
if (!empty($this->hostedDomain))
{
$url .= '&' . http_build_query(['hd' => $this->hostedDomain]);
}
if (!empty($this->accessType)) {
$url .= '&' . $this->httpBuildQuery(['access_type'=> $this->accessType]);
if (!empty($this->accessType))
{
$url .= '&' . http_build_query(['access_type'=> $this->accessType]);
}
return $url;

27
src/Grant/AuthorizationCode.php

@ -1,27 +0,0 @@
<?php
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Token\AccessToken;
class AuthorizationCode implements GrantInterface
{
public function __toString()
{
return 'authorization_code';
}
public function prepRequestParams($defaultParams, $params)
{
if (! isset($params['code']) || empty($params['code'])) {
throw new \BadMethodCallException('Missing authorization code');
}
return array_merge($defaultParams, $params);
}
public function handleResponse($response = [])
{
return new AccessToken($response);
}
}

25
src/Grant/ClientCredentials.php

@ -1,25 +0,0 @@
<?php
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Token\AccessToken;
class ClientCredentials implements GrantInterface
{
public function __toString()
{
return 'client_credentials';
}
public function prepRequestParams($defaultParams, $params)
{
$params['grant_type'] = 'client_credentials';
return array_merge($defaultParams, $params);
}
public function handleResponse($response = array())
{
return new AccessToken($response);
}
}

12
src/Grant/GrantInterface.php

@ -1,12 +0,0 @@
<?php
namespace League\OAuth2\Client\Grant;
interface GrantInterface
{
public function __toString();
public function handleResponse($response = []);
public function prepRequestParams($defaultParams, $params);
}

33
src/Grant/Password.php

@ -1,33 +0,0 @@
<?php
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Token\AccessToken;
class Password implements GrantInterface
{
public function __toString()
{
return 'password';
}
public function prepRequestParams($defaultParams, $params)
{
if (! isset($params['username']) || empty($params['username'])) {
throw new \BadMethodCallException('Missing username');
}
if (! isset($params['password']) || empty($params['password'])) {
throw new \BadMethodCallException('Missing password');
}
$params['grant_type'] = 'password';
return array_merge($defaultParams, $params);
}
public function handleResponse($response = array())
{
return new AccessToken($response);
}
}

29
src/Grant/RefreshToken.php

@ -1,29 +0,0 @@
<?php
namespace League\OAuth2\Client\Grant;
use League\OAuth2\Client\Token\AccessToken as AccessToken;
class RefreshToken implements GrantInterface
{
public function __toString()
{
return 'refresh_token';
}
public function prepRequestParams($defaultParams, $params)
{
if (! isset($params['refresh_token']) || empty($params['refresh_token'])) {
throw new \BadMethodCallException('Missing refresh_token');
}
$params['grant_type'] = 'refresh_token';
return array_merge($defaultParams, $params);
}
public function handleResponse($response = [])
{
return new AccessToken($response);
}
}

36
src/Instagram.php

@ -0,0 +1,36 @@
<?php
namespace OAuth2;
class Instagram extends AbstractProvider
{
public $scopes = ['basic'];
public $responseType = 'json';
public function urlAuthorize()
{
return 'https://api.instagram.com/oauth/authorize';
}
public function urlAccessToken()
{
return 'https://api.instagram.com/oauth/access_token';
}
public function urlUserDetails(AccessToken $token)
{
return 'https://api.instagram.com/v1/users/self?access_token='.$token;
}
public function userDetails($response, AccessToken $token)
{
$description = (isset($response->data->bio)) ? $response->data->bio : null;
return [
'uid' => $response->data->id,
'nickname' => $response->data->username,
'name' => $response->data->full_name,
'description' => $description,
'image_url' => $response->data->profile_picture,
];
}
}

32
src/Provider/LinkedIn.php → src/LinkedIn.php

@ -1,9 +1,6 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
use League\OAuth2\Client\Token\AccessToken;
namespace OAuth2;
class LinkedIn extends AbstractProvider
{
@ -33,14 +30,12 @@ class LinkedIn extends AbstractProvider
public function userDetails($response, AccessToken $token)
{
$user = new User();
$email = (isset($response->emailAddress)) ? $response->emailAddress : null;
$location = (isset($response->location->name)) ? $response->location->name : null;
$description = (isset($response->headline)) ? $response->headline : null;
$pictureUrl = (isset($response->pictureUrl)) ? $response->pictureUrl : null;
$user->exchangeArray([
return [
'uid' => $response->id,
'name' => $response->firstName.' '.$response->lastName,
'firstname' => $response->firstName,
@ -48,27 +43,8 @@ class LinkedIn extends AbstractProvider
'email' => $email,
'location' => $location,
'description' => $description,
'imageurl' => $pictureUrl,
'image_url' => $pictureUrl,
'urls' => $response->publicProfileUrl,
]);
return $user;
}
public function userUid($response, AccessToken $token)
{
return $response->id;
}
public function userEmail($response, AccessToken $token)
{
return isset($response->emailAddress) && $response->emailAddress
? $response->emailAddress
: null;
}
public function userScreenName($response, AccessToken $token)
{
return [$response->firstName, $response->lastName];
];
}
}

51
src/Microsoft.php

@ -0,0 +1,51 @@
<?php
namespace OAuth2;
class Microsoft extends AbstractProvider
{
public $scopes = ['wl.basic', 'wl.emails'];
public $responseType = 'json';
public function urlAuthorize()
{
return 'https://login.live.com/oauth20_authorize.srf';
}
public function urlAccessToken()
{
return 'https://login.live.com/oauth20_token.srf';
}
public function urlUserDetails(AccessToken $token)
{
return 'https://apis.live.net/v5.0/me?access_token='.$token;
}
public function userDetails($response, AccessToken $token)
{
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://apis.live.net/v5.0/'.$response->id.'/picture',
CURLOPT_FOLLOWLOCATION => 0,
CURLOPT_HEADER => 1,
CURLOPT_RETURNTRANSFER => 1,
]);
$redir = curl_exec($curl);
curl_close($curl);
preg_match('/^Location: (.*)$/im', $redir, $m);
$imageUrl = $m ? $m[1] : false;
$email = (isset($response->emails->preferred)) ? $response->emails->preferred : null;
return [
'uid' => $response->id,
'name' => $response->name,
'firstname' => $response->first_name,
'lastname' => $response->last_name,
'email' => $email,
'image_url' => $imageUrl,
'urls' => $response->link.'/cid-'.$response->id,
];
}
}

382
src/Provider/AbstractProvider.php

@ -1,382 +0,0 @@
<?php
namespace League\OAuth2\Client\Provider;
use Closure;
use Guzzle\Http\Exception\BadResponseException;
use Guzzle\Service\Client as GuzzleClient;
use League\OAuth2\Client\Exception\IDPException as IDPException;
use League\OAuth2\Client\Grant\GrantInterface;
use League\OAuth2\Client\Token\AccessToken as AccessToken;
abstract class AbstractProvider implements ProviderInterface
{
public $clientId = '';
public $clientSecret = '';
public $redirectUri = '';
public $state;
public $name;
public $uidKey = 'uid';
public $scopes = [];
public $method = 'post';
public $scopeSeparator = ',';
public $responseType = 'json';
public $headers = [];
public $authorizationHeader;
/**
* @var GuzzleClient
*/
protected $httpClient;
protected $redirectHandler;
/**
* @var int This represents: PHP_QUERY_RFC1738, which is the default value for php 5.4
* and the default encryption type for the http_build_query setup
*/
protected $httpBuildEncType = 1;
public function __construct($options = [])
{
foreach ($options as $option => $value) {
if (property_exists($this, $option)) {
$this->{$option} = $value;
}
}
$this->setHttpClient(new GuzzleClient());
}
public function setHttpClient(GuzzleClient $client)
{
$this->httpClient = $client;
return $this;
}
public function getHttpClient()
{
$client = clone $this->httpClient;
return $client;
}
/**
* Get the URL that this provider uses to begin authorization.
*
* @return string
*/
abstract public function urlAuthorize();
/**
* Get the URL that this provider users to request an access token.
*
* @return string
*/
abstract public function urlAccessToken();
/**
* Get the URL that this provider uses to request user details.
*
* Since this URL is typically an authorized route, most providers will require you to pass the access_token as
* a parameter to the request. For example, the google url is:
*
* 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token='.$token
*
* @param AccessToken $token
* @return string
*/
abstract public function urlUserDetails(AccessToken $token);
/**
* Given an object response from the server, process the user details into a format expected by the user
* of the client.
*
* @param object $response
* @param AccessToken $token
* @return mixed
*/
abstract public function userDetails($response, AccessToken $token);
public function getScopes()
{
return $this->scopes;
}
public function setScopes(array $scopes)
{
$this->scopes = $scopes;
}
public function getAuthorizationUrl($options = [])
{
$this->state = isset($options['state']) ? $options['state'] : md5(uniqid(rand(), true));
$params = [
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $this->state,
'scope' => is_array($this->scopes) ? implode($this->scopeSeparator, $this->scopes) : $this->scopes,
'response_type' => isset($options['response_type']) ? $options['response_type'] : 'code',
'approval_prompt' => isset($options['approval_prompt']) ? $options['approval_prompt'] : 'auto',
];
return $this->urlAuthorize().'?'.$this->httpBuildQuery($params, '', '&');
}
// @codeCoverageIgnoreStart
public function authorize($options = [])
{
$url = $this->getAuthorizationUrl($options);
if ($this->redirectHandler) {
$handler = $this->redirectHandler;
return $handler($url);
}
// @codeCoverageIgnoreStart
header('Location: ' . $url);
exit;
// @codeCoverageIgnoreEnd
}
public function getAccessToken($grant = 'authorization_code', $params = [])
{
if (is_string($grant)) {
// PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode'
$className = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $grant)));
$grant = 'League\\OAuth2\\Client\\Grant\\'.$className;
if (! class_exists($grant)) {
throw new \InvalidArgumentException('Unknown grant "'.$grant.'"');
}
$grant = new $grant();
} elseif (! $grant instanceof GrantInterface) {
$message = get_class($grant).' is not an instance of League\OAuth2\Client\Grant\GrantInterface';
throw new \InvalidArgumentException($message);
}
$defaultParams = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
'grant_type' => $grant,
];
$requestParams = $grant->prepRequestParams($defaultParams, $params);
try {
switch (strtoupper($this->method)) {
case 'GET':
// @codeCoverageIgnoreStart
// No providers included with this library use get but 3rd parties may
$client = $this->getHttpClient();
$client->setBaseUrl($this->urlAccessToken() . '?' . $this->httpBuildQuery($requestParams, '', '&'));
$request = $client->get(null, $this->getHeaders(), $requestParams)->send();
$response = $request->getBody();
break;
// @codeCoverageIgnoreEnd
case 'POST':
$client = $this->getHttpClient();
$client->setBaseUrl($this->urlAccessToken());
$request = $client->post(null, $this->getHeaders(), $requestParams)->send();
$response = $request->getBody();
break;
// @codeCoverageIgnoreStart
default:
throw new \InvalidArgumentException('Neither GET nor POST is specified for request');
// @codeCoverageIgnoreEnd
}
} catch (BadResponseException $e) {
// @codeCoverageIgnoreStart
$response = $e->getResponse()->getBody();
// @codeCoverageIgnoreEnd
}
switch ($this->responseType) {
case 'json':
$result = json_decode($response, true);
if (JSON_ERROR_NONE !== json_last_error()) {
$result = [];
}
break;
case 'string':
parse_str($response, $result);
break;
}
if (isset($result['error']) && ! empty($result['error'])) {
// @codeCoverageIgnoreStart
throw new IDPException($result);
// @codeCoverageIgnoreEnd
}
$result = $this->prepareAccessTokenResult($result);
return $grant->handleResponse($result);
}
/**
* Prepare the access token response for the grant. Custom mapping of
* expirations, etc should be done here.
*
* @param array $result
* @return array
*/
protected function prepareAccessTokenResult(array $result)
{
$this->setResultUid($result);
return $result;
}
/**
* Sets any result keys we've received matching our provider-defined uidKey to the key "uid".
*
* @param array $result
*/
protected function setResultUid(array &$result)
{
// If we're operating with the default uidKey there's nothing to do.
if ($this->uidKey === "uid") {
return;
}
if (isset($result[$this->uidKey])) {
// The AccessToken expects a "uid" to have the key "uid".
$result['uid'] = $result[$this->uidKey];
}
}
public function getUserDetails(AccessToken $token)
{
$response = $this->fetchUserDetails($token);
return $this->userDetails(json_decode($response), $token);
}
public function getUserUid(AccessToken $token)
{
$response = $this->fetchUserDetails($token, true);
return $this->userUid(json_decode($response), $token);
}
public function getUserEmail(AccessToken $token)
{
$response = $this->fetchUserDetails($token, true);
return $this->userEmail(json_decode($response), $token);
}
public function getUserScreenName(AccessToken $token)
{
$response = $this->fetchUserDetails($token, true);
return $this->userScreenName(json_decode($response), $token);
}
public function userUid($response, AccessToken $token)
{
return isset($response->id) && $response->id ? $response->id : null;
}
public function userEmail($response, AccessToken $token)
{
return isset($response->email) && $response->email ? $response->email : null;
}
public function userScreenName($response, AccessToken $token)
{
return isset($response->name) && $response->name ? $response->name : null;
}
/**
* Build HTTP the HTTP query, handling PHP version control options
*
* @param array $params
* @param integer $numeric_prefix
* @param string $arg_separator
* @param null|integer $enc_type
*
* @return string
* @codeCoverageIgnoreStart
*/
protected function httpBuildQuery($params, $numeric_prefix = 0, $arg_separator = '&', $enc_type = null)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !defined('HHVM_VERSION')) {
if ($enc_type === null) {
$enc_type = $this->httpBuildEncType;
}
$url = http_build_query($params, $numeric_prefix, $arg_separator, $enc_type);
} else {
$url = http_build_query($params, $numeric_prefix, $arg_separator);
}
return $url;
}
protected function fetchUserDetails(AccessToken $token)
{
$url = $this->urlUserDetails($token);
$headers = $this->getHeaders($token);
return $this->fetchProviderData($url, $headers);
}
protected function fetchProviderData($url, array $headers = [])
{
try {
$client = $this->getHttpClient();
$client->setBaseUrl($url);
if ($headers) {
$client->setDefaultOption('headers', $headers);
}
$request = $client->get()->send();
$response = $request->getBody();
} catch (BadResponseException $e) {
// @codeCoverageIgnoreStart
$raw_response = explode("\n", $e->getResponse());
throw new IDPException(end($raw_response));
// @codeCoverageIgnoreEnd
}
return $response;
}
protected function getAuthorizationHeaders($token)
{
$headers = [];
if ($this->authorizationHeader) {
$headers['Authorization'] = $this->authorizationHeader . ' ' . $token;
}
return $headers;
}
public function getHeaders($token = null)
{
$headers = $this->headers;
if ($token) {
$headers = array_merge($headers, $this->getAuthorizationHeaders($token));
}
return $headers;
}
public function setRedirectHandler(Closure $handler)
{
$this->redirectHandler = $handler;
}
}

51
src/Provider/Eventbrite.php

@ -1,51 +0,0 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
class Eventbrite extends AbstractProvider
{
public $authorizationHeader = 'Bearer';
public function urlAuthorize()
{
return 'https://www.eventbrite.com/oauth/authorize';
}
public function urlAccessToken()
{
return 'https://www.eventbrite.com/oauth/token';
}
public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
{
return 'https://www.eventbrite.com/json/user_get';
}
public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
{
$user = new User();
$user->exchangeArray([
'uid' => $response->user->user_id,
'email' => $response->user->email,
]);
return $user;
}
public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return $response->user->user_id;
}
public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return isset($response->user->email) && $response->user->email ? $response->user->email : null;
}
public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
{
return $response->user->user_id;
}
}

58
src/Provider/Instagram.php

@ -1,58 +0,0 @@
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Entity\User;
class Instagram extends AbstractProvider
{
public $scopes = ['basic'];
public $responseType = 'json';
public function urlAuthorize()
{