Files
tiku-backend.net/Tiku.Application/Security/ICurrentAccessContext.cs

150 lines
4.7 KiB
C#

using System.Text.Json;
namespace Tiku.Application.Security;
public enum DataScopeMode
{
Self,
Restricted,
All
}
public sealed record CurrentDataScope(
DataScopeMode Mode,
IReadOnlySet<Guid> RegionIds,
IReadOnlySet<Guid> ClassIds,
bool IncludesSelf)
{
public static CurrentDataScope Self { get; } = new(
DataScopeMode.Self,
new HashSet<Guid>(),
new HashSet<Guid>(),
true);
public bool AllowsResource(Guid currentUserId, Guid? ownerUserId = null, Guid? regionId = null, Guid? classId = null)
{
if (Mode == DataScopeMode.All)
{
return true;
}
if (IncludesSelf && ownerUserId == currentUserId)
{
return true;
}
return Mode == DataScopeMode.Restricted &&
((regionId.HasValue && RegionIds.Contains(regionId.Value)) ||
(classId.HasValue && ClassIds.Contains(classId.Value)));
}
public static CurrentDataScope Merge(IEnumerable<JsonElement> roleScopes)
{
var regionIds = new HashSet<Guid>();
var classIds = new HashSet<Guid>();
var includesSelf = false;
var hasRestrictedScope = false;
foreach (var roleScope in roleScopes)
{
var parsed = Parse(roleScope);
if (parsed.Mode == DataScopeMode.All)
{
return new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
}
includesSelf |= parsed.IncludesSelf;
hasRestrictedScope |= parsed.Mode == DataScopeMode.Restricted;
regionIds.UnionWith(parsed.RegionIds);
classIds.UnionWith(parsed.ClassIds);
}
return hasRestrictedScope || regionIds.Count > 0 || classIds.Count > 0
? new CurrentDataScope(DataScopeMode.Restricted, regionIds, classIds, includesSelf)
: Self;
}
private static CurrentDataScope Parse(JsonElement value)
{
if (value.ValueKind != JsonValueKind.Object)
{
return Self;
}
var mode = ReadString(value, "mode") ?? ReadString(value, "type");
if (string.Equals(mode, nameof(DataScopeMode.All), StringComparison.OrdinalIgnoreCase))
{
return new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
}
if (string.Equals(mode, nameof(DataScopeMode.Self), StringComparison.OrdinalIgnoreCase))
{
return Self;
}
var regions = ReadGuids(value, "regionIds");
var classes = ReadGuids(value, "classIds");
var restricted = string.Equals(mode, nameof(DataScopeMode.Restricted), StringComparison.OrdinalIgnoreCase) ||
regions.Count > 0 ||
classes.Count > 0;
return restricted
? new CurrentDataScope(DataScopeMode.Restricted, regions, classes, ReadBoolean(value, "includesSelf") || ReadBoolean(value, "ownLeadsOnly"))
: Self;
}
private static string? ReadString(JsonElement value, string propertyName)
{
return value.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
? property.GetString()
: null;
}
private static bool ReadBoolean(JsonElement value, string propertyName)
{
return value.TryGetProperty(propertyName, out var property) &&
property.ValueKind is JsonValueKind.True or JsonValueKind.False &&
property.GetBoolean();
}
private static HashSet<Guid> ReadGuids(JsonElement value, string propertyName)
{
var result = new HashSet<Guid>();
if (!value.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Array)
{
return result;
}
foreach (var item in property.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String && Guid.TryParse(item.GetString(), out var id))
{
result.Add(id);
}
}
return result;
}
}
public sealed record CurrentAccessSnapshot(
Guid? UserId,
Guid? TenantId,
bool IsUserActive,
bool IsCurrentTenantMember,
IReadOnlySet<string> TenantPermissions,
IReadOnlySet<string> PlatformPermissions,
CurrentDataScope DataScope)
{
public bool HasTenantPermission(string permissionCode) =>
IsCurrentTenantMember && TenantPermissions.Contains(permissionCode);
public bool HasPlatformPermission(string permissionCode) =>
IsUserActive && PlatformPermissions.Contains(permissionCode);
}
public interface ICurrentAccessContext
{
Task<CurrentAccessSnapshot> GetAsync(CancellationToken cancellationToken = default);
}