feat(security): complete capability messaging workflows
This commit is contained in:
@@ -324,6 +324,124 @@ internal sealed class PlatformAdminService(
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PlatformPlanModuleEntitlements> ReplacePlanModulesAsync(
|
||||
PlatformAdminActor actor,
|
||||
ReplacePlatformPlanModulesCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
|
||||
return await ExecuteSystemAsync("platform plan module entitlements replace", async (provider, dbContext) =>
|
||||
{
|
||||
var planCode = NormalizeCode(command.PlanCode);
|
||||
var plan = await dbContext.PlatformSaasPlans.SingleOrDefaultAsync(
|
||||
item => item.Code == planCode,
|
||||
cancellationToken) ?? throw new PlatformAdminException("SaaS plan was not found.", "plan_not_found");
|
||||
var moduleCodes = command.ModuleCodes
|
||||
.Select(NormalizeCode)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
var invalidModules = moduleCodes.Where(module => !ProductModuleCatalog.Contains(module)).ToArray();
|
||||
if (invalidModules.Length > 0)
|
||||
{
|
||||
throw new PlatformAdminException("One or more product modules are invalid.", "product_module_invalid");
|
||||
}
|
||||
|
||||
var existing = await dbContext.PlanModuleEntitlements
|
||||
.Where(item => item.PlanCode == planCode)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
var changedModules = existing.Select(item => item.ModuleCode)
|
||||
.Concat(moduleCodes)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
dbContext.PlanModuleEntitlements.RemoveRange(existing);
|
||||
dbContext.PlanModuleEntitlements.AddRange(moduleCodes.Select(moduleCode => new PlanModuleEntitlement
|
||||
{
|
||||
PlanCode = planCode,
|
||||
ModuleCode = moduleCode,
|
||||
Enabled = true
|
||||
}));
|
||||
AddAudit(dbContext, actor, "platform.plan.modules.replaced", plan.Id, new
|
||||
{
|
||||
planCode,
|
||||
previousModuleCodes = existing.Select(item => item.ModuleCode).Order(StringComparer.Ordinal),
|
||||
moduleCodes
|
||||
});
|
||||
|
||||
var tenantIds = await dbContext.TenantSubscriptions.AsNoTracking()
|
||||
.Where(item => item.PlanCode == planCode)
|
||||
.Select(item => item.TenantId)
|
||||
.Distinct()
|
||||
.ToArrayAsync(cancellationToken);
|
||||
var publisher = provider.GetRequiredService<ISecurityEventPublisher>();
|
||||
var version = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
foreach (var tenantId in tenantIds)
|
||||
{
|
||||
foreach (var moduleCode in changedModules)
|
||||
{
|
||||
await publisher.CapabilityChangedAsync(
|
||||
tenantId, moduleCode, "plan_entitlements_changed", version,
|
||||
$"plan-modules-{plan.Id:N}", cancellationToken);
|
||||
}
|
||||
}
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new PlatformPlanModuleEntitlements(planCode, moduleCodes);
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PlatformTenantModuleOverrideItem> UpsertTenantModuleOverrideAsync(
|
||||
PlatformAdminActor actor,
|
||||
UpsertPlatformTenantModuleOverrideCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken);
|
||||
return await ExecuteSystemAsync("platform tenant module override upsert", async (provider, dbContext) =>
|
||||
{
|
||||
await RequireTenantAsync(dbContext, command.TenantId, cancellationToken);
|
||||
var moduleCode = NormalizeCode(command.ModuleCode);
|
||||
if (!ProductModuleCatalog.Contains(moduleCode))
|
||||
{
|
||||
throw new PlatformAdminException("Product module was not found.", "product_module_not_found");
|
||||
}
|
||||
var reason = command.Reason.Trim();
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
throw new PlatformAdminException("Module override reason is required.", "module_override_reason_required");
|
||||
}
|
||||
|
||||
var item = await dbContext.TenantModuleOverrides.SingleOrDefaultAsync(
|
||||
value => value.TenantId == command.TenantId && value.ModuleCode == moduleCode,
|
||||
cancellationToken);
|
||||
if (item is null)
|
||||
{
|
||||
item = new TenantModuleOverride { TenantId = command.TenantId, ModuleCode = moduleCode };
|
||||
dbContext.TenantModuleOverrides.Add(item);
|
||||
}
|
||||
var previousMode = item.Mode;
|
||||
item.Mode = command.Mode;
|
||||
item.ExpiresAt = command.ExpiresAt;
|
||||
item.Reason = reason;
|
||||
AddAudit(dbContext, actor, "platform.tenant.module_override.updated", command.TenantId, new
|
||||
{
|
||||
moduleCode,
|
||||
previousMode,
|
||||
item.Mode,
|
||||
item.ExpiresAt,
|
||||
reason
|
||||
});
|
||||
await provider.GetRequiredService<ISecurityEventPublisher>().CapabilityChangedAsync(
|
||||
command.TenantId,
|
||||
moduleCode,
|
||||
"tenant_module_override_changed",
|
||||
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
$"tenant-module-override-{item.Id:N}",
|
||||
cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new PlatformTenantModuleOverrideItem(
|
||||
item.TenantId, item.ModuleCode, item.Mode, item.ExpiresAt, item.Reason);
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PlatformDomainList> GetDomainsAsync(
|
||||
PlatformAdminActor actor,
|
||||
PlatformAdminQuery query,
|
||||
@@ -768,7 +886,8 @@ internal sealed class PlatformAdminService(
|
||||
SystemScopeCallerType.Platform,
|
||||
nameof(PlatformAdminService),
|
||||
reason,
|
||||
Guid.NewGuid().ToString("N")),
|
||||
Guid.NewGuid().ToString("N"),
|
||||
IsGlobal: true),
|
||||
async (provider, _) => await operation(provider, provider.GetRequiredService<TikuDbContext>()),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user