Activities of "alexander.nikonov"

Not resolved. Reopening.

UPDATE: please have a look at what's going on during logout at Identity Server, probably would ring some bell to you: https://drive.google.com/open?id=12SCQSi6Je4G9cCTwPoxwf4CzFujMz0Rn&usp=drive_fs

To me it looks like concurrent calls to Task<IdentityUser> FindAsync (which is eventually cancelled as seen from the attached log) from:

    using AbxEps.CentralTools.AbxUsers;
    using AbxEps.CentralTools.Extensions;
    using AbxEps.CentralTools.Jobs;
    using AbxEps.CentralTools.Sessions;
    using AbxEps.CentralTools.Tenants;
    using IdentityServer4.Models;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Identity;
    using System;
    using System.Collections.Generic;
    using System.Security.Claims;
    using System.Security.Principal;
    using System.Threading.Tasks;
    using Volo.Abp.Identity;
    using Volo.Abp.IdentityServer.AspNetIdentity;
    using Volo.Abp.MultiTenancy;
    using Volo.Abp.PermissionManagement;
    using IdentityUser = Volo.Abp.Identity.IdentityUser;
    
    namespace AbxEps.CentralTools.IdentityServer.Profile
    {
        public class AbxProfileService : AbpProfileService
        {
            private readonly ITenantRepository           _abxTenantRepository;
            private readonly IAbxUserRepository          _abxUserRepository;
            private readonly ISessionRepository          _abxSessionRepository;
            private readonly IJobRepository              _abxJobRepository;
            private readonly IHttpContextAccessor        _httpContextAccessor;
    
            public AbxProfileService
            (
                IdentityUserManager                       userManager,
                IAbxUserRepository                        abxUserRepository,
                IUserClaimsPrincipalFactory&lt;IdentityUser&gt; claimsFactory,
                ICurrentTenant                            currentTenant,
                ITenantRepository                         abxTenantRepository,
                IHttpContextAccessor                      httpContextAccessor,
                ISessionRepository                        abxSessionRepository,
                IJobRepository                            abxJobRepository
            )
                :base(userManager, claimsFactory, currentTenant)
            {
                _abxTenantRepository        = abxTenantRepository;
                _abxUserRepository          = abxUserRepository;
                _httpContextAccessor        = httpContextAccessor;
                _abxSessionRepository       = abxSessionRepository;
                _abxJobRepository           = abxJobRepository;
            }
    
            public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                using (CurrentTenant.Change(context.Subject.FindTenantId())) //is invoked a couple of times and probably is invoking Task&lt;IdentityUser&gt; FindAsync too below
                {
                    await base.GetProfileDataAsync(context);
                    ...
                }
            }
    
            public override async Task IsActiveAsync(IsActiveContext context)
            {
                using (CurrentTenant.Change(context.Subject.FindTenantId())) //is invoked a couple of times and invoking Task&lt;IdentityUser&gt; FindAsync below
                {
                    var abpUser = await UserManager.GetUserAsync(context.Subject);
                    var abxUser = abpUser == null ? null : await _abxUserRepository.GetAsync(abpUser.Id);
    
                    await base.IsActiveAsync(context);
    
                    ...
                }
            }        
        }
    }
    

and the middleware:

    private async Task OnSessionEndRequestAsync(HttpContext httpContext, IdentityUserManager userManager)
    {
        try
        {
            var user = await userManager.GetUserAsync(httpContext.User); // Cancellation exception!
            ...
        }
        catch(Exception ex)
        {
            _logger.LogError(ex, "Session End handling error");
        }
    }

during logout...

