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

142 lines
5.7 KiB
C#

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<CurrentAccessSnapshot>? snapshotTask;
public Task<CurrentAccessSnapshot> 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<CurrentAccessSnapshot> 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<string>(StringComparer.Ordinal),
new HashSet<string>(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<string>(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<string>(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<string>(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<HashSet<string>> 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<string>(StringComparer.Ordinal),
new HashSet<string>(StringComparer.Ordinal),
CurrentDataScope.Self);
}
}