I've finally managed to resolve my issue. I will show my approach - probably this would come in handy for someone:
Actually two steps are needed:
Create permission itself and add it as a child either to another custom permission or to the root permission node (in my case it is roleSubgroupPermissionDefinition
):
readModuleRolePermissionDefinition = roleSubgroupPermissionDefinition?.AddChild(readRoleName).WithProviders(ModulePermissionRoleValueProvider.ProviderName);
Add the created permission to PermissionDefinitions
collection of ABP static permission store:
await (_staticPermissionDefinitionStore as IExtendedStaticPermissionDefinitionStore).AddPermissionDefinitionAsync(readModuleRolePermissionDefinition.Name, readModuleRolePermissionDefinition);
For this, i've extended ABP class with own methods. They are very simple - just to have access to PermissionDefinitions
collection:
public Task AddPermissionDefinitionAsync(string key, PermissionDefinition permissionDefinition)
{
if (!PermissionDefinitions.ContainsKey(key))
{
PermissionDefinitions.Add(key, permissionDefinition);
}
return Task.CompletedTask;
}
Nothing more is needed - no refresh or something...
Thank you for your cooperation.
I already did this before. And I'm getting the error about missing custom permission definition group:
All other permission definition groups are at place.
When I do not override StaticPermissionDefinitionStore
- the custom permission definition group is present as expected.
What do I need to adjust in MyStaticPermissionDefinitionStore
to make the group available?
To avoid compile errors, I had also to add providers: [SubscriptionService]
to app.component.ts - hope it is a correct string.
But anyway - closeAll()
call did not help: i'm still getting the same 401 error on logout. It happens to any method which is obviously being loaded at the moment of logout. The methods are pretty typical:
//some component
this.fineDigitLoading = true;
this.fineDigitsService
.getById(fineDigit.ordinanceId, fineDigit.number, fineDigit.subdivision, fineDigit.version)
.pipe(
finalize(() => (this.fineDigitLoading = false)),
takeUntil(this.unsubscriber$)
)
.subscribe((state: FineDigits.FineDigit) => {
...
});
//fineDigitsService
getById(
ordinanceId: number,
number: string,
subdivision: string,
version: number): Observable<FineDigits.FineDigit> {
return this.restService.request<void, FineDigits.FineDigit>({
method: 'GET',
url: `/api/md/fine-digits/${ordinanceId}/${number}/${version}/${subdivision === null ? '' : subdivision}`,
},
{ apiName: this.apiName });
}
//base class for any component
ngOnDestroy(): void {
this.ngOnDestroyInner();
this.unsubscriber$.next(null);
this.unsubscriber$.complete();
}
Probably the latter approach is the reason? When the user is logging out - the current component is not being destroyed, so ngOnDestroy
is not being triggered. If this is the case - please let me know the recommended approach to unsubscribe from all API calls by adjusting the code in base component class - adding 'logout' event handling which is now done in app.component.ts? Suprisingly I did not see any special handling for such scenario in ABP components...
UPDATE: I've added this to Base component constructor, but stil keep getting the same 401 errors:
this.oAuthService.events
.pipe(filter(event => event?.type === 'logout'), takeUntil(this.unsubscriber$))
.subscribe(() => {
this.unsubscriber$.next(null);
this.unsubscriber$.complete();
});
The above part IS triggered after I click "Logout" link. But after this my server API calls are still made. How it's possible if I use takeUntil(this.unsubscriber$)
for all API calls?
ABP 7.0.1 / Angular
My home page shows some information to an authenticated user via API calls. If the user logs out - these methods need not to be invoked anymore.
Seems like I've tried all possible ways - and it still DOES call those methods with "Not authorized (401)" error from server after I click "Logout" button. I also have tried to call Subscription$.unsubscribe()
while logging out, but it still does not work.
Another question: I can logout from any page, not just Home page. There are plenty of API call subscriptions on each of them. How am I supposed to unsubscribe from all such calls with minimal code changes??
Here is the piece of the code of my Home page:
ngOnInit() {
this.oAuthService.events
.pipe(
filter(event => event?.type === 'logout'),
tap(() => {
this.logout$.next(null); //those are called, but API calls are still invoked
this.logout$.complete();
}))
.subscribe();
this.homeService.getNewsForHomePage()
.pipe(filter(() => this.configStateService.getDeep('currentUser.isAuthenticated')), takeUntil(this.destroy), takeUntil(this.logout$))
.subscribe((newsResponse) => {
...
});
this.homeService.getUrlsForHomePage()
.pipe(filter(() => this.configStateService.getDeep('currentUser.isAuthenticated')), takeUntil(this.destroy), takeUntil(this.logout$))
.subscribe((newsUrlParameterResponse) => {
...
});
}
ngOnDestroy(): void {
this.destroy.next(null);
this.destroy.complete();
}
Moreover - when I am already at this page (where this.configStateService.getDeep('currentUser.isAuthenticated')
is supposed to be false
, I guess):
the API calls are still invoked.
Thank you! I've also removed this.routesService.refresh();
both occurences and seems to be working fine now.
I've overridden ABP PermissionAppService
and debugged it. No issue found inside - the code below is called and contains correct data:
public virtual async Task UpdateAsync(string providerName, string providerKey, UpdatePermissionsDto input)
{
await CheckProviderPolicy(providerName);
foreach (var permissionDto in input.Permissions)
{
await PermissionManager.SetAsync(permissionDto.Name, providerName, providerKey, permissionDto.IsGranted);
}
await RabbitMqManager.UpdateAbpPermissionsAsync(input.Permissions, providerName, providerKey, CurrentTenant.Id);
}
So moving on, I've overriden PermissionManager
too and found out that SetAsync
does not actually update DB:
I can see though, that there's no point to override PermissionManager
- its method is very simple:
public override async Task<PermissionDefinition> GetOrNullAsync(string name)
{
Check.NotNull(name, nameof(name));
return await _staticStore.GetOrNullAsync(name) ??
await _dynamicStore.GetOrNullAsync(name);
}
So I proceeded - overridden StaticPermissionDefinitionStore
:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IStaticPermissionDefinitionStore))]
public class ApiStaticPermissionDefinitionStore : StaticPermissionDefinitionStore, IApiStaticPermissionDefinitionStore
{
public ApiStaticPermissionDefinitionStore(
IServiceProvider serviceProvider,
IOptions<AbpPermissionOptions> options) : base(serviceProvider, options)
{
}
public void AddPermissionDefinition(string key, PermissionDefinition permissionDefinition)
{
if (!PermissionDefinitions.ContainsKey(key))
{
PermissionDefinitions.Add(key, permissionDefinition);
}
}
}
My idea was to create a method in my IStaticPermissionDefinitionStore
override, where I would FORCE adding a new PermissionDefinition
to the existing collection. However, after adding it I've discovered that a whole custom permission definition collection is broken. BTW - inside AddPermissionDefinition
I've looked at a whole PermissionDefinitions
collection and suprisingly discovered that not all definitions are there: my custom definitions were not there by this time (yet?)
Probably you can suggest another way around this?
Hi,
we need to check this remotely, as we are unable understand the tenant switch scenario. is that okay with you?
As I wrote before, probably to reproduce the issue, tenant switching is not needed: it can be just UI button click which triggers https://localhost:44337/[test-app]/application-configuration?includeLocalizationResources=false
call.
Where the overriden AbpApplicationConfigurationAppService
GetAsync()
method returns the mentioned structure in extraProperties
, so if some Module ID has false
- the relevant page needs to be removed from ABP navigation menu. If the Module ID is not there - the page needs to be shown again. And this structure is randomly generated on each button click (giving different Module IDs from the existing ones), imitating tenant switching. Does it make sense to you?
Anyway, our policies do not allow us to share the code or show it via screen-sharing... Sorry.
Will try, thank you. Will keep you posted.
I'm afraid I can't do that. This is a commercial project - I can just show some pieces of code. Besides - we don't even have SQL Server Express setup (we use another DB) and we don't use test ABP app generation, so all preparation work would take a way more time than we are given for bug-fixing.
Probably you have some ready testing environment at your side set up? In this case even tenant switching functionality itself can be omitted - you just need to rerun this API call (by the UI button?):
And in the extra-properties return something like hidePageMap
dictionary: { [moduleId of the page]: true, [moduleId of the page]: true }
- moduleIDs can be randomly selected from the predefined collection you use (see below), so it imitates different users permissions when switching tenants.
Module ID for any relevant page is supplied (and read in Angular app) here: and here:
So each time you click the button and return result from application configuration - the menu needs to be shown according to the "visible" module IDs only.
P.S. Maybe in future I will ask our management to give us time for building original ABP solution test environment to be able to create bug reproduction scenarios, so it will be easier for all.
Hi Anjali.
Thank you for the response.
First - window.location.href = '/'
does not help. The menu is still empty. But I was already able to make it work this way before using window.location.reload()
or something like that. However, I want to avoid this approach. The reason is that this Angular thing is a Single Page Application and it does not look nice to reload a whole page instead of just refreshing a navigation menu - this is exactly what I want to get.
The idea is the following: after I receive this.configStateService.getDeep$('extraProperties.modulePermissionMap')
from server, I want to make decision myself, what to show in the menu and what - not. Since ABP builds its menu based on ordinary permissions - I had to use the approach with
this.routesService.flat.filter(x => x.requiredPolicy && (x as any).data?.moduleId).forEach(x => x.invisible = true);
this.routesService.refresh();
to hide ALL the menu items from the very beginning and then start applying my logic (combineLatest([this.configStateService.getDeep$('extraProperties.modulePermissionMap'), this.routesService.flat$.pipe(take(1))])
).
Probably my approach is incorrect - then please advice me how to obtain my goal.