CaiWuXiTong/app/Services/Auth/DingTalkOAuthClient.php

117 lines
3.8 KiB
PHP

<?php
namespace App\Services\Auth;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class DingTalkOAuthClient
{
public function isConfigured(): bool
{
return (bool) config('dingtalk.enabled')
&& $this->configuredString('dingtalk.client_id') !== ''
&& $this->configuredString('dingtalk.client_secret') !== ''
&& $this->configuredString('dingtalk.redirect_uri') !== '';
}
public function authorizationUrl(string $state): string
{
$this->ensureConfigured();
return rtrim($this->configuredString('dingtalk.authorize_url'), '?') . '?' . http_build_query([
'redirect_uri' => $this->configuredString('dingtalk.redirect_uri'),
'response_type' => 'code',
'client_id' => $this->configuredString('dingtalk.client_id'),
'scope' => 'openid',
'prompt' => 'consent',
'state' => $state,
], '', '&', PHP_QUERY_RFC3986);
}
/**
* @return array{access_token: string, expires_in: int|null}
*/
public function exchangeAuthorizationCode(string $code): array
{
$this->ensureConfigured();
try {
$response = Http::asJson()
->acceptJson()
->timeout($this->timeout())
->post($this->configuredString('dingtalk.token_url'), [
'clientId' => $this->configuredString('dingtalk.client_id'),
'clientSecret' => $this->configuredString('dingtalk.client_secret'),
'code' => $code,
'grantType' => 'authorization_code',
])
->throw();
} catch (RequestException $exception) {
throw new RuntimeException('DingTalk token exchange failed.', 0, $exception);
}
$accessToken = trim((string) $response->json('accessToken'));
if ($accessToken === '') {
throw new RuntimeException('DingTalk token response did not contain an access token.');
}
return [
'access_token' => $accessToken,
'expires_in' => is_numeric($response->json('expireIn')) ? (int) $response->json('expireIn') : null,
];
}
/**
* @return array{union_id: string, open_id: string|null, nickname: string|null}
*/
public function currentUser(string $accessToken): array
{
try {
$response = Http::acceptJson()
->withHeaders(['x-acs-dingtalk-access-token' => $accessToken])
->timeout($this->timeout())
->get($this->configuredString('dingtalk.user_url'))
->throw();
} catch (RequestException $exception) {
throw new RuntimeException('DingTalk user lookup failed.', 0, $exception);
}
$unionId = trim((string) $response->json('unionId'));
if ($unionId === '') {
throw new RuntimeException('DingTalk user response did not contain a union id.');
}
return [
'union_id' => $unionId,
'open_id' => $this->nullableString($response->json('openId')),
'nickname' => $this->nullableString($response->json('nick')),
];
}
protected function ensureConfigured(): void
{
if (! $this->isConfigured()) {
throw new RuntimeException('DingTalk login is not configured.');
}
}
protected function configuredString(string $key): string
{
return trim((string) config($key, ''));
}
protected function timeout(): int
{
return max(3, min(30, (int) config('dingtalk.timeout', 10)));
}
protected function nullableString(mixed $value): ?string
{
$value = trim((string) $value);
return $value === '' ? null : $value;
}
}