Open Closed

Filter user data #3622


0
shobhit created
  • ABP Framework version: v4.2.2
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:"
  • user "user1" has role "role1"
  • User "user1" belongs to orgnization unit "org1"
  • after login for a entity how we can filter data based on user "role" or "organization unit"

8 Answer(s)
  • 0
    albert created
    Support Team

    sorry for the late response. @gterdem will help you on that

  • 0
    woodyarray created

    You can refer to the ANZ documentation which is largely applicable for ABP

    https://aspnetboilerplate.com/Pages/Documents/Zero/Organization-Units#common-use-cases

    https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-core

    The second link covers the case when a User can be assigned to only one Org Unit. If your use case covers M:M assignments it gets a bit more complicate. Here is sample code (I'm a noob so FWIW):

    Create a Customer UserClaimsPrincipalFactory and in it put

     public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
            {
                var principal = await base.CreateAsync(user);
                var organizationUnits = await _identityUserManager.GetOrganizationUnitsAsync(user);
                foreach (OrganizationUnit org in organizationUnits)
                {
                    principal.Identities.First().AddClaim(new Claim("OU", org.Code));
                }
                return principal;
            }
    

    In your DBContext

       protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
        {
            var expression = base.CreateFilterExpression<TEntity>();
    
            if (typeof(IMayHaveOrganizationUnit).IsAssignableFrom(typeof(TEntity)))
            {
                var predicate = PredicateBuilder.New<OrganizationUnit>();
                if (OrgUnitCodes == null || !OrgUnitCodes.Any())
                {
    
                    // If No organizations assigned to User, return no entities
                    predicate = predicate.And(p => false);
                }
                else
                {
                    foreach (var code in OrgUnitCodes)
                    {
                        predicate = predicate.Or(ou => ou.Code.StartsWith(code));
                    }
                }
                Expression<Func<TEntity, bool>> mayHaveOuFilter = e => !IsOrganizationUnitFilterEnabled
                 || OrganizationUnits
                 .Where(predicate)
                 .Select(s => s.Id).Contains(((IMayHaveOrganizationUnit)e).OrganizationUnitId.Value)
                 == IsOrganizationUnitFilterEnabled;
                expression = expression == null ? mayHaveOuFilter : CombineExpressions(expression, mayHaveOuFilter);
            }
    
            return expression;
        }
    
  • 0
    albert created
    Support Team

    thanks @woodyarray . the same should work on ABP because it's pretty same.

  • 0
    woodyarray created

    sorry shobhit my code does not work

    The filter expression is created once (first time entity is queried) and does not change regardless of current user.

  • 0
    maliming created
    Support Team

    user "user1" has role "role1" User "user1" belongs to orgnization unit "org1" after login for a entity how we can filter data based on user "role" or "organization unit"

    hi

    1. get user roles of the current user.
    2. get user organization units of the current user.
    3. add where to filter based on the 1 and 2 results.
  • 0
    woodyarray created

    You can refer to the ANZ documentation which is largely applicable for ABP

    https://aspnetboilerplate.com/Pages/Documents/Zero/Organization-Units#common-use-cases

    https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-core

    The second link covers the case when a User can be assigned to only one Org Unit. If your use case covers M:M assignments it gets a bit more complicate. Here is sample code (I'm a noob so FWIW):

    Create a Customer UserClaimsPrincipalFactory and in it put

     public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user) 
            { 
                var principal = await base.CreateAsync(user); 
                var organizationUnits = await _identityUserManager.GetOrganizationUnitsAsync(user); 
                foreach (OrganizationUnit org in organizationUnits) 
                { 
                    principal.Identities.First().AddClaim(new Claim("OU", org.Code)); 
                } 
                return principal; 
            } 
    

    In your DBContext

       protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() 
        { 
            var expression = base.CreateFilterExpression<TEntity>(); 
     
            if (typeof(IMayHaveOrganizationUnit).IsAssignableFrom(typeof(TEntity))) 
            { 
                var predicate = PredicateBuilder.New<OrganizationUnit>(); 
                if (OrgUnitCodes == null || !OrgUnitCodes.Any()) 
                { 
     
                    // If No organizations assigned to User, return no entities 
                    predicate = predicate.And(p => false); 
                } 
                else 
                { 
                    foreach (var code in OrgUnitCodes) 
                    { 
                        predicate = predicate.Or(ou => ou.Code.StartsWith(code)); 
                    } 
                } 
                Expression<Func<TEntity, bool>> mayHaveOuFilter = e => !IsOrganizationUnitFilterEnabled 
                 || OrganizationUnits 
                 .Where(predicate) 
                 .Select(s => s.Id).Contains(((IMayHaveOrganizationUnit)e).OrganizationUnitId.Value) 
                 == IsOrganizationUnitFilterEnabled; 
                expression = expression == null ? mayHaveOuFilter : CombineExpressions(expression, mayHaveOuFilter); 
            } 
     
            return expression; 
        } 
    

    In my example, I was attempting to implement user -> roles, user -> organization based Data Filters in DbContext.

    If the filter requires a "join query", it will not work because the predicate seems to run once and get fixed - so even if user changes the filter remains the same.

    I think this is a related post: https://stackoverflow.com/questions/67820361/entity-framework-core-global-dynamic-query-filter/68318214#68318214

    Using https://docs.microsoft.com/en-us/ef/core/modeling/dynamic-model might be a solution, but I feel like both ABP and EF don't want you to use Data Filter, EF Global Filters for anything other than simple comparisons that do not require a join (e.g., isActive == true).

  • 0
    maliming created
    Support Team

    Thanks @woodyarray

  • 0
    gterdem created
    Support Team

    This subject seems quite tied to your business logic. There are some cases to ponder upon:

    • An entity can belong to a single organization unit or multiple organization units since a user may be a member of multiple organization units.
    • The organization unit code can be set manually or can be automatically set for the entity when saving that increases complexity.
    • EfCore global filters do not allow complex queries especially when multiple organization units are involved.
    • Involving roles completely increase the complexity even more.

    Also using intentions may differ. Like, you may want to use the Organization Units as privileges to access data (like authorization filters) or use them only for data filtering.

    I'll spend some more time on it and pick a scenario for a community post.