feat(security): add distributed authorization foundation

This commit is contained in:
2026-07-29 10:40:10 +08:00
parent c7f9a4e3c9
commit df88fa19cb
76 changed files with 22020 additions and 88 deletions

View File

@@ -10,6 +10,7 @@ using Tiku.Domain.Platform;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
using Tiku.Infrastructure.Messaging;
namespace Tiku.Infrastructure.PlatformAdmin;
@@ -152,6 +153,11 @@ internal sealed class PlatformAdminService(
Metadata = JsonObjectOrDefault(command.Metadata)
};
dbContext.Tenants.Add(tenant);
dbContext.TenantAuthPolicies.Add(new TenantAuthPolicy
{
TenantId = tenant.Id,
AllowExternalStudentSelfRegistration = false
});
AddAudit(dbContext, actor, "platform.tenant.created", tenant.Id, new { tenant.Slug, tenant.Name, tenant.Status, tenant.BillingStatus });
await dbContext.SaveChangesAsync(cancellationToken);
return ToTenantItem(tenant, 0, null);
@@ -164,7 +170,7 @@ internal sealed class PlatformAdminService(
CancellationToken cancellationToken = default)
{
await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
return await ExecuteSystemAsync("platform tenant status update", async dbContext =>
return await ExecuteSystemAsync("platform tenant status update", async (provider, dbContext) =>
{
var tenant = await dbContext.Tenants
.SingleOrDefaultAsync(item => item.Id == command.TenantId && item.Mode != TenantMode.PlatformOwned, cancellationToken)
@@ -181,6 +187,13 @@ internal sealed class PlatformAdminService(
ToBillingStatus = tenant.BillingStatus,
command.Reason
});
await provider.GetRequiredService<ISecurityEventPublisher>().AuthorizationChangedAsync(
tenant.Id,
null,
"tenant_status_changed",
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
$"tenant-status-{tenant.Id:N}",
cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
var domainCount = await dbContext.TenantDomains.CountAsync(domain => domain.TenantId == tenant.Id, cancellationToken);
var expiresAt = await dbContext.TenantSubscriptions
@@ -255,7 +268,7 @@ internal sealed class PlatformAdminService(
CancellationToken cancellationToken = default)
{
await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
return await ExecuteSystemAsync("platform tenant subscription upsert", async dbContext =>
return await ExecuteSystemAsync("platform tenant subscription upsert", async (provider, dbContext) =>
{
await RequireTenantAsync(dbContext, command.TenantId, cancellationToken);
if (!await dbContext.PlatformSaasPlans.AnyAsync(plan => plan.Code == NormalizeCode(command.PlanCode), cancellationToken))
@@ -285,6 +298,27 @@ internal sealed class PlatformAdminService(
subscription.Status,
subscription.ExpiresAt
});
var moduleCodes = await dbContext.PlanModuleEntitlements.AsNoTracking()
.Where(item => item.PlanCode == subscription.PlanCode)
.Select(item => item.ModuleCode)
.Distinct()
.ToArrayAsync(cancellationToken);
if (moduleCodes.Length == 0)
{
moduleCodes = ["*"];
}
var eventPublisher = provider.GetRequiredService<ISecurityEventPublisher>();
var version = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (var moduleCode in moduleCodes)
{
await eventPublisher.CapabilityChangedAsync(
command.TenantId,
moduleCode,
"subscription_changed",
version,
$"tenant-subscription-{subscription.Id:N}",
cancellationToken);
}
await dbContext.SaveChangesAsync(cancellationToken);
return ToSubscriptionItem(subscription);
}, cancellationToken);
@@ -719,11 +753,23 @@ internal sealed class PlatformAdminService(
string reason,
Func<TikuDbContext, Task<TResult>> operation,
CancellationToken cancellationToken)
{
return ExecuteSystemAsync(reason, (_, dbContext) => operation(dbContext), cancellationToken);
}
private Task<TResult> ExecuteSystemAsync<TResult>(
string reason,
Func<IServiceProvider, TikuDbContext, Task<TResult>> operation,
CancellationToken cancellationToken)
{
return tenantExecutionScope.ExecuteAsync(
null,
reason,
async (provider, _) => await operation(provider.GetRequiredService<TikuDbContext>()),
new SystemScopeRequest(
null,
SystemScopeCallerType.Platform,
nameof(PlatformAdminService),
reason,
Guid.NewGuid().ToString("N")),
async (provider, _) => await operation(provider, provider.GetRequiredService<TikuDbContext>()),
cancellationToken);
}