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 }; }