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

@@ -16,7 +16,8 @@ namespace Tiku.Infrastructure.Jobs;
internal sealed class BackgroundJobService(
TikuDbContext dbContext,
IServiceProvider serviceProvider,
ITenantExecutionScope tenantExecutionScope) : IBackgroundJobService
ITenantExecutionScope tenantExecutionScope,
ICapabilityAccessEvaluator capabilityAccessEvaluator) : IBackgroundJobService
{
private static readonly TimeSpan LeaseDuration = TimeSpan.FromMinutes(5);
@@ -24,10 +25,19 @@ internal sealed class BackgroundJobService(
CreateBackgroundJobCommand command,
CancellationToken cancellationToken = default)
{
var normalizedJobType = NormalizeJobType(command.JobType);
if (!await capabilityAccessEvaluator.IsAllowedAsync(
command.TenantId,
ResolveCapabilityModule(normalizedJobType),
CapabilityOperation.Write,
cancellationToken))
{
throw new InvalidOperationException("Tenant capability does not allow this background job.");
}
var job = new BackgroundJob
{
TenantId = command.TenantId,
JobType = NormalizeJobType(command.JobType),
JobType = normalizedJobType,
Payload = command.Payload,
RunAfter = command.RunAfter,
MaxRetries = Math.Clamp(command.MaxRetries, 0, 20)
@@ -55,6 +65,19 @@ internal sealed class BackgroundJobService(
foreach (var job in jobs)
{
cancellationToken.ThrowIfCancellationRequested();
if (!await capabilityAccessEvaluator.IsAllowedAsync(
job.TenantId,
ResolveCapabilityModule(job.JobType),
CapabilityOperation.Write,
cancellationToken))
{
job.Status = BackgroundJobStatus.Failed;
job.CompletedAt = DateTimeOffset.UtcNow;
job.LastError = "Tenant capability was revoked before job execution.";
await dbContext.SaveChangesAsync(cancellationToken);
processed++;
continue;
}
job.Status = BackgroundJobStatus.Processing;
job.LockedBy = workerId;
job.LockExpiresAt = now.Add(LeaseDuration);
@@ -64,8 +87,9 @@ internal sealed class BackgroundJobService(
try
{
var result = await tenantExecutionScope.ExecuteAsync(
job.TenantId,
$"Background job {job.JobType}",
new SystemScopeRequest(
job.TenantId, SystemScopeCallerType.Worker, workerId,
$"Background job {job.JobType}", job.Id.ToString("N")),
(provider, token) => ProcessCoreAsync(provider, job, token),
cancellationToken);
job.Status = BackgroundJobStatus.Succeeded;
@@ -328,6 +352,15 @@ internal sealed class BackgroundJobService(
return jobType.Trim().ToLowerInvariant();
}
private static string ResolveCapabilityModule(string jobType) => jobType switch
{
"content_export" or "content_import" or "asset_security_scan" => "content",
"statistics_aggregation" => "dashboard",
"commerce_reconciliation" => "commerce",
"tenant_domain_recheck" => "settings",
_ => "job"
};
private static string NormalizeProvider(string? provider)
{
var normalized = (provider ?? string.Empty).Trim().ToLowerInvariant();