Activities of "alexander.nikonov"

  • ABP Framework version: v7.0.1.
  • UI type: Angular
  • DB provider: EF Core Identity Server Separated (Angular)

At some point the pages from "Identity Server" section became non-accessible - i.e. they are visible in the menu and corresponding permissions are present. But clicking on any of these pages gives error 404:

The app-routing.module.ts is quite typical:

app.component.ts contains some page replacement, but nothing related to "Identity Server" section:

constructor(
    ...
  ) {
      this.replaceableComponents.add({
        component: TenantsComponent,
        key: eSaasComponents.Tenants,
      }),
      this.replaceableComponents.add({
        component: UsersComponent,
        key: eIdentityComponents.Users
      }),
      this.replaceableComponents.add({
        component: RolesComponent,
        key: eIdentityComponents.Roles
      });
}

ngOnInit(): void {

  this.manageProfileTabs.patch(eAccountManageProfileTabNames.PersonalInfo, {
    component: AbxPersonalSettingsComponent,
  });
  this.routesService.remove([eIdentityRouteNames.OrganizationUnits]);
  this.routesService.remove([eIdentityRouteNames.Users]);
  this.routesService.remove([eIdentityRouteNames.Roles]);
  this.routesService.remove([eSaasRouteNames.Tenants]);
  ...
}

I cannot figure out, what could have caused the reported issue. Please, provide some suggestion - this code is all I can show, full project is not available.

Thank you very much. I will try to improve my implementation using this change. I think I may close the ticket now.

Ok. Waiting.

OK - so I abandoned the idea to override the RoutesService and implemented the following handler (retaining the idea to hide the elements ASAP and unhide them after getting service data) - please have a look if there could be a more efficient solution.

    import { ABP, RoutesService } from '@abp/ng.core';
    import { Inject, Injectable, OnDestroy } from '@angular/core';
    import { BehaviorSubject, ReplaySubject, Subscription, combineLatest } from 'rxjs';
    import { distinctUntilChanged, filter, takeLast, takeUntil } from 'rxjs/operators';
    import { ModulePermissionsService } from '../services/module-permissions.service';
    
    
    @Injectable({
      providedIn: 'root'
    })
    export class ModulePermissionHandler implements OnDestroy {
    
      modulePermissionMap$: BehaviorSubject<{[key: string]: string[]}> = new BehaviorSubject<{[key: string]: string[]}>({});
      
      private destroy: ReplaySubject<any> = new ReplaySubject<any>(1);
      
      private stopMenuScan$: ReplaySubject<any> = new ReplaySubject<any>(1);
      
      private mapBindingComplete: boolean = false;
    
      constructor(
        private routesService: RoutesService,
        private modulePermissionsService: ModulePermissionsService
      ) {
      }
    
      ngOnDestroy(): void {
        this.deinit();
      }
    
      init() {
        this.routesService.flat.filter(x => x.requiredPolicy && (x as any).data?.moduleId).forEach(x => x.invisible = true);
        this.routesService.refresh();
        combineLatest([this.modulePermissionMap$, this.routesService.flat$])
          .pipe
          (
            filter(result => !this.mapBindingComplete && Object.keys(result[0]).length > 0 && Object.keys(result[1]).length > 0),
            takeUntil(this.destroy),
            takeUntil(this.stopMenuScan$)
          )
          .subscribe(([modulePermissionMap, nonLazyLoadedRoute])  => {
            let permissionProhibitedPageIds: string[] = [];
            nonLazyLoadedRoute.filter(node => node.requiredPolicy).forEach((nonLazyRouteItem: ABP.Route) => {
              let moduleId = (nonLazyRouteItem as any).data?.moduleId;
              if (moduleId) {
                const moduleIdPolicyViolated = !modulePermissionMap[moduleId] || modulePermissionMap[moduleId] && !modulePermissionMap[moduleId].includes(nonLazyRouteItem.requiredPolicy as string);
                const ordinaryRolePolicyViolated = !modulePermissionMap['_ordinaryRole'] || modulePermissionMap['_ordinaryRole'] && !modulePermissionMap['_ordinaryRole'].includes(nonLazyRouteItem.requiredPolicy as string);
                if (moduleIdPolicyViolated && ordinaryRolePolicyViolated) {
                  permissionProhibitedPageIds.push(nonLazyRouteItem.name);
                }
                else {
                  nonLazyRouteItem.invisible = false;
                }
              }
            });
            this.stopMenuScan$.next(null);
            this.stopMenuScan$.complete();
            this.routesService.remove(permissionProhibitedPageIds);
            this.mapBindingComplete = true;
            this.stopMenuScan$ = new ReplaySubject(1);
          });
    
        this.modulePermissionsService.getModulePermissionMap()
          .pipe(takeUntil(this.destroy))
          .subscribe(modulePermissionMap => {
            this.mapBindingComplete = false;
            this.modulePermissionMap$.next(modulePermissionMap);
          });
      }
    
      deinit() {
        this.destroy.next(null);
        this.destroy.complete();
      }
    }
    

