- 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
sorry for the late response. @gterdem will help you on that
-
0
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
thanks @woodyarray . the same should work on ABP because it's pretty same.
-
0
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
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
- get user roles of the current user.
- get user organization units of the current user.
- add
where
to filter based on the 1 and 2 results.
-
0
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
Thanks @woodyarray
-
0
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.