using System.Text.Json; namespace Tiku.Application.Security; public enum DataScopeMode { Self, Restricted, All } public sealed record CurrentDataScope( DataScopeMode Mode, IReadOnlySet RegionIds, IReadOnlySet ClassIds, bool IncludesSelf) { public static CurrentDataScope Self { get; } = new( DataScopeMode.Self, new HashSet(), new HashSet(), 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 roleScopes) { var regionIds = new HashSet(); var classIds = new HashSet(); 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(), new HashSet(), 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(), new HashSet(), 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 ReadGuids(JsonElement value, string propertyName) { var result = new HashSet(); 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 TenantPermissions, IReadOnlySet 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 GetAsync(CancellationToken cancellationToken = default); }