Usage - app.component.ts:

   ngOnInit(): void {        
        this.oAuthService.events
          .pipe(filter(event => event?.type === 'logout'))
          .subscribe(() => {
            this.modulePermissionHandler.deinit();
          });
    
        this.currentUser$.subscribe(currentUser => {
          if (currentUser?.isAuthenticated) {
            this.modulePermissionHandler.init();
          }
        });
   }
   

And - yes - I had also to override PermissionGuard: I have copy-pasted this from route.provider.ts to app-routing.module.ts: and check this.modulePermissionMap$ in CanActivate method.

So far I have not found a better approach than hiding (setting invisible to an item with requiredPolicy and moduleId) in the createTree method) the items prior to removing or showing them inside subcription of combineLatest.

UPDATE: i've checked this approach and it has some drawbacks - after filtering the menu structure is broken (some intermediate parent nodes disappeared) and I cannot navigate to the displayed pages with "no matching route" error. I don't know if I am doing something wrong or it is bad idea to override RoutesService and hide the items in the createTree call.

Anjali, do you know where should I put my "hide" method? The small issue now is that initially the menu is shown fully - pause for API mapping call - and then it is filtered out accordingly. I want the menu was shown after it has been filtered out...

this.currentUser$.subscribe(currentUser => {
  if (currentUser?.isAuthenticated) {
    ...
    this.modulePermissionHandler.init(); // it is too late :(
    ...
});

I've tried to replace RoutesService:

export class AbxRoutesService extends AbstractNavTreeService<ABP.Route>

and make filtration inside

public override createTree(items: ABP.Route[]): TreeNode<ABP.Route>[]

But this method is synchronous and I cannot await mapping API call result. On other hand, I don't want to do something about flat$ getter, because the filtration needs to be done just before building the tree.

It is so frustrating...

Hi. Seems like I have eventually nailed it. My steps were:

  1. adding moduleId to non-lazy node collection in route.provider.ts(I copied the corresponding values from lazy-loading nodes (path: '' in the routing module). I applied your as any recipe, thank you;

  2. returning the following multiple permission result from back-end to be able to handle (hide) nodes in Angular app using mapping from back-end:

     public override Task<MultiplePermissionGrantResult> CheckAsync(PermissionValuesCheckContext context)
     {
         var permissionNames = context.Permissions.Select(x => x.Name).Distinct().ToList();
         Check.NotNullOrEmpty(permissionNames, nameof(permissionNames));
    
         return Task.FromResult(new MultiplePermissionGrantResult(permissionNames.ToArray(), PermissionGrantResult.Granted));
     }
    
  3. making API request retrieving the mapping between moduleId (or all simple roles) and their permissions for a current user (modulePermissionMap$);

  4. filtering out all the menu items which need to be hidden based on modulePermissionMap$, RoutersService.flat$ (combineLatest) and eventually applying RoutersService.remove method;

I will keep the ticket opened so far - in case our testing team finds something wrong during the nearest time...

Ok, I see. So RoutersService does not fit me. Still, I need some mechanism to show or not to show the specific lazy-loaded nodes based on their "moduleId". I need to do this before the menu is displayed, of course: How do I do this? Probably some Router events are in use here...

Hmm, probably it would work this way. I would give it a try. However, I just discovered I was not right in my suggestions. ABP RoutesService does not return all nodes in the menu - it returns only non-lazy nodes. So what I need is to load a whole structure of my menu into a variable and then check each node module ID inside the loop. The question is - what is a proper way to load all the nodes? Are there ready ABP methods for it? Or I need to use Angular methods? Or I need to create my own recursive method? I know it won't be complex, but don't want to re-invent a wheel...

I need to receive all nodes which are present in the menu, both lazy and non-lazy. And obtain data property of each node. Angular Router config DOES return data, but it does not return lazy nodes. ABP RoutesService flat$ / tree$ / visible$ DOES return all nodes, but it does not return data. So I need something which would return BOTH.

Showing 131 to 140 of 318 entries
Made with ❤️ on ABP v9.0.0-preview Updated on September 20, 2024, 08:30