Files

89 lines
2.6 KiB
C#

namespace Tiku.Application.Security;
public enum TenantResolutionSource
{
None,
Host,
TenantCode,
Jwt,
RefreshToken,
Worker,
System
}
public interface ITenantContext
{
Guid? TenantId { get; }
string? TenantCode { get; }
TenantResolutionSource ResolutionSource { get; }
bool IsResolved { get; }
bool IsSystem { get; }
}
public interface ITenantContextInitializer
{
void Initialize(Guid tenantId, string? tenantCode, TenantResolutionSource source);
void InitializeSystem(Guid? targetTenantId, string reason);
}
public sealed class TenantContext : ITenantContext, ITenantContextInitializer
{
public Guid? TenantId { get; private set; }
public string? TenantCode { get; private set; }
public TenantResolutionSource ResolutionSource { get; private set; }
public bool IsResolved => TenantId.HasValue;
public bool IsSystem { get; private set; }
internal string? SystemReason { get; private set; }
public void Initialize(Guid tenantId, string? tenantCode, TenantResolutionSource source)
{
if (tenantId == Guid.Empty)
{
throw new ArgumentException("Tenant ID cannot be empty.", nameof(tenantId));
}
if (IsSystem)
{
throw new InvalidOperationException("A system tenant context cannot be replaced.");
}
if (TenantId.HasValue && TenantId.Value != tenantId)
{
throw new TenantContextConflictException(TenantId.Value, tenantId);
}
var wasResolved = TenantId.HasValue;
TenantId = tenantId;
TenantCode = string.IsNullOrWhiteSpace(tenantCode) ? TenantCode : tenantCode.Trim();
if (!wasResolved)
{
ResolutionSource = source;
}
}
public void InitializeSystem(Guid? targetTenantId, string reason)
{
if (IsResolved || IsSystem)
{
throw new InvalidOperationException("Tenant context has already been initialized.");
}
if (string.IsNullOrWhiteSpace(reason))
{
throw new ArgumentException("A system scope requires an audit reason.", nameof(reason));
}
TenantId = targetTenantId;
ResolutionSource = TenantResolutionSource.System;
IsSystem = true;
SystemReason = reason.Trim();
}
}
public sealed class TenantContextConflictException(Guid expectedTenantId, Guid actualTenantId)
: Exception($"Resolved tenant '{expectedTenantId}' conflicts with authenticated tenant '{actualTenantId}'.")
{
public Guid ExpectedTenantId { get; } = expectedTenantId;
public Guid ActualTenantId { get; } = actualTenantId;
}