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

@@ -76,7 +76,7 @@ internal sealed class CurrentAccessAuthorizationHandler(ICurrentAccessContext ac
internal sealed class TenantPermissionAuthorizationHandler(
ICurrentAccessContext accessContext,
ICapabilityAccessEvaluator capabilityAccessEvaluator,
IFeatureAccessService featureAccessService,
IHttpContextAccessor httpContextAccessor) :
AuthorizationHandler<TenantPermissionRequirement>
{
@@ -90,24 +90,18 @@ internal sealed class TenantPermissionAuthorizationHandler(
}
var access = await accessContext.GetAsync();
var moduleCode = ResolveModuleCode(requirement.PermissionCode);
var operation = IsSafeMethod(httpContextAccessor.HttpContext?.Request.Method)
? CapabilityOperation.Read
: CapabilityOperation.Write;
? FeatureAccessOperation.Read
: FeatureAccessOperation.Write;
if (access.TenantId is { } tenantId &&
access.HasTenantPermission(requirement.PermissionCode) &&
await capabilityAccessEvaluator.IsAllowedAsync(tenantId, moduleCode, operation))
(await featureAccessService.FilterPermissionCodesAsync(
tenantId, [requirement.PermissionCode], operation)).Contains(requirement.PermissionCode))
{
context.Succeed(requirement);
}
}
private static string ResolveModuleCode(string permissionCode)
{
var parts = permissionCode.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permissionCode.ToLowerInvariant();
}
private static bool IsSafeMethod(string? method) =>
method is not null &&
(HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method));
@@ -197,7 +191,7 @@ public static class AccessAuthorizationServiceCollectionExtensions
public static IServiceCollection AddTikuRbacAuthorization(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.TryAddScoped<ICapabilityAccessEvaluator, CompatibilityCapabilityAccessEvaluator>();
services.TryAddScoped<IFeatureAccessService, CompatibilityFeatureAccessService>();
services.AddScoped<IAuthorizationHandler, CurrentAccessAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, CurrentPlatformAccessAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, TenantPermissionAuthorizationHandler>();
@@ -241,6 +235,13 @@ public static class AccessAuthorizationServiceCollectionExtensions
new CurrentTenantMemberRequirement(),
new TenantPermissionRequirement(BackendPermissions.TenantContentManage),
new AllDataScopeRequirement()));
options.AddPolicy(
TikuPolicies.TenantAllDataScope,
policy => policy
.RequireAuthenticatedUser()
.AddRequirements(
new CurrentTenantMemberRequirement(),
new AllDataScopeRequirement()));
options.AddPolicy(
TikuPolicies.TenantCommerceOperateAllScope,
policy => policy
@@ -275,17 +276,21 @@ public static class AccessAuthorizationServiceCollectionExtensions
}
}
internal sealed class CompatibilityCapabilityAccessEvaluator : ICapabilityAccessEvaluator
internal sealed class CompatibilityFeatureAccessService : IFeatureAccessService
{
public Task<bool> IsAllowedAsync(
Guid tenantId,
string moduleCode,
CapabilityOperation operation,
CancellationToken cancellationToken = default) => Task.FromResult(true);
public Task<FeatureAccessDecision> EvaluateAsync(Guid tenantId, string featureCode, FeatureAccessOperation operation, CancellationToken cancellationToken = default) =>
Task.FromResult(new FeatureAccessDecision(true, null, featureCode, operation));
public Task<IReadOnlySet<string>> GetEnabledModulesAsync(
Guid tenantId,
CapabilityOperation operation = CapabilityOperation.Read,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlySet<string>>(new HashSet<string>(StringComparer.Ordinal));
public Task<IReadOnlySet<string>> GetEnabledFeaturesAsync(Guid tenantId, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlySet<string>>(new HashSet<string>(SaasFeatureCatalog.All, StringComparer.Ordinal));
public Task<IReadOnlySet<string>> FilterPermissionCodesAsync(Guid tenantId, IEnumerable<string> permissionCodes, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlySet<string>>(permissionCodes.ToHashSet(StringComparer.Ordinal));
public Task<IReadOnlyCollection<FeatureQuotaSnapshot>> GetQuotaSummaryAsync(Guid tenantId, CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyCollection<FeatureQuotaSnapshot>>([]);
public Task<bool> TryConsumeQuotaAsync(Guid tenantId, string metricCode, long amount, CancellationToken cancellationToken = default) => Task.FromResult(true);
public Task ReleaseQuotaAsync(Guid tenantId, string metricCode, long amount, CancellationToken cancellationToken = default) => Task.CompletedTask;
}