Open Closed

Share OrganizationUnit Data (Sync organizationUnit ) ( or Permission , Role => Identity based classes) Between Modules #4085


0
selinkoykiran created

Hello, We know you have an abstraction module for user syncronization between multiple modules like below link . https://support.abp.io/QA/Questions/1442/Sync-Users-Between-Modules Do you think to add this abstraction layer for the rest of the identity models like organizationunit or role, permissions ... ? If you don't, can you explain why , is it a bad practice ? As we doing with User entity, we can also need organizationunit syncronization (or other identity entities) incase of deploying to another place this identity module, right ? What do you think about ?

Thank you ?

  • ABP Framework version: v5.3.3
  • UI type: MVC
  • DB provider: EF Core
  • **Tiered (MVC) : yes

4 Answer(s)
  • 0
    malik.masis created
    Support Team

    Hello,

    After discussing with the team, will return you back asap.

    Regards

  • 0
    hikalkan created
    Support Team

    Hi,

    I suppose you are talking about the Volo.Abp.Users.Abstractions package. We created it to not depend on the Microsoft Identity library from our other modules and make the Identity library replaceable in advanced scenarios.

    We don't plan and we don't want to create abstractions for organization units and roles. We already have abstractions for permissions (and settings, features, etc - these are defined in the core abp framework packages, while the database implementation are in separate modules). You don't need to have a reference to the Permission Management module, for example, to check a permission.

    We don't think it is true to create such a detailed abstraction. OU and roles are mainly used to organize permissions and your module should only need to the permission system dependency (it already has). These details are internals of the Identity module.

    Creating an abstraction is a very serious decision and has important costs. If we create abstractions for OUs and roles, we implicitly force all the implementations should support these systems (in ideal). Abstractions should be minimal and should have very few assumptions about the implementations. If we completely wrap all Identity details, no point to make an abstraction, we directly use it.

    Finally, such abstractions are useful while developing a modular framework. But you probably don't need it. You can directly depend on the Identity module from your other modules, especially if you don't need a system that makes possible to change the Identity without effecting other modules. Even if you need it, creating such a detailed abstraction may have a bigger cost than changing the Identity module when you need it in the future.

  • 0
    raif created

    OU and roles are mainly used to organize permissions and your module should only need to the permission system dependency (it already has). These details are internals of the Identity module.

    I think permission system dependency looks enough if you are building "policy based" authorization but not "row level" authorization.

    Let's assume use case where we are adding query filters according users organization unit detail. We may want to the user access more or less data according to their hierarchy in the organizational unit. Policy authorized one end point should return all organizational units lookup values for assignment.

    Let's define interface for this;

        public interface IHasAccessControl
        {
            public string OwnerId { get; }
    
            public string OrganizationId { get; }
        }
    

    Implement interface to the Aggregate Root

    public class Project : AuditedAggregateRoot<Guid>, IMultiTenant, IHasAccessControl
    {
        public virtual string Name { get; protected set; }
        // ...
        public virtual Guid? TenantId { get; protected set; }
        public virtual string OwnerId { get; protected set; }
        public virtual string OrganizationId { get; protected set; }
    
        public virtual void SetOwnerId([NotNull] string ownerId)
        {
    
        }
        
        public virtual void SetOrganizationId([NotNull] string organizationId)
        {
    
        }
    }
    
    

    Let's add organization id information to the token.

    public class OrganizationUnitPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
    {
        public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
        {
            var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();
    
            var userId = identity?.FindUserId();        
            if (userId.HasValue)
            {
                var userService = context.ServiceProvider.GetRequiredService< IdentityUserManager >(); 
    
                var user = await userService.FindByIdAsync(userId.ToString());
    
                if (user != null)
                {
                    user.OrganizationUnits
                        .Select(u => u.OrganizationUnitId).ToList()
                        .ForEach(unit => identity.AddClaim(new Claim(type: "organization_unit", value: unit.ToString())));
                }
            }
        }
    }
    

    Add short cut for identityServer (https://github.com/abpframework/abp/pull/7998)

    Configure<AbpClaimsServiceOptions>(options =>
    {
        options.RequestedClaims.AddRange(new[] { "organization_unit" });
    });
    
  • 0
    raif created

    Create extension for CurrentUser in order to get OrganizationIds from token.

    public static class CurrentUserExtensions
    {
        public static string[] GetOrganizationUnits(this ICurrentUser currentUser)
        {
           Claim[] claims = currentUser.FindClaims("organization_unit");
           
           return claims.Select(c => c.Value).ToArray();
        }
    }
    

    Add query filters for EF Core

    protected override bool ShouldFilterEntity< TEntity >(IMutableEntityType entityType)
    {
        if (typeof(IHasAccessControl).IsAssignableFrom(typeof(TEntity)))
        {
            return true;
        }
    
        return base.ShouldFilterEntity< TEntity >(entityType);
    }
    
    protected override Expression< Func< TEntity, bool > > CreateFilterExpression< TEntity >()
    {
        var expression = base.CreateFilterExpression< TEntity >();
        
        if (typeof(IHasAccessControl).IsAssignableFrom(typeof(TEntity)))
        {
            Expression< Func < TEntity, bool > > hasAccessControlFilter = e => CurrentUser.GetOrganizationUnits().Contains(EF.Property< string >(e, "OrganizationId")) || CurrentUser.Id == (EF.Property< string >(e, "OwnerId"));
            
            expression = expression == null ? hasAccessControlFilter : CombineExpressions(expression, hasAccessControlFilter);
        }
    
        return expression;
    }
    
    

    Let suppose we have microservices A and B. A for identity, auditing, saas etc, basic IT needs Where B is business microservice.

    After creating a new "project" object in microservice B, I want to assign it to a specific person or a specific organizational unit. (see global filter implementation)

    So we needed lookup values for organization units (it can be logins, tokens, roles, claims for another use cases)

    Information exchange between A and B can be

    • Synchronous
    • Asynchronous (in this specific case data can flow one way)

    Again let assume that we would like keep asynchronous communication where we were using UserEtos

    So, every time the user is updated in microservice A, I want to update the user information in microservice B via distributed events.

    However UserEto's doesn't carry information about

    • Claims
    • Roles
    • Logins
    • Tokens
    • Organization Units

    So how can we access asynchronously the above information about the user ?

    UserLookupService from Volo.Abp.Users package only forces IUser interface which is doesn't force claims, roles, tokens, ou etc..

    public abstract class UserLookupService<TUser, TUserRepository> : IUserLookupService<TUser>, ITransientDependency where TUser : class, IUser where TUserRepository : IUserRepository<TUser>