BastionSSO/app/Http/Controllers/Api/OpsClientController.php

312 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\AccessLog;
use App\Models\OpsProtocol;
use App\Models\OpsSoftware;
use App\Models\User;
use App\Models\UserOpsSoftwarePreference;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
#[Apidoc\Title('运维协议与软件管理')]
class OpsClientController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
$this->middleware('permission:platform.servers.manage,api')->only([
'storeProtocol',
'updateProtocol',
'destroyProtocol',
'storeSoftware',
'updateSoftware',
'destroySoftware',
]);
}
#[Apidoc\Title('运维协议与软件列表'), Apidoc\Method('GET'), Apidoc\Url('/ops-clients/meta')]
public function meta(Request $request): JsonResponse
{
/** @var User|null $user */
$user = auth('api')->user();
$isManager = (bool) ($user?->can('platform.servers.manage'));
$protocolQuery = OpsProtocol::query()->orderBy('sort')->orderBy('id');
if (! $isManager) {
$protocolQuery->where('is_active', true);
}
$protocols = $protocolQuery->with(['softwares' => function ($query) use ($isManager) {
if (! $isManager) {
$query->where('is_active', true);
}
}])->get();
$preferences = UserOpsSoftwarePreference::query()
->where('user_id', $user?->id)
->get()
->mapWithKeys(fn (UserOpsSoftwarePreference $item): array => [
(string) $item->ops_protocol_id => $item->ops_software_id,
]);
return response()->json([
'code' => 0,
'message' => 'ok',
'data' => [
'protocols' => $protocols,
'preferences' => $preferences,
'is_manager' => $isManager,
],
]);
}
#[Apidoc\Title('新增运维协议'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols')]
public function storeProtocol(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name'],
'bastion_protocol_id' => ['required', 'integer', 'min:1'],
'description' => ['nullable', 'string', 'max:255'],
'sort' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$protocol = OpsProtocol::query()->create($validated);
$this->auditLog($request, 'ops_protocol_create', ['metadata' => ['ops_protocol_id' => $protocol->id]]);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $protocol], 201);
}
#[Apidoc\Title('修改运维协议'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/protocols/{id}')]
public function updateProtocol(Request $request, int $id): JsonResponse
{
$protocol = OpsProtocol::query()->findOrFail($id);
$validated = $request->validate([
'name' => ['required', 'string', 'max:64', 'unique:ops_protocols,name,'.$protocol->id],
'bastion_protocol_id' => ['required', 'integer', 'min:1'],
'description' => ['nullable', 'string', 'max:255'],
'sort' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$protocol->update($validated);
$this->auditLog($request, 'ops_protocol_update', ['metadata' => ['ops_protocol_id' => $protocol->id]]);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $protocol]);
}
#[Apidoc\Title('删除运维协议'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/protocols/{id}')]
public function destroyProtocol(Request $request, int $id): JsonResponse
{
$protocol = OpsProtocol::query()->findOrFail($id);
$this->auditLog($request, 'ops_protocol_delete', ['metadata' => ['ops_protocol_id' => $protocol->id]]);
$protocol->delete();
return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
}
#[Apidoc\Title('新增运维软件'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/protocols/{id}/softwares')]
public function storeSoftware(Request $request, int $id): JsonResponse
{
OpsProtocol::query()->findOrFail($id);
$validated = $request->validate([
'name' => ['required', 'string', 'max:100'],
'client_path' => ['nullable', 'string', 'max:255'],
'sort' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$exists = OpsSoftware::query()
->where('ops_protocol_id', $id)
->where('name', $validated['name'])
->exists();
if ($exists) {
throw ValidationException::withMessages([
'name' => ['同一协议下软件名称不能重复。'],
]);
}
$software = OpsSoftware::query()->create([
...$validated,
'ops_protocol_id' => $id,
]);
$this->auditLog($request, 'ops_software_create', ['metadata' => ['ops_software_id' => $software->id, 'ops_protocol_id' => $id]]);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $software], 201);
}
#[Apidoc\Title('修改运维软件'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/softwares/{id}')]
public function updateSoftware(Request $request, int $id): JsonResponse
{
$software = OpsSoftware::query()->findOrFail($id);
$validated = $request->validate([
'name' => ['required', 'string', 'max:100'],
'client_path' => ['nullable', 'string', 'max:255'],
'sort' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$exists = OpsSoftware::query()
->where('ops_protocol_id', $software->ops_protocol_id)
->where('name', $validated['name'])
->where('id', '!=', $software->id)
->exists();
if ($exists) {
throw ValidationException::withMessages([
'name' => ['同一协议下软件名称不能重复。'],
]);
}
$software->update($validated);
$this->auditLog($request, 'ops_software_update', ['metadata' => ['ops_software_id' => $software->id]]);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $software]);
}
#[Apidoc\Title('删除运维软件'), Apidoc\Method('DELETE'), Apidoc\Url('/ops-clients/softwares/{id}')]
public function destroySoftware(Request $request, int $id): JsonResponse
{
$software = OpsSoftware::query()->findOrFail($id);
$this->auditLog($request, 'ops_software_delete', ['metadata' => ['ops_software_id' => $software->id]]);
$software->delete();
return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
}
#[Apidoc\Title('保存我的运维软件偏好'), Apidoc\Method('PUT'), Apidoc\Url('/ops-clients/preferences')]
public function savePreferences(Request $request): JsonResponse
{
$validated = $request->validate([
'items' => ['required', 'array'],
'items.*.protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'],
'items.*.software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'],
]);
/** @var User $user */
$user = auth('api')->user();
foreach ($validated['items'] as $item) {
$protocolId = (int) $item['protocol_id'];
$softwareId = isset($item['software_id']) ? (int) $item['software_id'] : null;
if ($softwareId !== null) {
$software = OpsSoftware::query()->findOrFail($softwareId);
if ((int) $software->ops_protocol_id !== $protocolId) {
throw ValidationException::withMessages([
'items' => ['软件与协议不匹配。'],
]);
}
UserOpsSoftwarePreference::query()->updateOrCreate(
['user_id' => $user->id, 'ops_protocol_id' => $protocolId],
['ops_software_id' => $softwareId]
);
} else {
UserOpsSoftwarePreference::query()
->where('user_id', $user->id)
->where('ops_protocol_id', $protocolId)
->delete();
}
}
$this->auditLog($request, 'ops_preference_update', ['metadata' => ['user_id' => $user->id]]);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]);
}
#[Apidoc\Title('生成运维连接'), Apidoc\Method('POST'), Apidoc\Url('/ops-clients/link')]
public function generateLink(Request $request): JsonResponse
{
$validated = $request->validate([
'protocol_id' => ['required', 'integer', 'exists:ops_protocols,id'],
'software_id' => ['nullable', 'integer', 'exists:ops_softwares,id'],
]);
/** @var User $user */
$user = auth('api')->user();
$protocol = OpsProtocol::query()->findOrFail((int) $validated['protocol_id']);
$softwareId = isset($validated['software_id']) ? (int) $validated['software_id'] : null;
if ($softwareId === null) {
$softwareId = (int) UserOpsSoftwarePreference::query()
->where('user_id', $user->id)
->where('ops_protocol_id', $protocol->id)
->value('ops_software_id');
}
$software = OpsSoftware::query()->find($softwareId ?: 0);
if (! $software || (int) $software->ops_protocol_id !== (int) $protocol->id) {
throw ValidationException::withMessages([
'software_id' => ['请先选择该协议对应的软件。'],
]);
}
$payload = [
'NODE_COMMON' => [
'Mode' => '1',
'IsGlobalSetting' => '0',
'IPv4' => (string) config('services.ops_client.ipv4', '172.16.1.2'),
'AssetIPv4' => (string) config('services.ops_client.asset_ipv4', '0.0.0.0'),
'Port' => '',
'AssetPort' => '',
'Protocol' => (string) $protocol->name,
'ClientName' => (string) $software->name,
'ClientPath' => (string) ($software->client_path ?? ''),
'Username' => '',
'SSOToken' => '',
'Title' => '',
],
'NODE_SFTP' => [
'NotUsed' => '1',
'Char-Set' => 'Utf=1',
],
];
$encoded = base64_encode((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$link = 'dasusm://'.$encoded;
UserOpsSoftwarePreference::query()->updateOrCreate(
['user_id' => $user->id, 'ops_protocol_id' => $protocol->id],
['ops_software_id' => $software->id]
);
AccessLog::query()->create([
'user_id' => $user->id,
'server_resource_id' => null,
'bastion_account_id' => null,
'protocol' => (string) $protocol->name,
'action' => 'ops_client_link',
'requested_at' => now(),
'metadata' => [
'ops_protocol_id' => $protocol->id,
'ops_software_id' => $software->id,
'ops_software_name' => $software->name,
],
]);
$this->auditLog($request, 'ops_client_link_generate', [
'metadata' => [
'ops_protocol_id' => $protocol->id,
'ops_software_id' => $software->id,
],
]);
return response()->json([
'code' => 0,
'message' => 'ok',
'data' => [
'link' => $link,
'encoded' => $encoded,
'payload' => $payload,
'protocol' => $protocol,
'software' => $software,
],
]);
}
}