I think all in all you are right about that. But there's another problem. So I put the debug point in intercept call - I'm getting there ONCE or TWO TIMES (those are request to Identity Server) after I already pressed "Logout" button: So I release the debug point and receive this bunch of API requests with error 401... I have created another ticket (https://support.abp.io/QA/Questions/5781/Logout-does-not-actually-logs-out-in-Angular-app) - that I actually cannot logout from the page other than Home page. The guy there answers he cannot do anything, because the problem is not reproduced on his end. So I don't know whether these two problems are related and what to do.

I understand. I suspect it could be a back-end issue with Identity Server, so that token revocation is not successful. Could you please tell me how to troubleshoot this? I may send our Identity Server log for the period of logging out, so maybe you would notice the root cause. Here are one (sometimes two) requests which triggers after I click the "Logout" button: Afterwards I'm getting API requests from my component which are not supposed to be run, because I'm logging out - so they produce error 401. And eventually I'm landed to Home page without logging out.

Saying honestly, I still hoped I would be able to use unsubscriber$ in the component base class to control everything. It's a pity there's no some observable i can watch to change it... I would prefer not to scatter the changes around different classes (e.g. HttpInterceptor, etc.)

Well, at least it's possible make it working using AbpPermissionOptions.. I don't know why it requires it. But seems like I managed to make "dynamic" permission definition provider work properly combining "static" and "dynamic" values:

    private IDictionary&lt;string, PermissionGroupDefinition&gt; _dynamicPermissionGroupDefinitions = new Dictionary&lt;string, PermissionGroupDefinition&gt;();
    protected IDictionary&lt;string, PermissionGroupDefinition&gt; PermissionGroupDefinitions
    {
        get
        {
            var staticGroupDefinitions = _lazyPermissionGroupDefinitions.Value;
            var combinedGroupDefinitions = new Dictionary&lt;string, PermissionGroupDefinition&gt;(staticGroupDefinitions);
            foreach (var userAddedGroupDefinition in _dynamicPermissionGroupDefinitions)
            {
                combinedGroupDefinitions[userAddedGroupDefinition.Key] = userAddedGroupDefinition.Value;
            }
            return combinedGroupDefinitions;
        }
    }
    private readonly Lazy&lt;Dictionary&lt;string, PermissionGroupDefinition&gt;> _lazyPermissionGroupDefinitions;

    private IDictionary&lt;string, PermissionDefinition&gt; _dynamicPermissionDefinitions = new Dictionary&lt;string, PermissionDefinition&gt;();
    protected IDictionary&lt;string, PermissionDefinition&gt; PermissionDefinitions
    {
        get
        {
            var staticDefinitions = _lazyPermissionDefinitions.Value;
            var combinedDefinitions = new Dictionary&lt;string, PermissionDefinition&gt;(staticDefinitions);
            foreach (var userAddedDefinition in _dynamicPermissionDefinitions)
            {
                combinedDefinitions[userAddedDefinition.Key] = userAddedDefinition.Value;
            }
            return combinedDefinitions;
        }
    }
    private readonly Lazy&lt;Dictionary&lt;string, PermissionDefinition&gt;> _lazyPermissionDefinitions;

    protected AbpPermissionOptions Options { get; }

    private readonly IServiceProvider _serviceProvider;

    public ExtendedStaticPermissionDefinitionStore(
        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
        );
    }

    public void AddPermissionDefinitionProvider(IPermissionDefinitionProvider permissionDefinitionProvider)
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var context = new PermissionDefinitionContext(scope.ServiceProvider);

            permissionDefinitionProvider.PreDefine(context);
            permissionDefinitionProvider.Define(context);
            permissionDefinitionProvider.PostDefine(context);

            foreach (var group in context.Groups)
            {
                _dynamicPermissionGroupDefinitions[group.Key] = group.Value;
            }

            var permissions = new Dictionary&lt;string, PermissionDefinition&gt;();

            foreach (var groupDefinition in _dynamicPermissionGroupDefinitions.Values)
            {
                foreach (var permission in groupDefinition.Permissions)
                {
                    AddPermissionToDictionaryRecursively(permissions, permission);
                }
            }

            foreach (var permission in permissions)
            {
                _dynamicPermissionDefinitions[permission.Key] = permission.Value;
            }
        }
    }
    

Probably would come in handy for someone. Closing the ticket... Thanks for inspiration!

Sure.

3 files here: Extension class which makes use of this "dynamic" provider,

overridden Static Store where the exception happens:

var providers = Options
    .DefinitionProviders
    .Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
    .ToList()

if I don't use the Configure<AbpPermissionOptions> approach

and the PortalAccessPermissionDefinitionProvider itself.

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...

This approach works - I've already checked it before. But the problem is that I have hundreds of components, each of which has dozen of API calls. The components use base class which contains unsubscriber$ and it is used in ngOnDestroy to be nullified and complete. Thanks to that, when I go to another component, any API anywhere is properly unsubscribed (takeUntil(this.unsubscriber$)).

Making use of one more check means I need to add this filter(...) in all those API calls. And this is not a very good approach, IMHO.... I'd prefer to make changes in the base class unsubscriber$ only - to cover logout scenario...

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()

Showing 61 to 70 of 276 entries
Made with ❤️ on ABP v9.0.0-preview Updated on September 20, 2024, 08:30