Open Closed

How to force adding PermissionDefinitionProvider only after the user is authenticated? #5799


User avatar
0
alexander.nikonov created
  • ABP Framework version: v7.0.1
  • UI Type: Angular
  • Database System: EF Core (Oracle)
  • Auth Server Separated

I have custom Permission Definition Provider:

    using AbxEps.CT.Core.Extensions;
    using AbxEps.CT.Core.Localization;
    using AbxEps.CT.Core.PortalMenu;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Threading.Tasks;
    using Volo.Abp.Authorization.Permissions;
    using Volo.Abp.Localization;
    using Volo.Abp.Threading;
    using Volo.Abp.Users;

    namespace AbxEps.CT.Core.Permissions
    {
        public class CorePermissionDefinitionProvider : PermissionDefinitionProvider
        {
            private readonly ICurrentUser _currentUser;
            private readonly IAbxPortalsMenuAppService _abxPortalsMenuAppService;
    
            public CorePermissionDefinitionProvider
            (
                IAbxPortalsMenuAppService abxPortalsMenuAppService,
                ICurrentUser currentUser
             )
            {
                _abxPortalsMenuAppService = abxPortalsMenuAppService;
                _currentUser = currentUser;
            }
    
            public override void Define(IPermissionDefinitionContext context)
            {
                var coreCtGroup = context.AddGroup(CorePermissions.GroupName, L("Permission:Core"));
                var fileMngPermission = coreCtGroup.AddPermission(CorePermissions.FileManager.Read, L("Permission:FileManager:Read"));
                fileMngPermission.AddChild(CorePermissions.FileManager.Modify, L("Permission:FileManager:Modify"));
                if (_currentUser.IsAuthenticated)
                {
                    AsyncHelper.RunSync(() => InitPortalAccessPermissionsAsync(context)); // Not called, because when the host is started the user is still not authenticated - so "if" condition is not invoked
                }
            }
    
            private async Task InitPortalAccessPermissionsAsync(IPermissionDefinitionContext context)
            {
                ...
            }
        }
    }
        

Whereas the permissions which are supposed to be added unconditionally (fileMngPermissions) are added successfully when host is running, the permissions which need to be added only after the user is authenticated (portalAccessPermissions) are obviously not added and not visible via IPermissionAppService:

    var allPermissionsForRole = await _permissionAppService.GetAsync("R", "Role 1");
    

How to add a whole CorePermissionDefinitionProvider or the part of its relevant permissions (portalAccessPermissions) conditionally - once the user got authenticated?

P.S. I'd prefer not to use Middleware, because its Invoke method is invoked on each request. Instead, I need to add my CorePermissionDefinitionProvider or its authentication-related permissions once after the user authentication was successful. I use external authentication via Identity Server. So this functionality needs to reside inside the application project, not Identity Server project.

Making PermissionDefinitionManager a ITransientDependency (or probably I can make CorePermissionDefinitionProvider transient too) does not sound good either: I dot not want to trigger the check each time. I just need to trigger it ONCE, but after the user has been authenticated.


