Awesome, thank you.
Hi there. Any updates on this?
There seems to be a bug in the way permissions are checked on toolbar buttons when the required policy is specified in the AddButton method, where we will get a spam of unnecessarily permissions checks on every minimal interaction with the page, like on every key press, for instance.
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:32 DBG] Get dynamic claims cache for user: 3a1beb3d-8133-1e8d-48ee-2869932f2b57
Default implementation where we observe the problem above, with the required permissions being specified as a parameter of the AddButton method:
private ValueTask SetToolbarItemsAsync()
{
Toolbar.AddButton(L["Page:Button:ButtonA"],
async () =>
{
await SomeActionAsync();
},
IconName.Sync,
Blazorise.Color.Primary,
requiredPolicyName: MyPermissions.MyPage.Default);
Toolbar.AddButton(L["Page:Button:ButtonB"],
async () =>
{
await SomeOtherActionAsync();
},
IconName.Download,
Blazorise.Color.Secondary,
requiredPolicyName: MyPermissions.MyPage.Default);
return ValueTask.CompletedTask;
}
As a workaround, when we wrap the permission check around the AddButton method while removing it as a parameter, we no longer observe the permission check spam:
private async ValueTask SetToolbarItemsAsync()
{
if (await PermissionChecker.IsGrantedAsync(MyPermissions.MyPage.Default))
{
Toolbar.AddButton(L["Page:Button:ButtonA"],
async () =>
{
await SomeActionAsync();
},
IconName.Sync,
Color.Primary);
Toolbar.AddButton(L["Page:Button:ButtonB"],
async () =>
{
await SomeOtherActionAsync();
},
IconName.Download,
Color.Secondary);
}
}
[maliming] said: hi
the distributed handler creates a separate UoW to run the migrations in with _unitOfWorkManager.Begin(requiresNew: true), which queries the database before the original UoW commits. Result: tenantConfiguration.ConnectionStrings.Default is null, so migrations don't run when they should.
The
TenantCreatedEtoevent should be published when a tenant is created. Which means the new tenant is already in the database.Thanks.
As I understand, the tenant technically is already in the database, but the transaction is not yet committed at that point, correct? Since if my TenantCreatedEto handler throws, the transaction is rolled back and it's like the entity was never added.
So, since the transaction is not yet committed, when MyAppTenantDatabaseMigrationHandler starts a completely new UoW with requiresNew=true before applying the migrations, it will not "see" the connection string I just set to the tenant in the previous handler, causing the migrations to not be applied. That is the problem.
We are testing a service to automatically provision the database and user for a tenant whenever a tenant is created, ensuring proper tenant isolation at a database level. Although the provisioning is working as we expect, the events that should be triggered after a tenant is created are not.
The current implementation consists of two handlers that listen to TenantCreatedEto:
1. Local handler (order -1): Runs first. Provisions database and sets connection string to the new tenant:
// Inside TenantDatabaseProvisioner.ProvisionAsync()
await CreateDatabaseAsync(hostConnectionString, databaseName);
await CreateUserAsync(hostConnectionString, userName, password);
await GrantPrivilegesAsync(hostConnectionString, databaseName, userName);
var tenantConnectionString = BuildTenantConnectionString(hostConnectionString, databaseName, userName, password);
var tenant = await tenantRepository.FindByIdAsync(tenantId);
tenant.SetDefaultConnectionString(tenantConnectionString);
await tenantRepository.UpdateAsync(tenant, true); // Still within the original UoW
2. Default MyAppTenantDatabaseMigrationHandler: This is default ABP code to run migrations/seeding:
private async Task MigrateAndSeedForTenantAsync(Guid tenantId, string adminEmail, string adminPassword)
{
using (_currentTenant.Change(tenantId))
{
// Creates a NEW Unit of Work
using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
{
var tenantConfiguration = await _tenantStore.FindAsync(tenantId);
// THIS CHECK FAILS - configuration/connection string is still null here, so migrations don't run
if (tenantConfiguration?.ConnectionStrings != null &&
!tenantConfiguration.ConnectionStrings.Default.IsNullOrWhiteSpace())
{
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.MigrateAsync();
}
}
await uow.CompleteAsync();
}
// Seed data
// ...
}
}
Problem: the distributed handler creates a separate UoW to run the migrations in with _unitOfWorkManager.Begin(requiresNew: true), which queries the database before the original UoW commits. Result: tenantConfiguration.ConnectionStrings.Default is null, so migrations don't run when they should.
Thanks!
Hi there.
Any way to get this behavior in the permissions modal?
Hi there. In our application we implemented a page that utilizes the default ABP app services IIdentityUserAppService and IIdentityRoleAppService to list users and roles. Therefore, this page requires the permissions "AbpIdentity.Users" and "AbpIdentity.Roles" to work correctly.
So, to avoid unexpected permission errors when setting up the roles for our application, we need to implement a proper permission dependency when defining our own permissions, as explained in the documentation here and here.
With this in mind, we added the dependencies to our permission definition:
public class MyModulePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myModuleGroupDefinition = context.AddGroup(MyModulePermissions.GroupName, L("Permission:MyModule"));
var myModuleGroup = myModuleGroupDefinition.AddPermission(MyModulePermissions.Home, L("Permission:MyModule:Home"), MultiTenancySides.Tenant);
var reportsPermissions = myModuleGroup.AddChild(MyModulePermissions.Reports.Default, L("Permission:MyModule:Reports"), MultiTenancySides.Tenant);
reportsPermissions.AddChild(MyModulePermissions.Reports.ReportManagement, L("Permission:MyModule:Reports:ReportManagement"), MultiTenancySides.Tenant)
.RequirePermissions("AbpIdentity.Users", "AbpIdentity.Roles"); // this is what we added
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<MyModuleResource>(name);
}
}
What I expected to happen: for my custom permission MyModulePermissions.Reports.ReportManagement to always require the permissions "AbpIdentity.Users" and "AbpIdentity.Roles", in a way that they should be automatically selected in the permissions pop-up when my custom permission is selected, or at least give me an error and not allow me to save until the dependent permissions are also selected, making the permission dependencies clear and mandatory when setting up the permissions for users or roles.
What actually happens: in the permissions pop-up I can select my custom permission MyModulePermissions.Reports.ReportManagement and save without any action or feedback that my custom permission depends on other permissions. From my tests, all RequirePermissions does is consider my custom permission not granted unless the other required permissions are also granted, which is very confusing since my menu entry won't appear for the user even when they have that permission granted (but not its dependencies).
Am I missing something or is this the expected behavior? If so, is there a way to implement permission dependencies in the way I described?
Thanks!
I appreciate the AI-generated response, but I'd like to hear the ABP team's input on this matter.
Thanks!
Hi,
From what I could find in the documentation and in this article, bundling JavaScript or CSS files is only mentioned for Blazor WebAssembly, no mention of Blazor Server. From my tests the BundleContributor approach does not seem to work for Blazor Server.
For now the workaround has been collocated JavaScript files with specific components.
Does ABP offer any way to do file bundling in Blazor Server? What would be the correct approach?
I have verified that adding UseAbpTimeZone to my application initialization does seem to solve the issue in the /audit-logs page and it now shows the correct local time:
Still, it's not clear to me when to have UseAbpTimeZone or not (I couldn't find any documentation on it besides your article).
The inconsistency shown in my previous post does seem like a bug... 🤔