138 lines
5.5 KiB
C#
138 lines
5.5 KiB
C#
using System.Security.Claims;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using MiGu.Server.Auth;
|
||
using MiGu.Server.Configs;
|
||
|
||
namespace MiGu.Server.Controllers;
|
||
|
||
/// <summary>
|
||
/// RBAC 管理端:用户 / 角色 / 权限页面分配。整个控制器要求 <c>RbacAdmin</c> 策略
|
||
/// (JWT 的 ops claim 含 <c>*</c> 或 <c>auth.manage</c>),即只有「超级管理员」类账号可访问。
|
||
///
|
||
/// 对应前端「平台配置中心 → 权限与角色」页(/admin/config/auth)。
|
||
/// </summary>
|
||
[ApiController]
|
||
[Authorize(Policy = "RbacAdmin")]
|
||
[Route("api/rbac")]
|
||
public class RbacController : ControllerBase
|
||
{
|
||
public sealed record OpDef(string Code, string Label);
|
||
public sealed record WidgetDef(string Id, string Label);
|
||
|
||
/// <summary>可分配的操作码候选(管理端配置角色时下拉/勾选用)。</summary>
|
||
private static readonly OpDef[] KnownOps =
|
||
{
|
||
new("*", "全部操作(通配)"),
|
||
new("ops.car.pause", "车辆 · 暂停"),
|
||
new("ops.car.resume", "车辆 · 恢复"),
|
||
new("ops.car.gohome", "车辆 · 回库"),
|
||
new("ops.car.resetSession", "车辆 · 重置会话"),
|
||
new("ops.car.manualCharge", "车辆 · 手动充电"),
|
||
new("ops.task.pause", "任务 · 暂停"),
|
||
new("ops.task.cancel", "任务 · 取消"),
|
||
new("ops.task.reassign", "任务 · 改派"),
|
||
new("ops.task.boostPriority", "任务 · 提升优先级"),
|
||
new("monitor.note.write", "监控 · 写运营备注"),
|
||
new("auth.manage", "系统 · 权限与角色管理"),
|
||
};
|
||
|
||
/// <summary>可配置可见性的控件候选。</summary>
|
||
private static readonly WidgetDef[] KnownWidgets =
|
||
{
|
||
new("MapEditor", "地图编辑器"),
|
||
new("CadToolbar", "CAD 工具栏"),
|
||
new("CarPanel", "车辆面板"),
|
||
new("MissionEditor", "任务编辑器"),
|
||
new("OpsActionPanel", "运维操作面板"),
|
||
new("ConfigCenter", "配置中心"),
|
||
};
|
||
|
||
private readonly RbacStore _store;
|
||
private readonly ILogger<RbacController> _log;
|
||
|
||
public RbacController(RbacStore store, ILogger<RbacController> log)
|
||
{
|
||
_store = store;
|
||
_log = log;
|
||
}
|
||
|
||
/// <summary>权限「字典」:页面清单 + 可选操作码 + 可选控件 + scope 选项。前端角色编辑器据此渲染勾选项。</summary>
|
||
[HttpGet("catalog")]
|
||
public IActionResult Catalog() => Ok(new
|
||
{
|
||
pages = PageCatalog.All,
|
||
ops = KnownOps,
|
||
widgets = KnownWidgets,
|
||
scopes = new[]
|
||
{
|
||
new { value = PageCatalog.ScopePlatform, label = "管理端 (Platform)" },
|
||
new { value = PageCatalog.ScopeMonitor, label = "运营端 (RCSMonitor)" },
|
||
new { value = PageCatalog.Wildcard, label = "通用 (全部域)" },
|
||
}
|
||
});
|
||
|
||
// ───────────────────────── 角色 ─────────────────────────
|
||
|
||
[HttpGet("roles")]
|
||
public IActionResult ListRoles() => Ok(_store.ListRoles());
|
||
|
||
[HttpPost("roles")]
|
||
public IActionResult CreateRole([FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.CreateRole(req)));
|
||
|
||
[HttpPut("roles/{id}")]
|
||
public IActionResult UpdateRole(string id, [FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.UpdateRole(id, req)));
|
||
|
||
[HttpDelete("roles/{id}")]
|
||
public IActionResult DeleteRole(string id) => Guard(() =>
|
||
{
|
||
_store.DeleteRole(id);
|
||
return Ok(new { ok = true });
|
||
});
|
||
|
||
// ───────────────────────── 用户 ─────────────────────────
|
||
|
||
[HttpGet("users")]
|
||
public IActionResult ListUsers() => Ok(_store.ListUsers());
|
||
|
||
[HttpPost("users")]
|
||
public IActionResult CreateUser([FromBody] CreateUserRequest req) => Guard(() => Ok(_store.CreateUser(req)));
|
||
|
||
[HttpPut("users/{id}")]
|
||
public IActionResult UpdateUser(string id, [FromBody] UpdateUserRequest req) => Guard(() =>
|
||
{
|
||
// 自我保护:禁止把当前登录账号自己停用,避免管理员把自己锁在门外。
|
||
if (id == CurrentUserId() && req.Enabled == false)
|
||
return (IActionResult)BadRequest(new { message = "不能停用当前登录的账号" });
|
||
return Ok(_store.UpdateUser(id, req));
|
||
});
|
||
|
||
[HttpPut("users/{id}/password")]
|
||
public IActionResult SetPassword(string id, [FromBody] SetPasswordRequest req) => Guard(() =>
|
||
{
|
||
_store.SetPassword(id, req.Password);
|
||
return Ok(new { ok = true });
|
||
});
|
||
|
||
[HttpDelete("users/{id}")]
|
||
public IActionResult DeleteUser(string id) => Guard(() =>
|
||
{
|
||
if (id == CurrentUserId())
|
||
return (IActionResult)BadRequest(new { message = "不能删除当前登录的账号" });
|
||
_store.DeleteUser(id);
|
||
return Ok(new { ok = true });
|
||
});
|
||
|
||
// ───────────────────────── 工具 ─────────────────────────
|
||
|
||
/// <summary>统一把 <see cref="RbacException"/> 翻译成 400 + message,其余异常向上抛。</summary>
|
||
private IActionResult Guard(Func<IActionResult> action)
|
||
{
|
||
try { return action(); }
|
||
catch (RbacException ex) { return BadRequest(new { message = ex.Message }); }
|
||
}
|
||
|
||
private string? CurrentUserId() =>
|
||
User.FindFirstValue("sub") ?? User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||
}
|