96 lines
3.6 KiB
PHP
96 lines
3.6 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\mcp\controller;
|
||
|
||
use app\BaseController;
|
||
use app\mcp\service\AuditLogger;
|
||
use app\mcp\service\Catalog;
|
||
use app\mcp\service\GrantService;
|
||
use app\mcp\service\Guard;
|
||
use app\mcp\service\McpConfig;
|
||
use app\mcp\service\McpException;
|
||
use think\Response;
|
||
|
||
/**
|
||
* AI 授权接口(供行知等客户端调用):
|
||
* POST /mcp/auth/grant 账号 + 密码 → 只读令牌(密码只用于本次校验,不保存)
|
||
* POST /mcp/auth/revoke 撤销当前令牌(Bearer)
|
||
* GET /mcp/auth/whoami 当前令牌对应的账号(Bearer)
|
||
*/
|
||
class AuthController extends BaseController
|
||
{
|
||
public function grant(): Response
|
||
{
|
||
$blocked = $this->blocked('POST');
|
||
if ($blocked) {
|
||
return $blocked;
|
||
}
|
||
$input = json_decode((string) $this->request->getInput(), true);
|
||
if (!is_array($input)) {
|
||
$input = $this->request->post();
|
||
}
|
||
$ip = $this->request->ip();
|
||
try {
|
||
$data = GrantService::issue($input, $ip);
|
||
AuditLogger::log(['grant_id' => $data['grant_id'], 'admin_id' => $data['admin']['id'], 'tool' => 'auth.grant',
|
||
'arguments' => ['client' => $input['client'] ?? '', 'client_instance' => $input['client_instance'] ?? ''], 'status' => 'ok', 'ip' => $ip]);
|
||
return Guard::envelope(1, '授权成功', $data);
|
||
} catch (McpException $e) {
|
||
AuditLogger::log(['tool' => 'auth.grant', 'arguments' => ['account' => (string) ($input['account'] ?? '')], 'status' => 'denied',
|
||
'message' => $e->reason, 'ip' => $ip]);
|
||
return Guard::envelope(0, $e->getMessage(), ['reason' => $e->reason], $e->httpStatus === 401 ? 200 : $e->httpStatus, 1);
|
||
}
|
||
}
|
||
|
||
public function revoke(): Response
|
||
{
|
||
$blocked = $this->blocked('POST');
|
||
if ($blocked) {
|
||
return $blocked;
|
||
}
|
||
try {
|
||
$identity = GrantService::authenticate($this->request);
|
||
} catch (McpException $e) {
|
||
return Guard::envelope(-1, $e->getMessage(), ['reason' => $e->reason], 401);
|
||
}
|
||
GrantService::close((int) $identity->grant['id'], GrantService::STATUS_REVOKED, 'client_revoke');
|
||
AuditLogger::log(['grant_id' => $identity->grant['id'], 'admin_id' => $identity->adminId, 'tool' => 'auth.revoke', 'status' => 'ok', 'ip' => $this->request->ip()]);
|
||
return Guard::envelope(1, '已撤销');
|
||
}
|
||
|
||
public function whoami(): Response
|
||
{
|
||
$blocked = $this->blocked('GET');
|
||
if ($blocked) {
|
||
return $blocked;
|
||
}
|
||
try {
|
||
$identity = GrantService::authenticate($this->request);
|
||
} catch (McpException $e) {
|
||
return Guard::envelope(-1, $e->getMessage(), ['reason' => $e->reason], 401);
|
||
}
|
||
return Guard::envelope(1, '', [
|
||
'admin' => $identity->publicProfile(),
|
||
'grant' => GrantService::publicGrant($identity->grant),
|
||
'data_scope' => $identity->dataScopeText(),
|
||
'resources' => ['open' => count(Catalog::openFor($identity))],
|
||
]);
|
||
}
|
||
|
||
private function blocked(string $method): ?Response
|
||
{
|
||
if (!McpConfig::enabled()) {
|
||
return Guard::envelope(0, 'AI 助手接口未启用', ['reason' => 'feature_disabled'], 503, 1);
|
||
}
|
||
if ($this->request->method(true) !== $method) {
|
||
return response('', 405)->header(['Allow' => $method]);
|
||
}
|
||
$guard = Guard::check($this->request);
|
||
if ($guard !== null) {
|
||
return Guard::envelope(0, $guard[1], ['reason' => $guard[2]], 200, 1);
|
||
}
|
||
return null;
|
||
}
|
||
}
|