Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public ExpandedRoleGroupSecurityRule(IEnumerable<ExpandedRolesSecurityRule> chil
}

public IEnumerable<ExpandedRolesSecurityRule> 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
Expand Down
25 changes: 15 additions & 10 deletions src/SecuritySystem/Services/DomainSecurityProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public class DomainSecurityProviderFactory<TDomainObject>(
ISecurityRuleDeepOptimizer deepOptimizer,
IRoleBaseSecurityProviderFactory<TDomainObject> roleBaseSecurityProviderFactory) : IDomainSecurityProviderFactory<TDomainObject>
{
public virtual ISecurityProvider<TDomainObject> Create(DomainSecurityRule securityRule, SecurityPath<TDomainObject> securityPath) =>
this.CreateInternal(deepOptimizer.Optimize(securityRule), securityPath);
public virtual ISecurityProvider<TDomainObject> Create(DomainSecurityRule securityRule, SecurityPath<TDomainObject> securityPath)
{
return this.CreateInternal(deepOptimizer.Optimize(securityRule), securityPath);
}

protected virtual ISecurityProvider<TDomainObject> CreateInternal(
DomainSecurityRule baseSecurityRule,
Expand All @@ -34,9 +36,8 @@ protected virtual ISecurityProvider<TDomainObject> 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!)
Expand Down Expand Up @@ -105,23 +106,27 @@ protected virtual ISecurityProvider<TDomainObject> CreateInternal(

var dynamicRoleFactory = (IFactory<DomainSecurityRule>)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:
Expand Down
83 changes: 55 additions & 28 deletions src/SecuritySystem/UserSource/CurrentUserSecurityProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

using Microsoft.Extensions.DependencyInjection;

using SecuritySystem.Credential;
using SecuritySystem.Providers;
using SecuritySystem.SecurityAccessor;
using SecuritySystem.Services;
Expand All @@ -20,18 +21,29 @@ public class CurrentUserSecurityProvider<TDomainObject>(
IServiceProxyFactory serviceProxyFactory,
IEnumerable<UserSourceInfo> userSourceInfoList,
IIdentityInfoSource identityInfoSource,
SecurityRuleCredential? securityRuleCredential = null,
CurrentUserSecurityProviderRelativeKey? key = null) : ISecurityProvider<TDomainObject>
{
private readonly Lazy<ISecurityProvider<TDomainObject>> lazyInnerProvider = new(() =>
private readonly Lazy<ISecurityProvider<TDomainObject>> 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<ISecurityProvider<TDomainObject>>(
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<ISecurityProvider<TDomainObject>>(innerServiceType, innerServiceArgs);

(UserSourceInfo, object)? TryGetActualUserSourceInfo()
{
Expand All @@ -55,40 +67,55 @@ public class CurrentUserSecurityProvider<TDomainObject>(
}
});

private ISecurityProvider<TDomainObject> InnerService => this.lazyInnerService.Value;

private ISecurityProvider<TDomainObject> InnerProvider => this.lazyInnerProvider.Value;

public IQueryable<TDomainObject> InjectFilter(IQueryable<TDomainObject> queryable) => this.InnerProvider.InjectFilter(queryable);
public IQueryable<TDomainObject> InjectFilter(IQueryable<TDomainObject> 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<TDomainObject, TUser, TIdent>(
IExpressionEvaluatorStorage expressionEvaluatorStorage,
IRelativeDomainPathInfo<TDomainObject, TUser> relativeDomainPathInfo,
IdentityInfo<TUser, TIdent> identityInfo,
IVisualIdentityInfoSource visualIdentityInfoSource,
ISecurityIdentityConverter<TIdent> securityIdentityConverter,
ICurrentUserSource<TUser> currentUserSource) : SecurityProvider<TDomainObject>(expressionEvaluatorStorage)
where TUser : class
where TIdent : notnull
IExpressionEvaluatorStorage expressionEvaluatorStorage,
IRelativeDomainPathInfo<TDomainObject, TUser> relativeDomainPathInfo,
IdentityInfo<TUser, TIdent> identityInfo,
IVisualIdentityInfoSource visualIdentityInfoSource,
ISecurityIdentityConverter<TIdent> securityIdentityConverter,
IUserNameResolver<TUser> userNameResolver,
IUserSource<TUser> userSource,
SecurityRuleCredential? baseSecurityRuleCredential = null) : SecurityProvider<TDomainObject>(expressionEvaluatorStorage)
where TUser : class
where TIdent : notnull
{
private readonly Func<TUser, string> nameSelector = visualIdentityInfoSource.GetVisualIdentityInfo<TUser>().Name.Getter;
private readonly Func<TUser, string> nameSelector = visualIdentityInfoSource.GetVisualIdentityInfo<TUser>().Name.Getter;

public override Expression<Func<TDomainObject, bool>> SecurityFilter { get; } =
public override Expression<Func<TDomainObject, bool>> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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<IRepositoryFactory<Employee>>().Create(securityRule).GetQueryable()
.GenericToListAsync(this.CancellationToken);

// Assert
realExpectedUsers.OrderBy(v => v).Should().BeEquivalentTo(employees.Select(e => e.Login));
}


public static IEnumerable<object?[]> 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 }
];
}
}
2 changes: 1 addition & 1 deletion src/__SolutionItems/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading