BastionSSO/app/Services/ServerUserManagementClient.php

233 lines
8.0 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\ServerResource;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Validation\ValidationException;
class ServerUserManagementClient
{
public function users(ServerResource $server): array
{
return $this->request($server, 'get', '/users')->json();
}
public function user(ServerResource $server, string $username): array
{
return $this->request($server, 'get', '/users/'.$this->encodePath($username))->json();
}
public function userExists(ServerResource $server, string $username): bool
{
try {
$this->user($server, $username);
return true;
} catch (ValidationException $exception) {
$errors = $exception->errors();
$messages = collect($errors)->flatten()->map(fn ($message): string => (string) $message);
if ($messages->contains(fn (string $message): bool => str_contains(mb_strtolower($message), 'not found') || str_contains($message, '不存在'))) {
return false;
}
throw $exception;
}
}
public function createUser(ServerResource $server, array $payload): array
{
return $this->request($server, 'post', '/users', $payload)->json();
}
public function deleteUser(ServerResource $server, string $username): array
{
return $this->request($server, 'delete', '/users/'.$this->encodePath($username))->json();
}
public function updatePassword(ServerResource $server, string $username, string $passwordHash): array
{
return $this->request($server, 'patch', '/users/'.$this->encodePath($username).'/password', [
'password_hash' => $passwordHash,
])->json();
}
public function userEnvironment(ServerResource $server, string $username): array
{
return $this->request($server, 'get', '/users/'.$this->encodePath($username).'/environment')->json();
}
public function updateUserEnvironment(ServerResource $server, string $username, string $content): array
{
return $this->request($server, 'put', '/users/'.$this->encodePath($username).'/environment', [
'content' => $content,
])->json();
}
public function updateAllUserEnvironments(ServerResource $server, string $content): array
{
return $this->request($server, 'put', '/users/environment', [
'content' => $content,
])->json();
}
public function groups(ServerResource $server): array
{
return $this->request($server, 'get', '/groups')->json();
}
public function createGroup(ServerResource $server, string $groupname): array
{
return $this->request($server, 'post', '/groups', ['groupname' => $groupname])->json();
}
public function deleteGroup(ServerResource $server, string $groupname): array
{
return $this->request($server, 'delete', '/groups/'.$this->encodePath($groupname))->json();
}
public function userGroups(ServerResource $server, string $username): array
{
return $this->request($server, 'get', '/users/'.$this->encodePath($username).'/groups')->json();
}
public function syncUserGroups(ServerResource $server, string $username, array $groups, string $mode = 'replace'): array
{
return $this->request($server, 'post', '/users/'.$this->encodePath($username).'/groups', [
'groups' => array_values($groups),
'mode' => $mode,
])->json();
}
public function removeUserGroups(ServerResource $server, string $username, array $groups): array
{
return $this->request($server, 'delete', '/users/'.$this->encodePath($username).'/groups', [
'groups' => array_values($groups),
'mode' => 'append',
])->json();
}
private function request(ServerResource $server, string $method, string $path, array $payload = [])
{
$target = $this->resolveServer($server);
$baseUrl = rtrim((string) $target->user_api_base_url, '/');
$token = trim((string) $target->user_api_token);
if ($baseUrl === '' || $token === '') {
throw ValidationException::withMessages([
'server' => ['该服务器未配置用户管理 API 地址或密钥。'],
]);
}
try {
$pending = Http::baseUrl($baseUrl)
->acceptJson()
->asJson()
->timeout((int) config('services.server_user_management.timeout', 15))
->connectTimeout((int) config('services.server_user_management.connect_timeout', 5))
->retry(2, 200, throw: false)
->withToken($token)
->withOptions([
'verify' => (bool) config('services.server_user_management.verify_ssl', false),
]);
$response = match ($method) {
'post' => $pending->post($path, $payload),
'put' => $pending->put($path, $payload),
'patch' => $pending->patch($path, $payload),
'delete' => empty($payload) ? $pending->delete($path) : $pending->send('DELETE', $path, ['json' => $payload]),
default => $pending->get($path),
};
} catch (ConnectionException|RequestException $exception) {
throw ValidationException::withMessages([
'server' => ['服务器用户管理 API 调用失败:'.$exception->getMessage()],
]);
}
if (! $response->successful()) {
$message = $this->errorMessage($response->json());
throw ValidationException::withMessages([
'server' => [$message],
]);
}
return $response;
}
private function resolveServer(ServerResource $server): ServerResource
{
if (! $server->parent_id) {
return $server;
}
return $server->parent()->firstOrFail();
}
private function encodePath(string $value): string
{
return rawurlencode($value);
}
private function errorMessage(mixed $payload): string
{
if (! is_array($payload)) {
return '服务器用户管理 API 返回异常';
}
$message = data_get($payload, 'message')
?: data_get($payload, 'detail.message')
?: data_get($payload, 'detail');
if (is_string($message) && $message !== '') {
return $message;
}
if (is_array($message)) {
$messages = collect($message)
->map(function (mixed $item): string {
if (is_string($item)) {
return $item;
}
if (is_array($item)) {
return (string) (data_get($item, 'message')
?: data_get($item, 'msg')
?: data_get($item, 'detail')
?: json_encode($item, JSON_UNESCAPED_UNICODE));
}
return '';
})
->filter()
->values();
if ($messages->isNotEmpty()) {
return $messages->join('');
}
}
$failedUsers = collect(data_get($payload, 'failed_users', []))
->map(function (mixed $item): string {
if (! is_array($item)) {
return '';
}
$username = (string) data_get($item, 'username', '');
$error = (string) (data_get($item, 'message') ?: data_get($item, 'code', ''));
return trim($username.($error !== '' ? ': '.$error : ''));
})
->filter()
->values();
if ($failedUsers->isNotEmpty()) {
return '部分服务器用户环境变量设置失败:'.$failedUsers->join('');
}
return '服务器用户管理 API 返回异常';
}
}