diff --git a/src/SecuritySystem.Abstractions/SecurityRule/DomainSecurityRule.cs b/src/SecuritySystem.Abstractions/SecurityRule/DomainSecurityRule.cs index 5fc9378..41adc54 100644 --- a/src/SecuritySystem.Abstractions/SecurityRule/DomainSecurityRule.cs +++ b/src/SecuritySystem.Abstractions/SecurityRule/DomainSecurityRule.cs @@ -140,7 +140,7 @@ public ExpandedRoleGroupSecurityRule(IEnumerable chil } public IEnumerable GetActualChildren() => - this.Children.Select(c => c.ApplyCustoms(this)); + this.HasDefaultCustoms() ? this.Children : this.Children.Select(c => c.ApplyCustoms(this)); } public record OperationSecurityRule(SecurityOperation SecurityOperation) : RoleBaseSecurityRule diff --git a/src/SecuritySystem/Services/DomainSecurityProviderFactory.cs b/src/SecuritySystem/Services/DomainSecurityProviderFactory.cs index 56d5c93..535bfc9 100644 --- a/src/SecuritySystem/Services/DomainSecurityProviderFactory.cs +++ b/src/SecuritySystem/Services/DomainSecurityProviderFactory.cs @@ -18,8 +18,10 @@ public class DomainSecurityProviderFactory( ISecurityRuleDeepOptimizer deepOptimizer, IRoleBaseSecurityProviderFactory roleBaseSecurityProviderFactory) : IDomainSecurityProviderFactory { - public virtual ISecurityProvider Create(DomainSecurityRule securityRule, SecurityPath securityPath) => - this.CreateInternal(deepOptimizer.Optimize(securityRule), securityPath); + public virtual ISecurityProvider Create(DomainSecurityRule securityRule, SecurityPath securityPath) + { + return this.CreateInternal(deepOptimizer.Optimize(securityRule), securityPath); + } protected virtual ISecurityProvider CreateInternal( DomainSecurityRule baseSecurityRule, @@ -34,9 +36,8 @@ protected virtual ISecurityProvider CreateInternal( { var args = new object?[] { - securityRule.RelativePathKey == null - ? null - : new CurrentUserSecurityProviderRelativeKey(securityRule.RelativePathKey) + securityRule.RelativePathKey.Maybe(v => new CurrentUserSecurityProviderRelativeKey(v)), + securityRule.CustomCredential } .Where(arg => arg != null) .Select(arg => arg!) @@ -105,23 +106,27 @@ protected virtual ISecurityProvider CreateInternal( var dynamicRoleFactory = (IFactory)dynamicRoleFactoryUntyped; - return this.CreateInternal(dynamicRoleFactory.Create(), securityPath); + var dynamicRole = dynamicRoleFactory.Create(); + + return this.CreateInternal(dynamicRole.ForceApply(securityRule.CustomCredential), securityPath); } case DomainSecurityRule.OverrideAccessDeniedMessageSecurityRule securityRule: { - return this.CreateInternal(securityRule.BaseSecurityRule, securityPath) + return this.CreateInternal(securityRule.BaseSecurityRule.ForceApply(securityRule.CustomCredential), securityPath) .OverrideAccessDeniedResult(accessDeniedResult => accessDeniedResult with { CustomMessage = securityRule.CustomMessage }); } case DomainSecurityRule.OrSecurityRule securityRule: - return this.CreateInternal(securityRule.Left, securityPath).Or(this.CreateInternal(securityRule.Right, securityPath)); + return this.CreateInternal(securityRule.Left.ForceApply(securityRule.CustomCredential), securityPath) + .Or(this.CreateInternal(securityRule.Right.ForceApply(securityRule.CustomCredential), securityPath)); case DomainSecurityRule.AndSecurityRule securityRule: - return this.CreateInternal(securityRule.Left, securityPath).And(this.CreateInternal(securityRule.Right, securityPath)); + return this.CreateInternal(securityRule.Left.ForceApply(securityRule.CustomCredential), securityPath) + .And(this.CreateInternal(securityRule.Right.ForceApply(securityRule.CustomCredential), securityPath)); case DomainSecurityRule.NegateSecurityRule securityRule: - return this.CreateInternal(securityRule.InnerRule, securityPath).Negate(); + return this.CreateInternal(securityRule.InnerRule.ForceApply(securityRule.CustomCredential), securityPath).Negate(); case DomainSecurityRule.DomainModeSecurityRule: case DomainSecurityRule.SecurityRuleHeader: diff --git a/src/SecuritySystem/UserSource/CurrentUserSecurityProvider.cs b/src/SecuritySystem/UserSource/CurrentUserSecurityProvider.cs index 7043e97..fabc47f 100644 --- a/src/SecuritySystem/UserSource/CurrentUserSecurityProvider.cs +++ b/src/SecuritySystem/UserSource/CurrentUserSecurityProvider.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; +using SecuritySystem.Credential; using SecuritySystem.Providers; using SecuritySystem.SecurityAccessor; using SecuritySystem.Services; @@ -20,18 +21,29 @@ public class CurrentUserSecurityProvider( IServiceProxyFactory serviceProxyFactory, IEnumerable userSourceInfoList, IIdentityInfoSource identityInfoSource, + SecurityRuleCredential? securityRuleCredential = null, CurrentUserSecurityProviderRelativeKey? key = null) : ISecurityProvider { - private readonly Lazy> lazyInnerProvider = new(() => + private readonly Lazy> lazyInnerService = new(() => { var (actualUserSourceInfo, actualRelativeDomainPathInfo) = TryGetActualUserSourceInfo() ?? throw new SecuritySystemException($"Can't found RelativePath for {typeof(TDomainObject)}"); var identityInfo = identityInfoSource.GetIdentityInfo(actualUserSourceInfo.UserType); - return serviceProxyFactory.Create>( - typeof(CurrentUserSecurityProvider<,,>).MakeGenericType(typeof(TDomainObject), actualUserSourceInfo.UserType, identityInfo.IdentityType), - actualRelativeDomainPathInfo, identityInfo); + var innerServiceType = + typeof(CurrentUserSecurityProvider<,,>).MakeGenericType(typeof(TDomainObject), actualUserSourceInfo.UserType, identityInfo.IdentityType); + + var innerServiceArgs = new[] + { + actualRelativeDomainPathInfo, + identityInfo, + securityRuleCredential + }.Where(arg => arg != null) + .Select(arg => arg!) + .ToArray(); + + return serviceProxyFactory.Create>(innerServiceType, innerServiceArgs); (UserSourceInfo, object)? TryGetActualUserSourceInfo() { @@ -55,40 +67,55 @@ public class CurrentUserSecurityProvider( } }); + private ISecurityProvider InnerService => this.lazyInnerService.Value; - private ISecurityProvider InnerProvider => this.lazyInnerProvider.Value; - - public IQueryable InjectFilter(IQueryable queryable) => this.InnerProvider.InjectFilter(queryable); + public IQueryable InjectFilter(IQueryable queryable) => this.InnerService.InjectFilter(queryable); - public AccessResult GetAccessResult(TDomainObject domainObject) => this.InnerProvider.GetAccessResult(domainObject); + public AccessResult GetAccessResult(TDomainObject domainObject) => this.InnerService.GetAccessResult(domainObject); - public bool HasAccess(TDomainObject domainObject) => this.InnerProvider.HasAccess(domainObject); + public bool HasAccess(TDomainObject domainObject) => this.InnerService.HasAccess(domainObject); - public SecurityAccessorData GetAccessorData(TDomainObject domainObject) => this.InnerProvider.GetAccessorData(domainObject); + public SecurityAccessorData GetAccessorData(TDomainObject domainObject) => this.InnerService.GetAccessorData(domainObject); } public class CurrentUserSecurityProvider( - IExpressionEvaluatorStorage expressionEvaluatorStorage, - IRelativeDomainPathInfo relativeDomainPathInfo, - IdentityInfo identityInfo, - IVisualIdentityInfoSource visualIdentityInfoSource, - ISecurityIdentityConverter securityIdentityConverter, - ICurrentUserSource currentUserSource) : SecurityProvider(expressionEvaluatorStorage) - where TUser : class - where TIdent : notnull + IExpressionEvaluatorStorage expressionEvaluatorStorage, + IRelativeDomainPathInfo relativeDomainPathInfo, + IdentityInfo identityInfo, + IVisualIdentityInfoSource visualIdentityInfoSource, + ISecurityIdentityConverter securityIdentityConverter, + IUserNameResolver userNameResolver, + IUserSource userSource, + SecurityRuleCredential? baseSecurityRuleCredential = null) : SecurityProvider(expressionEvaluatorStorage) + where TUser : class + where TIdent : notnull { - private readonly Func nameSelector = visualIdentityInfoSource.GetVisualIdentityInfo().Name.Getter; + private readonly Func nameSelector = visualIdentityInfoSource.GetVisualIdentityInfo().Name.Getter; - public override Expression> SecurityFilter { get; } = + public override Expression> SecurityFilter { get; } = - relativeDomainPathInfo.CreateCondition( - identityInfo.Id.Path.Select( - ExpressionHelper.GetEqualityWithExpr(securityIdentityConverter.Convert(currentUserSource.ToSimple().CurrentUser.Identity).Id))); + (baseSecurityRuleCredential ?? new SecurityRuleCredential.CurrentUserWithRunAsCredential()) + .Pipe(securityRuleCredential => + { + var userName = userNameResolver.Resolve(securityRuleCredential); - public override SecurityAccessorData GetAccessorData(TDomainObject domainObject) - { - var users = relativeDomainPathInfo.GetRelativeObjects(domainObject); + if (userName == null) + { + return _ => true; + } + else + { + var userId = securityIdentityConverter.Convert(userSource.ToSimple().GetUser(userName).Identity).Id; + + return identityInfo.Id.Path.Select(ExpressionHelper.GetEqualityWithExpr(userId)); + } + }) + .Pipe(relativeDomainPathInfo.CreateCondition); + + public override SecurityAccessorData GetAccessorData(TDomainObject domainObject) + { + var users = relativeDomainPathInfo.GetRelativeObjects(domainObject); - return SecurityAccessorData.Return(users.Select(nameSelector)); - } + return SecurityAccessorData.Return(users.Select(nameSelector)); + } } \ No newline at end of file diff --git a/src/_Example/ExampleApp.IntegrationTests/DomainSecurityRuleCredentialTests.cs b/src/_Example/ExampleApp.IntegrationTests/DomainSecurityRuleCredentialTests.cs new file mode 100644 index 0000000..f0beb19 --- /dev/null +++ b/src/_Example/ExampleApp.IntegrationTests/DomainSecurityRuleCredentialTests.cs @@ -0,0 +1,69 @@ +using ExampleApp.Application; +using ExampleApp.Domain; + +using GenericQueryable; + +using Microsoft.Extensions.DependencyInjection; + +using SecuritySystem; + +namespace ExampleApp.IntegrationTests; + +public class DomainSecurityRuleCredentialTests : TestBase +{ + [Theory] + [MemberData(nameof(GetEmployees_ReturnsExpectedUsers_Cases))] + public async Task GetEmployees_ReturnsExpectedUsers(SecurityRule securityRule, string?[] expectedUsers) + { + // Arrange + var realExpectedUsers = expectedUsers.Select(userName => userName ?? this.AuthenticationService.GetUserName()); + + // Act + await using var scope = this.RootServiceProvider.CreateAsyncScope(); + + var employees = await scope.ServiceProvider.GetRequiredService>().Create(securityRule).GetQueryable() + .GenericToListAsync(this.CancellationToken); + + // Assert + realExpectedUsers.OrderBy(v => v).Should().BeEquivalentTo(employees.Select(e => e.Login)); + } + + + public static IEnumerable GetEmployees_ReturnsExpectedUsers_Cases() + { + string? user0 = null; + string? user1 = "TestEmployee1"; + string? user2 = "TestEmployee2"; + + + yield return + [ + DomainSecurityRule.CurrentUser, + new[] { user0 } + ]; + + yield return + [ + DomainSecurityRule.CurrentUser with { CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user1) }, + new[] { user1 } + ]; + + yield return + [ + (DomainSecurityRule.CurrentUser with { CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user1) }) + .Or(DomainSecurityRule.CurrentUser with { CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user2) }), + new[] { user1, user2 } + ]; + + yield return + [ + (DomainSecurityRule.CurrentUser with { CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user0) }) + .Or(DomainSecurityRule.CurrentUser with { CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user1) }) + with + { + CustomCredential = new SecurityRuleCredential.CustomUserSecurityRuleCredential(user2) + }, + new[] { user2 } + ]; + } +} \ No newline at end of file diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs index e8718fb..56ff642 100644 --- a/src/__SolutionItems/CommonAssemblyInfo.cs +++ b/src/__SolutionItems/CommonAssemblyInfo.cs @@ -3,7 +3,7 @@ [assembly: AssemblyProduct("SecuritySystem")] [assembly: AssemblyCompany("IvAt")] -[assembly: AssemblyVersion("2.1.0.0")] +[assembly: AssemblyVersion("2.1.1.0")] [assembly: AssemblyInformationalVersion("changes at build")] #if DEBUG