This commit is contained in:
2025-10-20 10:02:41 +08:00
parent a4858d47fc
commit dc0a271adf
2805 changed files with 451240 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: overtrue
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL

9
vendor/overtrue/socialite/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
/vendor
composer.phar
composer.lock
.DS_Store
/.idea
Thumbs.db
/*.php
sftp-config.json
.php_cs.cache

28
vendor/overtrue/socialite/.php_cs vendored Normal file
View File

@@ -0,0 +1,28 @@
<?php
$header = <<<EOF
This file is part of the overtrue/socialite.
(c) overtrue <i@overtrue.me>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules(array(
'@Symfony' => true,
'header_comment' => array('header' => $header),
'array_syntax' => array('syntax' => 'short'),
'ordered_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'php_unit_construct' => true,
'php_unit_strict' => true,
))
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
;

13
vendor/overtrue/socialite/.travis.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
language: php
php:
- 7.0
- 7.1
- 7.2
sudo: false
dist: trusty
install: travis_retry composer install --no-interaction --prefer-source
script: vendor/bin/phpunit --verbose

21
vendor/overtrue/socialite/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) overtrue <i@overtrue.me>
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.

267
vendor/overtrue/socialite/README.md vendored Normal file
View File

@@ -0,0 +1,267 @@
<h1 align="center"> Socialite</h1>
<p align="center">
<a href="https://travis-ci.org/overtrue/socialite"><img src="https://travis-ci.org/overtrue/socialite.svg?branch=master" alt="Build Status"></a>
<a href="https://packagist.org/packages/overtrue/socialite"><img src="https://poser.pugx.org/overtrue/socialite/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/overtrue/socialite"><img src="https://poser.pugx.org/overtrue/socialite/v/unstable.svg" alt="Latest Unstable Version"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/socialite/build-status/master"><img src="https://scrutinizer-ci.com/g/overtrue/socialite/badges/build.png?b=master" alt="Build Status"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/socialite/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/socialite/badges/quality-score.png?b=master" alt="Scrutinizer Code Quality"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/socialite/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/socialite/badges/coverage.png?b=master" alt="Code Coverage"></a>
<a href="https://packagist.org/packages/overtrue/socialite"><img src="https://poser.pugx.org/overtrue/socialite/downloads" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/overtrue/socialite"><img src="https://poser.pugx.org/overtrue/socialite/license" alt="License"></a>
</p>
<p align="center">Socialite is an OAuth2 Authentication tool. It is inspired by <a href="https://github.com/laravel/socialite">laravel/socialite</a>, You can easily use it in any PHP project.</p>
# Requirement
```
PHP >= 5.6
```
# Installation
```shell
$ composer require "overtrue/socialite" -vvv
```
# Usage
For Laravel 5: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite)
`authorize.php`:
```php
<?php
use Overtrue\Socialite\SocialiteManager;
$config = [
'github' => [
'client_id' => 'your-app-id',
'client_secret' => 'your-app-secret',
'redirect' => 'http://localhost/socialite/callback.php',
],
];
$socialite = new SocialiteManager($config);
$response = $socialite->driver('github')->redirect();
echo $response;// or $response->send();
```
`callback.php`:
```php
<?php
use Overtrue\Socialite\SocialiteManager;
$config = [
'github' => [
'client_id' => 'your-app-id',
'client_secret' => 'your-app-secret',
'redirect' => 'http://localhost/socialite/callback.php',
],
];
$socialite = new SocialiteManager($config);
$user = $socialite->driver('github')->user();
$user->getId(); // 1472352
$user->getNickname(); // "overtrue"
$user->getUsername(); // "overtrue"
$user->getName(); // "安正超"
$user->getEmail(); // "anzhengchao@gmail.com"
$user->getProviderName(); // GitHub
...
```
### Configuration
Now we support the following sites:
`facebook`, `github`, `google`, `linkedin`, `outlook`, `weibo`, `taobao`, `qq`, `wechat`, `douyin`, `baidu`, `feishu`, and `douban`.
Each driver uses the same configuration keys: `client_id`, `client_secret`, `redirect`.
Example:
```
...
'weibo' => [
'client_id' => 'your-app-id',
'client_secret' => 'your-app-secret',
'redirect' => 'http://localhost/socialite/callback.php',
],
...
```
### Scope
Before redirecting the user, you may also set "scopes" on the request using the scope method. This method will overwrite all existing scopes:
```php
$response = $socialite->driver('github')
->scopes(['scope1', 'scope2'])->redirect();
```
### Redirect URL
You may also want to dynamicly set `redirect`you can use the following methods to change the `redirect` URL:
```php
$socialite->redirect($url);
// or
$socialite->withRedirectUrl($url)->redirect();
// or
$socialite->setRedirectUrl($url)->redirect();
```
> WeChat scopes:
- `snsapi_base`, `snsapi_userinfo` - Used to Media Platform Authentication.
- `snsapi_login` - Used to web Authentication.
### Additional parameters
To include any optional parameters in the request, call the with method with an associative array:
```php
$response = $socialite->driver('google')
->with(['hd' => 'example.com'])->redirect();
```
### User interface
#### Standard user api:
```php
$user = $socialite->driver('weibo')->user();
```
```json
{
"id": 1472352,
"nickname": "overtrue",
"name": "安正超",
"email": "anzhengchao@gmail.com",
"avatar": "https://avatars.githubusercontent.com/u/1472352?v=3",
"original": {
"login": "overtrue",
"id": 1472352,
"avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/overtrue",
"html_url": "https://github.com/overtrue",
...
},
"token": {
"access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0",
"token_type": "bearer",
"scope": "user:email"
}
}
```
You can fetch the user attribute as a array keys like these:
```php
$user['id']; // 1472352
$user['nickname']; // "overtrue"
$user['name']; // "安正超"
$user['email']; // "anzhengchao@gmail.com"
...
```
Or using the method:
```php
$user->getId();
$user->getNickname();
$user->getName();
$user->getEmail();
$user->getAvatar();
$user->getOriginal();
$user->getToken();// or $user->getAccessToken()
$user->getProviderName(); // GitHub/Google/Facebook...
```
#### Get original response from OAuth API
The `$user->getOriginal()` method will return an array of the API raw response.
#### Get access token Object
You can get the access token instance of current session by call `$user->getToken()` or `$user->getAccessToken()` or `$user['token']` .
### Get user with access token
```php
$accessToken = new AccessToken(['access_token' => $accessToken]);
$user = $socialite->user($accessToken);
```
### Custom Session or Request instance.
You can set the request with your custom `Request` instance which instanceof `Symfony\Component\HttpFoundation\Request` before you call `driver` method.
```php
$request = new Request(); // or use AnotherCustomRequest.
$socialite = new SocialiteManager($config, $request);
```
Or set request to `SocialiteManager` instance:
```php
$socialite->setRequest($request);
```
You can get the request from the `SocialiteManager` instance by `getRequest()`:
```php
$request = $socialite->getRequest();
```
#### Set custom session manager.
By default, the `SocialiteManager` uses the `Symfony\Component\HttpFoundation\Session\Session` instance as session manager, you can change it as follows:
```php
$session = new YourCustomSessionManager();
$socialite->getRequest()->setSession($session);
```
> Your custom session manager must be implement the [`Symfony\Component\HttpFoundation\Session\SessionInterface`](http://api.symfony.com/3.0/Symfony/Component/HttpFoundation/Session/SessionInterface.html).
Enjoy it! :heart:
# Reference
- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect)
- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api)
- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2)
- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E)
- [QQ - OAuth 2.0 登录QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B)
- [微信公众平台 - OAuth文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html)
- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN)
- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN)
- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2)
- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc)
- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
# License
MIT

34
vendor/overtrue/socialite/composer.json vendored Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "overtrue/socialite",
"description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
"keywords": [
"OAuth",
"social",
"login",
"Weibo",
"WeChat",
"QQ"
],
"autoload": {
"psr-4": {
"Overtrue\\Socialite\\": "src/"
}
},
"require": {
"php": ">=5.6",
"guzzlehttp/guzzle": "^5.0|^6.0|^7.0",
"symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0",
"ext-json": "*"
},
"require-dev": {
"mockery/mockery": "~1.2",
"phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0"
},
"license": "MIT",
"authors": [
{
"name": "overtrue",
"email": "anzhengchao@gmail.com"
}
]
}

18
vendor/overtrue/socialite/phpunit.xml vendored Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
use ArrayAccess;
use InvalidArgumentException;
use JsonSerializable;
/**
* Class AccessToken.
*/
class AccessToken implements AccessTokenInterface, ArrayAccess, JsonSerializable
{
use HasAttributes;
/**
* AccessToken constructor.
*
* @param array $attributes
*/
public function __construct(array $attributes)
{
if (empty($attributes['access_token'])) {
throw new InvalidArgumentException('The key "access_token" could not be empty.');
}
$this->attributes = $attributes;
}
/**
* Return the access token string.
*
* @return string
*/
public function getToken()
{
return $this->getAttribute('access_token');
}
/**
* Return the refresh token string.
*
* @return string
*/
public function getRefreshToken()
{
return $this->getAttribute('refresh_token');
}
/**
* Set refresh token into this object.
*
* @param string $token
*/
public function setRefreshToken($token)
{
$this->setAttribute('refresh_token', $token);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return strval($this->getAttribute('access_token', ''));
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return $this->getToken();
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
/**
* Interface AccessTokenInterface.
*/
interface AccessTokenInterface
{
/**
* Return the access token string.
*
* @return string
*/
public function getToken();
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
class AuthorizeFailedException extends \RuntimeException
{
/**
* Response body.
*
* @var array
*/
public $body;
/**
* Constructor.
*
* @param string $message
* @param array $body
*/
public function __construct($message, $body)
{
parent::__construct($message, -1);
$this->body = $body;
}
}

180
vendor/overtrue/socialite/src/Config.php vendored Normal file
View File

@@ -0,0 +1,180 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
use ArrayAccess;
use InvalidArgumentException;
/**
* Class Config.
*/
class Config implements ArrayAccess
{
/**
* @var array
*/
protected $config;
/**
* Config constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->config = $config;
}
/**
* Get an item from an array using "dot" notation.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get($key, $default = null)
{
$config = $this->config;
if (is_null($key)) {
return $config;
}
if (isset($config[$key])) {
return $config[$key];
}
foreach (explode('.', $key) as $segment) {
if (!is_array($config) || !array_key_exists($segment, $config)) {
return $default;
}
$config = $config[$segment];
}
return $config;
}
/**
* Set an array item to a given value using "dot" notation.
*
* @param string $key
* @param mixed $value
*
* @return array
*/
public function set($key, $value)
{
if (is_null($key)) {
throw new InvalidArgumentException('Invalid config key.');
}
$keys = explode('.', $key);
$config = &$this->config;
while (count($keys) > 1) {
$key = array_shift($keys);
if (!isset($config[$key]) || !is_array($config[$key])) {
$config[$key] = [];
}
$config = &$config[$key];
}
$config[array_shift($keys)] = $value;
return $config;
}
/**
* Determine if the given configuration value exists.
*
* @param string $key
*
* @return bool
*/
public function has($key)
{
return (bool) $this->get($key);
}
/**
* Whether a offset exists.
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned
*
* @since 5.0.0
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->config);
}
/**
* Offset to retrieve.
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types
*
* @since 5.0.0
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Offset to set.
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
/**
* Offset to unset.
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*
* @since 5.0.0
*/
public function offsetUnset($offset)
{
$this->set($offset, null);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
/**
* Interface FactoryInterface.
*/
interface FactoryInterface
{
/**
* Get an OAuth provider implementation.
*
* @param string $driver
*
* @return \Overtrue\Socialite\ProviderInterface
*/
public function driver($driver);
}

View File

@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
/**
* Trait HasAttributes.
*/
trait HasAttributes
{
/**
* @var array
*/
protected $attributes = [];
/**
* Return the attributes.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Return the extra attribute.
*
* @param string $name
* @param string $default
*
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
/**
* Set extra attributes.
*
* @param string $name
* @param mixed $value
*
* @return $this
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
return $this;
}
/**
* Map the given array onto the user's properties.
*
* @param array $attributes
*
* @return $this
*/
public function merge(array $attributes)
{
$this->attributes = array_merge($this->attributes, $attributes);
return $this;
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->attributes);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return $this->getAttribute($offset);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
$this->setAttribute($offset, $value);
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
unset($this->attributes[$offset]);
}
/**
* {@inheritdoc}
*/
public function __get($property)
{
return $this->getAttribute($property);
}
/**
* Return array.
*
* @return array
*/
public function toArray()
{
return $this->getAttributes();
}
/**
* Return JSON.
*
* @return string
*/
public function toJSON()
{
return json_encode($this->getAttributes(), JSON_UNESCAPED_UNICODE);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
class InvalidArgumentException extends \InvalidArgumentException
{
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
class InvalidStateException extends \InvalidArgumentException
{
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
interface ProviderInterface
{
/**
* Redirect the user to the authentication page for the provider.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirect();
/**
* Get the User instance for the authenticated user.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return \Overtrue\Socialite\User
*/
public function user(AccessTokenInterface $token = null);
}

View File

@@ -0,0 +1,585 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Overtrue\Socialite\AccessToken;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\AuthorizeFailedException;
use Overtrue\Socialite\Config;
use Overtrue\Socialite\InvalidStateException;
use Overtrue\Socialite\ProviderInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Class AbstractProvider.
*/
abstract class AbstractProvider implements ProviderInterface
{
/**
* Provider name.
*
* @var string
*/
protected $name;
/**
* The HTTP request instance.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Driver config.
*
* @var Config
*/
protected $config;
/**
* The client ID.
*
* @var string
*/
protected $clientId;
/**
* The client secret.
*
* @var string
*/
protected $clientSecret;
/**
* @var \Overtrue\Socialite\AccessTokenInterface
*/
protected $accessToken;
/**
* The redirect URL.
*
* @var string
*/
protected $redirectUrl;
/**
* The custom parameters to be sent with the request.
*
* @var array
*/
protected $parameters = [];
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = [];
/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ',';
/**
* The type of the encoding in the query.
*
* @var int Can be either PHP_QUERY_RFC3986 or PHP_QUERY_RFC1738
*/
protected $encodingType = PHP_QUERY_RFC1738;
/**
* Indicates if the session state should be utilized.
*
* @var bool
*/
protected $stateless = false;
/**
* The options for guzzle\client.
*
* @var array
*/
protected static $guzzleOptions = ['http_errors' => false];
/**
* Create a new provider instance.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param array $config
*/
public function __construct(Request $request, $config)
{
// 兼容处理
if (!\is_array($config)) {
$config = [
'client_id' => \func_get_arg(1),
'client_secret' => \func_get_arg(2),
'redirect' => \func_get_arg(3) ?: null,
];
}
$this->config = new Config($config);
$this->request = $request;
$this->clientId = $config['client_id'];
$this->clientSecret = $config['client_secret'];
$this->redirectUrl = isset($config['redirect']) ? $config['redirect'] : null;
}
/**
* Get the authentication URL for the provider.
*
* @param string $state
*
* @return string
*/
abstract protected function getAuthUrl($state);
/**
* Get the token URL for the provider.
*
* @return string
*/
abstract protected function getTokenUrl();
/**
* Get the raw user for the given access token.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return array
*/
abstract protected function getUserByToken(AccessTokenInterface $token);
/**
* Map the raw user array to a Socialite User instance.
*
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
abstract protected function mapUserToObject(array $user);
/**
* Redirect the user of the application to the provider's authentication screen.
*
* @param string $redirectUrl
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirect($redirectUrl = null)
{
$state = null;
if (!is_null($redirectUrl)) {
$this->redirectUrl = $redirectUrl;
}
if ($this->usesState()) {
$state = $this->makeState();
}
return new RedirectResponse($this->getAuthUrl($state));
}
/**
* {@inheritdoc}
*/
public function user(AccessTokenInterface $token = null)
{
if (is_null($token) && $this->hasInvalidState()) {
throw new InvalidStateException();
}
$token = $token ?: $this->getAccessToken($this->getCode());
$user = $this->getUserByToken($token);
$user = $this->mapUserToObject($user)->merge(['original' => $user]);
return $user->setToken($token)->setProviderName($this->getName());
}
/**
* Set redirect url.
*
* @param string $redirectUrl
*
* @return $this
*/
public function setRedirectUrl($redirectUrl)
{
$this->redirectUrl = $redirectUrl;
return $this;
}
/**
* Set redirect url.
*
* @param string $redirectUrl
*
* @return $this
*/
public function withRedirectUrl($redirectUrl)
{
$this->redirectUrl = $redirectUrl;
return $this;
}
/**
* Return the redirect url.
*
* @return string
*/
public function getRedirectUrl()
{
return $this->redirectUrl;
}
/**
* @param \Overtrue\Socialite\AccessTokenInterface $accessToken
*
* @return $this
*/
public function setAccessToken(AccessTokenInterface $accessToken)
{
$this->accessToken = $accessToken;
return $this;
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessTokenInterface
*/
public function getAccessToken($code)
{
if ($this->accessToken) {
return $this->accessToken;
}
$guzzleVersion = \defined(ClientInterface::class.'::VERSION') ? \constant(ClientInterface::class.'::VERSION') : 7;
$postKey = (1 === version_compare($guzzleVersion, '6')) ? 'form_params' : 'body';
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'headers' => ['Accept' => 'application/json'],
$postKey => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody());
}
/**
* Set the scopes of the requested access.
*
* @param array $scopes
*
* @return $this
*/
public function scopes(array $scopes)
{
$this->scopes = $scopes;
return $this;
}
/**
* Set the request instance.
*
* @param Request $request
*
* @return $this
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Get the request instance.
*
* @return \Symfony\Component\HttpFoundation\Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Indicates that the provider should operate as stateless.
*
* @return $this
*/
public function stateless()
{
$this->stateless = true;
return $this;
}
/**
* Set the custom parameters of the request.
*
* @param array $parameters
*
* @return $this
*/
public function with(array $parameters)
{
$this->parameters = $parameters;
return $this;
}
/**
* @throws \ReflectionException
*
* @return string
*/
public function getName()
{
if (empty($this->name)) {
$this->name = strstr((new \ReflectionClass(get_class($this)))->getShortName(), 'Provider', true);
}
return $this->name;
}
/**
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* Get the authentication URL for the provider.
*
* @param string $url
* @param string $state
*
* @return string
*/
protected function buildAuthUrlFromBase($url, $state)
{
return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
}
/**
* Get the GET parameters for the code request.
*
* @param string|null $state
*
* @return array
*/
protected function getCodeFields($state = null)
{
$fields = array_merge([
'client_id' => $this->config['client_id'],
'redirect_uri' => $this->redirectUrl,
'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
'response_type' => 'code',
], $this->parameters);
if ($this->usesState()) {
$fields['state'] = $state;
}
return $fields;
}
/**
* Format the given scopes.
*
* @param array $scopes
* @param string $scopeSeparator
*
* @return string
*/
protected function formatScopes(array $scopes, $scopeSeparator)
{
return implode($scopeSeparator, $scopes);
}
/**
* Determine if the current request / session has a mismatching "state".
*
* @return bool
*/
protected function hasInvalidState()
{
if ($this->isStateless()) {
return false;
}
$state = $this->request->getSession()->get('state');
return !(strlen($state) > 0 && $this->request->get('state') === $state);
}
/**
* Get the POST fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return [
'client_id' => $this->getConfig()->get('client_id'),
'client_secret' => $this->getConfig()->get('client_secret'),
'code' => $code,
'redirect_uri' => $this->redirectUrl,
];
}
/**
* Get the access token from the token response body.
*
* @param \Psr\Http\Message\StreamInterface|array $body
*
* @return \Overtrue\Socialite\AccessTokenInterface
*/
protected function parseAccessToken($body)
{
if (!is_array($body)) {
$body = json_decode($body, true);
}
if (empty($body['access_token'])) {
throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body);
}
return new AccessToken($body);
}
/**
* Get the code from the request.
*
* @return string
*/
protected function getCode()
{
return $this->request->get('code');
}
/**
* Get a fresh instance of the Guzzle HTTP client.
*
* @return \GuzzleHttp\Client
*/
protected function getHttpClient()
{
return new Client(self::$guzzleOptions);
}
/**
* Set options for Guzzle HTTP client.
*
* @param array $config
*
* @return array
*/
public static function setGuzzleOptions($config = [])
{
return self::$guzzleOptions = $config;
}
/**
* Determine if the provider is operating with state.
*
* @return bool
*/
protected function usesState()
{
return !$this->stateless;
}
/**
* Determine if the provider is operating as stateless.
*
* @return bool
*/
protected function isStateless()
{
return !$this->request->hasSession() || $this->stateless;
}
/**
* Return array item by key.
*
* @param array $array
* @param string $key
* @param mixed $default
*
* @return mixed
*/
protected function arrayItem(array $array, $key, $default = null)
{
if (is_null($key)) {
return $array;
}
if (isset($array[$key])) {
return $array[$key];
}
foreach (explode('.', $key) as $segment) {
if (!is_array($array) || !array_key_exists($segment, $array)) {
return $default;
}
$array = $array[$segment];
}
return $array;
}
/**
* Put state to session storage and return it.
*
* @return string|bool
*/
protected function makeState()
{
if (!$this->request->hasSession()) {
return false;
}
$state = sha1(uniqid(mt_rand(1, 1000000), true));
$session = $this->request->getSession();
if (is_callable([$session, 'put'])) {
$session->put('state', $state);
} elseif (is_callable([$session, 'set'])) {
$session->set('state', $state);
} else {
return false;
}
return $state;
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class BaiduProvider.
*
* @see https://developer.baidu.com/wiki/index.php?title=docs/oauth [OAuth 2.0 授权机制说明]
*/
class BaiduProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base url of Weibo API.
*
* @var string
*/
protected $baseUrl = 'https://openapi.baidu.com';
/**
* The API version for the request.
*
* @var string
*/
protected $version = '2.0';
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = [''];
/**
* The uid of user authorized.
*
* @var int
*/
protected $uid;
protected $display = 'popup';
/**
* Get the authentication URL for the provider.
*
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/oauth/'.$this->version.'/authorize', $state);
}
/**
* {@inheritdoc}.
*/
protected function getCodeFields($state = null)
{
return array_merge([
'response_type' => 'code',
'client_id' => $this->getConfig()->get('client_id'),
'redirect_uri' => $this->redirectUrl,
'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
'display' => $this->display,
], $this->parameters);
}
/**
* Get the token URL for the provider.
*
* @return string
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/oauth/'.$this->version.'/token';
}
/**
* Get the Post fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* Get the raw user for the given access token.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return array
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get($this->baseUrl.'/rest/'.$this->version.'/passport/users/getInfo', [
'query' => [
'access_token' => $token->getToken(),
],
'headers' => [
'Accept' => 'application/json',
],
]);
return json_decode($response->getBody(), true);
}
/**
* Map the raw user array to a Socialite User instance.
*
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
protected function mapUserToObject(array $user)
{
$realname = $this->arrayItem($user, 'realname');
return new User([
'id' => $this->arrayItem($user, 'userid'),
'nickname' => empty($realname) ? '' : $realname,
'name' => $this->arrayItem($user, 'username'),
'email' => '',
'avatar' => $this->arrayItem($user, 'portrait'),
]);
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessToken;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class DouYinProvider.
*
* @author haoliang@qiyuankeji.vip
*
* @see http://open.douyin.com/platform
*/
class DouYinProvider extends AbstractProvider implements ProviderInterface
{
/**
* 抖音接口域名.
*
* @var string
*/
protected $baseUrl = 'https://open.douyin.com';
/**
* 应用授权作用域.
*
* @var array
*/
protected $scopes = ['user_info'];
/**
* 获取登录页面地址.
*
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/platform/oauth/connect', $state);
}
/**
* 获取授权码接口参数.
*
* @param string|null $state
*
* @return array
*/
public function getCodeFields($state = null)
{
$fields = [
'client_key' => $this->getConfig()->get('client_id'),
'redirect_uri' => $this->redirectUrl,
'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
'response_type' => 'code',
];
if ($this->usesState()) {
$fields['state'] = $state;
}
return $fields;
}
/**
* 获取access_token地址.
*
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/oauth/access_token';
}
/**
* 通过code获取access_token.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->get($this->getTokenUrl(), [
'query' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody()->getContents());
}
/**
* 获取access_token接口参数.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return [
'client_key' => $this->getConfig()->get('client_id'),
'client_secret' => $this->getConfig()->get('client_secret'),
'code' => $code,
'grant_type' => 'authorization_code',
];
}
/**
* 格式化token.
*
* @param \Psr\Http\Message\StreamInterface|array $body
*
* @return \Overtrue\Socialite\AccessTokenInterface
*/
protected function parseAccessToken($body)
{
if (!is_array($body)) {
$body = json_decode($body, true);
}
if (empty($body['data']['access_token'])) {
throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body);
}
return new AccessToken($body['data']);
}
/**
* 通过token 获取用户信息.
*
* @param AccessTokenInterface $token
*
* @return array|mixed
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$userUrl = $this->baseUrl.'/oauth/userinfo/';
$response = $this->getHttpClient()->get(
$userUrl,
[
'query' => [
'access_token' => $token->getToken(),
'open_id' => $token['open_id'],
],
]
);
return json_decode($response->getBody(), true);
}
/**
* 格式化用户信息.
*
* @param array $user
*
* @return User
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'open_id'),
'username' => $this->arrayItem($user, 'nickname'),
'nickname' => $this->arrayItem($user, 'nickname'),
'avatar' => $this->arrayItem($user, 'avatar'),
]);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class DoubanProvider.
*
* @see http://developers.douban.com/wiki/?title=oauth2 [使用 OAuth 2.0 访问豆瓣 API]
*/
class DoubanProvider extends AbstractProvider implements ProviderInterface
{
/**
* {@inheritdoc}.
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.douban.com/service/auth2/auth', $state);
}
/**
* {@inheritdoc}.
*/
protected function getTokenUrl()
{
return 'https://www.douban.com/service/auth2/token';
}
/**
* {@inheritdoc}.
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get('https://api.douban.com/v2/user/~me', [
'headers' => [
'Authorization' => 'Bearer '.$token->getToken(),
],
]);
return json_decode($response->getBody()->getContents(), true);
}
/**
* {@inheritdoc}.
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'id'),
'nickname' => $this->arrayItem($user, 'name'),
'name' => $this->arrayItem($user, 'name'),
'avatar' => $this->arrayItem($user, 'large_avatar'),
'email' => null,
]);
}
/**
* {@inheritdoc}.
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}.
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'form_params' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody()->getContents());
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class FacebookProvider.
*
* @see https://developers.facebook.com/docs/graph-api [Facebook - Graph API]
*/
class FacebookProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base Facebook Graph URL.
*
* @var string
*/
protected $graphUrl = 'https://graph.facebook.com';
/**
* The Graph API version for the request.
*
* @var string
*/
protected $version = 'v3.3';
/**
* The user fields being requested.
*
* @var array
*/
protected $fields = ['first_name', 'last_name', 'email', 'gender', 'verified'];
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['email'];
/**
* Display the dialog in a popup view.
*
* @var bool
*/
protected $popup = false;
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.facebook.com/'.$this->version.'/dialog/oauth', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return $this->graphUrl.'/oauth/access_token';
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->get($this->getTokenUrl(), [
'query' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody());
}
/**
* {@inheritdoc}
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$appSecretProof = hash_hmac('sha256', $token->getToken(), $this->getConfig()->get('client_secret'));
$response = $this->getHttpClient()->get($this->graphUrl.'/'.$this->version.'/me?access_token='.$token.'&appsecret_proof='.$appSecretProof.'&fields='.implode(',', $this->fields), [
'headers' => [
'Accept' => 'application/json',
],
]);
return json_decode($response->getBody(), true);
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
$userId = $this->arrayItem($user, 'id');
$avatarUrl = $this->graphUrl.'/'.$this->version.'/'.$userId.'/picture';
$firstName = $this->arrayItem($user, 'first_name');
$lastName = $this->arrayItem($user, 'last_name');
return new User([
'id' => $this->arrayItem($user, 'id'),
'nickname' => null,
'name' => $firstName.' '.$lastName,
'email' => $this->arrayItem($user, 'email'),
'avatar' => $userId ? $avatarUrl.'?type=normal' : null,
'avatar_original' => $userId ? $avatarUrl.'?width=1920' : null,
]);
}
/**
* {@inheritdoc}
*/
protected function getCodeFields($state = null)
{
$fields = parent::getCodeFields($state);
if ($this->popup) {
$fields['display'] = 'popup';
}
return $fields;
}
/**
* Set the user fields to request from Facebook.
*
* @param array $fields
*
* @return $this
*/
public function fields(array $fields)
{
$this->fields = $fields;
return $this;
}
/**
* Set the dialog to be displayed as a popup.
*
* @return $this
*/
public function asPopup()
{
$this->popup = true;
return $this;
}
}

View File

@@ -0,0 +1,192 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessToken;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\AuthorizeFailedException;
use Overtrue\Socialite\InvalidStateException;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class FeiShuProvider.
*
* @author qijian.song@show.world
*
* @see https://open.feishu.cn/
*/
class FeiShuProvider extends AbstractProvider implements ProviderInterface
{
/**
* 飞书接口域名.
*
* @var string
*/
protected $baseUrl = 'https://open.feishu.cn';
/**
* 应用授权作用域.
*
* @var array
*/
protected $scopes = ['user_info'];
/**
* 获取登录页面地址.
*
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/open-apis/authen/v1/index', $state);
}
/**
* 获取授权码接口参数.
*
* @param string|null $state
*
* @return array
*/
protected function getCodeFields($state = null)
{
$fields = [
'redirect_uri' => $this->redirectUrl,
'app_id' => $this->getConfig()->get('client_id'),
];
if ($this->usesState()) {
$fields['state'] = $state;
}
return $fields;
}
/**
* 获取 app_access_token 地址.
*
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/open-apis/auth/v3/app_access_token/internal';
}
/**
* 获取 app_access_token.
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code = '')
{
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'headers' => ['Content-Type' => 'application/json'],
'json' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody()->getContents());
}
/**
* 获取 app_access_token 接口参数.
*
* @return array
*/
protected function getTokenFields($code)
{
return [
'app_id' => $this->getConfig()->get('client_id'),
'app_secret' => $this->getConfig()->get('client_secret'),
];
}
/**
* 格式化 token.
*
* @param \Psr\Http\Message\StreamInterface|array $body
*
* @return \Overtrue\Socialite\AccessTokenInterface
*/
protected function parseAccessToken($body)
{
if (!is_array($body)) {
$body = json_decode($body, true);
}
if (empty($body['app_access_token'])) {
throw new AuthorizeFailedException('Authorize Failed: '.json_encode($body, JSON_UNESCAPED_UNICODE), $body);
}
$data['access_token'] = $body['app_access_token'];
return new AccessToken($data);
}
/**
* 获取用户信息.
*
* @return array|mixed
*/
public function user(AccessTokenInterface $token = null)
{
if (is_null($token) && $this->hasInvalidState()) {
throw new InvalidStateException();
}
$token = $token ?: $this->getAccessToken();
$user = $this->getUserByToken($token, $this->getCode());
$user = $this->mapUserToObject($user)->merge(['original' => $user]);
return $user->setToken($token)->setProviderName($this->getName());
}
/**
* 通过 token 获取用户信息.
*
* @return array|mixed
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$userUrl = $this->baseUrl.'/open-apis/authen/v1/access_token';
$response = $this->getHttpClient()->post(
$userUrl,
[
'json' => [
'app_access_token' => $token->getToken(),
'code' => $this->getCode(),
'grant_type' => 'authorization_code',
],
]
);
$result = json_decode($response->getBody(), true);
return $result['data'];
}
/**
* 格式化用户信息.
*
* @return User
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'open_id'),
'username' => $this->arrayItem($user, 'name'),
'nickname' => $this->arrayItem($user, 'name'),
'avatar' => $this->arrayItem($user, 'avatar_url'),
]);
}
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Exception;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class GitHubProvider.
*/
class GitHubProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['user:email'];
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://github.com/login/oauth/authorize', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://github.com/login/oauth/access_token';
}
/**
* {@inheritdoc}
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$userUrl = 'https://api.github.com/user';
$response = $this->getHttpClient()->get(
$userUrl,
$this->createAuthorizationHeaders($token)
);
$user = json_decode($response->getBody(), true);
if (in_array('user:email', $this->scopes)) {
$user['email'] = $this->getEmailByToken($token);
}
return $user;
}
/**
* Get the email for the given access token.
*
* @param string $token
*
* @return string|null
*/
protected function getEmailByToken($token)
{
$emailsUrl = 'https://api.github.com/user/emails';
try {
$response = $this->getHttpClient()->get(
$emailsUrl,
$this->createAuthorizationHeaders($token)
);
} catch (Exception $e) {
return;
}
foreach (json_decode($response->getBody(), true) as $email) {
if ($email['primary'] && $email['verified']) {
return $email['email'];
}
}
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'id'),
'username' => $this->arrayItem($user, 'login'),
'nickname' => $this->arrayItem($user, 'login'),
'name' => $this->arrayItem($user, 'name'),
'email' => $this->arrayItem($user, 'email'),
'avatar' => $this->arrayItem($user, 'avatar_url'),
]);
}
/**
* Get the default options for an HTTP request.
*
* @param string $token
*
* @return array
*/
protected function createAuthorizationHeaders(string $token)
{
return [
'headers' => [
'Accept' => 'application/vnd.github.v3+json',
'Authorization' => sprintf('token %s', $token),
],
];
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use GuzzleHttp\ClientInterface;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class GoogleProvider.
*
* @see https://developers.google.com/identity/protocols/OpenIDConnect [OpenID Connect]
*/
class GoogleProvider extends AbstractProvider implements ProviderInterface
{
/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/v2/auth', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.googleapis.com/oauth2/v4/token';
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return string
*/
public function getAccessToken($code)
{
$guzzleVersion = \defined(ClientInterface::class.'::VERSION') ? \constant(ClientInterface::class.'::VERSION') : 7;
$postKey = (1 === version_compare($guzzleVersion, '6')) ? 'form_params' : 'body';
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
$postKey => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody());
}
/**
* Get the POST fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get('https://www.googleapis.com/userinfo/v2/me', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$token->getToken(),
],
]);
return json_decode($response->getBody(), true);
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'id'),
'username' => $this->arrayItem($user, 'email'),
'nickname' => $this->arrayItem($user, 'name'),
'name' => $this->arrayItem($user, 'name'),
'email' => $this->arrayItem($user, 'email'),
'avatar' => $this->arrayItem($user, 'picture'),
]);
}
}

View File

@@ -0,0 +1,181 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class LinkedinProvider.
*
* @see https://developer.linkedin.com/docs/oauth2 [Authenticating with OAuth 2.0]
*/
class LinkedinProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['r_liteprofile', 'r_emailaddress'];
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()
->post($this->getTokenUrl(), ['form_params' => $this->getTokenFields($code)]);
return $this->parseAccessToken($response->getBody());
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}
/**
* Get the POST fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {@inheritdoc}
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$basicProfile = $this->getBasicProfile($token);
$emailAddress = $this->getEmailAddress($token);
return array_merge($basicProfile, $emailAddress);
}
/**
* Get the basic profile fields for the user.
*
* @param string $token
*
* @return array
*/
protected function getBasicProfile($token)
{
$url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return (array) json_decode($response->getBody(), true);
}
/**
* Get the email address for the user.
*
* @param string $token
*
* @return array
*/
protected function getEmailAddress($token)
{
$url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
]);
return (array) $this->arrayItem(json_decode($response->getBody(), true), 'elements.0.handle~');
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
$preferredLocale = $this->arrayItem($user, 'firstName.preferredLocale.language').'_'.$this->arrayItem($user, 'firstName.preferredLocale.country');
$firstName = $this->arrayItem($user, 'firstName.localized.'.$preferredLocale);
$lastName = $this->arrayItem($user, 'lastName.localized.'.$preferredLocale);
$name = $firstName.' '.$lastName;
$images = (array) $this->arrayItem($user, 'profilePicture.displayImage~.elements', []);
$avatars = array_filter($images, function ($image) {
return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100;
});
$avatar = array_shift($avatars);
$originalAvatars = array_filter($images, function ($image) {
return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800;
});
$originalAvatar = array_shift($originalAvatars);
return new User([
'id' => $this->arrayItem($user, 'id'),
'nickname' => $name,
'name' => $name,
'email' => $this->arrayItem($user, 'emailAddress'),
'avatar' => $avatar ? $this->arrayItem($avatar, 'identifiers.0.identifier') : null,
'avatar_original' => $originalAvatar ? $this->arrayItem($originalAvatar, 'identifiers.0.identifier') : null,
]);
}
/**
* Set the user fields to request from LinkedIn.
*
* @param array $fields
*
* @return $this
*/
public function fields(array $fields)
{
$this->fields = $fields;
return $this;
}
/**
* Determine if the provider is operating as stateless.
*
* @return bool
*/
protected function isStateless()
{
return true;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class OutlookProvider.
*/
class OutlookProvider extends AbstractProvider implements ProviderInterface
{
/**
* {@inheritdoc}
*/
protected $scopes = ['User.Read'];
/**
* {@inheritdoc}
*/
protected $scopeSeparator = ' ';
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://login.microsoftonline.com/common/oauth2/v2.0/authorize', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
}
/**
* {@inheritdoc}
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get(
'https://graph.microsoft.com/v1.0/me',
['headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$token->getToken(),
],
]
);
return json_decode($response->getBody()->getContents(), true);
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'id'),
'nickname' => null,
'name' => $this->arrayItem($user, 'displayName'),
'email' => $this->arrayItem($user, 'userPrincipalName'),
'avatar' => null,
]);
}
/**
* {@inheritdoc}
*/
protected function getTokenFields($code)
{
return array_merge(parent::getTokenFields($code), [
'grant_type' => 'authorization_code',
]);
}
}

View File

@@ -0,0 +1,206 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class QQProvider.
*
* @see http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B [QQ - OAuth 2.0 登录QQ]
*/
class QQProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base url of QQ API.
*
* @var string
*/
protected $baseUrl = 'https://graph.qq.com';
/**
* User openid.
*
* @var string
*/
protected $openId;
/**
* get token(openid) with unionid.
*
* @var bool
*/
protected $withUnionId = false;
/**
* User unionid.
*
* @var string
*/
protected $unionId;
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['get_user_info'];
/**
* The uid of user authorized.
*
* @var int
*/
protected $uid;
/**
* Get the authentication URL for the provider.
*
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/oauth2.0/authorize', $state);
}
/**
* Get the token URL for the provider.
*
* @return string
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/oauth2.0/token';
}
/**
* Get the Post fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->get($this->getTokenUrl(), [
'query' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody()->getContents());
}
/**
* Get the access token from the token response body.
*
* @param string $body
*
* @return \Overtrue\Socialite\AccessToken
*/
public function parseAccessToken($body)
{
parse_str($body, $token);
return parent::parseAccessToken($token);
}
/**
* @return self
*/
public function withUnionId()
{
$this->withUnionId = true;
return $this;
}
/**
* Get the raw user for the given access token.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return array
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$url = $this->baseUrl.'/oauth2.0/me?access_token='.$token->getToken();
$this->withUnionId && $url .= '&unionid=1';
$response = $this->getHttpClient()->get($url);
$me = json_decode($this->removeCallback($response->getBody()->getContents()), true);
$this->openId = $me['openid'];
$this->unionId = isset($me['unionid']) ? $me['unionid'] : '';
$queries = [
'access_token' => $token->getToken(),
'openid' => $this->openId,
'oauth_consumer_key' => $this->getConfig()->get('client_id'),
];
$response = $this->getHttpClient()->get($this->baseUrl.'/user/get_user_info?'.http_build_query($queries));
return json_decode($this->removeCallback($response->getBody()->getContents()), true);
}
/**
* Map the raw user array to a Socialite User instance.
*
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->openId,
'unionid' => $this->unionId,
'nickname' => $this->arrayItem($user, 'nickname'),
'name' => $this->arrayItem($user, 'nickname'),
'email' => $this->arrayItem($user, 'email'),
'avatar' => $this->arrayItem($user, 'figureurl_qq_2'),
]);
}
/**
* Remove the fucking callback parentheses.
*
* @param string $response
*
* @return string
*/
protected function removeCallback($response)
{
if (false !== strpos($response, 'callback')) {
$lpos = strpos($response, '(');
$rpos = strrpos($response, ')');
$response = substr($response, $lpos + 1, $rpos - $lpos - 1);
}
return $response;
}
}

View File

@@ -0,0 +1,242 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class TaobaoProvider.
*
* @author mechono <haodouliu@gmail.com>
*
* @see https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search [Taobao - OAuth 2.0 授权登录]
*/
class TaobaoProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base url of Taobao API.
*
* @var string
*/
protected $baseUrl = 'https://oauth.taobao.com';
/**
* Taobao API service URL address.
*
* @var string
*/
protected $gatewayUrl = 'https://eco.taobao.com/router/rest';
/**
* The API version for the request.
*
* @var string
*/
protected $version = '2.0';
/**
* @var string
*/
protected $format = 'json';
/**
* @var string
*/
protected $signMethod = 'md5';
/**
* Web 对应 PC 端(淘宝 logo 浏览器页面样式Tmall 对应天猫的浏览器页面样式Wap 对应无线端的浏览器页面样式。
*/
protected $view = 'web';
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['user_info'];
/**
* Get the authentication URL for the provider.
*
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/authorize', $state);
}
/**
* 获取授权码接口参数.
*
* @param string|null $state
*
* @return array
*/
public function getCodeFields($state = null)
{
$fields = [
'client_id' => $this->getConfig()->get('client_id'),
'redirect_uri' => $this->redirectUrl,
'view' => $this->view,
'response_type' => 'code',
];
if ($this->usesState()) {
$fields['state'] = $state;
}
return $fields;
}
/**
* Get the token URL for the provider.
*
* @return string
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/token';
}
/**
* Get the Post fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code', 'view' => $this->view];
}
/**
* Get the access token for the given code.
*
* @param string $code
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'query' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody()->getContents());
}
/**
* Get the access token from the token response body.
*
* @param string $body
*
* @return \Overtrue\Socialite\AccessToken
*/
public function parseAccessToken($body)
{
return parent::parseAccessToken($body);
}
/**
* Get the raw user for the given access token.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return array
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->post($this->getUserInfoUrl($this->gatewayUrl, $token));
return json_decode($response->getBody(), true);
}
/**
* Map the raw user array to a Socialite User instance.
*
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'open_id'),
'nickname' => $this->arrayItem($user, 'nick'),
'name' => $this->arrayItem($user, 'nick'),
'avatar' => $this->arrayItem($user, 'avatar'),
]);
}
/**
* @param $params
*
* @return string
*/
protected function generateSign($params)
{
ksort($params);
$stringToBeSigned = $this->getConfig()->get('client_secret');
foreach ($params as $k => $v) {
if (!is_array($v) && '@' != substr($v, 0, 1)) {
$stringToBeSigned .= "$k$v";
}
}
$stringToBeSigned .= $this->getConfig()->get('client_secret');
return strtoupper(md5($stringToBeSigned));
}
/**
* @param \Overtrue\Socialite\AccessTokenInterface $token
* @param array $apiFields
*
* @return array
*/
protected function getPublicFields(AccessTokenInterface $token, array $apiFields = [])
{
$fields = [
'app_key' => $this->getConfig()->get('client_id'),
'sign_method' => $this->signMethod,
'session' => $token->getToken(),
'timestamp' => date('Y-m-d H:i:s'),
'v' => $this->version,
'format' => $this->format,
];
$fields = array_merge($apiFields, $fields);
$fields['sign'] = $this->generateSign($fields);
return $fields;
}
/**
* {@inheritdoc}.
*/
protected function getUserInfoUrl($url, AccessTokenInterface $token)
{
$apiFields = ['method' => 'taobao.miniapp.userInfo.get'];
$query = http_build_query($this->getPublicFields($token, $apiFields), '', '&', $this->encodingType);
return $url.'?'.$query;
}
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\InvalidArgumentException;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
use Overtrue\Socialite\WeChatComponentInterface;
/**
* Class WeChatProvider.
*
* @see http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html [WeChat - 公众平台OAuth文档]
* @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN [网站应用微信登录开发指南]
*/
class WeChatProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base url of WeChat API.
*
* @var string
*/
protected $baseUrl = 'https://api.weixin.qq.com/sns';
/**
* {@inheritdoc}.
*/
protected $openId;
/**
* {@inheritdoc}.
*/
protected $scopes = ['snsapi_login'];
/**
* Indicates if the session state should be utilized.
*
* @var bool
*/
protected $stateless = true;
/**
* Return country code instead of country name.
*
* @var bool
*/
protected $withCountryCode = false;
/**
* @var WeChatComponentInterface
*/
protected $component;
/**
* Return country code instead of country name.
*
* @return $this
*/
public function withCountryCode()
{
$this->withCountryCode = true;
return $this;
}
/**
* WeChat OpenPlatform 3rd component.
*
* @param WeChatComponentInterface $component
*
* @return $this
*/
public function component(WeChatComponentInterface $component)
{
$this->scopes = ['snsapi_base'];
$this->component = $component;
return $this;
}
/**
* {@inheritdoc}.
*/
public function getAccessToken($code)
{
$response = $this->getHttpClient()->get($this->getTokenUrl(), [
'headers' => ['Accept' => 'application/json'],
'query' => $this->getTokenFields($code),
]);
return $this->parseAccessToken($response->getBody());
}
/**
* {@inheritdoc}.
*/
protected function getAuthUrl($state)
{
$path = 'oauth2/authorize';
if (in_array('snsapi_login', $this->scopes)) {
$path = 'qrconnect';
}
return $this->buildAuthUrlFromBase("https://open.weixin.qq.com/connect/{$path}", $state);
}
/**
* {@inheritdoc}.
*/
protected function buildAuthUrlFromBase($url, $state)
{
$query = http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
return $url.'?'.$query.'#wechat_redirect';
}
/**
* {@inheritdoc}.
*/
protected function getCodeFields($state = null)
{
if ($this->component) {
$this->with(array_merge($this->parameters, ['component_appid' => $this->component->getAppId()]));
}
return array_merge([
'appid' => $this->getConfig()->get('client_id'),
'redirect_uri' => $this->redirectUrl,
'response_type' => 'code',
'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
'state' => $state ?: md5(time()),
'connect_redirect' => 1,
], $this->parameters);
}
/**
* {@inheritdoc}.
*/
protected function getTokenUrl()
{
if ($this->component) {
return $this->baseUrl.'/oauth2/component/access_token';
}
return $this->baseUrl.'/oauth2/access_token';
}
/**
* {@inheritdoc}.
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$scopes = explode(',', $token->getAttribute('scope', ''));
if (in_array('snsapi_base', $scopes)) {
return $token->toArray();
}
if (empty($token['openid'])) {
throw new InvalidArgumentException('openid of AccessToken is required.');
}
$language = $this->withCountryCode ? null : (isset($this->parameters['lang']) ? $this->parameters['lang'] : 'zh_CN');
$response = $this->getHttpClient()->get($this->baseUrl.'/userinfo', [
'query' => array_filter([
'access_token' => $token->getToken(),
'openid' => $token['openid'],
'lang' => $language,
]),
]);
return json_decode($response->getBody(), true);
}
/**
* {@inheritdoc}.
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'openid'),
'name' => $this->arrayItem($user, 'nickname'),
'nickname' => $this->arrayItem($user, 'nickname'),
'avatar' => $this->arrayItem($user, 'headimgurl'),
'email' => null,
]);
}
/**
* {@inheritdoc}.
*/
protected function getTokenFields($code)
{
return array_filter([
'appid' => $this->getConfig()->get('client_id'),
'secret' => $this->getConfig()->get('client_secret'),
'component_appid' => $this->component ? $this->component->getAppId() : null,
'component_access_token' => $this->component ? $this->component->getToken() : null,
'code' => $code,
'grant_type' => 'authorization_code',
]);
}
/**
* Remove the fucking callback parentheses.
*
* @param mixed $response
*
* @return string
*/
protected function removeCallback($response)
{
if (false !== strpos($response, 'callback')) {
$lpos = strpos($response, '(');
$rpos = strrpos($response, ')');
$response = substr($response, $lpos + 1, $rpos - $lpos - 1);
}
return $response;
}
}

View File

@@ -0,0 +1,214 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class WeWorkProvider.
*
* @author mingyoung <mingyoungcheung@gmail.com>
*/
class WeWorkProvider extends AbstractProvider implements ProviderInterface
{
/**
* @var string
*/
protected $agentId;
/**
* @var bool
*/
protected $detailed = false;
/**
* Set agent id.
*
* @param string $agentId
*
* @return $this
*/
public function setAgentId($agentId)
{
$this->agentId = $agentId;
return $this;
}
/**
* @param string $agentId
*
* @return $this
*/
public function agent($agentId)
{
return $this->setAgentId($agentId);
}
/**
* Return user details.
*
* @return $this
*/
public function detailed()
{
$this->detailed = true;
return $this;
}
/**
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
// 网页授权登录
if (!empty($this->scopes)) {
return $this->getOAuthUrl($state);
}
// 第三方网页应用登录(扫码登录)
return $this->getQrConnectUrl($state);
}
/**
* OAuth url.
*
* @param string $state
*
* @return string
*/
protected function getOAuthUrl($state)
{
$queries = [
'appid' => $this->getConfig()->get('client_id'),
'redirect_uri' => $this->redirectUrl,
'response_type' => 'code',
'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
'agentid' => $this->agentId,
'state' => $state,
];
return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries));
}
/**
* Qr connect url.
*
* @param string $state
*
* @return string
*/
protected function getQrConnectUrl($state)
{
$queries = [
'appid' => $this->getConfig()->get('client_id'),
'agentid' => $this->agentId,
'redirect_uri' => $this->redirectUrl,
'state' => $state,
];
return 'https://open.work.weixin.qq.com/wwopen/sso/qrConnect?'.http_build_query($queries);
}
protected function getTokenUrl()
{
return null;
}
/**
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return mixed
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$userInfo = $this->getUserInfo($token);
if ($this->detailed && isset($userInfo['user_ticket'])) {
return $this->getUserDetail($token, $userInfo['user_ticket']);
}
$this->detailed = false;
return $userInfo;
}
/**
* Get user base info.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return mixed
*/
protected function getUserInfo(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get('https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo', [
'query' => array_filter([
'access_token' => $token->getToken(),
'code' => $this->getCode(),
]),
]);
return json_decode($response->getBody(), true);
}
/**
* Get user detail info.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
* @param $ticket
*
* @return mixed
*/
protected function getUserDetail(AccessTokenInterface $token, $ticket)
{
$response = $this->getHttpClient()->post('https://qyapi.weixin.qq.com/cgi-bin/user/getuserdetail', [
'query' => [
'access_token' => $token->getToken(),
],
'json' => [
'user_ticket' => $ticket,
],
]);
return json_decode($response->getBody(), true);
}
/**
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
protected function mapUserToObject(array $user)
{
if ($this->detailed) {
return new User([
'id' => $this->arrayItem($user, 'userid'),
'name' => $this->arrayItem($user, 'name'),
'avatar' => $this->arrayItem($user, 'avatar'),
'email' => $this->arrayItem($user, 'email'),
]);
}
return new User(array_filter([
'id' => $this->arrayItem($user, 'UserId') ?: $this->arrayItem($user, 'OpenId'),
'userId' => $this->arrayItem($user, 'UserId'),
'openid' => $this->arrayItem($user, 'OpenId'),
'deviceId' => $this->arrayItem($user, 'DeviceId'),
]));
}
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite\Providers;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\ProviderInterface;
use Overtrue\Socialite\User;
/**
* Class WeiboProvider.
*
* @see http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E [OAuth 2.0 授权机制说明]
*/
class WeiboProvider extends AbstractProvider implements ProviderInterface
{
/**
* The base url of Weibo API.
*
* @var string
*/
protected $baseUrl = 'https://api.weibo.com';
/**
* The API version for the request.
*
* @var string
*/
protected $version = '2';
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['email'];
/**
* The uid of user authorized.
*
* @var int
*/
protected $uid;
/**
* Get the authentication URL for the provider.
*
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->baseUrl.'/oauth2/authorize', $state);
}
/**
* Get the token URL for the provider.
*
* @return string
*/
protected function getTokenUrl()
{
return $this->baseUrl.'/'.$this->version.'/oauth2/access_token';
}
/**
* Get the Post fields for the token request.
*
* @param string $code
*
* @return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* Get the raw user for the given access token.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return array
*/
protected function getUserByToken(AccessTokenInterface $token)
{
$response = $this->getHttpClient()->get($this->baseUrl.'/'.$this->version.'/users/show.json', [
'query' => [
'uid' => $token['uid'],
'access_token' => $token->getToken(),
],
'headers' => [
'Accept' => 'application/json',
],
]);
return json_decode($response->getBody(), true);
}
/**
* Map the raw user array to a Socialite User instance.
*
* @param array $user
*
* @return \Overtrue\Socialite\User
*/
protected function mapUserToObject(array $user)
{
return new User([
'id' => $this->arrayItem($user, 'id'),
'nickname' => $this->arrayItem($user, 'screen_name'),
'name' => $this->arrayItem($user, 'name'),
'email' => $this->arrayItem($user, 'email'),
'avatar' => $this->arrayItem($user, 'avatar_large'),
]);
}
}

View File

@@ -0,0 +1,251 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
use Closure;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
/**
* Class SocialiteManager.
*/
class SocialiteManager implements FactoryInterface
{
/**
* The configuration.
*
* @var \Overtrue\Socialite\Config
*/
protected $config;
/**
* The request instance.
*
* @var Request
*/
protected $request;
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* The initial drivers.
*
* @var array
*/
protected $initialDrivers = [
'facebook' => 'Facebook',
'github' => 'GitHub',
'google' => 'Google',
'linkedin' => 'Linkedin',
'weibo' => 'Weibo',
'qq' => 'QQ',
'wechat' => 'WeChat',
'douban' => 'Douban',
'wework' => 'WeWork',
'outlook' => 'Outlook',
'douyin' => 'DouYin',
'taobao' => 'Taobao',
'feishu' => 'FeiShu',
];
/**
* The array of created "drivers".
*
* @var ProviderInterface[]
*/
protected $drivers = [];
/**
* SocialiteManager constructor.
*
* @param array $config
* @param Request|null $request
*/
public function __construct(array $config, Request $request = null)
{
$this->config = new Config($config);
if ($this->config->has('guzzle')) {
Providers\AbstractProvider::setGuzzleOptions($this->config->get('guzzle'));
}
if ($request) {
$this->setRequest($request);
}
}
/**
* Set config instance.
*
* @param \Overtrue\Socialite\Config $config
*
* @return $this
*/
public function config(Config $config)
{
$this->config = $config;
return $this;
}
/**
* Get a driver instance.
*
* @param string $driver
*
* @return ProviderInterface
*/
public function driver($driver)
{
$driver = strtolower($driver);
if (!isset($this->drivers[$driver])) {
$this->drivers[$driver] = $this->createDriver($driver);
}
return $this->drivers[$driver];
}
/**
* @param \Symfony\Component\HttpFoundation\Request $request
*
* @return $this
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* @return \Symfony\Component\HttpFoundation\Request
*/
public function getRequest()
{
return $this->request ?: $this->createDefaultRequest();
}
/**
* Create a new driver instance.
*
* @param string $driver
*
* @throws \InvalidArgumentException
*
* @return ProviderInterface
*/
protected function createDriver($driver)
{
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
}
if (isset($this->initialDrivers[$driver])) {
$provider = $this->initialDrivers[$driver];
$provider = __NAMESPACE__.'\\Providers\\'.$provider.'Provider';
return $this->buildProvider($provider, $this->formatConfig($this->config->get($driver)));
}
throw new InvalidArgumentException("Driver [$driver] not supported.");
}
/**
* Call a custom driver creator.
*
* @param string $driver
*
* @return ProviderInterface
*/
protected function callCustomCreator($driver)
{
return $this->customCreators[$driver]($this->config);
}
/**
* Create default request instance.
*
* @return Request
*/
protected function createDefaultRequest()
{
$request = Request::createFromGlobals();
$session = new Session();
$request->setSession($session);
return $request;
}
/**
* Register a custom driver creator Closure.
*
* @param string $driver
* @param \Closure $callback
*
* @return $this
*/
public function extend($driver, Closure $callback)
{
$driver = strtolower($driver);
$this->customCreators[$driver] = $callback;
return $this;
}
/**
* Get all of the created "drivers".
*
* @return ProviderInterface[]
*/
public function getDrivers()
{
return $this->drivers;
}
/**
* Build an OAuth 2 provider instance.
*
* @param string $provider
* @param array $config
*
* @return ProviderInterface
*/
public function buildProvider($provider, $config)
{
return new $provider($this->getRequest(), $config);
}
/**
* Format the server configuration.
*
* @param array $config
*
* @return array
*/
public function formatConfig(array $config)
{
return array_merge([
'identifier' => $config['client_id'],
'secret' => $config['client_secret'],
'callback_uri' => $config['redirect'],
], $config);
}
}

