| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- 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;
- /// <summary>
- /// S8-ACTION-PERMISSION-1:异常单动作的授权门。取代这些 Action 上原有的
- /// <see cref="S8PermissionAttribute"/>。
- ///
- /// <para><b>为什么要换掉</b>:<see cref="S8PermissionAttribute"/> 依赖平台的
- /// <c>GetOwnBtnPermList()</c>,而那条链<b>不校验角色的租户</b>,实测可被别家租户的角色
- /// 带进 <c>s8:exception:assign</c> 等能力(取证见 <see cref="S8TenantRoleResolver"/>)。
- /// 同一个问题不能有两个 authority,所以异常动作统一改由
- /// <see cref="IS8ExceptionActionAuthorizer"/> 回答;<see cref="S8PermissionAttribute"/>
- /// 继续负责配置面(<c>s8:config:*</c>),并同样补上租户交集。</para>
- ///
- /// <para><b>仍在 Action 之前返回 403</b>:与旧实现同一位置、同一文案,
- /// DB 零写入,不把权限拓扑回显给调用方。</para>
- /// </summary>
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public sealed class S8ExceptionActionAttribute : Attribute, IAsyncAuthorizationFilter
- {
- /// <summary>本 Action 要求的动作码,必须取自 <see cref="S8ExceptionActionCode"/>。</summary>
- 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<UserManager>();
- if (userManager is { SuperAdmin: true }) return;
- var authorizer = services.GetService<IS8ExceptionActionAuthorizer>();
- var scopeResolver = services.GetService<S8TrustedScopeResolver>();
- 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
- };
- }
|