28 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can't add Permissions by the currentUser. also not recommended to call the async method in a sync method.

    You can consider overriding the StaticPermissionDefinitionStore.

    https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs#L12

  • User Avatar
    0
    alexander.nikonov created

    I have already overridden StaticPermissionDefinitionStore (for other task), so I can extend it with some new functionality. But I need to understand how should I implement the task. The aim is the following: there's configurable parameter in the database which can be different depending on the tenant and other things and in fact it determines the list of permissions. So I need to read this parameter once the user is authenticated and his current tenant is known. After this, I need to add just read permissions to the rest of permissions (and be able to assign them to roles as usual). I know there are dynamic permissions in ABP, but I'd prefer not to make use of them.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I know there are dynamic permissions in ABP, but I'd prefer not to make use of them.

    dynamic permissions is the best choice, otherwise your implementation may not be compatible with the current design.

  • User Avatar
    0
    alexander.nikonov created

    Our solution is ALREADY a very customized (and we have not used dynamic permissions so far, I don't know what would turn out from this), so I am not afraid to experiment with static permissions. But anyway, I'd like to know the place where I can assign my per-tenant permissions. It needs to be a part of solution and invoked once after the user has been authorized. It must not happen in Identity Server project. As I see it, using a middleware is a bad choice, because the middleware Invoke method is get invoked on each request. Instead, I need to make it only once.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I'd like to know the place where I can assign my per-tenant permissions

    In the AbpPermissionGrants table. The TenantId column.

    IPermissionManager will read/write this table, then cache permissions in Redis.

  • User Avatar
    0
    alexander.nikonov created

    Sorry, it's not what I meant. I mean what is a suitable ABP code (handler, contributor, service method) which is executed once after the user has been successfully authenticated and which is not a part of Identity Server project?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Each request will attempt to authenticate the user(verifying cookies access_token.)

    app.UseAuthentication();

  • User Avatar
    0
    alexander.nikonov created

    So the only place I can place the check is a middleware - meaning on each request "if [user is authenticated]"? I wanted to avoid this, but if there's no other place which is executed only once, I have no choice.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    This is how web application works: request and response one by one.

  • User Avatar
    0
    alexander.nikonov created

    I know the basic principles of web app :) After all I see that there's no magic place to place this code. So I will need to use "if" guard condition inside a middleware checking if the request is authorized, so the permission definition provider can be added (once). I'm not closing the ticket yet: could be another questions coming along the way.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    sure

  • User Avatar
    0
    alexander.nikonov created

    Hi again. I've added the following middleware:

    public class DynamicPermissionDefinitionMiddleware<T> where T : IPermissionDefinitionProvider
    {
        private readonly IOptions<AbpPermissionOptions> _permissionOptions;
        private readonly RequestDelegate _next;
        private bool _isAuthenticated;
    
        public DynamicPermissionDefinitionMiddleware(RequestDelegate next, IOptions<AbpPermissionOptions> permissionOptions)
        {
            _next = next;
            _permissionOptions = permissionOptions;
            _isAuthenticated = false;
        }
    
        public async Task Invoke(HttpContext context)
        {
            if (context.User.Identity.IsAuthenticated && !_isAuthenticated)
            {
                _permissionOptions.Value.DefinitionProviders.Add(typeof(T)); //UPDATES DefinitionProviders as expected!
                _isAuthenticated = true;
            }
            else if (!context.User.Identity.IsAuthenticated && _isAuthenticated)
            {
                _permissionOptions.Value.DefinitionProviders.Remove(typeof(T));
                _isAuthenticated = false;
            }
            await _next(context);
        }
    }
    

    into HttpApiHost project: app.UseMiddleware<DynamicPermissionDefinitionMiddleware<CorePermissionDefinitionProvider>>();

    Despite DefinitionProviders collection is filled as expected, the CorePermissionDefinitionProvider is not touched - even the constructor is not invoked (DisableConventionalRegistration is used to prevent trying ABP to add this provider automatically while the host is being loaded):

    [DisableConventionalRegistration]
    public class CorePermissionDefinitionProvider : PermissionDefinitionProvider
    {
        private readonly ICurrentUser _currentUser;
        private readonly IServiceProvider _serviceProvider;
    
        public CorePermissionDefinitionProvider
        (
            IServiceProvider serviceProvider,
            ICurrentUser currentUser
         )
        {
            _serviceProvider = serviceProvider; //NOT INVOKED!
            _currentUser = currentUser;
        }
        ...
    }
    

    What am I missing?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Three services use the IOptions<AbpPermissionOptions> You can consider overriding some of them.

  • User Avatar
    0
    alexander.nikonov created

    I took a look at the static store implemenation and it looks like there's nothing I can do here:

    public StaticPermissionDefinitionStore(
        IServiceProvider serviceProvider,
        IOptions&lt;AbpPermissionOptions&gt; options)
    {
        _serviceProvider = serviceProvider;
        Options = options.Value;
    
        _lazyPermissionDefinitions = new Lazy&lt;Dictionary&lt;string, PermissionDefinition&gt;>(
            CreatePermissionDefinitions,
            isThreadSafe: true
        );
    
        _lazyPermissionGroupDefinitions = new Lazy&lt;Dictionary&lt;string, PermissionGroupDefinition&gt;>(
            CreatePermissionGroupDefinitions,
            isThreadSafe: true
        );
    }
    

    After the store is created, it uses CreatePermissionGroupDefinitions to generate the list of the available definition providers at the moment. This typically occurs when the host is started, before any authenticated app begins sending requests. Once this collection is established, it is not intended to be altered during runtime.

    While it is possible to modify this behavior by overriding the class, doing so can introduce complexity and unpredictable consequences.

    In the given situation I am ready to consider dynamic permissions usage. Could you please point me in right direction - how to use the CorePermissionDefinitionProvider(or its permissions) as dynamic permissions, not static ones? I've tried to find some info on ABP documentation site, but have not found anything relevant besides using boolean flag in the options indicating whether I want to use dynamic permissions.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Dynamic permissions are suitable for distributed/microservice projects, and your situation is special. You'd better consider rewriting the above service custom code to solve it.

  • User Avatar
    0
    alexander.nikonov created

    My situation is not very especial: in fact, all I need is to retrieve per-tenant specific data from DB table and build permissions based on this data. How am I supposed to do that with either static or dynamic permissions?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I found a new discuss, You can take a look https://github.com/abpframework/abp/issues/4448

  • User Avatar
    0
    alexander.nikonov created

    hi

    I found a new discuss, You can take a look https://github.com/abpframework/abp/issues/4448

    I've seen this discussion before... The workaround with making PermissionDefinitionManager a ITransientDependency and using try-catch block looks like a last resort. @realLiangshiwei mentions an alternative - using "Refresh" method, but does not go into details. I wonder what does this "Refresh" method need to look like, probably this one would fit me: in my middleware I would need to "refresh" the list of static permissions so that static permissions would "see" a newly-added permission definition provider... But looking at the relevant ABP classes, I'm not sure what is the proper way to do that.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    The StaticPermissionDefinitionStore is ISingletonDependency You can inject it and use it anywhere. Use your logic to control PermissionDefinitions

  • User Avatar
    0
    alexander.nikonov created

    I understand that. And I see the code of StaticPermissionDefinitionStore:

    public StaticPermissionDefinitionStore(
        IServiceProvider serviceProvider,
        IOptions&lt;AbpPermissionOptions&gt; options)
    {
        _serviceProvider = serviceProvider;
        Options = options.Value;
    
        _lazyPermissionDefinitions = new Lazy&lt;Dictionary&lt;string, PermissionDefinition&gt;>(
            CreatePermissionDefinitions,
            isThreadSafe: true
        );
    
        _lazyPermissionGroupDefinitions = new Lazy&lt;Dictionary&lt;string, PermissionGroupDefinition&gt;>(
            CreatePermissionGroupDefinitions,
            isThreadSafe: true
        );
    }
    

    I understand that I can override the method which creates the groups:

    protected virtual Dictionary&lt;string, PermissionGroupDefinition&gt; CreatePermissionGroupDefinitions()
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var context = new PermissionDefinitionContext(scope.ServiceProvider);
    
            var providers = Options
                .DefinitionProviders
                .Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
                .ToList(); // `CorePermissionDefinitionProvider` is still not known here - this list is created when `StaticPermissionDefinitionStore` singleton is instantiated, but prior to the moment the first request from client is sent
            ...                
    }
    

    But how will it help me? The method determines the way to retrieve the providers, not the moment in time they are retrieved. This method is invoked when StaticPermissionDefinitionStore singleton is instantiated: at this time I still don't have my CorePermissionDefinitionProvider available - it's added to the collection only when the application is authenticated and sends the first authenticated request.

    Probably I don't see something obvious?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You don't need to refer to the current implementation. You don't need to use CreatePermissionGroupDefinitions and Lazy<Dictionary<string at all.

    You can maintain a collection by yourself.

  • User Avatar
    0
    alexander.nikonov created

    I think I've almost managed to do this. However, quick question. How to prevent PermissionDefinitionProvider from being automatically handled by ABP? I've decorated it with DisableConventionalRegistration. But this is not enough, because Static Store uses one more mechanism, Options.DefinitionProviders, to read them. So in addition to DisableConventionalRegistration I need to manually remove it from IPermissionOptions DefinitionProviders collection (something for ABP to think about).

    But probably there's a more comfortable way to make ABP ignore the provider?

    The requested service 'AbxEps.CT.Core.Permissions.PortalAccessPermissionDefinitionProvider' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency. Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'AbxEps.CT.Core.Permissions.PortalAccessPermissionDefinitionProvider' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency. at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable1 parameters) at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable1 parameters) at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType) at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetRequiredService(Type serviceType) at AbxEps.CT.ModulePermission.ExtendedStaticPermissionDefinitionStore.<>c__DisplayClass17_0.<CreatePermissionGroupDefinitions>b__0(Type p) at System.Linq.Enumerable.SelectIListIterator2.ToList() at AbxEps.CT.ModulePermission.ExtendedStaticPermissionDefinitionStore.CreatePermissionGroupDefinitions() at System.Lazy1.ViaFactory(LazyThreadSafetyMode mode) --- End of stack trace from previous location --- at System.Lazy`1.CreateValue() at AbxEps.CT.ModulePermission.ExtendedStaticPermissionDefinitionStore.get_PermissionGroupDefinitions() at AbxEps.CT.ModulePermission.ExtendedStaticPermissionDefinitionStore.GetGroupsAsync()

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can remove the services from AbpPermissionOptions, right?

  • User Avatar
    0
    alexander.nikonov created

    Yes, I can and it works. However, I'd prefer to mark the class with attribute like DisableConventionalRegistration and be sure it is ignored everywhere. With this approach:

        Configure&lt;AbpPermissionOptions&gt;(options =>
        {
            options.DefinitionProviders.Remove(typeof(PortalAccessPermissionDefinitionProvider));
        })
    

    I need to remember doing it in every project which uses the given class...

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The DisableConventionalRegistration should work also.

    Can you share the code of PortalAccessPermissionDefinitionProvider?

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11