204
vendor/overtrue/socialite/src/User.php vendored Normal file
View File

@@ -0,0 +1,204 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
use ArrayAccess;
use JsonSerializable;
/**
* Class User.
*/
class User implements ArrayAccess, UserInterface, JsonSerializable, \Serializable
{
use HasAttributes;
/**
* User constructor.
*
* @param array $attributes
*/
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
/**
* Get the unique identifier for the user.
*
* @return string
*/
public function getId()
{
return $this->getAttribute('id');
}
/**
* Get the username for the user.
*
* @return string
*/
public function getUsername()
{
return $this->getAttribute('username', $this->getId());
}
/**
* Get the nickname / username for the user.
*
* @return string
*/
public function getNickname()
{
return $this->getAttribute('nickname');
}
/**
* Get the full name of the user.
*
* @return string
*/
public function getName()
{
return $this->getAttribute('name');
}
/**
* Get the e-mail address of the user.
*
* @return string
*/
public function getEmail()
{
return $this->getAttribute('email');
}
/**
* Get the avatar / image URL for the user.
*
* @return string
*/
public function getAvatar()
{
return $this->getAttribute('avatar');
}
/**
* Set the token on the user.
*
* @param \Overtrue\Socialite\AccessTokenInterface $token
*
* @return $this
*/
public function setToken(AccessTokenInterface $token)
{
$this->setAttribute('token', $token->getToken());
$this->setAttribute('access_token', $token->getToken());
if (\is_callable([$token, 'getRefreshToken'])) {
$this->setAttribute('refresh_token', $token->getRefreshToken());
}
return $this;
}
/**
* @param string $provider
*
* @return $this
*/
public function setProviderName($provider)
{
$this->setAttribute('provider', $provider);
return $this;
}
/**
* @return string
*/
public function getProviderName()
{
return $this->getAttribute('provider');
}
/**
* Get the authorized token.
*
* @return \Overtrue\Socialite\AccessToken
*/
public function getToken()
{
return new AccessToken([
'access_token' => $this->getAccessToken(),
'refresh_token' => $this->getAttribute('refresh_token')
]);
}
/**
* Get user access token.
*
* @return string
*/
public function getAccessToken()
{
return $this->getAttribute('token') ?: $this->getAttribute('access_token');
}
/**
* Get user refresh token.
*
* @return string
*/
public function getRefreshToken()
{
return $this->getAttribute('refresh_token');
}
/**
* Get the original attributes.
*
* @return array
*/
public function getOriginal()
{
return $this->getAttribute('original');
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return $this->attributes;
}
public function serialize()
{
return serialize($this->attributes);
}
/**
* Constructs the object.
*
* @see https://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized <p>
* The string representation of the object.
* </p>
*
* @since 5.1.0
*/
public function unserialize($serialized)
{
$this->attributes = unserialize($serialized) ?: [];
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
/**
* Interface UserInterface.
*/
interface UserInterface
{
/**
* Get the unique identifier for the user.
*
* @return string
*/
public function getId();
/**
* Get the nickname / username for the user.
*
* @return string
*/
public function getNickname();
/**
* Get the full name of the user.
*
* @return string
*/
public function getName();
/**
* Get the e-mail address of the user.
*
* @return string
*/
public function getEmail();
/**
* Get the avatar / image URL for the user.
*
* @return string
*/
public function getAvatar();
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\Socialite;
/**
* Interface WeChatComponentInterface.
*/
interface WeChatComponentInterface
{
/**
* Return the open-platform component app id.
*
* @return string
*/
public function getAppId();
/**
* Return the open-platform component access token string.
*
* @return string
*/
public function getToken();
}

View File

@@ -0,0 +1,243 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Mockery as m;
use Overtrue\Socialite\AccessTokenInterface;
use Overtrue\Socialite\Providers\AbstractProvider;
use Overtrue\Socialite\User;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
class OAuthTest extends TestCase
{
public function tearDown()
{
m::close();
}
public function testAbstractProviderBackwardCompatible()
{
$request = Request::create('foo');
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('put')->once();
$provider = new OAuthTwoTestProviderStub($request, 'client_id', 'client_secret', 'redirect');
$this->assertSame('client_id', $provider->getConfig()['client_id']);
$this->assertSame('client_secret', $provider->getConfig()['client_secret']);
$this->assertSame('redirect', $provider->getConfig()['redirect']);
$response = $provider->redirect();
$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
$this->assertSame('http://auth.url', $response->getTargetUrl());
}
public function testRedirectGeneratesTheProperSymfonyRedirectResponse()
{
$request = Request::create('foo');
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('put')->once();
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect',
]
);
$response = $provider->redirect();
$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
$this->assertSame('http://auth.url', $response->getTargetUrl());
}
public function testRedirectUrl()
{
$request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']);
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
]
);
$this->assertNull($provider->getRedirectUrl());
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect_uri',
]
);
$this->assertSame('redirect_uri', $provider->getRedirectUrl());
$provider->setRedirectUrl('overtrue.me');
$this->assertSame('overtrue.me', $provider->getRedirectUrl());
$provider->withRedirectUrl('http://overtrue.me');
$this->assertSame('http://overtrue.me', $provider->getRedirectUrl());
}
public function testUserReturnsAUserInstanceForTheAuthenticatedRequest()
{
$request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']);
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40));
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect_uri',
]
);
$provider->http = m::mock('StdClass');
$provider->http->shouldReceive('post')->once()->with(
'http://token.url',
[
'headers' => ['Accept' => 'application/json'],
'form_params' => [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'code' => 'code',
'redirect_uri' => 'redirect_uri',
],
]
)->andReturn($response = m::mock('StdClass'));
$response->shouldReceive('getBody')->once()->andReturn('{"access_token":"access_token"}');
$user = $provider->user();
$this->assertInstanceOf('Overtrue\Socialite\User', $user);
$this->assertSame('foo', $user->getId());
}
/**
* @expectedException \Overtrue\Socialite\InvalidStateException
*/
public function testExceptionIsThrownIfStateIsInvalid()
{
$request = Request::create('foo', 'GET', ['state' => str_repeat('B', 40), 'code' => 'code']);
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40));
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect',
]
);
$user = $provider->user();
}
/**
* @expectedException \Overtrue\Socialite\AuthorizeFailedException
* @expectedExceptionMessage Authorize Failed: {"error":"scope is invalid"}
*/
public function testExceptionisThrownIfAuthorizeFailed()
{
$request = Request::create('foo', 'GET', ['state' => str_repeat('A', 40), 'code' => 'code']);
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('get')->once()->with('state')->andReturn(str_repeat('A', 40));
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect_uri',
]
);
$provider->http = m::mock('StdClass');
$provider->http->shouldReceive('post')->once()->with(
'http://token.url',
[
'headers' => ['Accept' => 'application/json'],
'form_params' => [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'code' => 'code',
'redirect_uri' => 'redirect_uri',
],
]
)->andReturn($response = m::mock('StdClass'));
$response->shouldReceive('getBody')->once()->andReturn('{"error":"scope is invalid"}');
$user = $provider->user();
}
/**
* @expectedException \Overtrue\Socialite\InvalidStateException
*/
public function testExceptionIsThrownIfStateIsNotSet()
{
$request = Request::create('foo', 'GET', ['state' => 'state', 'code' => 'code']);
$request->setSession($session = m::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
$session->shouldReceive('get')->once()->with('state');
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect',
]
);
$user = $provider->user();
}
public function testDriverName()
{
$request = Request::create('foo', 'GET', ['state' => 'state', 'code' => 'code']);
$provider = new OAuthTwoTestProviderStub(
$request, [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'redirect',
]
);
$this->assertSame('OAuthTwoTest', $provider->getName());
}
}
class OAuthTwoTestProviderStub extends AbstractProvider
{
public $http;
protected function getAuthUrl($state)
{
return 'http://auth.url';
}
protected function getTokenUrl()
{
return 'http://token.url';
}
protected function getUserByToken(AccessTokenInterface $token)
{
return ['id' => 'foo'];
}
protected function mapUserToObject(array $user)
{
return new User(['id' => $user['id']]);
}
/**
* Get a fresh instance of the Guzzle HTTP client.
*
* @return \GuzzleHttp\Client
*/
protected function getHttpClient()
{
if ($this->http) {
return $this->http;
}
return $this->http = m::mock('StdClass');
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Overtrue\Socialite\Providers\WeWorkProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
class WeWorkProviderTest extends TestCase
{
public function testQrConnect()
{
$response = (new WeWorkProvider(Request::create('foo'), [
'client_id' => 'ww100000a5f2191',
'client_secret' => 'client_secret',
'redirect' => 'http://www.oa.com',
]))
->setAgentId('1000000')
->stateless()
->redirect();
$this->assertSame('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=ww100000a5f2191&agentid=1000000&redirect_uri=http%3A%2F%2Fwww.oa.com', $response->getTargetUrl());
}
public function testOAuthWithAgentId()
{
$response = (new WeWorkProvider(Request::create('foo'), [
'client_id' => 'CORPID',
'client_secret' => 'client_secret',
'redirect' => 'REDIRECT_URI',
]))
->scopes(['snsapi_base'])
->setAgentId('1000000')
->stateless()
->redirect();
$this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&agentid=1000000#wechat_redirect', $response->getTargetUrl());
}
public function testOAuthWithoutAgentId()
{
$response = (new WeWorkProvider(Request::create('foo'), [
'client_id' => 'CORPID',
'client_secret' => 'client_secret',
'redirect' => 'REDIRECT_URI',
]))
->scopes(['snsapi_base'])
->stateless()
->redirect();
$this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response->getTargetUrl());
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Overtrue\Socialite\AccessToken;
use Overtrue\Socialite\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function testJsonserialize()
{
$this->assertSame('[]', json_encode(new User([])));
$this->assertSame('{"token":"mock-token"}', json_encode(new User(['token' => new AccessToken(['access_token' => 'mock-token'])])));
}
public function test_it_can_get_refresh_token()
{
$user = new User([
'access_token' => 'mock-token',
'refresh_token' => 'fake_refresh',
]);
// 能通过用 User 对象获取 refresh token
$this->assertSame('fake_refresh', $user->getRefreshToken());
// json 序列化只有 token 字段
$this->assertSame('{"access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user));
$user = new User([]);
$user->setToken(new AccessToken([
'access_token' => 'mock-token',
'refresh_token' => 'fake_refresh',
]));
$this->assertSame('fake_refresh', $user->getRefreshToken());
$this->assertSame('{"token":"mock-token","access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user));
}
}

View File

@@ -0,0 +1,137 @@
<?php
/*
* This file is part of the overtrue/socialite.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Overtrue\Socialite\Providers\WeChatProvider as RealWeChatProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
class WechatProviderTest extends TestCase
{
public function testWeChatProviderHasCorrectlyRedirectResponse()
{
$response = (new WeChatProvider(Request::create('foo'), [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'http://localhost/socialite/callback.php',
]))->redirect();
$this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
$this->assertStringStartsWith('https://open.weixin.qq.com/connect/qrconnect', $response->getTargetUrl());
$this->assertRegExp('/redirect_uri=http%3A%2F%2Flocalhost%2Fsocialite%2Fcallback.php/', $response->getTargetUrl());
}
public function testWeChatProviderTokenUrlAndRequestFields()
{
$provider = new WeChatProvider(Request::create('foo'), [
'client_id' => 'client_id',
'client_secret' => 'client_secret',
'redirect' => 'http://localhost/socialite/callback.php',
]);
$this->assertSame('https://api.weixin.qq.com/sns/oauth2/access_token', $provider->tokenUrl());
$this->assertSame([
'appid' => 'client_id',
'secret' => 'client_secret',
'code' => 'iloveyou',
'grant_type' => 'authorization_code',
], $provider->tokenFields('iloveyou'));
$this->assertSame([
'appid' => 'client_id',
'redirect_uri' => 'http://localhost/socialite/callback.php',
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => 'wechat-state',
'connect_redirect' => 1,
], $provider->codeFields('wechat-state'));
}
public function testOpenPlatformComponent()
{
$provider = new WeChatProvider(Request::create('foo'), [
'client_id' => 'client_id',
'client_secret' => null,
'redirect' => 'redirect-url',
]);
$provider->component(new WeChatComponent());
$this->assertSame([
'appid' => 'client_id',
'redirect_uri' => 'redirect-url',
'response_type' => 'code',
'scope' => 'snsapi_base',
'state' => 'state',
'connect_redirect' => 1,
'component_appid' => 'component-app-id',
], $provider->codeFields('state'));
$this->assertSame([
'appid' => 'client_id',
'component_appid' => 'component-app-id',
'component_access_token' => 'token',
'code' => 'simcode',
'grant_type' => 'authorization_code',
], $provider->tokenFields('simcode'));
$this->assertSame('https://api.weixin.qq.com/sns/oauth2/component/access_token', $provider->tokenUrl());
}
public function testOpenPlatformComponentWithCustomParameters()
{
$provider = new WeChatProvider(Request::create('foo'), [
'client_id' => 'client_id',
'client_secret' => null,
'redirect' => 'redirect-url',
]);
$provider->component(new WeChatComponent());
$provider->with(['foo' => 'bar']);
$fields = $provider->codeFields('wechat-state');
$this->assertArrayHasKey('foo', $fields);
$this->assertSame('bar', $fields['foo']);
}
}
trait ProviderTrait
{
public function tokenUrl()
{
return $this->getTokenUrl();
}
public function tokenFields($code)
{
return $this->getTokenFields($code);
}
public function codeFields($state = null)
{
return $this->getCodeFields($state);
}
}
class WeChatProvider extends RealWeChatProvider
{
use ProviderTrait;
}
class WeChatComponent implements \Overtrue\Socialite\WeChatComponentInterface
{
public function getAppId()
{
return 'component-app-id';
}
public function getToken()
{
return 'token';
}
}