data_center/vendor/overtrue/socialite/src/Providers/QCloud.php

244 lines
7.3 KiB
PHP
Raw Normal View History

2023-09-18 09:11:13 +08:00
<?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] ?? '';
}
}