diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 3040008..4d6b182 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -8,10 +8,10 @@
-
-
-
-
+
+
+
+
@@ -21,7 +21,7 @@
-
+
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/FakePrincipalManagementService.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/FakePrincipalManagementService.cs
index 1a7a2cd..754ca0d 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/FakePrincipalManagementService.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/FakePrincipalManagementService.cs
@@ -8,7 +8,7 @@ public class FakePrincipalManagementService : IPrincipalManagementService
{
public Type PrincipalType => throw new InvalidOperationException();
- public Task CreatePrincipalAsync(string principalName, IEnumerable typedPermissions, CancellationToken cancellationToken = default)
+ public Task CreatePrincipalAsync(string principalName, IEnumerable managedPermissions, CancellationToken cancellationToken = default)
{
throw new InvalidOperationException();
}
@@ -23,7 +23,7 @@ public Task RemovePrincipalAsync(UserCredential userCredential, b
throw new InvalidOperationException();
}
- public Task> UpdatePermissionsAsync(UserCredential userCredential, IEnumerable typedPermissions, CancellationToken cancellationToken)
+ public Task> UpdatePermissionsAsync(UserCredential userCredential, IEnumerable managedPermissions, CancellationToken cancellationToken)
{
throw new InvalidOperationException();
}
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/IPrincipalManagementService.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/IPrincipalManagementService.cs
index e0032e4..73df437 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/IPrincipalManagementService.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/IPrincipalManagementService.cs
@@ -8,12 +8,12 @@ public interface IPrincipalManagementService
{
Type PrincipalType { get; }
- Task CreatePrincipalAsync(string principalName, IEnumerable typedPermissions, CancellationToken cancellationToken = default);
+ Task CreatePrincipalAsync(string principalName, IEnumerable managedPermissions, CancellationToken cancellationToken = default);
Task UpdatePrincipalNameAsync(UserCredential userCredential, string principalName, CancellationToken cancellationToken);
Task RemovePrincipalAsync(UserCredential userCredential, bool force, CancellationToken cancellationToken = default);
- Task> UpdatePermissionsAsync(UserCredential userCredential, IEnumerable typedPermissions,
+ Task> UpdatePermissionsAsync(UserCredential userCredential, IEnumerable managedPermissions,
CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermission.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermission.cs
index 497f2a7..37dad88 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermission.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermission.cs
@@ -1,8 +1,36 @@
-namespace SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
-public record ManagedPermission : ManagedPermissionData
+namespace SecuritySystem.ExternalSystem.Management;
+
+public record ManagedPermission
{
public required SecurityIdentity Identity { get; init; }
- public required bool IsVirtual { get; init; }
+ public bool ForceApplyIdentity { get; init; }
+
+ public bool IsVirtual { get; init; }
+
+ public required SecurityRole SecurityRole { get; init; }
+
+ public PermissionPeriod Period { get; init; } = PermissionPeriod.Eternity;
+
+ public string Comment { get; init; } = "";
+
+ public SecurityIdentity DelegatedFrom { get; init; } = SecurityIdentity.Default;
+
+ public ImmutableDictionary Restrictions { get; init; } = [];
+
+ public ImmutableDictionary ExtendedData { get; init; } = [];
+
+
+ public static implicit operator ManagedPermission(SecurityRole securityRole) => new() { SecurityRole = securityRole, Identity = SecurityIdentity.Default };
+
+ public ManagedPermission WithExtendedData(string key, object value)
+ {
+ var newExtendedData = this.ExtendedData.ToDictionary();
+
+ newExtendedData[key] = value;
+
+ return this with { ExtendedData = newExtendedData.ToImmutableDictionary() };
+ }
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs
deleted file mode 100644
index 836aaba..0000000
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Collections.Immutable;
-
-namespace SecuritySystem.ExternalSystem.Management;
-
-public record ManagedPermissionData
-{
- public required SecurityRole SecurityRole { get; init; }
-
- public PermissionPeriod Period { get; init; } = PermissionPeriod.Eternity;
-
- public string Comment { get; init; } = "";
-
- public ImmutableDictionary Restrictions { get; init; } = [];
-
- public ImmutableDictionary ExtendedData { get; init; } = [];
-
- public static implicit operator ManagedPermissionData(SecurityRole securityRole) => new() { SecurityRole = securityRole };
-}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionDataExtensions.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionDataExtensions.cs
deleted file mode 100644
index af635d5..0000000
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionDataExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Immutable;
-
-namespace SecuritySystem.ExternalSystem.Management;
-
-public static class ManagedPermissionDataExtensions
-{
- public static TManagedPermissionData WithExtendedData(this TManagedPermissionData managedPermissionData, string key, object value)
- where TManagedPermissionData : ManagedPermissionData
- {
- var newExtendedData = managedPermissionData.ExtendedData.ToDictionary();
-
- newExtendedData[key] = value;
-
- return managedPermissionData with { ExtendedData = newExtendedData.ToImmutableDictionary() };
- }
-}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipal.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipal.cs
index 0266e8d..d6ffd4f 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipal.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipal.cs
@@ -1,3 +1,5 @@
-namespace SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
-public record ManagedPrincipal(ManagedPrincipalHeader Header, IReadOnlyList Permissions);
+namespace SecuritySystem.ExternalSystem.Management;
+
+public record ManagedPrincipal(ManagedPrincipalHeader Header, ImmutableArray Permissions);
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/PermissionData.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/PermissionData.cs
index d58d9bb..8abd732 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/PermissionData.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/PermissionData.cs
@@ -1,20 +1,22 @@
-namespace SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
+
+namespace SecuritySystem.ExternalSystem.Management;
public abstract record PermissionData
{
- public abstract Type PermissionTypeType { get; }
+ public abstract Type PermissionType { get; }
}
public abstract record PermissionData(TPermission Permission) : PermissionData
{
- public override Type PermissionTypeType { get; } = typeof(TPermission);
+ public override Type PermissionType { get; } = typeof(TPermission);
}
-public record PermissionData(TPermission Permission, IReadOnlyList Restrictions)
+public record PermissionData(TPermission Permission, ImmutableArray Restrictions)
: PermissionData(Permission)
{
public PermissionData(TPermission permission, IEnumerable restrictions)
- : this(permission, restrictions.ToList())
+ : this(permission, [..restrictions])
{
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/PrincipalData.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/PrincipalData.cs
index ab8b7af..efb8a4e 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/PrincipalData.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/PrincipalData.cs
@@ -1,4 +1,6 @@
-namespace SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
+
+namespace SecuritySystem.ExternalSystem.Management;
public abstract record PrincipalData
{
@@ -11,10 +13,10 @@ public abstract record PrincipalData
public record PrincipalData(
TPrincipal Principal,
- IReadOnlyList> PermissionDataList) : PrincipalData(Principal)
+ ImmutableArray> PermissionDataList) : PrincipalData(Principal)
{
public PrincipalData(TPrincipal principal, IEnumerable> permissionDataList)
- : this(principal, permissionDataList.ToList())
+ : this(principal, [..permissionDataList])
{
}
diff --git a/src/SecuritySystem.Abstractions/PermissionPeriod.cs b/src/SecuritySystem.Abstractions/PermissionPeriod.cs
index 61d2d59..a65557e 100644
--- a/src/SecuritySystem.Abstractions/PermissionPeriod.cs
+++ b/src/SecuritySystem.Abstractions/PermissionPeriod.cs
@@ -13,5 +13,16 @@ public bool IsIntersected(PermissionPeriod otherPeriod)
return start1 <= end2 && start2 <= end1;
}
+ public bool Contains(PermissionPeriod otherPeriod)
+ {
+ var start1 = this.StartDate ?? DateTime.MinValue;
+ var end1 = this.EndDate ?? DateTime.MaxValue;
+
+ var start2 = otherPeriod.StartDate ?? DateTime.MinValue;
+ var end2 = otherPeriod.EndDate ?? DateTime.MaxValue;
+
+ return start1 <= start2 && end2 <= end1;
+ }
+
public static PermissionPeriod Eternity { get; } = new (null, null);
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Abstractions/SecurityAccessor/SecurityAccessorData.cs b/src/SecuritySystem.Abstractions/SecurityAccessor/SecurityAccessorData.cs
index edd5b6a..815d684 100644
--- a/src/SecuritySystem.Abstractions/SecurityAccessor/SecurityAccessorData.cs
+++ b/src/SecuritySystem.Abstractions/SecurityAccessor/SecurityAccessorData.cs
@@ -1,4 +1,6 @@
-namespace SecuritySystem.SecurityAccessor;
+using System.Collections.Immutable;
+
+namespace SecuritySystem.SecurityAccessor;
public abstract record SecurityAccessorData
{
@@ -6,13 +8,13 @@ public abstract record SecurityAccessorData
public static SecurityAccessorData Empty { get; } = Return();
- public static SecurityAccessorData Return(params string[] items) => new FixedSecurityAccessorData(items);
+ public static SecurityAccessorData Return(params string[] items) => new FixedSecurityAccessorData([..items]);
public static SecurityAccessorData Return(IEnumerable items) => Return(items.ToArray());
public static SecurityAccessorData TryReturn(string? item) => string.IsNullOrWhiteSpace(item) ? Empty : Return(item);
- public record FixedSecurityAccessorData(IReadOnlyList Items) : SecurityAccessorData;
+ public record FixedSecurityAccessorData(ImmutableArray Items) : SecurityAccessorData;
public record InfinitySecurityAccessorData : SecurityAccessorData;
diff --git a/src/SecuritySystem.Abstractions/SecurityContextInfo/ISecurityContextInfoSource.cs b/src/SecuritySystem.Abstractions/SecurityContextInfo/ISecurityContextInfoSource.cs
index cc37f7b..6a0510d 100644
--- a/src/SecuritySystem.Abstractions/SecurityContextInfo/ISecurityContextInfoSource.cs
+++ b/src/SecuritySystem.Abstractions/SecurityContextInfo/ISecurityContextInfoSource.cs
@@ -1,9 +1,11 @@
-// ReSharper disable once CheckNamespace
+using System.Collections.Immutable;
+
+// ReSharper disable once CheckNamespace
namespace SecuritySystem;
public interface ISecurityContextInfoSource
{
- IReadOnlyList SecurityContextInfoList { get; }
+ ImmutableArray SecurityContextInfoList { get; }
SecurityContextInfo GetSecurityContextInfo(Type type);
diff --git a/src/SecuritySystem.Abstractions/SecurityRole/ISecurityRoleSource.cs b/src/SecuritySystem.Abstractions/SecurityRole/ISecurityRoleSource.cs
index 4e3cbb0..c76e452 100644
--- a/src/SecuritySystem.Abstractions/SecurityRole/ISecurityRoleSource.cs
+++ b/src/SecuritySystem.Abstractions/SecurityRole/ISecurityRoleSource.cs
@@ -1,9 +1,11 @@
-// ReSharper disable once CheckNamespace
+using System.Collections.Immutable;
+
+// ReSharper disable once CheckNamespace
namespace SecuritySystem;
public interface ISecurityRoleSource
{
- IReadOnlyList SecurityRoles { get; }
+ ImmutableArray SecurityRoles { get; }
FullSecurityRole GetSecurityRole(SecurityRole securityRole);
diff --git a/src/SecuritySystem.Abstractions/SecurityRole/SecurityRoleInfo.cs b/src/SecuritySystem.Abstractions/SecurityRole/SecurityRoleInfo.cs
index 7a0530c..60b6c60 100644
--- a/src/SecuritySystem.Abstractions/SecurityRole/SecurityRoleInfo.cs
+++ b/src/SecuritySystem.Abstractions/SecurityRole/SecurityRoleInfo.cs
@@ -1,4 +1,5 @@
-using HierarchicalExpand;
+using System.Collections.Immutable;
+using HierarchicalExpand;
// ReSharper disable once CheckNamespace
namespace SecuritySystem;
@@ -9,9 +10,9 @@ public record SecurityRoleInfo(TypedSecurityIdentity Identity)
public SecurityPathRestriction Restriction { get; init; } = SecurityPathRestriction.Default;
- public IReadOnlyList Operations { get; init; } = [];
+ public ImmutableArray Operations { get; init; } = [];
- public IReadOnlyList Children { get; init; } = [];
+ public ImmutableArray Children { get; init; } = [];
public string? Description { get; init; }
diff --git a/src/SecuritySystem.Abstractions/SecurityRule/SecurityRuleExtensions.cs b/src/SecuritySystem.Abstractions/SecurityRule/SecurityRuleExtensions.cs
index d3e95e2..6e93d80 100644
--- a/src/SecuritySystem.Abstractions/SecurityRule/SecurityRuleExtensions.cs
+++ b/src/SecuritySystem.Abstractions/SecurityRule/SecurityRuleExtensions.cs
@@ -161,6 +161,11 @@ public TSecurityRule TryApply(HierarchicalExpandType? customExpandType) =>
customExpandType == null || securityRule.CustomExpandType != null
? securityRule
: securityRule with { CustomExpandType = customExpandType };
+
+ public TSecurityRule WithDefaultCustoms() =>
+ securityRule.CustomCredential == null && securityRule.CustomExpandType == null && securityRule.CustomRestriction == null
+ ? securityRule
+ : securityRule with { CustomCredential = null, CustomExpandType = null, CustomRestriction = null };
}
@@ -178,7 +183,7 @@ public TSecurityRule ForceApply(SecurityRuleCredential? customCredential) =>
: securityRule with { CustomCredential = customCredential };
public TSecurityRule WithDefaultCredential() =>
- securityRule with { CustomCredential = null };
+ securityRule.CustomCredential == null ? securityRule : securityRule with { CustomCredential = null };
public TResult WithDefaultCredential(Func selector)
where TResult : SecurityRule =>
diff --git a/src/SecuritySystem.Abstractions/Services/ISecurityRolesIdentsResolver.cs b/src/SecuritySystem.Abstractions/Services/ISecurityRolesIdentsResolver.cs
index 8822fcd..f5b7cbd 100644
--- a/src/SecuritySystem.Abstractions/Services/ISecurityRolesIdentsResolver.cs
+++ b/src/SecuritySystem.Abstractions/Services/ISecurityRolesIdentsResolver.cs
@@ -1,6 +1,8 @@
-namespace SecuritySystem.Services;
+using System.Collections.Immutable;
+
+namespace SecuritySystem.Services;
public interface ISecurityRolesIdentsResolver
{
- IReadOnlyDictionary Resolve(DomainSecurityRule.RoleBaseSecurityRule securityRule, bool includeVirtual = false);
+ ImmutableDictionary Resolve(DomainSecurityRule.RoleBaseSecurityRule securityRule, bool includeVirtual = false);
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Configurator/Handlers/GetPrincipalHandler.cs b/src/SecuritySystem.Configurator/Handlers/GetPrincipalHandler.cs
index f902942..24a50a2 100644
--- a/src/SecuritySystem.Configurator/Handlers/GetPrincipalHandler.cs
+++ b/src/SecuritySystem.Configurator/Handlers/GetPrincipalHandler.cs
@@ -44,7 +44,8 @@ private async Task> GetPermissionsAsync(UserCredential userC
Role = permission.SecurityRole.Name,
RoleId = securityRoleSource.GetSecurityRole(permission.SecurityRole).Identity.GetId().ToString()!,
Comment = permission.Comment,
- StartDate = permission.Period.StartDate,
+ DelegatedFromId = permission.DelegatedFrom.GetId().ToString()!,
+ StartDate = permission.Period.StartDate,
EndDate = permission.Period.EndDate,
Contexts = permission
.Restrictions
diff --git a/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs b/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
index de4bdeb..ab91e5f 100644
--- a/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
+++ b/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
@@ -24,9 +24,9 @@ public async Task Execute(HttpContext context, CancellationToken cancellationTok
var permissions = await this.ParseRequestBodyAsync>(context);
- var typedPermissions = permissions.Select(this.ToManagedPermission).ToList();
+ var managedPermissions = permissions.Select(this.ToManagedPermission).ToList();
- var mergeResult = await principalManagementService.UpdatePermissionsAsync(context.ExtractSecurityIdentity(), typedPermissions, cancellationToken);
+ var mergeResult = await principalManagementService.UpdatePermissionsAsync(context.ExtractSecurityIdentity(), managedPermissions, cancellationToken);
if (configuratorIntegrationEvents != null)
{
@@ -66,6 +66,7 @@ from restriction in permission.Contexts
SecurityRole = securityRoleSource.GetSecurityRole(new UntypedSecurityIdentity(permission.RoleId)),
Period = new PermissionPeriod(permission.StartDate, permission.EndDate),
Comment = permission.Comment,
+ DelegatedFrom = new UntypedSecurityIdentity(permission.DelegatedFromId),
Restrictions = restrictionsRequest.ToImmutableDictionary()
};
}
@@ -86,6 +87,8 @@ private class RequestBodyDto
public List Contexts { get; set; } = default!;
+ public string DelegatedFromId { get; set; } = default!;
+
public class ContextDto
{
public string Id { get; set; } = default!;
diff --git a/src/SecuritySystem.Configurator/Models/PermissionDto.cs b/src/SecuritySystem.Configurator/Models/PermissionDto.cs
index e92f370..8cd4734 100644
--- a/src/SecuritySystem.Configurator/Models/PermissionDto.cs
+++ b/src/SecuritySystem.Configurator/Models/PermissionDto.cs
@@ -10,6 +10,8 @@ public record PermissionDto
public required string Comment { get; init; }
+ public required string DelegatedFromId { get; init; }
+
public required IReadOnlyList Contexts { get; init; }
public required DateTime? StartDate { get; init; }
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs b/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
index b8b0b16..3643c9f 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
@@ -40,8 +40,8 @@ public class GeneralPrincipalManagementService(
public Type PrincipalType => this.InnerService.PrincipalType;
- public Task CreatePrincipalAsync(string principalName, IEnumerable typedPermissions, CancellationToken cancellationToken = default) =>
- this.InnerService.CreatePrincipalAsync(principalName, typedPermissions, cancellationToken);
+ public Task CreatePrincipalAsync(string principalName, IEnumerable managedPermissions, CancellationToken cancellationToken = default) =>
+ this.InnerService.CreatePrincipalAsync(principalName, managedPermissions, cancellationToken);
public Task UpdatePrincipalNameAsync(UserCredential userCredential, string principalName, CancellationToken cancellationToken) =>
this.InnerService.UpdatePrincipalNameAsync(userCredential, principalName, cancellationToken);
@@ -50,8 +50,8 @@ public Task RemovePrincipalAsync(UserCredential userCredential, b
this.InnerService.RemovePrincipalAsync(userCredential, force, cancellationToken);
public Task> UpdatePermissionsAsync(UserCredential userCredential,
- IEnumerable typedPermissions, CancellationToken cancellationToken = default) =>
- this.InnerService.UpdatePermissionsAsync(userCredential, typedPermissions, cancellationToken);
+ IEnumerable managedPermissions, CancellationToken cancellationToken = default) =>
+ this.InnerService.UpdatePermissionsAsync(userCredential, managedPermissions, cancellationToken);
}
public class GeneralPrincipalManagementService(
@@ -66,20 +66,20 @@ public class GeneralPrincipalManagementService principalVisualIdentityInfo)
: IPrincipalManagementService
- where TPrincipal : class, new()
- where TPermission : class, new()
- where TPermissionRestriction : class, new()
+ where TPrincipal : class
+ where TPermission : class
+ where TPermissionRestriction : class
{
public Type PrincipalType { get; } = typeof(TPrincipal);
public async Task CreatePrincipalAsync(
string principalName,
- IEnumerable typedPermissions,
+ IEnumerable managedPermissions,
CancellationToken cancellationToken)
{
var principal = await principalDomainService.GetOrCreateAsync(principalName, cancellationToken);
- var result = await this.UpdatePermissionsAsync(principal, [], typedPermissions, cancellationToken);
+ var result = await this.UpdatePermissionsAsync(principal, [], managedPermissions, cancellationToken);
return new PrincipalData(principal,
result.AddingItems.Cast>());
@@ -115,38 +115,30 @@ private async Task await this.ToPermissionData(dbPermission, cancellationToken));
+ var permissionsData = await dbPermissions.SyncWhenAll(async dbPermission => await permissionRestrictionLoader.ToPermissionData(dbPermission, cancellationToken));
return new PrincipalData(dbPrincipal, permissionsData);
}
- private async Task> ToPermissionData(TPermission dbPermission,
- CancellationToken cancellationToken)
- {
- var dbRestrictions = await permissionRestrictionLoader.LoadAsync(dbPermission, cancellationToken);
-
- return new PermissionData(dbPermission, dbRestrictions);
- }
-
public async Task> UpdatePermissionsAsync(
UserCredential userCredential,
- IEnumerable typedPermissions,
+ IEnumerable managedPermissions,
CancellationToken cancellationToken)
{
var dbPrincipal = await principalUserSource.GetUserAsync(userCredential, cancellationToken);
var dbPermissions = await permissionLoader.LoadAsync(dbPrincipal, cancellationToken);
- return await this.UpdatePermissionsAsync(dbPrincipal, dbPermissions, typedPermissions, cancellationToken);
+ return await this.UpdatePermissionsAsync(dbPrincipal, dbPermissions, managedPermissions, cancellationToken);
}
private async Task> UpdatePermissionsAsync(
TPrincipal dbPrincipal,
List dbPermissions,
- IEnumerable typedPermissions,
+ IEnumerable managedPermissions,
CancellationToken cancellationToken)
{
- var permissionMergeResult = dbPermissions.GetMergeResult(typedPermissions, permissionIdentityExtractor.Extract,
+ var permissionMergeResult = dbPermissions.GetMergeResult(managedPermissions, permissionIdentityExtractor.Extract,
p => p.Identity.IsDefault ? new object() : permissionIdentityExtractor.Converter.Convert(p.Identity));
var newPermissions = await this.CreatePermissionsAsync(dbPrincipal, permissionMergeResult.AddingItems, cancellationToken);
@@ -155,7 +147,7 @@ private async Task> UpdatePermission
var removingPermissions = await permissionMergeResult.RemovingItems.SyncWhenAll(async oldDbPermission =>
{
- var result = await this.ToPermissionData(oldDbPermission, cancellationToken);
+ var result = await permissionRestrictionLoader.ToPermissionData(oldDbPermission, cancellationToken);
foreach (var dbRestriction in result.Restrictions)
{
@@ -180,10 +172,10 @@ await principalValidator.ValidateAsync(
private async Task[]> CreatePermissionsAsync(
TPrincipal dbPrincipal,
- IEnumerable typedPermissions,
+ IEnumerable managedPermissions,
CancellationToken cancellationToken)
{
- return await typedPermissions.SyncWhenAll(managedPermission => permissionManagementService.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken));
+ return await managedPermissions.SyncWhenAll(managedPermission => permissionManagementService.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken));
}
private async Task<(PermissionData PermissonData, bool Updated)[]> UpdatePermissionsAsync(
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/IPermissionRestrictionLoader.cs b/src/SecuritySystem.GeneralPermission.Runtime/IPermissionRestrictionLoader.cs
index fc3e7d4..ac22bb3 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/IPermissionRestrictionLoader.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/IPermissionRestrictionLoader.cs
@@ -1,6 +1,15 @@
-namespace SecuritySystem.GeneralPermission;
+using SecuritySystem.ExternalSystem.Management;
-public interface IPermissionRestrictionLoader
+namespace SecuritySystem.GeneralPermission;
+
+public interface IPermissionRestrictionLoader
{
Task> LoadAsync(TPermission permission, CancellationToken cancellationToken);
+
+ async Task> ToPermissionData(TPermission dbPermission, CancellationToken cancellationToken)
+ {
+ var dbRestrictions = await this.LoadAsync(dbPermission, cancellationToken);
+
+ return new PermissionData(dbPermission, dbRestrictions);
+ }
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs
index 2164c79..af159d2 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs
@@ -7,11 +7,13 @@
using SecuritySystem.Services;
using System.Collections.Immutable;
+using CommonFramework.IdentitySource;
namespace SecuritySystem.GeneralPermission;
public class PermissionManagementService(
IServiceProxyFactory serviceProxyFactory,
+ IIdentityInfoSource identityInfoSource,
IPermissionBindingInfoSource bindingInfoSource,
IGeneralPermissionBindingInfoSource generalBindingInfoSource,
IGeneralPermissionRestrictionBindingInfoSource restrictionBindingInfoSource)
@@ -25,20 +27,24 @@ public class PermissionManagementService)
+ var permissionIdentityInfo = identityInfoSource.GetIdentityInfo(bindingInfo.PermissionType);
+
+ var innerServiceType = typeof(PermissionManagementService<,,,,,,>)
.MakeGenericType(
bindingInfo.PrincipalType,
bindingInfo.PermissionType,
generalBindingInfo.SecurityRoleType,
restrictionBindingInfo.PermissionRestrictionType,
restrictionBindingInfo.SecurityContextTypeType,
- restrictionBindingInfo.SecurityContextObjectIdentType);
+ restrictionBindingInfo.SecurityContextObjectIdentType,
+ permissionIdentityInfo.IdentityType);
return serviceProxyFactory.Create>(
innerServiceType,
bindingInfo,
generalBindingInfo,
- restrictionBindingInfo);
+ restrictionBindingInfo,
+ permissionIdentityInfo);
});
private IPermissionManagementService InnerService => this.lazyInnerService.Value;
@@ -54,11 +60,11 @@ public virtual Task> CreateP
this.InnerService.UpdatePermission(dbPermission, managedPermission, cancellationToken);
}
-public class PermissionManagementService(
+public class PermissionManagementService(
PermissionBindingInfo bindingInfo,
GeneralPermissionBindingInfo generalBindingInfo,
GeneralPermissionRestrictionBindingInfo restrictionBindingInfo,
-
+ IdentityInfo permissionIdentityInfo,
IPermissionSecurityRoleResolver permissionSecurityRoleResolver,
IRawPermissionRestrictionLoader rawPermissionRestrictionLoader,
ISecurityIdentityExtractor permissionSecurityIdentityExtractor,
@@ -69,7 +75,10 @@ public class PermissionManagementService securityRoleIdentityExtractor,
ISecurityIdentityExtractor securityContextTypeIdentityExtractor,
- IGenericRepository genericRepository)
+ ISecurityIdentityExtractor permissionIdentityExtractor,
+ ISecurityIdentityConverter permissionIdentityConverter,
+ IGenericRepository genericRepository,
+ ISecurityRepository permissionRepository)
: IPermissionManagementService
where TPermission : class, new()
@@ -77,7 +86,7 @@ public class PermissionManagementService ToManagedPermissionAsync(TPermission dbPermission, CancellationToken cancellationToken) =>
new()
@@ -87,6 +96,9 @@ public async Task ToManagedPermissionAsync(TPermission dbPerm
SecurityRole = permissionSecurityRoleResolver.Resolve(dbPermission),
Period = bindingInfo.GetSafePeriod(dbPermission),
Comment = bindingInfo.GetSafeComment(dbPermission),
+ DelegatedFrom = bindingInfo.DelegatedFrom?.Getter.Invoke(dbPermission) is { } delegatedFromPermission
+ ? permissionIdentityExtractor.Extract(delegatedFromPermission)
+ : SecurityIdentity.Default,
Restrictions = (await rawPermissionRestrictionLoader.LoadAsync(dbPermission, cancellationToken)).ToImmutableDictionary()
};
@@ -95,9 +107,9 @@ public async Task> CreatePer
ManagedPermission managedPermission,
CancellationToken cancellationToken)
{
- if (!managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
+ if (managedPermission.IsVirtual || (!managedPermission.Identity.IsDefault && !managedPermission.ForceApplyIdentity))
{
- throw new SecuritySystemException("wrong typed permission");
+ throw new SecuritySystemException("wrong permission");
}
var securityRole = securityRoleSource.GetSecurityRole(managedPermission.SecurityRole);
@@ -106,6 +118,13 @@ public async Task> CreatePer
var newDbPermission = new TPermission();
+ if (!managedPermission.Identity.IsDefault)
+ {
+ var permissionIdentity = permissionIdentityConverter.Convert(managedPermission.Identity);
+
+ permissionIdentityInfo.Id.Setter.Invoke(newDbPermission, permissionIdentity.Id);
+ }
+
bindingInfo.Principal.Setter(newDbPermission, dbPrincipal);
generalBindingInfo.SecurityRole.Setter(newDbPermission, dbRole);
@@ -113,6 +132,15 @@ public async Task> CreatePer
bindingInfo.PermissionEndDate?.Setter(newDbPermission, managedPermission.Period.EndDate);
bindingInfo.PermissionComment?.Setter(newDbPermission, managedPermission.Comment);
+ if (!managedPermission.DelegatedFrom.IsDefault)
+ {
+ var delegatedFromAccessors = bindingInfo.DelegatedFrom ?? throw new InvalidOperationException("Delegated Permission Binding not initialized");
+
+ var delegatedFromPermission = await permissionRepository.GetObjectAsync(managedPermission.DelegatedFrom, cancellationToken);
+
+ delegatedFromAccessors.Setter(newDbPermission, delegatedFromPermission);
+ }
+
await genericRepository.SaveAsync(newDbPermission, cancellationToken);
var newPermissionRestrictions = await managedPermission.Restrictions.SyncWhenAll(async restrictionGroup =>
@@ -145,9 +173,21 @@ public async Task> CreatePer
ManagedPermission managedPermission,
CancellationToken cancellationToken)
{
- if (managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
+ if (managedPermission.IsVirtual || managedPermission.Identity.IsDefault)
+ {
+ throw new SecuritySystemException("wrong permission");
+ }
+
+ if (!managedPermission.DelegatedFrom.IsDefault)
{
- throw new SecuritySystemException("wrong typed permission");
+ var delegatedFromAccessors = bindingInfo.DelegatedFrom ?? throw new InvalidOperationException("Delegated Permission Binding not initialized");
+
+ var delegatedFromPermission = await permissionRepository.GetObjectAsync(managedPermission.DelegatedFrom, cancellationToken);
+
+ if (delegatedFromPermission != delegatedFromAccessors.Getter(dbPermission))
+ {
+ throw new InvalidOperationException("Delegated source can't be changed");
+ }
}
var securityRole = generalBindingInfo
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
index 2c02e59..712027c 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
@@ -36,12 +36,12 @@ public class ManagedPrincipalConverter ToManagedPrincipalAsync(TPrincipal principal, CancellationToken cancellationToken)
+ public async Task ToManagedPrincipalAsync(TPrincipal dbPrincipal, CancellationToken cancellationToken)
{
- var permissions = await permissionLoader.LoadAsync(principal, cancellationToken);
+ var dbPermissions = await permissionLoader.LoadAsync(dbPrincipal, cancellationToken);
- return new ManagedPrincipal(
- headerConverter.Convert(principal),
- await permissions.SyncWhenAll(permission => permissionManagementService.ToManagedPermissionAsync(permission, cancellationToken)));
+ var permissions = await dbPermissions.SyncWhenAll(permission => permissionManagementService.ToManagedPermissionAsync(permission, cancellationToken));
+
+ return new ManagedPrincipal(headerConverter.Convert(dbPrincipal), [..permissions]);
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/PermissionRestrictionTypeFilterFactory.cs b/src/SecuritySystem.GeneralPermission.Runtime/PermissionRestrictionTypeFilterFactory.cs
index 2af83fa..9f6e89b 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/PermissionRestrictionTypeFilterFactory.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/PermissionRestrictionTypeFilterFactory.cs
@@ -46,7 +46,7 @@ public class PermissionRestrictionTypeFilterFactory cache = new();
+ private readonly ConcurrentDictionary cache = [];
public Expression> CreateFilter()
where TSecurityContext : class, ISecurityContext
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/RawPermissionConverter.cs b/src/SecuritySystem.GeneralPermission.Runtime/RawPermissionConverter.cs
index e1ab73c..5142721 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/RawPermissionConverter.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/RawPermissionConverter.cs
@@ -62,7 +62,7 @@ public Dictionary ConvertPermission(DomainSecurityRule.RoleBaseSecu
private TSecurityContextObjectIdent[] ApplySecurityContextFilter(Array securityContextIdents, SecurityContextRestrictionFilterInfo restrictionFilterInfo)
{
- return new Func, IReadOnlyList>(
+ return new Func, TSecurityContextObjectIdent[]>(
this.ApplySecurityContextFilter)
.CreateGenericMethod(restrictionFilterInfo.SecurityContextType)
.Invoke(this, securityContextIdents, restrictionFilterInfo);
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/Validation/DisplayPermissionService.cs b/src/SecuritySystem.GeneralPermission.Runtime/Validation/DisplayPermissionService.cs
index 2512d24..aae546b 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/Validation/DisplayPermissionService.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/Validation/DisplayPermissionService.cs
@@ -32,11 +32,9 @@ public class DisplayPermissionService(
generalBindingInfo);
});
-
- public string ToString(PermissionData permissionData) => this.lazyInnerService.Value.ToString(permissionData);
+ public string Format(PermissionData permissionData) => this.lazyInnerService.Value.Format(permissionData);
}
-
public class DisplayPermissionService(
PermissionBindingInfo bindingInfo,
GeneralPermissionBindingInfo generalBindingInfo,
@@ -47,7 +45,7 @@ public class DisplayPermissionService
where TSecurityRole : class
{
- public string ToString(PermissionData permissionData)
+ public string Format(PermissionData permissionData)
{
return this.GetPermissionVisualParts(permissionData).Join(" | ");
}
@@ -72,11 +70,11 @@ private IEnumerable GetPermissionVisualParts(PermissionData v.Name).Join(", ")}";
+ yield return $"{securityContextInfo.Name}: {securityContextList.Select(v => v.Name).Join(", ")}";
}
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/Validation/IDisplayPermissionService.cs b/src/SecuritySystem.GeneralPermission.Runtime/Validation/IDisplayPermissionService.cs
index 30cee96..bea1795 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/Validation/IDisplayPermissionService.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/Validation/IDisplayPermissionService.cs
@@ -4,5 +4,5 @@ namespace SecuritySystem.GeneralPermission.Validation;
public interface IDisplayPermissionService
{
- string ToString(PermissionData permissionData);
+ string Format(PermissionData permissionData);
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/Validation/Permission/PermissionDelegationValidator.cs b/src/SecuritySystem.GeneralPermission.Runtime/Validation/Permission/PermissionDelegationValidator.cs
new file mode 100644
index 0000000..5bd4810
--- /dev/null
+++ b/src/SecuritySystem.GeneralPermission.Runtime/Validation/Permission/PermissionDelegationValidator.cs
@@ -0,0 +1,179 @@
+using CommonFramework;
+using CommonFramework.GenericRepository;
+using CommonFramework.VisualIdentitySource;
+
+using GenericQueryable;
+
+using HierarchicalExpand;
+
+using SecuritySystem.ExternalSystem.Management;
+using SecuritySystem.Services;
+using SecuritySystem.Validation;
+
+namespace SecuritySystem.GeneralPermission.Validation.Permission;
+
+public class PermissionDelegationValidator(
+ IServiceProxyFactory serviceProxyFactory,
+ IPermissionBindingInfoSource bindingInfoSource)
+ : IPermissionValidator
+{
+ private readonly Lazy> lazyInnerService = new(() =>
+ {
+ var bindingInfo = bindingInfoSource.GetForPermission(typeof(TPermission));
+
+ var innerServiceType = typeof(PermissionDelegationValidator<,,>)
+ .MakeGenericType(
+ bindingInfo.PrincipalType,
+ bindingInfo.PermissionType,
+ typeof(TPermissionRestriction));
+
+ return serviceProxyFactory.Create>(innerServiceType, bindingInfo);
+ });
+
+ public Task ValidateAsync(PermissionData value, CancellationToken cancellationToken) =>
+ this.lazyInnerService.Value.ValidateAsync(value, cancellationToken);
+}
+
+public class PermissionDelegationValidator(
+ PermissionBindingInfo permissionBindingInfo,
+ IPermissionRestrictionRawConverter permissionRestrictionRawConverter,
+ IDomainObjectDisplayService domainObjectDisplayService,
+ ISecurityContextInfoSource securityContextInfoSource,
+ ISecurityRoleSource securityRoleSource,
+ IPermissionSecurityRoleResolver permissionSecurityRoleResolver,
+ IQueryableSource queryableSource,
+ IPermissionRestrictionLoader permissionRestrictionLoader,
+ IHierarchicalObjectExpanderFactory hierarchicalObjectExpanderFactory)
+ : IPermissionValidator
+
+ where TPrincipal : class
+ where TPermission : class
+{
+ public async Task ValidateAsync(PermissionData permissionData, CancellationToken cancellationToken)
+ {
+ if (permissionBindingInfo.DelegatedFrom == null)
+ {
+ return;
+ }
+
+ var permission = permissionData.Permission;
+
+ var delegatedFrom = permissionBindingInfo.DelegatedFrom.Getter(permission);
+
+ if (delegatedFrom != null)
+ {
+ if (permissionBindingInfo.Principal.Getter(delegatedFrom) == permissionBindingInfo.Principal.Getter(permission))
+ {
+ throw new SecuritySystemValidationException("Invalid delegation target: the permission cannot be delegated to its original principal");
+ }
+
+ var delegatedFromData = await permissionRestrictionLoader.ToPermissionData(delegatedFrom, cancellationToken);
+
+ this.ValidatePermissionDelegatedFrom(permissionData, delegatedFromData);
+ }
+
+ var subPermissions = await queryableSource
+ .GetQueryable()
+ .Where(permissionBindingInfo.DelegatedFrom.Path.Select(p => p == permission))
+ .GenericToListAsync(cancellationToken);
+
+ foreach (var subPermission in subPermissions)
+ {
+ var subPermissionData = await permissionRestrictionLoader.ToPermissionData(subPermission, cancellationToken);
+
+ this.ValidatePermissionDelegatedFrom(subPermissionData, permissionData);
+ }
+ }
+
+ private void ValidatePermissionDelegatedFrom(
+ PermissionData subPermissionData,
+ PermissionData delegatedFromData)
+ {
+ var subPermission = subPermissionData.Permission;
+ var delegatedFrom = delegatedFromData.Permission;
+
+ if (!this.IsCorrectRoleSubset(subPermission, delegatedFrom))
+ {
+ throw new SecuritySystemValidationException(
+ $"Invalid delegated permission role: the selected role \"{permissionSecurityRoleResolver.Resolve(subPermission)}\" is not a subset of \"{permissionSecurityRoleResolver.Resolve(delegatedFrom)}\"");
+ }
+
+ if (!this.IsCorrectPeriodSubset(subPermission, delegatedFrom))
+ {
+ throw new SecuritySystemValidationException(
+ $"Invalid delegated permission period: the selected period \"{permissionBindingInfo.GetSafePeriod(subPermission)}\" is not a subset of \"{permissionBindingInfo.GetSafePeriod(delegatedFrom)}\"");
+ }
+
+ {
+ var invalidSecurityContextDict = this.GetInvalidSecurityContextDict(subPermissionData, delegatedFromData).ToList();
+
+ if (invalidSecurityContextDict.Any())
+ {
+ throw new SecuritySystemValidationException(
+ string.Format(
+ "Invalid security context delegation: the security contexts of \"{1}\" exceed those granted by \"{0}\": {2}",
+ domainObjectDisplayService.ToString(permissionBindingInfo.Principal.Getter(delegatedFrom)),
+ domainObjectDisplayService.ToString(permissionBindingInfo.Principal.Getter(subPermission)),
+ invalidSecurityContextDict.Join(
+ " | ",
+ g =>
+ {
+ var invalidValues = g.Value.Length == 0
+ ? "Unrestricted"
+ : g.Value.OfType