132 lines
5.1 KiB
PHP
132 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service\iam;
|
|
|
|
use app\adminapi\logic\LoginLogic;
|
|
use app\adminapi\service\AdminTokenService;
|
|
use app\common\model\auth\Admin;
|
|
use app\common\model\auth\AdminRole;
|
|
use app\common\service\FileService;
|
|
use RuntimeException;
|
|
use think\facade\Config;
|
|
use think\facade\Db;
|
|
|
|
class IamAdminIdentityService
|
|
{
|
|
private array $config;
|
|
private ?IamHubClient $client;
|
|
|
|
public function __construct(?array $config = null, ?IamHubClient $client = null)
|
|
{
|
|
$this->config = $config ?? (array) config('iam_hub');
|
|
$this->client = $client;
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function login(array $claims, int $terminal): array
|
|
{
|
|
$subject = trim((string) ($claims['sub'] ?? ''));
|
|
if ($subject === '') {
|
|
throw new RuntimeException('IAM subject is missing');
|
|
}
|
|
$client = $this->client ?? new IamHubClient($this->config);
|
|
$context = $client->provisioningContext($subject);
|
|
$employee = $context['employee'] ?? null;
|
|
if (!is_array($employee) || !hash_equals($subject, (string) ($employee['oidcSubject'] ?? ''))) {
|
|
throw new RuntimeException('IAM provisioning context does not match the login identity');
|
|
}
|
|
|
|
Db::startTrans();
|
|
try {
|
|
$admin = Admin::where('iam_subject', '=', $subject)->find();
|
|
$externalId = trim((string) ($context['externalAccountId'] ?? ''));
|
|
if (!$admin && ctype_digit($externalId) && (int) $externalId > 0) {
|
|
$admin = Admin::find((int) $externalId);
|
|
if ($admin && trim((string) ($admin->iam_subject ?? '')) !== ''
|
|
&& !hash_equals($subject, (string) $admin->iam_subject)) {
|
|
throw new RuntimeException('本地管理员账号已绑定其他统一身份');
|
|
}
|
|
}
|
|
|
|
if (!$admin) {
|
|
$account = self::stableAccount($subject);
|
|
$collision = Admin::where('account', '=', $account)->find();
|
|
if ($collision) {
|
|
throw new RuntimeException('统一身份账号映射冲突');
|
|
}
|
|
$admin = Admin::create([
|
|
'account' => $account,
|
|
'name' => (string) ($employee['displayName'] ?? $claims['name'] ?? $claims['preferred_username'] ?? $account),
|
|
'password' => '',
|
|
'avatar' => Config::get('project.default_image.admin_avatar'),
|
|
'root' => 0,
|
|
'disable' => 0,
|
|
'multipoint_login' => 1,
|
|
'is_paw' => 1,
|
|
'iam_subject' => $subject,
|
|
'iam_managed' => 1,
|
|
'iam_revoked_at' => 0,
|
|
]);
|
|
foreach ($this->defaultRoleIds() as $roleId) {
|
|
AdminRole::create(['admin_id' => $admin->id, 'role_id' => $roleId]);
|
|
}
|
|
} else {
|
|
if ((int) ($admin->disable ?? 0) === 1 && (int) ($admin->iam_revoked_at ?? 0) === 0) {
|
|
throw new RuntimeException('本地管理员账号已被平台禁用');
|
|
}
|
|
$admin->iam_subject = $subject;
|
|
$admin->iam_managed = 1;
|
|
if ((int) ($admin->iam_revoked_at ?? 0) > 0) {
|
|
$admin->disable = 0;
|
|
$admin->iam_revoked_at = 0;
|
|
}
|
|
$admin->save();
|
|
}
|
|
$admin->login_time = time();
|
|
$admin->login_ip = request()->ip();
|
|
$admin->save();
|
|
Db::commit();
|
|
} catch (\Throwable $error) {
|
|
Db::rollback();
|
|
throw $error;
|
|
}
|
|
|
|
$client->bindAccount($subject, (int) $admin->id);
|
|
$adminInfo = AdminTokenService::setToken($admin->id, $terminal, $admin->multipoint_login);
|
|
if (!is_array($adminInfo) || empty($adminInfo['token'])) {
|
|
throw new RuntimeException('本地管理员会话创建失败');
|
|
}
|
|
$avatar = $admin->avatar ?: Config::get('project.default_image.admin_avatar');
|
|
return [
|
|
'name' => $adminInfo['name'],
|
|
'avatar' => FileService::getFileUrl($avatar),
|
|
'role_name' => $adminInfo['role_name'],
|
|
'token' => $adminInfo['token'],
|
|
'is_paw' => 1,
|
|
'need_bind_work_wechat' => LoginLogic::adminMustBindWorkWechat([
|
|
'root' => (int) ($admin->root ?? 0),
|
|
'work_wechat_userid' => (string) ($admin->work_wechat_userid ?? ''),
|
|
]),
|
|
];
|
|
}
|
|
|
|
public static function stableAccount(string $subject): string
|
|
{
|
|
return 'iam_' . substr(hash('sha256', $subject), 0, 20);
|
|
}
|
|
|
|
/** @return int[] */
|
|
private function defaultRoleIds(): array
|
|
{
|
|
$values = explode(',', (string) ($this->config['default_role_ids'] ?? ''));
|
|
$roles = array_values(array_unique(array_filter(
|
|
array_map('intval', $values),
|
|
static fn (int $value): bool => $value > 0
|
|
)));
|
|
sort($roles, SORT_NUMERIC);
|
|
return $roles;
|
|
}
|
|
}
|