using Admin.NET.Core;
using Admin.NET.Plugin.AiDOP.Const.S8;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
///
/// S8-ACTION-PERMISSION-1:异常单动作的授权门。取代这些 Action 上原有的
/// 。
///
/// 为什么要换掉: 依赖平台的
/// GetOwnBtnPermList(),而那条链不校验角色的租户,实测可被别家租户的角色
/// 带进 s8:exception:assign 等能力(取证见 )。
/// 同一个问题不能有两个 authority,所以异常动作统一改由
/// 回答;
/// 继续负责配置面(s8:config:*),并同样补上租户交集。
///
/// 仍在 Action 之前返回 403:与旧实现同一位置、同一文案,
/// DB 零写入,不把权限拓扑回显给调用方。
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class S8ExceptionActionAttribute : Attribute, IAsyncAuthorizationFilter
{
/// 本 Action 要求的动作码,必须取自 。
public string Code { get; }
public S8ExceptionActionAttribute(string code)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentException("S8 动作码不能为空", nameof(code));
if (!S8ExceptionActionCatalog.IsKnown(code))
throw new ArgumentException($"S8 动作码不在目录中:{code}", nameof(code));
Code = code;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (user?.Identity?.IsAuthenticated != true) return; // 401 交回平台管道
var services = context.HttpContext.RequestServices;
// 超管豁免:与 JwtHandler.CheckAuthorizeAsync / S8PermissionAttribute 同口径。
var userManager = services.GetService();
if (userManager is { SuperAdmin: true }) return;
var authorizer = services.GetService();
var scopeResolver = services.GetService();
if (authorizer == null || scopeResolver == null || userManager == null)
{
// 拿不到判定所需服务时按拒绝处理。绝不 fail-open ——
// 本模块的事故类型全部是「本不该放行却放行了」。
context.Result = Forbid();
return;
}
try
{
var scope = await scopeResolver.ResolveAsync();
var result = await authorizer.AuthorizeAsync(scope.TenantId, userManager.UserId, Code);
if (result.Allowed) return;
}
catch
{
// 作用域解析失败(如超管未选租户)同样按拒绝处理,语义与既有 400/403 边界一致。
}
context.Result = Forbid();
}
private static ObjectResult Forbid() => new(new { message = "没有权限执行该操作" })
{
StatusCode = StatusCodes.Status403Forbidden
};
}