Files
ulthon_admin/extend/base/admin/controller/system/McpKeyBase.php

262 lines
9.7 KiB
PHP
Raw 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 base\admin\controller\system;
use app\admin\model\SystemAdmin;
use app\admin\model\SystemMcpKeyNode;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnotation;
use app\admin\service\NodeService;
use app\common\controller\AdminController;
use app\common\service\AuthService;
use think\App;
/**
* Class McpKeyBase.
* @ControllerAnnotation(title="MCP密钥管理")
*/
class McpKeyBase extends AdminController
{
use \app\admin\traits\Curd;
protected $sort = [
'id' => 'desc',
];
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new \app\admin\model\SystemMcpKey();
$this->assign('select_list_status', $this->model::SELECT_LIST_STATUS, true);
// 行内修改仅允许 statuskey/key_prefix/bind_admin_id 为敏感字段,一律排除
$this->allowModifyFields = [
'status',
];
}
/**
* @NodeAnotation(title="列表")
*/
public function index()
{
if ($this->request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames();
$count = $this->model
->where($where)
->group($group)
->count();
$list = $this->model
->where($where)
->page($page, $limit)
->order($this->sort)
->group($group)
->select();
// 附加创建者用户名:不用 withJoinsystem_admin 与主表有 id/status 等同名字段会歧义),
// 一次 IN 查询建立 id => username 映射后回填,避免 N+1。
$adminIds = [];
foreach ($list as $vo) {
!empty($vo->bind_admin_id) && $adminIds[] = $vo->bind_admin_id;
}
$adminNames = empty($adminIds) ? [] : SystemAdmin::whereIn('id', array_unique($adminIds))->column('username', 'id');
foreach ($list as $vo) {
$vo->bind_admin_username = $adminNames[$vo->bind_admin_id] ?? '';
}
$data = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $list,
];
return json($data);
}
return $this->fetch();
}
/**
* @NodeAnotation(title="添加")
*/
public function add()
{
if ($this->request->isPost()) {
$post = $this->request->post();
// 表单仅提交 title/status/remark密钥三要素与统计字段一律服务端生成忽略客户端传入
unset($post['id'], $post['key'], $post['key_prefix'], $post['bind_admin_id'], $post['use_num'], $post['last_use_time']);
$rule = [
'title|密钥名称' => 'require|max:50',
];
$this->validate($post, $rule);
// 明文密钥仅此一次出现在内存与响应中:不落库、不写日志
$secretKey = 'sk-mcp-' . bin2hex(random_bytes(24));
$post['key'] = hash('sha256', $secretKey);
$post['key_prefix'] = substr($secretKey, 0, 16);
$post['bind_admin_id'] = $this->getAdminId();
try {
$save = $this->model->save($post);
} catch (\Exception $e) {
$this->error('保存失败:' . $e->getMessage());
}
if ($save) {
// data 携带明文密钥,前端弹层展示一次后即丢弃
$this->success('保存成功', ['secret_key' => $secretKey]);
}
$this->error('保存失败');
}
return $this->fetch();
}
/**
* @NodeAnotation(title="编辑")
*/
public function edit($id)
{
$row = $this->model->find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
// 白名单:仅允许改 title/status/remarkkey/key_prefix/bind_admin_id 及统计字段不可改
$post = array_intersect_key($post, array_flip(['title', 'status', 'remark']));
$rule = [
'title|密钥名称' => 'require|max:50',
];
$this->validate($post, $rule);
try {
$save = $row->save($post);
} catch (\Exception $e) {
$this->error('保存失败:' . $e->getMessage());
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$this->assign('row', $row);
return $this->fetch();
}
/**
* 授权页:勾选创建者当前权限的子集给密钥.
* @NodeAnotation(title="授权")
*/
public function authorize($id)
{
$row = $this->model->find($id);
empty($row) && $this->error('数据不存在');
// 全量动作节点来自 getNodeTree 展开全部 type=2 节点
$nodeService = new NodeService();
$module_list = $nodeService->getNodeTree();
// 创建者当前节点集合:与 checkNode 完全一致的判定链(含超管直通/auth_on/动态黑名单)
$authService = new AuthService($row->bind_admin_id);
$allActionNodes = [];
foreach ($module_list as $module) {
foreach ($module['children'] as $controller) {
foreach ($controller['children'] as $action) {
$allActionNodes[] = $action['node'];
}
}
}
$creatorAllowedNodes = $authService->getAdminAllowedNodes($allActionNodes, $row->bind_admin_id);
// 已选节点 = 库中记录 ∩ 创建者当前集合(防漂移:创建者权限被回收后旧勾选自动失效)
$checkedNodeList = (new SystemMcpKeyNode())
->where('key_id', $id)
->column('node');
$checkedNodeList = array_values(array_intersect($checkedNodeList, $creatorAllowedNodes));
// 树过滤:动作节点不在创建者集合内整级剔除;无动作节点的控制器、无控制器的模块不显示
foreach ($module_list as $module_key => $module) {
foreach ($module['children'] as $controllerKey => $controller) {
foreach ($controller['children'] as $actionKey => $action) {
if (!in_array($action['node'], $creatorAllowedNodes)) {
unset($module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]);
continue;
}
$checked = in_array($action['node'], $checkedNodeList);
$checked_string = $checked ? 'checked' : '';
$disabled_string = '';
if (!$action['auth']) {
// 免鉴权节点创建者恒有权限,照 auth/authorize 惯例固定勾选并禁用
$checked_string = 'checked';
$disabled_string = 'disabled';
}
$module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]['checked'] = $checked_string;
$module_list[$module_key]['children'][$controllerKey]['children'][$actionKey]['disabled'] = $disabled_string;
}
if (empty($module_list[$module_key]['children'][$controllerKey]['children'])) {
unset($module_list[$module_key]['children'][$controllerKey]);
}
}
if (empty($module_list[$module_key]['children'])) {
unset($module_list[$module_key]);
}
}
$this->assign('row', $row);
$this->assign('checked_node_list', $checkedNodeList);
$this->assign('module_list', array_values($module_list));
return $this->fetch();
}
/**
* 授权保存:先删后插,服务端强校验防越权.
* @NodeAnotation(title="授权保存")
*/
public function saveAuthorize()
{
$this->checkPostRequest();
$id = $this->request->post('id');
$node = (array) $this->request->post('node', []);
$row = $this->model->find($id);
empty($row) && $this->error('数据不存在');
// 服务端强校验:每个提交节点必须 ∈ 创建者当前节点集合(重新计算,防权限已变)
$nodeService = new NodeService();
$allActionNodes = [];
foreach ($nodeService->getNodeTree() as $module) {
foreach ($module['children'] as $controller) {
foreach ($controller['children'] as $action) {
$allActionNodes[] = $action['node'];
}
}
}
$authService = new AuthService($row->bind_admin_id);
$creatorAllowedNodes = $authService->getAdminAllowedNodes($allActionNodes, $row->bind_admin_id);
foreach ($node as $vo) {
if (!in_array($authService->parseNodeStr($vo), $creatorAllowedNodes)) {
$this->error('包含无权限节点');
}
}
try {
$keyNode = new SystemMcpKeyNode();
$keyNode->where('key_id', $id)->delete();
if (!empty($node)) {
$saveAll = [];
foreach ($node as $vo) {
$saveAll[] = [
'key_id' => $id,
'node' => $authService->parseNodeStr($vo),
];
}
$keyNode->saveAll($saveAll);
}
} catch (\Exception $e) {
$this->error('保存失败');
}
$this->success('保存成功');
}
}