Since I'm doing this from AppService
not AbpModule
as in the original code, I've simplified the code. I took the DI instances of some required services and used the code below (_dynamicPermissionDefinitionStore
is not needed at all I think, because we do not use dynamic permissions, anyway, it makes no difference):
private async Task RefreshAbpStaticAndDynamicPermissionsAsync()
{
await Policy
.Handle<Exception>()
.WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(RandomHelper.GetRandom((int)Math.Pow(2, retryAttempt) * 8, (int)Math.Pow(2, retryAttempt) * 12)))
.ExecuteAsync(async _ => await _staticPermissionSaver.SaveAsync(), _cancellationTokenProvider.Token);
await _dynamicPermissionDefinitionStore.GetGroupsAsync();
}
However, it still does not work.
Do you have any further suggestions?
@maliming
so basically I need to sequentially call SaveStaticPermissionsToDatabaseAsync
and then - PreCacheDynamicPermissionsAsync
?
Not resolved yet
I create a custom permission definition in DB. On next step I assign it to a user role. This next step is impossible until I restart the app (so the list of permission definitions is being actualized). I do not want app restart - I need a "hot-reload" for permission definitions. How can I implement this? No dramatic changes, no switching to dynamic permissions! - I just need a small "Refresh" method, which would trigger permission definition list reload - so they all would be available for role assignment. Please, show a piece of code.
I found out, that the following routing was somehow lost:
{
path: 'identity-server',
loadChildren: () => import('@volo/abp.ng.identity-server').then(m => m.IdentityServerModule.forLazy()),
},
Please, regain my points.
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 - 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.