244 lines
7.3 KiB
PHP
244 lines
7.3 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Overtrue\Socialite\Providers;
|
||
|
|
||
|
use JetBrains\PhpStorm\Pure;
|
||
|
use Overtrue\Socialite\Contracts;
|
||
|
use Overtrue\Socialite\Exceptions;
|
||
|
use Overtrue\Socialite\User;
|
||
|
|
||
|
class QCloud extends Base
|
||
|
{
|
||
|
public const NAME = 'qcloud';
|
||
|
|
||
|
protected array $scopes = ['login'];
|
||
|
|
||
|
protected string $accessTokenKey = 'UserAccessToken';
|
||
|
|
||
|
protected string $refreshTokenKey = 'UserRefreshToken';
|
||
|
|
||
|
protected string $expiresInKey = 'ExpiresAt';
|
||
|
|
||
|
protected ?string $openId;
|
||
|
|
||
|
protected ?string $unionId;
|
||
|
|
||
|
protected function getAuthUrl(): string
|
||
|
{
|
||
|
return $this->buildAuthUrlFromBase('https://cloud.tencent.com/open/authorize');
|
||
|
}
|
||
|
|
||
|
protected function getTokenUrl(): string
|
||
|
{
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
protected function getAppId(): string
|
||
|
{
|
||
|
return $this->config->get(Contracts\ABNF_APP_ID) ?? $this->getClientId();
|
||
|
}
|
||
|
|
||
|
protected function getSecretId(): string
|
||
|
{
|
||
|
return $this->config->get('secret_id');
|
||
|
}
|
||
|
|
||
|
protected function getSecretKey(): string
|
||
|
{
|
||
|
return $this->config->get('secret_key');
|
||
|
}
|
||
|
|
||
|
public function TokenFromCode(string $code): array
|
||
|
{
|
||
|
$response = $this->performRequest(
|
||
|
'GET',
|
||
|
'open.tencentcloudapi.com',
|
||
|
'GetUserAccessToken',
|
||
|
'2018-12-25',
|
||
|
[
|
||
|
'query' => [
|
||
|
'UserAuthCode' => $code,
|
||
|
],
|
||
|
]
|
||
|
);
|
||
|
|
||
|
return $this->parseAccessToken($response);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @throws Exceptions\AuthorizeFailedException
|
||
|
*/
|
||
|
protected function getUserByToken(string $token): array
|
||
|
{
|
||
|
$secret = $this->getFederationToken($token);
|
||
|
|
||
|
return $this->performRequest(
|
||
|
'GET',
|
||
|
'open.tencentcloudapi.com',
|
||
|
'GetUserBaseInfo',
|
||
|
'2018-12-25',
|
||
|
[
|
||
|
'headers' => [
|
||
|
'X-TC-Token' => $secret['Token'],
|
||
|
],
|
||
|
],
|
||
|
$secret['TmpSecretId'],
|
||
|
$secret['TmpSecretKey'],
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[Pure]
|
||
|
protected function mapUserToObject(array $user): Contracts\UserInterface
|
||
|
{
|
||
|
return new User([
|
||
|
Contracts\ABNF_ID => $this->openId ?? null,
|
||
|
Contracts\ABNF_NAME => $user['Nickname'] ?? null,
|
||
|
Contracts\ABNF_NICKNAME => $user['Nickname'] ?? null,
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @throws Exceptions\AuthorizeFailedException
|
||
|
*/
|
||
|
public function performRequest(string $method, string $host, string $action, string $version, array $options = [], ?string $secretId = null, ?string $secretKey = null): array
|
||
|
{
|
||
|
$method = \strtoupper($method);
|
||
|
$timestamp = \time();
|
||
|
$credential = \sprintf('%s/%s/tc3_request', \gmdate('Y-m-d', $timestamp), $this->getServiceFromHost($host));
|
||
|
$options['headers'] = \array_merge(
|
||
|
$options['headers'] ?? [],
|
||
|
[
|
||
|
'X-TC-Action' => $action,
|
||
|
'X-TC-Timestamp' => $timestamp,
|
||
|
'X-TC-Version' => $version,
|
||
|
'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$signature = $this->sign($method, $host, $options['query'] ?? [], '', $options['headers'], $credential, $secretKey);
|
||
|
$options['headers']['Authorization'] =
|
||
|
\sprintf(
|
||
|
'TC3-HMAC-SHA256 Credential=%s/%s, SignedHeaders=content-type;host, Signature=%s',
|
||
|
$secretId ?? $this->getSecretId(),
|
||
|
$credential,
|
||
|
$signature
|
||
|
);
|
||
|
$response = $this->getHttpClient()->get("https://{$host}/", $options);
|
||
|
|
||
|
$response = $this->fromJsonBody($response);
|
||
|
|
||
|
if (! empty($response['Response']['Error'])) {
|
||
|
throw new Exceptions\AuthorizeFailedException(
|
||
|
\sprintf('%s: %s', $response['Response']['Error']['Code'], $response['Response']['Error']['Message']),
|
||
|
$response
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $response['Response'] ?? [];
|
||
|
}
|
||
|
|
||
|
protected function sign(string $requestMethod, string $host, array $query, string $payload, array $headers, string $credential, ?string $secretKey = null): bool|string
|
||
|
{
|
||
|
$canonicalRequestString = \implode(
|
||
|
"\n",
|
||
|
[
|
||
|
$requestMethod,
|
||
|
'/',
|
||
|
\http_build_query($query),
|
||
|
"content-type:{$headers['Content-Type']}\nhost:{$host}\n",
|
||
|
'content-type;host',
|
||
|
\hash('SHA256', $payload),
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$signString = \implode(
|
||
|
"\n",
|
||
|
[
|
||
|
'TC3-HMAC-SHA256',
|
||
|
$headers['X-TC-Timestamp'],
|
||
|
$credential,
|
||
|
\hash('SHA256', $canonicalRequestString),
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$secretKey = $secretKey ?? $this->getSecretKey();
|
||
|
$secretDate = \hash_hmac('SHA256', \gmdate('Y-m-d', $headers['X-TC-Timestamp']), "TC3{$secretKey}", true);
|
||
|
$secretService = \hash_hmac('SHA256', $this->getServiceFromHost($host), $secretDate, true);
|
||
|
$secretSigning = \hash_hmac('SHA256', 'tc3_request', $secretService, true);
|
||
|
|
||
|
return \hash_hmac('SHA256', $signString, $secretSigning);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @throws Exceptions\AuthorizeFailedException
|
||
|
*/
|
||
|
protected function parseAccessToken(array|string $body): array
|
||
|
{
|
||
|
if (! \is_array($body)) {
|
||
|
$body = \json_decode($body, true);
|
||
|
}
|
||
|
|
||
|
if (empty($body['UserOpenId'] ?? null)) {
|
||
|
throw new Exceptions\AuthorizeFailedException('Authorize Failed: '.\json_encode($body, JSON_UNESCAPED_UNICODE), $body);
|
||
|
}
|
||
|
|
||
|
$this->openId = $body['UserOpenId'] ?? null;
|
||
|
$this->unionId = $body['UserUnionId'] ?? null;
|
||
|
|
||
|
return $body;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @throws Exceptions\AuthorizeFailedException
|
||
|
*/
|
||
|
protected function getFederationToken(string $accessToken): array
|
||
|
{
|
||
|
$response = $this->performRequest(
|
||
|
'GET',
|
||
|
'sts.tencentcloudapi.com',
|
||
|
'GetThirdPartyFederationToken',
|
||
|
'2018-08-13',
|
||
|
[
|
||
|
'query' => [
|
||
|
'UserAccessToken' => $accessToken,
|
||
|
'Duration' => 7200,
|
||
|
'ApiAppId' => 0,
|
||
|
],
|
||
|
'headers' => [
|
||
|
'X-TC-Region' => 'ap-guangzhou', // 官方人员说写死
|
||
|
],
|
||
|
]
|
||
|
);
|
||
|
|
||
|
if (empty($response['Credentials'] ?? null)) {
|
||
|
throw new Exceptions\AuthorizeFailedException('Get Federation Token failed.', $response);
|
||
|
}
|
||
|
|
||
|
return $response['Credentials'];
|
||
|
}
|
||
|
|
||
|
protected function getCodeFields(): array
|
||
|
{
|
||
|
$fields = \array_merge(
|
||
|
[
|
||
|
Contracts\ABNF_APP_ID => $this->getAppId(),
|
||
|
Contracts\RFC6749_ABNF_REDIRECT_URI => $this->redirectUrl,
|
||
|
Contracts\RFC6749_ABNF_SCOPE => $this->formatScopes($this->scopes, $this->scopeSeparator),
|
||
|
Contracts\RFC6749_ABNF_RESPONSE_TYPE => Contracts\RFC6749_ABNF_CODE,
|
||
|
],
|
||
|
$this->parameters
|
||
|
);
|
||
|
|
||
|
if ($this->state) {
|
||
|
$fields[Contracts\RFC6749_ABNF_STATE] = $this->state;
|
||
|
}
|
||
|
|
||
|
return $fields;
|
||
|
}
|
||
|
|
||
|
protected function getServiceFromHost(string $host): string
|
||
|
{
|
||
|
return \explode('.', $host)[0] ?? '';
|
||
|
}
|
||
|
}
|