using Microsoft.EntityFrameworkCore; using Tiku.Application.Security; using Tiku.Domain.Identity; using Tiku.Domain.Operations; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Security; internal sealed class CurrentAccessContext( ICurrentUser currentUser, ITenantContext tenantContext, TikuDbContext dbContext) : ICurrentAccessContext { private Task? snapshotTask; public Task GetAsync(CancellationToken cancellationToken = default) { // The context is scoped to one request. Do not allow an aborted authorization // check to poison the cached access snapshot used later in that request. return snapshotTask ??= LoadAsync(CancellationToken.None); } private async Task LoadAsync(CancellationToken cancellationToken) { if (!currentUser.IsAuthenticated || currentUser.UserId is not { } userId) { return Empty(); } var isUserActive = await dbContext.Users.AsNoTracking() .AnyAsync(user => user.Id == userId && user.Status == UserStatus.Active, cancellationToken); if (!isUserActive) { return new CurrentAccessSnapshot( userId, tenantContext.TenantId, false, false, new HashSet(StringComparer.Ordinal), new HashSet(StringComparer.Ordinal), CurrentDataScope.Self); } var platformPermissions = await LoadPlatformPermissionsAsync(userId, cancellationToken); if (tenantContext.TenantId is not { } tenantId) { return new CurrentAccessSnapshot( userId, null, true, false, new HashSet(StringComparer.Ordinal), platformPermissions, CurrentDataScope.Self); } var isTenantActive = await dbContext.Tenants.AsNoTracking() .AnyAsync(tenant => tenant.Id == tenantId && tenant.Status == TenantStatus.Active, cancellationToken); var isActiveMember = isTenantActive && await dbContext.TenantMemberships.AsNoTracking() .AnyAsync( membership => membership.TenantId == tenantId && membership.UserId == userId && membership.Status == MembershipStatus.Active, cancellationToken); if (!isActiveMember) { return new CurrentAccessSnapshot( userId, tenantId, true, false, new HashSet(StringComparer.Ordinal), platformPermissions, CurrentDataScope.Self); } var tenantRoles = await ( from userRole in dbContext.TenantBackendUserRoles.AsNoTracking() join role in dbContext.TenantBackendRoles.AsNoTracking() on userRole.RoleId equals role.Id where userRole.TenantId == tenantId && userRole.UserId == userId && role.Status == BackendRoleStatus.Active select new { role.Id, role.DataScope }) .ToArrayAsync(cancellationToken); var roleIds = tenantRoles.Select(role => role.Id).ToArray(); var tenantPermissions = roleIds.Length == 0 ? new HashSet(StringComparer.Ordinal) : (await ( from binding in dbContext.TenantBackendRolePermissions.AsNoTracking() join permission in dbContext.BackendPermissions.AsNoTracking() on binding.PermissionCode equals permission.Code where binding.TenantId == tenantId && roleIds.Contains(binding.RoleId) && (permission.Area == BackendPermissionArea.Tenant || permission.Area == BackendPermissionArea.Both) select binding.PermissionCode) .Distinct() .ToArrayAsync(cancellationToken)) .ToHashSet(StringComparer.Ordinal); return new CurrentAccessSnapshot( userId, tenantId, true, true, tenantPermissions, platformPermissions, CurrentDataScope.Merge(tenantRoles.Select(role => role.DataScope))); } private async Task> LoadPlatformPermissionsAsync(Guid userId, CancellationToken cancellationToken) { return (await ( from userRole in dbContext.PlatformBackendUserRoles.AsNoTracking() join role in dbContext.PlatformBackendRoles.AsNoTracking() on userRole.RoleId equals role.Id join binding in dbContext.PlatformBackendRolePermissions.AsNoTracking() on role.Id equals binding.RoleId join permission in dbContext.BackendPermissions.AsNoTracking() on binding.PermissionCode equals permission.Code where userRole.UserId == userId && role.Status == BackendRoleStatus.Active && (permission.Area == BackendPermissionArea.Platform || permission.Area == BackendPermissionArea.Both) select binding.PermissionCode) .Distinct() .ToArrayAsync(cancellationToken)) .ToHashSet(StringComparer.Ordinal); } private CurrentAccessSnapshot Empty() { return new CurrentAccessSnapshot( null, tenantContext.TenantId, false, false, new HashSet(StringComparer.Ordinal), new HashSet(StringComparer.Ordinal), CurrentDataScope.Self); } }