90 lines
4.4 KiB
C#
90 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
|
using Microsoft.AspNetCore.Mvc.Routing;
|
|
using Tiku.Application.Security;
|
|
|
|
namespace Tiku.Api.Security;
|
|
|
|
public sealed record EndpointAuthorizationMetadata(
|
|
string Realm,
|
|
string? Module,
|
|
IReadOnlyList<string> RequiredFeatures,
|
|
string? Permission,
|
|
CapabilityOperation Operation,
|
|
bool RequiresAllDataScope,
|
|
string AuditAction);
|
|
|
|
internal sealed class EndpointAuthorizationMetadataConvention : IApplicationModelConvention
|
|
{
|
|
public void Apply(ApplicationModel application)
|
|
{
|
|
foreach (var controller in application.Controllers)
|
|
{
|
|
foreach (var action in controller.Actions)
|
|
{
|
|
var anonymous = controller.Attributes.OfType<AllowAnonymousAttribute>().Any() ||
|
|
action.Attributes.OfType<AllowAnonymousAttribute>().Any();
|
|
if (anonymous)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var policies = controller.Attributes.OfType<AuthorizeAttribute>()
|
|
.Concat(action.Attributes.OfType<AuthorizeAttribute>())
|
|
.Select(attribute => attribute.Policy)
|
|
.Where(policy => !string.IsNullOrWhiteSpace(policy))
|
|
.Cast<string>()
|
|
.ToArray();
|
|
var permission = policies.FirstOrDefault(policy =>
|
|
BackendPermissions.Tenant.Contains(policy) || BackendPermissions.Platform.Contains(policy));
|
|
var realm = permission is not null && BackendPermissions.Platform.Contains(permission) ||
|
|
policies.Any(policy => policy.StartsWith("platform", StringComparison.Ordinal))
|
|
? "platform"
|
|
: permission is not null && BackendPermissions.Tenant.Contains(permission) ||
|
|
policies.Any(policy => policy.StartsWith("tenant", StringComparison.Ordinal))
|
|
? "tenant"
|
|
: "authenticated";
|
|
var module = permission is null ? null : PermissionModuleCatalog.ResolvePermissionModuleCode(permission);
|
|
var requiredFeatures = controller.Attributes.OfType<RequireSaasFeatureAttribute>()
|
|
.Concat(action.Attributes.OfType<RequireSaasFeatureAttribute>())
|
|
.Select(attribute => attribute.FeatureCode)
|
|
.Concat(module is not null &&
|
|
PermissionModuleCatalog.RequiredFeatures.TryGetValue(module, out var moduleFeature) &&
|
|
moduleFeature is not null
|
|
? [moduleFeature]
|
|
: [])
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Order(StringComparer.Ordinal)
|
|
.Concat(action.Attributes.OfType<RequireSaasFeatureFromRouteAttribute>()
|
|
.Select(attribute => $"route:{attribute.RouteValueName}"))
|
|
.ToArray();
|
|
var httpMethods = action.Attributes.OfType<HttpMethodAttribute>()
|
|
.SelectMany(attribute => attribute.HttpMethods)
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray();
|
|
var operation = httpMethods.All(IsSafeMethod)
|
|
? CapabilityOperation.Read
|
|
: CapabilityOperation.Write;
|
|
var route = $"{controller.ControllerName}.{action.ActionName}";
|
|
var metadata = new EndpointAuthorizationMetadata(
|
|
realm,
|
|
module,
|
|
requiredFeatures,
|
|
permission,
|
|
operation,
|
|
policies.Contains(TikuPolicies.TenantContentManageAllScope, StringComparer.Ordinal) ||
|
|
policies.Contains(TikuPolicies.TenantAllDataScope, StringComparer.Ordinal) ||
|
|
policies.Contains(TikuPolicies.TenantCommerceOperateAllScope, StringComparer.Ordinal),
|
|
$"{string.Join(',', httpMethods.Order(StringComparer.Ordinal))}:{route}");
|
|
foreach (var selector in action.Selectors)
|
|
{
|
|
selector.EndpointMetadata.Add(metadata);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsSafeMethod(string method) =>
|
|
HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method);
|
|
}
|