diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index d7c1557..3040008 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -3,24 +3,24 @@
true
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs
index 9be7bfd..836aaba 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionData.cs
@@ -1,4 +1,6 @@
-namespace SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
+
+namespace SecuritySystem.ExternalSystem.Management;
public record ManagedPermissionData
{
@@ -8,7 +10,9 @@ public record ManagedPermissionData
public string Comment { get; init; } = "";
- public IReadOnlyDictionary Restrictions { get; init; } = new Dictionary();
+ 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
new file mode 100644
index 0000000..af635d5
--- /dev/null
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPermissionDataExtensions.cs
@@ -0,0 +1,16 @@
+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/ManagedPrincipalHeader.cs b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipalHeader.cs
index a0a5701..1d7c1db 100644
--- a/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipalHeader.cs
+++ b/src/SecuritySystem.Abstractions/ExternalSystem/Management/ManagedPrincipalHeader.cs
@@ -1,3 +1,3 @@
namespace SecuritySystem.ExternalSystem.Management;
-public record ManagedPrincipalHeader(SecurityIdentity Identity, string Name, bool IsVirtual);
+public record ManagedPrincipalHeader(SecurityIdentity Identity, string Name, bool IsVirtual);
\ No newline at end of file
diff --git a/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs b/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
index 9929c1b..de4bdeb 100644
--- a/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
+++ b/src/SecuritySystem.Configurator/Handlers/UpdatePermissionsHandler.cs
@@ -1,4 +1,6 @@
-using Microsoft.AspNetCore.Http;
+using CommonFramework;
+
+using Microsoft.AspNetCore.Http;
using SecuritySystem.Attributes;
using SecuritySystem.Configurator.Interfaces;
@@ -64,7 +66,7 @@ from restriction in permission.Contexts
SecurityRole = securityRoleSource.GetSecurityRole(new UntypedSecurityIdentity(permission.RoleId)),
Period = new PermissionPeriod(permission.StartDate, permission.EndDate),
Comment = permission.Comment,
- Restrictions = restrictionsRequest.ToDictionary()
+ Restrictions = restrictionsRequest.ToImmutableDictionary()
};
}
diff --git a/src/SecuritySystem.DiTests/Services/ExamplePermissionSource.cs b/src/SecuritySystem.DiTests/Services/ExamplePermissionSource.cs
index 5f83200..82487ff 100644
--- a/src/SecuritySystem.DiTests/Services/ExamplePermissionSource.cs
+++ b/src/SecuritySystem.DiTests/Services/ExamplePermissionSource.cs
@@ -4,7 +4,7 @@
namespace SecuritySystem.DiTests.Services;
-public class ExamplePermissionSource(TestPermissionData data, DomainSecurityRule.ExpandedRoleGroupSecurityRule securityRule) : IPermissionSource
+public class ExamplePermissionSource(TestPermissions data, DomainSecurityRule.ExpandedRoleGroupSecurityRule securityRule) : IPermissionSource
{
public bool HasAccess() => throw new NotImplementedException();
diff --git a/src/SecuritySystem.DiTests/Services/ExamplePermissionSystem.cs b/src/SecuritySystem.DiTests/Services/ExamplePermissionSystem.cs
index f0cd340..54fbc7c 100644
--- a/src/SecuritySystem.DiTests/Services/ExamplePermissionSystem.cs
+++ b/src/SecuritySystem.DiTests/Services/ExamplePermissionSystem.cs
@@ -3,7 +3,7 @@
namespace SecuritySystem.DiTests.Services;
-public class ExamplePermissionSystem(ISecurityRuleExpander securityRuleExpander, TestPermissionData data) : IPermissionSystem
+public class ExamplePermissionSystem(ISecurityRuleExpander securityRuleExpander, TestPermissions data) : IPermissionSystem
{
public Type PermissionType => throw new NotImplementedException();
diff --git a/src/SecuritySystem.DiTests/Services/ExamplePermissionSystemFactory.cs b/src/SecuritySystem.DiTests/Services/ExamplePermissionSystemFactory.cs
index 4168396..bae08fc 100644
--- a/src/SecuritySystem.DiTests/Services/ExamplePermissionSystemFactory.cs
+++ b/src/SecuritySystem.DiTests/Services/ExamplePermissionSystemFactory.cs
@@ -3,7 +3,7 @@
namespace SecuritySystem.DiTests.Services;
-public class ExamplePermissionSystemFactory(ISecurityRuleExpander securityRuleExpander, TestPermissionData data) : IPermissionSystemFactory
+public class ExamplePermissionSystemFactory(ISecurityRuleExpander securityRuleExpander, TestPermissions data) : IPermissionSystemFactory
{
public IPermissionSystem Create(SecurityRuleCredential securityRuleCredential) =>
new ExamplePermissionSystem(securityRuleExpander, data);
diff --git a/src/SecuritySystem.DiTests/Services/TestPermissionData.cs b/src/SecuritySystem.DiTests/Services/TestPermissionData.cs
deleted file mode 100644
index 0d8114b..0000000
--- a/src/SecuritySystem.DiTests/Services/TestPermissionData.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace SecuritySystem.DiTests.Services;
-
-public record TestPermissionData(List Permissions);
diff --git a/src/SecuritySystem.DiTests/Services/TestPermissions.cs b/src/SecuritySystem.DiTests/Services/TestPermissions.cs
new file mode 100644
index 0000000..a6cf187
--- /dev/null
+++ b/src/SecuritySystem.DiTests/Services/TestPermissions.cs
@@ -0,0 +1,3 @@
+namespace SecuritySystem.DiTests.Services;
+
+public record TestPermissions(List Permissions);
diff --git a/src/SecuritySystem.DiTests/TestBase.cs b/src/SecuritySystem.DiTests/TestBase.cs
index e812f0e..0cb22bf 100644
--- a/src/SecuritySystem.DiTests/TestBase.cs
+++ b/src/SecuritySystem.DiTests/TestBase.cs
@@ -94,6 +94,6 @@ protected virtual IServiceCollection CreateServices(IServiceCollection serviceCo
.AddRelativeDomainPath((Employee employee) => employee)
.AddSingleton(typeof(TestCheckboxConditionFactory<>))
- .AddSingleton(_ => new TestPermissionData(this.GetPermissions().ToList()));
+ .AddSingleton(_ => new TestPermissions(this.GetPermissions().ToList()));
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs b/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
index badafc3..b8b0b16 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/GeneralPrincipalManagementService.cs
@@ -2,8 +2,6 @@
using CommonFramework.GenericRepository;
using CommonFramework.VisualIdentitySource;
-using GenericQueryable;
-
using Microsoft.Extensions.DependencyInjection;
using SecuritySystem.Credential;
@@ -18,35 +16,24 @@ public class GeneralPrincipalManagementService(
IServiceProxyFactory serviceProxyFactory,
IVisualIdentityInfoSource visualIdentityInfoSource,
IEnumerable bindingInfoList,
- IGeneralPermissionBindingInfoSource generalBindingInfoSource,
IGeneralPermissionRestrictionBindingInfoSource restrictionBindingInfoSource)
: IPrincipalManagementService
{
private readonly Lazy lazyInnerService = new(() =>
{
- var bindingInfo = bindingInfoList.Single(bi => !bi.IsReadonly);
-
- var generalBindingInfo = generalBindingInfoSource.GetForPermission(bindingInfo.PermissionType);
+ var bindingInfo = bindingInfoList.Single(
+ bi => !bi.IsReadonly,
+ () => new InvalidOperationException("No writable management service was found"),
+ () => new InvalidOperationException("Multiple writable management services were found"));
var restrictionBindingInfo = restrictionBindingInfoSource.GetForPermission(bindingInfo.PermissionType);
var principalVisualIdentityInfo = visualIdentityInfoSource.GetVisualIdentityInfo(bindingInfo.PrincipalType);
- var innerServiceType = typeof(GeneralPrincipalManagementService<,,,,,>)
- .MakeGenericType(
- bindingInfo.PrincipalType,
- bindingInfo.PermissionType,
- generalBindingInfo.SecurityRoleType,
- restrictionBindingInfo.PermissionRestrictionType,
- restrictionBindingInfo.SecurityContextTypeType,
- restrictionBindingInfo.SecurityContextObjectIdentType);
-
- return serviceProxyFactory.Create(
- innerServiceType,
- bindingInfo,
- generalBindingInfo,
- restrictionBindingInfo,
- principalVisualIdentityInfo);
+ var innerServiceType = typeof(GeneralPrincipalManagementService<,,>)
+ .MakeGenericType(bindingInfo.PrincipalType, bindingInfo.PermissionType, restrictionBindingInfo.PermissionRestrictionType);
+
+ return serviceProxyFactory.Create(innerServiceType, principalVisualIdentityInfo);
});
private IPrincipalManagementService InnerService => this.lazyInnerService.Value;
@@ -67,34 +54,21 @@ public Task> UpdatePermissionsAsync(
this.InnerService.UpdatePermissionsAsync(userCredential, typedPermissions, cancellationToken);
}
-public class GeneralPrincipalManagementService(
- PermissionBindingInfo bindingInfo,
- GeneralPermissionBindingInfo generalBindingInfo,
- GeneralPermissionRestrictionBindingInfo restrictionBindingInfo,
- ISecurityRepository securityRoleRepository,
- IQueryableSource queryableSource,
+public class GeneralPrincipalManagementService(
[FromKeyedServices("Root")] IPrincipalValidator principalValidator,
- ISecurityRoleSource securityRoleSource,
IGenericRepository genericRepository,
- ISecurityRepository securityContextTypeRepository,
- ISecurityContextInfoSource securityContextInfoSource,
IPrincipalDomainService principalDomainService,
IUserSource principalUserSource,
ISecurityIdentityExtractor permissionIdentityExtractor,
- ISecurityIdentityExtractor securityRoleIdentityExtractor,
- ISecurityIdentityExtractor securityContextTypeIdentityExtractor,
IPermissionLoader permissionLoader,
IPermissionRestrictionLoader permissionRestrictionLoader,
+ IPermissionManagementService permissionManagementService,
VisualIdentityInfo principalVisualIdentityInfo)
: IPrincipalManagementService
where TPrincipal : class, new()
where TPermission : class, new()
- where TSecurityRole : class
where TPermissionRestriction : class, new()
- where TSecurityContextType : class
- where TSecurityContextObjectIdent : notnull
{
public Type PrincipalType { get; } = typeof(TPrincipal);
@@ -209,141 +183,13 @@ private async Task[]> Create
IEnumerable typedPermissions,
CancellationToken cancellationToken)
{
- return await typedPermissions.SyncWhenAll(managedPermission => this.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken));
- }
-
- private async Task> CreatePermissionAsync(
- TPrincipal dbPrincipal,
- ManagedPermission managedPermission,
- CancellationToken cancellationToken)
- {
- if (!managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
- {
- throw new SecuritySystemException("wrong typed permission");
- }
-
- var securityRole = securityRoleSource.GetSecurityRole(managedPermission.SecurityRole);
-
- var dbRole = await securityRoleRepository.GetObjectAsync(securityRole.Identity, cancellationToken);
-
- var newDbPermission = new TPermission();
-
- bindingInfo.Principal.Setter(newDbPermission, dbPrincipal);
- generalBindingInfo.SecurityRole.Setter(newDbPermission, dbRole);
-
- bindingInfo.PermissionStartDate?.Setter(newDbPermission, managedPermission.Period.StartDate);
- bindingInfo.PermissionEndDate?.Setter(newDbPermission, managedPermission.Period.EndDate);
- bindingInfo.PermissionComment?.Setter(newDbPermission, managedPermission.Comment);
-
- await genericRepository.SaveAsync(newDbPermission, cancellationToken);
-
- var newPermissionRestrictions = await managedPermission.Restrictions.SyncWhenAll(async restrictionGroup =>
- {
- var securityContextTypeIdentity = securityContextInfoSource.GetSecurityContextInfo(restrictionGroup.Key).Identity;
-
- var dbSecurityContextType = await securityContextTypeRepository.GetObjectAsync(securityContextTypeIdentity, cancellationToken);
-
- var newPermissionRestrictions = await restrictionGroup.Value.Cast().SyncWhenAll(async securityContextId =>
- {
- var newDbPermissionRestriction = new TPermissionRestriction();
-
- restrictionBindingInfo.Permission.Setter(newDbPermissionRestriction, newDbPermission);
- restrictionBindingInfo.SecurityContextObjectId.Setter(newDbPermissionRestriction, securityContextId);
- restrictionBindingInfo.SecurityContextType.Setter(newDbPermissionRestriction, dbSecurityContextType);
-
- await genericRepository.SaveAsync(newDbPermissionRestriction, cancellationToken);
-
- return newDbPermissionRestriction;
- });
-
- return newPermissionRestrictions;
- });
-
- return new PermissionData(newDbPermission, newPermissionRestrictions.SelectMany());
+ return await typedPermissions.SyncWhenAll(managedPermission => permissionManagementService.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken));
}
private async Task<(PermissionData PermissonData, bool Updated)[]> UpdatePermissionsAsync(
IReadOnlyList<(TPermission, ManagedPermission)> permissionPairs,
CancellationToken cancellationToken)
{
- return await permissionPairs.SyncWhenAll(permissionPair => this.UpdatePermission(permissionPair.Item1, permissionPair.Item2, cancellationToken));
- }
-
- private async Task<(PermissionData PermissonData, bool Updated)> UpdatePermission(TPermission dbPermission,
- ManagedPermission managedPermission, CancellationToken cancellationToken)
- {
- if (managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
- {
- throw new SecuritySystemException("wrong typed permission");
- }
-
- var securityRole = generalBindingInfo
- .SecurityRole
- .Getter(dbPermission)
- .Pipe(securityRoleIdentityExtractor.Extract)
- .Pipe(securityRoleSource.GetSecurityRole);
-
- if (securityRole != managedPermission.SecurityRole)
- {
- throw new SecuritySystemException("Permission role can't be changed");
- }
-
- var dbRestrictions = await queryableSource.GetQueryable()
- .Where(restrictionBindingInfo.Permission.Path.Select(p => p == dbPermission))
- .GenericToListAsync(cancellationToken);
-
- var restrictionMergeResult = dbRestrictions.GetMergeResult(
- managedPermission.Restrictions
- .ChangeKey(t => securityContextInfoSource.GetSecurityContextInfo(t).Identity)
- .SelectMany(pair => pair.Value.Cast().Select(securityContextId => (pair.Key, securityContextId))),
- pr => (
- securityContextTypeIdentityExtractor.Extract(restrictionBindingInfo.SecurityContextType.Getter(pr)),
- restrictionBindingInfo.SecurityContextObjectId.Getter(pr)),
- pair => pair);
-
- if (restrictionMergeResult.IsEmpty
- && (bindingInfo.PermissionComment == null || bindingInfo.PermissionComment.Getter(dbPermission) == managedPermission.Comment)
- && (bindingInfo.PermissionStartDate == null || bindingInfo.PermissionStartDate.Getter(dbPermission) == managedPermission.Period.StartDate)
- && (bindingInfo.PermissionEndDate == null || bindingInfo.PermissionEndDate.Getter(dbPermission) == managedPermission.Period.EndDate))
- {
- var permissionData = new PermissionData(dbPermission,
- restrictionMergeResult.CombineItems.Select(v => v.Item1));
-
- return (permissionData, false);
- }
- else
- {
- bindingInfo.PermissionComment?.Setter.Invoke(dbPermission, managedPermission.Comment);
- bindingInfo.PermissionStartDate?.Setter.Invoke(dbPermission, managedPermission.Period.StartDate);
- bindingInfo.PermissionEndDate?.Setter.Invoke(dbPermission, managedPermission.Period.EndDate);
-
- var newPermissionRestrictions = await restrictionMergeResult.AddingItems.SyncWhenAll(async restriction =>
- {
- var newPermissionRestriction = new TPermissionRestriction();
-
- var dbSecurityContextType =
- await securityContextTypeRepository.GetObjectAsync(restriction.Key, cancellationToken);
-
- restrictionBindingInfo.Permission.Setter(newPermissionRestriction, dbPermission);
- restrictionBindingInfo.SecurityContextObjectId.Setter(newPermissionRestriction, restriction.securityContextId);
- restrictionBindingInfo.SecurityContextType.Setter(newPermissionRestriction, dbSecurityContextType);
-
- await genericRepository.SaveAsync(newPermissionRestriction, cancellationToken);
-
- return newPermissionRestriction;
- });
-
- foreach (var dbRestriction in restrictionMergeResult.RemovingItems)
- {
- await genericRepository.RemoveAsync(dbRestriction, cancellationToken);
- }
-
- var permissionData = new PermissionData(dbPermission,
- restrictionMergeResult.CombineItems.Select(v => v.Item1).Concat(newPermissionRestrictions));
-
- await genericRepository.SaveAsync(dbPermission, cancellationToken);
-
- return (permissionData, true);
- }
+ return await permissionPairs.SyncWhenAll(permissionPair => permissionManagementService.UpdatePermission(permissionPair.Item1, permissionPair.Item2, cancellationToken));
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/IPermissionManagementService.cs b/src/SecuritySystem.GeneralPermission.Runtime/IPermissionManagementService.cs
new file mode 100644
index 0000000..7ad788a
--- /dev/null
+++ b/src/SecuritySystem.GeneralPermission.Runtime/IPermissionManagementService.cs
@@ -0,0 +1,18 @@
+using SecuritySystem.ExternalSystem.Management;
+
+namespace SecuritySystem.GeneralPermission;
+
+public interface IPermissionManagementService
+{
+ Task ToManagedPermissionAsync(TPermission permission, CancellationToken cancellationToken);
+
+ Task> CreatePermissionAsync(
+ TPrincipal dbPrincipal,
+ ManagedPermission managedPermission,
+ CancellationToken cancellationToken);
+
+ Task<(PermissionData PermissonData, bool Updated)> UpdatePermission(
+ TPermission dbPermission,
+ ManagedPermission managedPermission,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs
new file mode 100644
index 0000000..2164c79
--- /dev/null
+++ b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPermissionMapper.cs
@@ -0,0 +1,222 @@
+using CommonFramework;
+using CommonFramework.GenericRepository;
+
+using GenericQueryable;
+
+using SecuritySystem.ExternalSystem.Management;
+using SecuritySystem.Services;
+
+using System.Collections.Immutable;
+
+namespace SecuritySystem.GeneralPermission;
+
+public class PermissionManagementService(
+ IServiceProxyFactory serviceProxyFactory,
+ IPermissionBindingInfoSource bindingInfoSource,
+ IGeneralPermissionBindingInfoSource generalBindingInfoSource,
+ IGeneralPermissionRestrictionBindingInfoSource restrictionBindingInfoSource)
+ : IPermissionManagementService
+{
+ private readonly Lazy> lazyInnerService = new(() =>
+ {
+ var bindingInfo = bindingInfoSource.GetForPermission(typeof(TPermission));
+
+ var generalBindingInfo = generalBindingInfoSource.GetForPermission(bindingInfo.PermissionType);
+
+ var restrictionBindingInfo = restrictionBindingInfoSource.GetForPermission(bindingInfo.PermissionType);
+
+ var innerServiceType = typeof(PermissionManagementService<,,,,,>)
+ .MakeGenericType(
+ bindingInfo.PrincipalType,
+ bindingInfo.PermissionType,
+ generalBindingInfo.SecurityRoleType,
+ restrictionBindingInfo.PermissionRestrictionType,
+ restrictionBindingInfo.SecurityContextTypeType,
+ restrictionBindingInfo.SecurityContextObjectIdentType);
+
+ return serviceProxyFactory.Create>(
+ innerServiceType,
+ bindingInfo,
+ generalBindingInfo,
+ restrictionBindingInfo);
+ });
+
+ private IPermissionManagementService InnerService => this.lazyInnerService.Value;
+
+ public virtual Task ToManagedPermissionAsync(TPermission dbPermission, CancellationToken cancellationToken) =>
+ this.InnerService.ToManagedPermissionAsync(dbPermission, cancellationToken);
+
+ public virtual Task> CreatePermissionAsync(TPrincipal dbPrincipal, ManagedPermission managedPermission, CancellationToken cancellationToken) =>
+ this.InnerService.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken);
+
+ public virtual Task<(PermissionData PermissonData, bool Updated)> UpdatePermission(TPermission dbPermission,
+ ManagedPermission managedPermission, CancellationToken cancellationToken) =>
+ this.InnerService.UpdatePermission(dbPermission, managedPermission, cancellationToken);
+}
+
+public class PermissionManagementService(
+ PermissionBindingInfo bindingInfo,
+ GeneralPermissionBindingInfo generalBindingInfo,
+ GeneralPermissionRestrictionBindingInfo restrictionBindingInfo,
+
+ IPermissionSecurityRoleResolver permissionSecurityRoleResolver,
+ IRawPermissionRestrictionLoader rawPermissionRestrictionLoader,
+ ISecurityIdentityExtractor permissionSecurityIdentityExtractor,
+ ISecurityRepository securityRoleRepository,
+ IQueryableSource queryableSource,
+ ISecurityRoleSource securityRoleSource,
+ ISecurityRepository securityContextTypeRepository,
+ ISecurityContextInfoSource securityContextInfoSource,
+ ISecurityIdentityExtractor securityRoleIdentityExtractor,
+ ISecurityIdentityExtractor securityContextTypeIdentityExtractor,
+ IGenericRepository genericRepository)
+ : IPermissionManagementService
+
+ where TPermission : class, new()
+ where TSecurityRole : class
+ where TPermissionRestriction : class, new()
+ where TSecurityContextType : class
+ where TSecurityContextObjectIdent : notnull
+
+{
+ public async Task ToManagedPermissionAsync(TPermission dbPermission, CancellationToken cancellationToken) =>
+ new()
+ {
+ Identity = permissionSecurityIdentityExtractor.Extract(dbPermission),
+ IsVirtual = bindingInfo.IsReadonly,
+ SecurityRole = permissionSecurityRoleResolver.Resolve(dbPermission),
+ Period = bindingInfo.GetSafePeriod(dbPermission),
+ Comment = bindingInfo.GetSafeComment(dbPermission),
+ Restrictions = (await rawPermissionRestrictionLoader.LoadAsync(dbPermission, cancellationToken)).ToImmutableDictionary()
+ };
+
+ public async Task> CreatePermissionAsync(
+ TPrincipal dbPrincipal,
+ ManagedPermission managedPermission,
+ CancellationToken cancellationToken)
+ {
+ if (!managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
+ {
+ throw new SecuritySystemException("wrong typed permission");
+ }
+
+ var securityRole = securityRoleSource.GetSecurityRole(managedPermission.SecurityRole);
+
+ var dbRole = await securityRoleRepository.GetObjectAsync(securityRole.Identity, cancellationToken);
+
+ var newDbPermission = new TPermission();
+
+ bindingInfo.Principal.Setter(newDbPermission, dbPrincipal);
+ generalBindingInfo.SecurityRole.Setter(newDbPermission, dbRole);
+
+ bindingInfo.PermissionStartDate?.Setter(newDbPermission, managedPermission.Period.StartDate);
+ bindingInfo.PermissionEndDate?.Setter(newDbPermission, managedPermission.Period.EndDate);
+ bindingInfo.PermissionComment?.Setter(newDbPermission, managedPermission.Comment);
+
+ await genericRepository.SaveAsync(newDbPermission, cancellationToken);
+
+ var newPermissionRestrictions = await managedPermission.Restrictions.SyncWhenAll(async restrictionGroup =>
+ {
+ var securityContextTypeIdentity = securityContextInfoSource.GetSecurityContextInfo(restrictionGroup.Key).Identity;
+
+ var dbSecurityContextType = await securityContextTypeRepository.GetObjectAsync(securityContextTypeIdentity, cancellationToken);
+
+ var newPermissionRestrictions = await restrictionGroup.Value.Cast().SyncWhenAll(async securityContextId =>
+ {
+ var newDbPermissionRestriction = new TPermissionRestriction();
+
+ restrictionBindingInfo.Permission.Setter(newDbPermissionRestriction, newDbPermission);
+ restrictionBindingInfo.SecurityContextObjectId.Setter(newDbPermissionRestriction, securityContextId);
+ restrictionBindingInfo.SecurityContextType.Setter(newDbPermissionRestriction, dbSecurityContextType);
+
+ await genericRepository.SaveAsync(newDbPermissionRestriction, cancellationToken);
+
+ return newDbPermissionRestriction;
+ });
+
+ return newPermissionRestrictions;
+ });
+
+ return new PermissionData(newDbPermission, newPermissionRestrictions.SelectMany());
+ }
+
+ public async Task<(PermissionData PermissonData, bool Updated)> UpdatePermission(
+ TPermission dbPermission,
+ ManagedPermission managedPermission,
+ CancellationToken cancellationToken)
+ {
+ if (managedPermission.Identity.IsDefault || managedPermission.IsVirtual)
+ {
+ throw new SecuritySystemException("wrong typed permission");
+ }
+
+ var securityRole = generalBindingInfo
+ .SecurityRole
+ .Getter(dbPermission)
+ .Pipe(securityRoleIdentityExtractor.Extract)
+ .Pipe(securityRoleSource.GetSecurityRole);
+
+ if (securityRole != managedPermission.SecurityRole)
+ {
+ throw new SecuritySystemException("Permission role can't be changed");
+ }
+
+ var dbRestrictions = await queryableSource.GetQueryable()
+ .Where(restrictionBindingInfo.Permission.Path.Select(p => p == dbPermission))
+ .GenericToListAsync(cancellationToken);
+
+ var restrictionMergeResult = dbRestrictions.GetMergeResult(
+ managedPermission.Restrictions
+ .ChangeKey(t => securityContextInfoSource.GetSecurityContextInfo(t).Identity)
+ .SelectMany(pair => pair.Value.Cast().Select(securityContextId => (pair.Key, securityContextId))),
+ pr => (
+ securityContextTypeIdentityExtractor.Extract(restrictionBindingInfo.SecurityContextType.Getter(pr)),
+ restrictionBindingInfo.SecurityContextObjectId.Getter(pr)),
+ pair => pair);
+
+ if (restrictionMergeResult.IsEmpty
+ && (bindingInfo.PermissionComment == null || bindingInfo.PermissionComment.Getter(dbPermission) == managedPermission.Comment)
+ && (bindingInfo.PermissionStartDate == null || bindingInfo.PermissionStartDate.Getter(dbPermission) == managedPermission.Period.StartDate)
+ && (bindingInfo.PermissionEndDate == null || bindingInfo.PermissionEndDate.Getter(dbPermission) == managedPermission.Period.EndDate))
+ {
+ var permissionData = new PermissionData(dbPermission,
+ restrictionMergeResult.CombineItems.Select(v => v.Item1));
+
+ return (permissionData, false);
+ }
+ else
+ {
+ bindingInfo.PermissionComment?.Setter.Invoke(dbPermission, managedPermission.Comment);
+ bindingInfo.PermissionStartDate?.Setter.Invoke(dbPermission, managedPermission.Period.StartDate);
+ bindingInfo.PermissionEndDate?.Setter.Invoke(dbPermission, managedPermission.Period.EndDate);
+
+ var newPermissionRestrictions = await restrictionMergeResult.AddingItems.SyncWhenAll(async restriction =>
+ {
+ var newPermissionRestriction = new TPermissionRestriction();
+
+ var dbSecurityContextType =
+ await securityContextTypeRepository.GetObjectAsync(restriction.Key, cancellationToken);
+
+ restrictionBindingInfo.Permission.Setter(newPermissionRestriction, dbPermission);
+ restrictionBindingInfo.SecurityContextObjectId.Setter(newPermissionRestriction, restriction.securityContextId);
+ restrictionBindingInfo.SecurityContextType.Setter(newPermissionRestriction, dbSecurityContextType);
+
+ await genericRepository.SaveAsync(newPermissionRestriction, cancellationToken);
+
+ return newPermissionRestriction;
+ });
+
+ foreach (var dbRestriction in restrictionMergeResult.RemovingItems)
+ {
+ await genericRepository.RemoveAsync(dbRestriction, cancellationToken);
+ }
+
+ var permissionData = new PermissionData(dbPermission,
+ restrictionMergeResult.CombineItems.Select(v => v.Item1).Concat(newPermissionRestrictions));
+
+ await genericRepository.SaveAsync(dbPermission, cancellationToken);
+
+ return (permissionData, true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
index 83edf8b..2c02e59 100644
--- a/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
+++ b/src/SecuritySystem.GeneralPermission.Runtime/ManagedPrincipalConverter.cs
@@ -5,31 +5,34 @@
namespace SecuritySystem.GeneralPermission;
-public class ManagedPrincipalConverter(IServiceProxyFactory serviceProxyFactory, IPermissionBindingInfoSource bindingInfoSource)
+public class ManagedPrincipalConverter(
+ IServiceProxyFactory serviceProxyFactory,
+ IPermissionBindingInfoSource bindingInfoSource,
+ IGeneralPermissionRestrictionBindingInfoSource restrictionBindingInfoSource)
: IManagedPrincipalConverter
{
private readonly Lazy> lazyInnerService = new(() =>
{
var bindingInfo = bindingInfoSource.GetForPrincipal(typeof(TPrincipal));
- var innerServiceType = typeof(ManagedPrincipalConverter<,>).MakeGenericType(
+ var restrictionBindingInfo = restrictionBindingInfoSource.GetForPermission(bindingInfo.PermissionType);
+
+ var innerServiceType = typeof(ManagedPrincipalConverter<,,>).MakeGenericType(
bindingInfo.PrincipalType,
- bindingInfo.PermissionType);
+ bindingInfo.PermissionType,
+ restrictionBindingInfo.PermissionRestrictionType);
- return serviceProxyFactory.Create>(innerServiceType, bindingInfo);
+ return serviceProxyFactory.Create>(innerServiceType);
});
public Task ToManagedPrincipalAsync(TPrincipal principal, CancellationToken cancellationToken) =>
this.lazyInnerService.Value.ToManagedPrincipalAsync(principal, cancellationToken);
}
-public class ManagedPrincipalConverter(
- PermissionBindingInfo bindingInfo,
+public class ManagedPrincipalConverter(
IManagedPrincipalHeaderConverter headerConverter,
- IPermissionSecurityRoleResolver permissionSecurityRoleResolver,
IPermissionLoader permissionLoader,
- IRawPermissionRestrictionLoader rawPermissionRestrictionLoader,
- ISecurityIdentityExtractor permissionSecurityIdentityExtractor) : IManagedPrincipalConverter
+ IPermissionManagementService permissionManagementService) : IManagedPrincipalConverter
where TPrincipal : class
where TPermission : class
{
@@ -39,17 +42,6 @@ public async Task ToManagedPrincipalAsync(TPrincipal principal
return new ManagedPrincipal(
headerConverter.Convert(principal),
- await permissions.SyncWhenAll(permission => this.ToManagedPermissionAsync(permission, cancellationToken)));
+ await permissions.SyncWhenAll(permission => permissionManagementService.ToManagedPermissionAsync(permission, cancellationToken)));
}
-
- private async Task ToManagedPermissionAsync(TPermission permission, CancellationToken cancellationToken) =>
- new()
- {
- Identity = permissionSecurityIdentityExtractor.Extract(permission),
- IsVirtual = bindingInfo.IsReadonly,
- SecurityRole = permissionSecurityRoleResolver.Resolve(permission),
- Period = bindingInfo.GetSafePeriod(permission),
- Comment = bindingInfo.GetSafeComment(permission),
- Restrictions = await rawPermissionRestrictionLoader.LoadAsync(permission, cancellationToken)
- };
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission/DependencyInjection/GeneralPermissionSettings.cs b/src/SecuritySystem.GeneralPermission/DependencyInjection/GeneralPermissionSettings.cs
index d17401b..4157f8b 100644
--- a/src/SecuritySystem.GeneralPermission/DependencyInjection/GeneralPermissionSettings.cs
+++ b/src/SecuritySystem.GeneralPermission/DependencyInjection/GeneralPermissionSettings.cs
@@ -1,10 +1,12 @@
using System.Linq.Expressions;
using CommonFramework;
+using SecuritySystem.GeneralPermission.Validation;
namespace SecuritySystem.GeneralPermission.DependencyInjection;
-public class GeneralPermissionSettings : IGeneralPermissionSettings
+public class GeneralPermissionSettings :
+ IGeneralPermissionSettings
where TPermission : class
where TSecurityRole : notnull
{
@@ -18,6 +20,10 @@ public class GeneralPermissionSettings :
private bool? isReadonly;
+ public Type? PermissionEqualityComparerType { get; private set; }
+
+ public Type? PermissionManagementServiceType { get; private set; }
+
public TPermissionBindingInfo ApplyOptionalPaths(TPermissionBindingInfo bindingInfo)
where TPermissionBindingInfo : PermissionBindingInfo
@@ -36,7 +42,7 @@ public TGeneralPermissionBindingInfo ApplyGeneralOptionalPaths b with { SecurityRoleDescription = v.ToPropertyAccessors() });
}
- public IGeneralPermissionSettings SetPermissionPeriod(
+ public IGeneralPermissionSettings SetPermissionPeriod(
PropertyAccessors? startDatePropertyAccessor,
PropertyAccessors? endDatePropertyAccessor)
{
@@ -46,7 +52,7 @@ public IGeneralPermissionSettings SetPermissionPerio
return this;
}
- public IGeneralPermissionSettings SetPermissionPeriod(
+ public IGeneralPermissionSettings SetPermissionPeriod(
Expression>? startDatePath,
Expression>? endDatePath)
{
@@ -55,24 +61,43 @@ public IGeneralPermissionSettings SetPermissionPerio
endDatePath == null ? null : new PropertyAccessors(endDatePath));
}
- public IGeneralPermissionSettings SetPermissionComment(Expression> newCommentPath)
+ public IGeneralPermissionSettings SetPermissionComment(
+ Expression> newCommentPath)
{
this.commentPath = newCommentPath;
return this;
}
- public IGeneralPermissionSettings SetSecurityRoleDescription(Expression>? newDescriptionPath)
+ public IGeneralPermissionSettings SetSecurityRoleDescription(
+ Expression>? newDescriptionPath)
{
this.descriptionPath = newDescriptionPath;
return this;
}
- public IGeneralPermissionSettings SetReadonly(bool value = true)
+ public IGeneralPermissionSettings SetReadonly(bool value = true)
{
this.isReadonly = value;
return this;
}
+
+ public IGeneralPermissionSettings SetPermissionEqualityComparer()
+ where TComparer : IPermissionEqualityComparer
+ {
+ this.PermissionEqualityComparerType = typeof(TComparer);
+
+ return this;
+ }
+
+ public IGeneralPermissionSettings SetPermissionManagementService<
+ TPermissionManagementService>()
+ where TPermissionManagementService : IPermissionManagementService
+ {
+ this.PermissionManagementServiceType = typeof(TPermissionManagementService);
+
+ return this;
+ }
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission/DependencyInjection/IGeneralPermissionSettings.cs b/src/SecuritySystem.GeneralPermission/DependencyInjection/IGeneralPermissionSettings.cs
index 8e900b4..7f0f73c 100644
--- a/src/SecuritySystem.GeneralPermission/DependencyInjection/IGeneralPermissionSettings.cs
+++ b/src/SecuritySystem.GeneralPermission/DependencyInjection/IGeneralPermissionSettings.cs
@@ -2,21 +2,31 @@
using System.Linq.Expressions;
+using SecuritySystem.GeneralPermission.Validation;
+
namespace SecuritySystem.GeneralPermission.DependencyInjection;
-public interface IGeneralPermissionSettings
+public interface IGeneralPermissionSettings
{
- public IGeneralPermissionSettings SetPermissionPeriod(
+ public IGeneralPermissionSettings SetPermissionPeriod(
PropertyAccessors? startDatePropertyAccessor,
PropertyAccessors? endDatePropertyAccessor);
- public IGeneralPermissionSettings SetPermissionPeriod(
+ public IGeneralPermissionSettings SetPermissionPeriod(
Expression>? startDatePath,
Expression>? endDatePath);
- IGeneralPermissionSettings SetPermissionComment(Expression> commentPath);
+ IGeneralPermissionSettings SetPermissionComment(
+ Expression> commentPath);
+
+ IGeneralPermissionSettings SetSecurityRoleDescription(
+ Expression>? descriptionPath);
+
+ IGeneralPermissionSettings SetReadonly(bool value = true);
- IGeneralPermissionSettings SetSecurityRoleDescription(Expression>? descriptionPath);
+ IGeneralPermissionSettings SetPermissionEqualityComparer()
+ where TComparer : IPermissionEqualityComparer;
- IGeneralPermissionSettings SetReadonly(bool value = true);
+ IGeneralPermissionSettings SetPermissionManagementService()
+ where TPermissionManagementService : IPermissionManagementService;
}
\ No newline at end of file
diff --git a/src/SecuritySystem.GeneralPermission/DependencyInjection/SecuritySystemSettingsExtensions.cs b/src/SecuritySystem.GeneralPermission/DependencyInjection/SecuritySystemSettingsExtensions.cs
index 597dc2d..f7289ab 100644
--- a/src/SecuritySystem.GeneralPermission/DependencyInjection/SecuritySystemSettingsExtensions.cs
+++ b/src/SecuritySystem.GeneralPermission/DependencyInjection/SecuritySystemSettingsExtensions.cs
@@ -1,4 +1,5 @@
using CommonFramework;
+using CommonFramework.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
@@ -22,9 +23,13 @@ public static class SecuritySystemSettingsExtensions
public ISecuritySystemSettings AddGeneralPermission(
PermissionBindingInfo bindingInfo,
GeneralPermissionBindingInfo generalBindingInfo,
- GeneralPermissionRestrictionBindingInfo restrictionBindingInfo)
+ GeneralPermissionRestrictionBindingInfo restrictionBindingInfo,
+ Type? permissionEqualityComparerType,
+ Type? permissionManagementServiceType)
{
return securitySystemSettings
+ .AddGeneralServices()
+
.AddPermissionSystem(sp => new GeneralPermissionSystemFactory(sp, bindingInfo))
.AddExtensions(services => services
@@ -32,51 +37,20 @@ public ISecuritySystemSettings AddGeneralPermission(
.AddSingleton(generalBindingInfo)
.AddSingleton(restrictionBindingInfo)
- .AddSingleton(typeof(IPermissionRestrictionTypeFilterFactory<>), typeof(PermissionRestrictionTypeFilterFactory<>))
- .AddScoped(typeof(IPermissionRestrictionFilterFactory<>), typeof(PermissionRestrictionFilterFactory<>))
- .AddScoped(typeof(IRawPermissionConverter<>), typeof(RawPermissionConverter<>))
- .AddSingleton(typeof(IPermissionSecurityRoleFilterFactory<>), typeof(PermissionSecurityRoleFilterFactory<>))
- .AddScoped(typeof(IPermissionFilterFactory<>), typeof(PermissionFilterFactory<>))
-
-
- .AddScoped()
- .AddScoped(typeof(ISecurityRoleInitializer<>), typeof(SecurityRoleInitializer<>))
-
- .AddScoped()
- .AddScoped(typeof(ISecurityContextInitializer<>), typeof(SecurityContextInitializer<>))
-
- .AddScoped(typeof(IManagedPrincipalConverter<>), typeof(ManagedPrincipalConverter<>))
-
- .AddScoped(typeof(IDisplayPermissionService<,>), typeof(DisplayPermissionService<,>))
- .AddSingleton(typeof(IPermissionEqualityComparer<,>), typeof(PermissionEqualityComparer<,>))
-
- .AddKeyedScoped(typeof(IPrincipalValidator<,,>), "Root", typeof(PrincipalRootValidator<,,>))
- .AddScoped(typeof(IPrincipalValidator<,,>), typeof(PrincipalUniquePermissionValidator<,,>))
-
- .AddKeyedScoped(typeof(IPermissionValidator<,>), "Root", typeof(PermissionRootValidator<,>))
- .AddSingleton(typeof(IPermissionValidator<,>), typeof(PermissionRequiredContextValidator<,>))
-
- .AddKeyedScoped(typeof(IPermissionRestrictionValidator<>), "Root", typeof(PermissionRestrictionRootValidator<>))
- .AddSingleton(typeof(IPermissionRestrictionValidator<>), typeof(AllowedTypePermissionRestrictionValidator<>))
- .AddScoped(typeof(IPermissionRestrictionValidator<>), typeof(ExistsPermissionRestrictionValidator<>))
- .AddScoped(typeof(IPermissionRestrictionValidator<>), typeof(AllowedFilterPermissionRestrictionValidator<>))
+ .PipeMaybe(permissionEqualityComparerType, (sc, v) => sc
+ .AddScoped(
+ typeof(IPermissionEqualityComparer<,>)
+ .MakeGenericType(restrictionBindingInfo.PermissionType, restrictionBindingInfo.PermissionRestrictionType), v))
- .AddScoped(typeof(IPermissionLoader<,>), typeof(PermissionLoader<,>))
- .AddScoped(typeof(IPermissionRestrictionLoader<,>), typeof(PermissionRestrictionLoader<,>))
- .AddScoped(typeof(IRawPermissionRestrictionLoader<>), typeof(RawPermissionRestrictionLoader<>))
+ .PipeMaybe(permissionManagementServiceType, (sc, v) => sc
+ .AddScoped(
+ typeof(IPermissionManagementService<,,>)
+ .MakeGenericType(bindingInfo.PrincipalType, restrictionBindingInfo.PermissionType, restrictionBindingInfo.PermissionRestrictionType), v))
- .AddSingleton(typeof(IPermissionRestrictionRawConverter<>), typeof(PermissionRestrictionRawConverter<>))
- .AddSingleton(typeof(IPermissionSecurityRoleResolver<>), typeof(PermissionSecurityRoleResolver<>))
- .AddSingleton(typeof(IPermissionRestrictionSecurityContextTypeResolver<>), typeof(PermissionRestrictionSecurityContextTypeResolver<>))
-
- .AddSingleton()
+ .AddScoped(typeof(IPrincipalSourceService), typeof(GeneralPrincipalSourceService<>).MakeGenericType(bindingInfo.PrincipalType)));
+ }
- .AddSingleton()
- .AddSingleton()
- .AddScoped(typeof(IPrincipalSourceService), typeof(GeneralPrincipalSourceService<>).MakeGenericType(bindingInfo.PrincipalType)))
- .Pipe(!bindingInfo.IsReadonly, v => v.SetPrincipalManagementService());
- }
public ISecuritySystemSettings AddGeneralPermission(
@@ -85,7 +59,7 @@ public ISecuritySystemSettings AddGeneralPermission(
Expression> permissionPath,
Expression> securityContextTypePath,
Expression> securityContextObjectIdPath,
- Action>? setupAction = null)
+ Action>? setupAction = null)
where TPrincipal : class
where TPermission : class
where TSecurityRole : class
@@ -93,7 +67,7 @@ public ISecuritySystemSettings AddGeneralPermission(
where TSecurityContextType : class
where TSecurityContextObjectIdent : notnull
{
- var settings = new GeneralPermissionSettings();
+ var settings = new GeneralPermissionSettings();
setupAction?.Invoke(settings);
@@ -115,7 +89,60 @@ public ISecuritySystemSettings AddGeneralPermission(
SecurityContextObjectId = securityContextObjectIdPath.ToPropertyAccessors()
};
- return securitySystemSettings.AddGeneralPermission(bindingInfo, generalBindingInfo, restrictionBindingInfo);
+ return securitySystemSettings.AddGeneralPermission(
+ bindingInfo,
+ generalBindingInfo,
+ restrictionBindingInfo,
+ settings.PermissionEqualityComparerType,
+ settings.PermissionManagementServiceType);
+ }
+
+ private ISecuritySystemSettings AddGeneralServices()
+ {
+ return securitySystemSettings
+ .AddExtensions(services =>
+ {
+ if (services.AlreadyInitialized())
+ {
+ return;
+ }
+
+ services
+ .AddSingleton(typeof(IPermissionRestrictionTypeFilterFactory<>), typeof(PermissionRestrictionTypeFilterFactory<>))
+ .AddScoped(typeof(IPermissionRestrictionFilterFactory<>), typeof(PermissionRestrictionFilterFactory<>))
+ .AddScoped(typeof(IRawPermissionConverter<>), typeof(RawPermissionConverter<>))
+ .AddSingleton(typeof(IPermissionSecurityRoleFilterFactory<>), typeof(PermissionSecurityRoleFilterFactory<>))
+ .AddScoped(typeof(IPermissionFilterFactory<>), typeof(PermissionFilterFactory<>))
+ .AddScoped()
+ .AddScoped(typeof(ISecurityRoleInitializer<>), typeof(SecurityRoleInitializer<>))
+ .AddScoped()
+ .AddScoped(typeof(ISecurityContextInitializer<>), typeof(SecurityContextInitializer<>))
+ .AddScoped(typeof(IManagedPrincipalConverter<>), typeof(ManagedPrincipalConverter<>))
+ .AddScoped(typeof(IDisplayPermissionService<,>), typeof(DisplayPermissionService<,>))
+
+ .AddSingleton(typeof(IPermissionEqualityComparer<,>), typeof(PermissionEqualityComparer<,>))
+
+ .AddKeyedScoped(typeof(IPrincipalValidator<,,>), "Root", typeof(PrincipalRootValidator<,,>))
+ .AddScoped(typeof(IPrincipalValidator<,,>), typeof(PrincipalUniquePermissionValidator<,,>))
+ .AddKeyedScoped(typeof(IPermissionValidator<,>), "Root", typeof(PermissionRootValidator<,>))
+ .AddSingleton(typeof(IPermissionValidator<,>), typeof(PermissionRequiredContextValidator<,>))
+ .AddKeyedScoped(typeof(IPermissionRestrictionValidator<>), "Root", typeof(PermissionRestrictionRootValidator<>))
+ .AddSingleton(typeof(IPermissionRestrictionValidator<>), typeof(AllowedTypePermissionRestrictionValidator<>))
+ .AddScoped(typeof(IPermissionRestrictionValidator<>), typeof(ExistsPermissionRestrictionValidator<>))
+ .AddScoped(typeof(IPermissionRestrictionValidator<>), typeof(AllowedFilterPermissionRestrictionValidator<>))
+ .AddScoped(typeof(IPermissionLoader<,>), typeof(PermissionLoader<,>))
+ .AddScoped(typeof(IPermissionRestrictionLoader<,>), typeof(PermissionRestrictionLoader<,>))
+ .AddScoped(typeof(IRawPermissionRestrictionLoader<>), typeof(RawPermissionRestrictionLoader<>))
+ .AddSingleton(typeof(IPermissionRestrictionRawConverter<>), typeof(PermissionRestrictionRawConverter<>))
+ .AddSingleton(typeof(IPermissionSecurityRoleResolver<>), typeof(PermissionSecurityRoleResolver<>))
+ .AddSingleton(typeof(IPermissionRestrictionSecurityContextTypeResolver<>), typeof(PermissionRestrictionSecurityContextTypeResolver<>))
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddScoped(typeof(IPermissionManagementService<,,>), typeof(PermissionManagementService<,,>));
+ })
+
+ .SetPrincipalManagementService();
}
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Runtime/Services/SecurityRolesIdentsResolver.cs b/src/SecuritySystem.Runtime/Services/SecurityRolesIdentsResolver.cs
index cfa2023..afeba0a 100644
--- a/src/SecuritySystem.Runtime/Services/SecurityRolesIdentsResolver.cs
+++ b/src/SecuritySystem.Runtime/Services/SecurityRolesIdentsResolver.cs
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
+using System.Collections.Immutable;
using CommonFramework;
@@ -8,7 +9,7 @@ namespace SecuritySystem.Services;
public class SecurityRolesIdentsResolver(ISecurityRuleExpander securityRuleExpander, ISecurityRoleSource securityRoleSource) : ISecurityRolesIdentsResolver
{
- private readonly ConcurrentDictionary<(DomainSecurityRule.RoleBaseSecurityRule, bool), Dictionary> cache = new();
+ private readonly ConcurrentDictionary<(DomainSecurityRule.RoleBaseSecurityRule, bool), ImmutableDictionary> cache = new();
public IReadOnlyDictionary Resolve(DomainSecurityRule.RoleBaseSecurityRule baseSecurityRule, bool includeVirtual = false)
{
@@ -23,6 +24,6 @@ public IReadOnlyDictionary Resolve(DomainSecurityRule.RoleBaseSecur
.Where(sr => includeVirtual || !sr.Information.IsVirtual)
.Select(sr => sr.Identity)
.GroupBy(i => i.IdentType, i => i.GetId())
- .ToDictionary(g => g.Key, g => g.ToArray(g.Key)));
+ .ToImmutableDictionary(g => g.Key, g => g.ToArray(g.Key)));
}
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Testing/TestPermission.cs b/src/SecuritySystem.Testing/TestPermission.cs
index fb1c56b..0461ec4 100644
--- a/src/SecuritySystem.Testing/TestPermission.cs
+++ b/src/SecuritySystem.Testing/TestPermission.cs
@@ -1,4 +1,6 @@
-using SecuritySystem.ExternalSystem.Management;
+using System.Collections.Immutable;
+
+using SecuritySystem.ExternalSystem.Management;
namespace SecuritySystem.Testing;
@@ -18,9 +20,11 @@ public TestPermission()
public PermissionPeriod Period { get; set; } = PermissionPeriod.Eternity;
- public Dictionary Restrictions { get; } = new();
+ public Dictionary Restrictions { get; } = [];
+
+ public Dictionary ExtendedData { get; set; } = [];
- public string Comment { get; set; }
+ public string Comment { get; set; } = "";
public TypedSecurityIdentity? GetSingle()
where TSecurityContext : ISecurityContext
@@ -72,21 +76,14 @@ public void SetMany(TypedSecurityIdentity[] va
}
}
- public ManagedPermissionData ToManagedPermissionData()
+ public ManagedPermissionData ToManagedPermissionData() => new()
{
- if (this.SecurityRole is null)
- {
- throw new InvalidOperationException($"{nameof(this.SecurityRole)} not initialized");
- }
-
- return new ManagedPermissionData
- {
- SecurityRole = this.SecurityRole,
- Period = this.Period,
- Comment = this.Comment,
- Restrictions = this.Restrictions.Where(pair => pair.Value.Length > 0).ToDictionary()
- };
- }
+ SecurityRole = this.SecurityRole ?? throw new InvalidOperationException($"{nameof(this.SecurityRole)} not initialized"),
+ Period = this.Period,
+ Comment = this.Comment,
+ Restrictions = this.Restrictions.Where(pair => pair.Value.Length > 0).ToImmutableDictionary(),
+ ExtendedData = this.ExtendedData.ToImmutableDictionary()
+ };
public static implicit operator ManagedPermissionData(TestPermission testPermissionBuilder) => testPermissionBuilder.ToManagedPermissionData();
}
\ No newline at end of file
diff --git a/src/SecuritySystem.Testing/UserCredentialManager.cs b/src/SecuritySystem.Testing/UserCredentialManager.cs
index 1cbdb9b..0fb3079 100644
--- a/src/SecuritySystem.Testing/UserCredentialManager.cs
+++ b/src/SecuritySystem.Testing/UserCredentialManager.cs
@@ -40,7 +40,8 @@ public async Task AddUserRoleAsync(ManagedPermissionData[] tes
SecurityRole = testPermission.SecurityRole,
Period = testPermission.Period,
Comment = testPermission.Comment,
- Restrictions = testPermission.Restrictions
+ Restrictions = testPermission.Restrictions,
+ ExtendedData = testPermission.ExtendedData
});
var existsPrincipal = await principalSourceService.TryGetPrincipalAsync(this.userCredential, cancellationToken);
diff --git a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfo.cs b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfo.cs
index ae8b965..fd954c8 100644
--- a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfo.cs
+++ b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfo.cs
@@ -1,4 +1,6 @@
-using CommonFramework;
+using System.Collections.Immutable;
+
+using CommonFramework;
using CommonFramework.ExpressionEvaluate;
using CommonFramework.IdentitySource;
@@ -12,14 +14,15 @@ public abstract record VirtualPermissionBindingInfo
public required SecurityRole SecurityRole { get; init; }
- public IReadOnlyList Restrictions { get; init; } = [];
+ public ImmutableList Restrictions { get; init; } = [];
- public IEnumerable GetSecurityContextTypes()
- {
- return this.Restrictions
- .Select(restrictionPath => restrictionPath.ReturnType.GetCollectionElementTypeOrSelf())
- .Distinct();
- }
+ public ImmutableList SecurityContextTypes =>
+ field ??=
+ [
+ ..this.Restrictions
+ .Select(restrictionPath => restrictionPath.ReturnType.GetCollectionElementTypeOrSelf())
+ .Distinct()
+ ];
}
public record VirtualPermissionBindingInfo : VirtualPermissionBindingInfo
diff --git a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfoValidator.cs b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfoValidator.cs
index 6fd6d10..81a4b08 100644
--- a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfoValidator.cs
+++ b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPermissionBindingInfoValidator.cs
@@ -35,7 +35,7 @@ private void InternalValidate(VirtualPermissionBindingInfo virtualBindingInfo)
if (securityContextRestrictions != null)
{
- var bindingContextTypes = virtualBindingInfo.GetSecurityContextTypes().ToList();
+ var bindingContextTypes = virtualBindingInfo.SecurityContextTypes;
var invalidTypes = bindingContextTypes.Except(securityContextRestrictions.Select(r => r.SecurityContextType)).ToList();
diff --git a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPrincipalSourceService.cs b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPrincipalSourceService.cs
index 24b50b6..7ff0c9e 100644
--- a/src/SecuritySystem.VirtualPermission.Runtime/VirtualPrincipalSourceService.cs
+++ b/src/SecuritySystem.VirtualPermission.Runtime/VirtualPrincipalSourceService.cs
@@ -1,16 +1,18 @@
-using CommonFramework;
+using System.Linq.Expressions;
+using System.Reflection;
+
+using CommonFramework;
using CommonFramework.ExpressionEvaluate;
using CommonFramework.GenericRepository;
using CommonFramework.IdentitySource;
using CommonFramework.VisualIdentitySource;
+
using GenericQueryable;
+
using SecuritySystem.Credential;
using SecuritySystem.ExternalSystem.Management;
using SecuritySystem.Services;
using SecuritySystem.UserSource;
-using System.Linq.Expressions;
-using System.Reflection;
-using System.Xml.Linq;
namespace SecuritySystem.VirtualPermission;
@@ -71,10 +73,7 @@ public class VirtualPrincipalSourceService(
public Type PrincipalType { get; } = typeof(TPrincipal);
- public async Task> GetPrincipalsAsync(
- string nameFilter,
- int limit,
- CancellationToken cancellationToken)
+ public async Task> GetPrincipalsAsync(string nameFilter, int limit, CancellationToken cancellationToken)
{
return await queryableSource
.GetQueryable()
@@ -117,13 +116,13 @@ private ManagedPermission ToManagedPermission(TPermission permission)
var getRestrictionsMethod = this.GetType().GetMethod(nameof(this.GetRestrictionArray), BindingFlags.Instance | BindingFlags.NonPublic)!;
var restrictions = virtualBindingInfo
- .GetSecurityContextTypes()
+ .SecurityContextTypes
.Select(identityInfoSource.GetIdentityInfo)
.Select(identityInfo =>
(identityInfo.DomainObjectType, getRestrictionsMethod
.MakeGenericMethod(identityInfo.DomainObjectType, identityInfo.IdentityType)
.Invoke(this, permission, identityInfo)))
- .ToDictionary();
+ .ToImmutableDictionary();
return new ManagedPermission
{
@@ -136,9 +135,7 @@ private ManagedPermission ToManagedPermission(TPermission permission)
};
}
- public async Task> GetLinkedPrincipalsAsync(
- IEnumerable securityRoles,
- CancellationToken cancellationToken)
+ public async Task> GetLinkedPrincipalsAsync(IEnumerable securityRoles, CancellationToken cancellationToken)
{
if (securityRoles.Contains(virtualBindingInfo.SecurityRole))
{
@@ -154,7 +151,8 @@ public async Task> GetLinkedPrincipalsAsync(
}
}
- private Array GetRestrictionArray(TPermission permission,
+ private TSecurityContextIdent[] GetRestrictionArray(
+ TPermission permission,
IdentityInfo identityInfo)
where TSecurityContext : ISecurityContext
where TSecurityContextIdent : notnull
@@ -162,7 +160,8 @@ private Array GetRestrictionArray(TPerm
return this.GetRestrictionIdents(permission, identityInfo).ToArray();
}
- private IEnumerable GetRestrictionIdents(TPermission permission,
+ private IEnumerable GetRestrictionIdents(
+ TPermission permission,
IdentityInfo identityInfo)
where TSecurityContext : ISecurityContext
where TSecurityContextIdent : notnull
diff --git a/src/SecuritySystem.VirtualPermission/DependencyInjection/VirtualBindingInfoSettingsBuilder.cs b/src/SecuritySystem.VirtualPermission/DependencyInjection/VirtualBindingInfoSettingsBuilder.cs
index bf8385b..18ca64f 100644
--- a/src/SecuritySystem.VirtualPermission/DependencyInjection/VirtualBindingInfoSettingsBuilder.cs
+++ b/src/SecuritySystem.VirtualPermission/DependencyInjection/VirtualBindingInfoSettingsBuilder.cs
@@ -1,4 +1,5 @@
-using System.Linq.Expressions;
+using System.Collections.Immutable;
+using System.Linq.Expressions;
using CommonFramework;
@@ -13,7 +14,7 @@ public IVirtualBindingInfoSettingsBuilder AddRestriction>> path)
where TSecurityContext : ISecurityContext
{
- this.initList.Add(v => v with { Restrictions = v.Restrictions.Concat([path]).ToList() });
+ this.initList.Add(v => v with { Restrictions = v.Restrictions.Concat([path]).ToImmutableList() });
return this;
}
@@ -22,7 +23,7 @@ public IVirtualBindingInfoSettingsBuilder AddRestriction> path)
where TSecurityContext : ISecurityContext
{
- this.initList.Add(v => v with { Restrictions = v.Restrictions.Concat([path]).ToList() });
+ this.initList.Add(v => v with { Restrictions = v.Restrictions.Concat([path]).ToImmutableList() });
return this;
}
diff --git a/src/_Example/ExampleApp.Domain/Auth/General/Permission.cs b/src/_Example/ExampleApp.Domain/Auth/General/Permission.cs
index 56429fc..443a002 100644
--- a/src/_Example/ExampleApp.Domain/Auth/General/Permission.cs
+++ b/src/_Example/ExampleApp.Domain/Auth/General/Permission.cs
@@ -13,4 +13,6 @@ public class Permission
public DateTime? EndDate { get; set; }
public string Comment { get; set; } = "";
+
+ public string ExtendedValue { get; set; } = "";
}
\ No newline at end of file
diff --git a/src/_Example/ExampleApp.Domain/Auth/General/SecurityRole.cs b/src/_Example/ExampleApp.Domain/Auth/General/SecurityRole.cs
index e3ab44a..0c04fe0 100644
--- a/src/_Example/ExampleApp.Domain/Auth/General/SecurityRole.cs
+++ b/src/_Example/ExampleApp.Domain/Auth/General/SecurityRole.cs
@@ -6,5 +6,5 @@ public class SecurityRole
public required string Name { get; set; }
- public required string Description { get; set; }
+ public required string Description { get; set; } = "";
}
\ No newline at end of file
diff --git a/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ExtendedPermissionManagementService.cs b/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ExtendedPermissionManagementService.cs
new file mode 100644
index 0000000..a3c656b
--- /dev/null
+++ b/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ExtendedPermissionManagementService.cs
@@ -0,0 +1,59 @@
+using CommonFramework;
+using CommonFramework.GenericRepository;
+
+using AuthGeneral = ExampleApp.Domain.Auth.General;
+
+using SecuritySystem.GeneralPermission;
+using SecuritySystem.Services;
+using SecuritySystem.ExternalSystem.Management;
+
+namespace ExampleApp.Infrastructure.DependencyInjection;
+
+public class ExtendedPermissionManagementService(
+ IServiceProxyFactory serviceProxyFactory,
+ IPermissionBindingInfoSource bindingInfoSource,
+ IGeneralPermissionBindingInfoSource generalBindingInfoSource,
+ IGeneralPermissionRestrictionBindingInfoSource restrictionBindingInfoSource,
+ IGenericRepository genericRepository) :
+ PermissionManagementService(serviceProxyFactory, bindingInfoSource,
+ generalBindingInfoSource, restrictionBindingInfoSource)
+{
+ private const string ExtendedKey = nameof(AuthGeneral.Permission.ExtendedValue);
+
+ public override async Task> CreatePermissionAsync(
+ AuthGeneral.Principal dbPrincipal,
+ ManagedPermission managedPermission,
+ CancellationToken cancellationToken)
+ {
+ var baseResult = await base.CreatePermissionAsync(dbPrincipal, managedPermission, cancellationToken);
+
+ if (managedPermission.ExtendedData.TryGetValue(ExtendedKey, out var extendedValue))
+ {
+ baseResult.Permission.ExtendedValue = (string)extendedValue;
+
+ await genericRepository.SaveAsync(baseResult.Permission, cancellationToken);
+ }
+
+ return baseResult;
+ }
+
+ public override async Task ToManagedPermissionAsync(AuthGeneral.Permission dbPermission, CancellationToken cancellationToken)
+ {
+ var baseResult = await base.ToManagedPermissionAsync(dbPermission, cancellationToken);
+
+ return baseResult.WithExtendedData(ExtendedKey, dbPermission.ExtendedValue);
+ }
+
+ public override async Task<(PermissionData PermissonData, bool Updated)> UpdatePermission(
+ AuthGeneral.Permission dbPermission, ManagedPermission managedPermission, CancellationToken cancellationToken)
+ {
+ var baseResult = await base.UpdatePermission(dbPermission, managedPermission, cancellationToken);
+
+ if (managedPermission.ExtendedData.TryGetValue(ExtendedKey, out var extendedValue) && (string)extendedValue != dbPermission.ExtendedValue)
+ {
+ throw new InvalidOperationException($"{ExtendedKey} can't be changed");
+ }
+
+ return baseResult;
+ }
+}
\ No newline at end of file
diff --git a/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs b/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
index 58d5470..9eb2345 100644
--- a/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/_Example/ExampleApp.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
@@ -129,7 +129,8 @@ private IServiceCollection AddSecuritySystem()
v => v.StartDate,
(permission, startDate) => permission.StartDate = startDate ?? DateTime.MinValue),
new PropertyAccessors(v => v.EndDate))
- .SetPermissionComment(v => v.Comment)));
+ .SetPermissionComment(v => v.Comment)
+ .SetPermissionManagementService()));
}
}
}
\ No newline at end of file
diff --git a/src/_Example/ExampleApp.Infrastructure/TestDbContext.cs b/src/_Example/ExampleApp.Infrastructure/TestDbContext.cs
index e962c09..feab8d3 100644
--- a/src/_Example/ExampleApp.Infrastructure/TestDbContext.cs
+++ b/src/_Example/ExampleApp.Infrastructure/TestDbContext.cs
@@ -137,7 +137,9 @@ private void InitGeneralPermission(ModelBuilder modelBuilder)
entity.HasIndex(e => e.Name).IsUnique();
entity.Property(e => e.Name).IsRequired().HasMaxLength(DefaultMaxLength);
- }
+
+ entity.Property(e => e.Description).IsRequired().HasMaxLength(DefaultMaxLength);
+ }
{
var entity = modelBuilder.Entity().ToTable(nameof(Permission), AuthSchema);
@@ -145,6 +147,9 @@ private void InitGeneralPermission(ModelBuilder modelBuilder)
entity.HasOne(e => e.SecurityRole).WithMany().HasForeignKey($"{nameof(Permission.SecurityRole)}{DefaultIdPostfix}").IsRequired();
entity.HasOne(e => e.Principal).WithMany().HasForeignKey($"{nameof(Permission.Principal)}{DefaultIdPostfix}").IsRequired();
+
+ entity.Property(e => e.Comment).IsRequired().HasMaxLength(DefaultMaxLength);
+ entity.Property(e => e.ExtendedValue).IsRequired().HasMaxLength(DefaultMaxLength);
}
{
diff --git a/src/_Example/ExampleApp.IntegrationTests/ExtendedValuePermissionTests.cs b/src/_Example/ExampleApp.IntegrationTests/ExtendedValuePermissionTests.cs
new file mode 100644
index 0000000..c5fff39
--- /dev/null
+++ b/src/_Example/ExampleApp.IntegrationTests/ExtendedValuePermissionTests.cs
@@ -0,0 +1,30 @@
+using ExampleApp.Application;
+
+using SecuritySystem.Testing;
+
+namespace ExampleApp.IntegrationTests;
+
+public class ExtendedValuePermissionTests : TestBase
+{
+ [Fact]
+ public async Task SetRoleAsync_WithExtendedValue_ShouldPersistExtendedData()
+ {
+ // Arrange
+ var principalName = "TestPrincipal";
+
+ var extendedValue = "abc";
+
+ var testPermission = new TestPermission(ExampleRoles.DefaultRole) { ExtendedValue = extendedValue }.ToManagedPermissionData();
+
+ // Act
+ var principalIdentity = await this.AuthManager.For(principalName).SetRoleAsync(testPermission, this.CancellationToken);
+
+ // Assert
+ var managedPrincipal = await this.AuthManager.For(principalIdentity).GetPrincipalAsync(this.CancellationToken);
+
+ var managedPermission = managedPrincipal.Permissions.Should().ContainSingle().Subject;
+
+ managedPermission.ExtendedData.GetValueOrDefault(TestPermissionExtensions.ExtendedKey)
+ .Should().Be(extendedValue);
+ }
+}
\ No newline at end of file
diff --git a/src/_Example/ExampleApp.IntegrationTests/TestPermissionExtensions.cs b/src/_Example/ExampleApp.IntegrationTests/TestPermissionExtensions.cs
index c18f425..b8d30a1 100644
--- a/src/_Example/ExampleApp.IntegrationTests/TestPermissionExtensions.cs
+++ b/src/_Example/ExampleApp.IntegrationTests/TestPermissionExtensions.cs
@@ -7,6 +7,8 @@ namespace ExampleApp.IntegrationTests;
public static class TestPermissionExtensions
{
+ public const string ExtendedKey = nameof(Domain.Auth.General.Permission.ExtendedValue);
+
extension(TestPermission testPermission)
{
public TypedSecurityIdentity? BusinessUnit
@@ -20,5 +22,11 @@ public TypedSecurityIdentity[] BusinessUnits
get => testPermission.GetMany();
set => testPermission.SetMany(value);
}
+
+ public string ExtendedValue
+ {
+ get => (string)testPermission.ExtendedData[ExtendedKey];
+ set => testPermission.ExtendedData[ExtendedKey] = value;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs
index a341232..66cc063 100644
--- a/src/__SolutionItems/CommonAssemblyInfo.cs
+++ b/src/__SolutionItems/CommonAssemblyInfo.cs
@@ -3,7 +3,7 @@
[assembly: AssemblyProduct("SecuritySystem")]
[assembly: AssemblyCompany("IvAt")]
-[assembly: AssemblyVersion("2.2.0.0")]
+[assembly: AssemblyVersion("2.2.1.0")]
[assembly: AssemblyInformationalVersion("changes at build")]
#if DEBUG