Files
tiku-backend.net/Tiku.Infrastructure/Security/CapabilityAccessEvaluator.cs

88 lines
3.4 KiB
C#

using Microsoft.EntityFrameworkCore;
using Tiku.Application.Security;
using Tiku.Domain.Commerce;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Security;
internal sealed class CapabilityAccessEvaluator(TikuDbContext dbContext) : ICapabilityAccessEvaluator
{
public async Task<bool> IsAllowedAsync(
Guid tenantId,
string moduleCode,
CapabilityOperation operation,
CancellationToken cancellationToken = default)
{
var normalized = moduleCode.Trim().ToLowerInvariant();
var moduleExists = await dbContext.ProductModules.AsNoTracking()
.AnyAsync(item => item.Code == normalized && item.Status == ProductModuleStatus.Active, cancellationToken);
if (!moduleExists)
{
// Compatibility while the fixed module catalog is introduced module-by-module.
return true;
}
var tenantActive = await dbContext.Tenants.AsNoTracking()
.AnyAsync(item => item.Id == tenantId && item.Status == TenantStatus.Active, cancellationToken);
if (!tenantActive)
{
return false;
}
var now = DateTimeOffset.UtcNow;
var overrideMode = await dbContext.TenantModuleOverrides.AsNoTracking()
.Where(item => item.TenantId == tenantId && item.ModuleCode == normalized &&
(item.ExpiresAt == null || item.ExpiresAt > now))
.Select(item => (TenantModuleOverrideMode?)item.Mode)
.SingleOrDefaultAsync(cancellationToken);
if (overrideMode == TenantModuleOverrideMode.Disabled)
{
return false;
}
var subscription = await dbContext.TenantSubscriptions.AsNoTracking()
.Where(item => item.TenantId == tenantId)
.OrderByDescending(item => item.UpdatedAt)
.Select(item => new { item.PlanCode, item.Status, item.StartsAt, item.ExpiresAt })
.FirstOrDefaultAsync(cancellationToken);
if (subscription is null || subscription.StartsAt > now || subscription.ExpiresAt <= now)
{
return false;
}
var entitled = overrideMode == TenantModuleOverrideMode.Enabled ||
await dbContext.PlanModuleEntitlements.AsNoTracking().AnyAsync(
item => item.PlanCode == subscription.PlanCode && item.ModuleCode == normalized && item.Enabled,
cancellationToken);
if (!entitled)
{
return false;
}
return operation == CapabilityOperation.Read ||
subscription.Status is TenantSubscriptionStatus.Trial or TenantSubscriptionStatus.Active;
}
public async Task<IReadOnlySet<string>> GetEnabledModulesAsync(
Guid tenantId,
CapabilityOperation operation = CapabilityOperation.Read,
CancellationToken cancellationToken = default)
{
var modules = await dbContext.ProductModules.AsNoTracking()
.Where(item => item.Status == ProductModuleStatus.Active)
.Select(item => item.Code)
.ToArrayAsync(cancellationToken);
var enabled = new HashSet<string>(StringComparer.Ordinal);
foreach (var module in modules)
{
if (await IsAllowedAsync(tenantId, module, operation, cancellationToken))
{
enabled.Add(module);
}
}
return enabled;
}
}