feat(saas): implement marketplace and tenant onboarding

This commit is contained in:
2026-07-29 13:58:59 +08:00
parent 76606029e2
commit 6db200a2fc
145 changed files with 16862 additions and 94529 deletions

View File

@@ -8,6 +8,7 @@ namespace Tiku.Api.Security;
public sealed record EndpointAuthorizationMetadata(
string Realm,
string? Module,
IReadOnlyList<string> RequiredFeatures,
string? Permission,
CapabilityOperation Operation,
bool RequiresAllDataScope,
@@ -43,7 +44,20 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode
policies.Any(policy => policy.StartsWith("tenant", StringComparison.Ordinal))
? "tenant"
: "authenticated";
var module = permission is null ? null : ResolveModule(permission);
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)
@@ -55,9 +69,11 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode
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)
@@ -68,12 +84,6 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode
}
}
private static string ResolveModule(string permission)
{
var parts = permission.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permission.ToLowerInvariant();
}
private static bool IsSafeMethod(string method) =>
HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method);
}