Project Information: ABP version: 10.3 .NET version: .NET 10 UI framework: Blazor WebAssembly Solution type: Tiered Relevant projects: .Application, .Application.Contracts, .AuthServer, .Blazor, .Core, .DB, .DbMigrator, .Domain, .Domain.Seed, .Domain.Shared, .EntityFrameworkCore .HttpApi, .HttpApi.Host, .HttpApi.Client
Issue: After upgrading our solution to ABP 10, the administrator users faced issues when they tried to change the roles of another use. After 3 days of debugging we discovered that the UpdateRolesAsync function had been changed in ABP 10, as now it checks whether the CurrentUser.IsInRole(AbpRoleConsts.AdminRoleName). The problem came from the const AdminRoleName because our own solution has different role names. The solution we chose is to override the UpdateRolesAsync in order to use our own role name, which worked. We would like to propose that the const is changed to property so that it can be changed through configuration. Here is an example: public static class AbpRoleConsts { private static string _adminRoleName = "admin"; // default public static string AdminRoleName { get => _adminRoleName; set => _adminRoleName = value; } } Thank you in advance
1 Answer(s)
-
0
Hi,
This is the intended behavior, not a bug. The change comes from PR #24775 - Prevent Privilege Escalation: Add Assignment Restrictions for Roles and Permissions, which closes a real privilege escalation hole: a non-admin operator with
IdentityPermissions.Users.Updatecould otherwise assign any role (includingadmin) to other users, or to themselves.The new rule in
IdentityUserAppService.UpdateRolesAsyncis:- If the current operator is in the
adminrole (HasAdminRoleAsync()returns true), the input is used as-is. - Otherwise, the operator can only grant roles they themselves already have. Roles the target user already has but the operator does not have are preserved (so the operator cannot strip a higher-privileged role either).
The "is operator an admin?" check intentionally uses the well-known
AbpRoleConsts.AdminRoleName("admin") because that's the role the framework itself seeds and treats as superuser everywhere (data seeding, permission management, etc.). It's aconst, and changing it to a settable property is something we'd rather not do - it would break binary compatibility (aconstis inlined at compile time into every consumer), andIdentityDataSeederdoesconst string adminRoleName = AbpRoleConsts.AdminRoleName;, which only compiles because the right-hand side is a compile-time constant. So we don't recommend renaming the framework'sadminrole - it's much safer to keep anadminrole in your DB even if your business uses different role names elsewhere.That said, if you really need a different role name to act as the "superuser" for this check, you don't need to override
UpdateRolesAsync. We left a smaller, more precise extension point:HasAdminRoleAsync()isprotected virtual. Just override that one method in your*.Applicationproject:using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.Identity; using Volo.Abp.Identity.Emailing; using Volo.Abp.Identity.UserInvitations; using Volo.Abp.Identity.UserSharing; using Volo.Abp.Threading; namespace YourProject.Identity; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService))] public class MyIdentityUserAppService : IdentityUserAppService { public MyIdentityUserAppService( IdentityUserManager userManager, IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository, IOrganizationUnitRepository organizationUnitRepository, IIdentityClaimTypeRepository identityClaimTypeRepository, IdentityProTwoFactorManager identityProTwoFactorManager, IOptions<IdentityOptions> identityOptions, IDistributedEventBus distributedEventBus, IOptions<AbpIdentityOptions> abpIdentityOptions, IPermissionChecker permissionChecker, IDistributedCache<IdentityUserDownloadTokenCacheItem, string> downloadTokenCache, IDistributedCache<ImportInvalidUsersCacheItem, string> importInvalidUsersCache, IdentitySessionManager identitySessionManager, IdentityUserTwoFactorChecker identityUserTwoFactorChecker, ICancellationTokenProvider cancellationTokenProvider, UserSharingManager userSharingManager, UserInvitationManager userInvitationManager, IIdentityUserInvitationRepository userInvitationRepository, IIdentityEmailSender identityEmailSender) : base( userManager, userRepository, roleRepository, organizationUnitRepository, identityClaimTypeRepository, identityProTwoFactorManager, identityOptions, distributedEventBus, abpIdentityOptions, permissionChecker, downloadTokenCache, importInvalidUsersCache, identitySessionManager, identityUserTwoFactorChecker, cancellationTokenProvider, userSharingManager, userInvitationManager, userInvitationRepository, identityEmailSender) { } protected override Task<bool> HasAdminRoleAsync() { return Task.FromResult(CurrentUser.IsInRole("YourCustomAdminRoleName")); } }I verified locally on
Volo.Abp.Identity.Pro.Application.Testsagainst ABP 10.3 - the override is picked up correctly, an operator with the custom admin role claim can assign any role (including ones they don't have themselves), and all the existing role-filtering / unmanageable-role-preservation tests keep passing.Thanks.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - If the current operator is in the