Reputation: 3
I am using ABP version 3.9 for ASP.NET Core. We have an existing Identity Server 4 instance that provides role information in the form of claims (via OIDC). I would like to hook into ABP's permissions system for dynamic menu-ing, among other things. Since I'm not using a local Identity Server1 implementation, I don't see a way to convert the claims to permissions.
My thought is to use custom middleware to handle it like this:
public class ClaimsToAbpPermissionsMiddleware
{
private readonly RequestDelegate _next;
public ClaimsToAbpPermissionsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Get user
ClaimsPrincipal user = context.User;
// foreach claim of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
foreach (Claim claim in roleClaims)
{
switch (claim.Value)
{
case "TestResults":
// Assign applicable permission
// ...
break;
default:
break;
}
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
The problem is that I don't know how to apply the permissions. User Management2 suggests using UserManager
, which appears to have the necessary functionality.
First, Abp.ZeroCore
doesn't have this class. Can I use Abp.Zero
in an ASP.NET Core app?
Second, it looks like Abp.Authorization.Users.AbpUserManager
has similar functionality. Can I use this? However, I'm not sure how to inject this into Startup
, as it requires some types for injecting AbpUserManager<TRole, TUser>
and I'm not clear on what types to use.
Any help would be appreciated. Additionally, is what I'm trying feasible? Is there a better approach?
The issue is that I'm not using Module Zero's User Management2. All of our users are in a separate ASP.NET Identity (Identity Server) implementation, and I'm using the template from ABP that doesn't include the models/pages for user/tenant/role, so I don't have the types to inject AbpUserManager<TRole, TUser>
.
1https://aspnetboilerplate.com/Pages/Documents/Zero/Identity-Server
2https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management
Update: So I implemented IPermissionChecker as per the answer below:
PermissionChecker (not in love with the switch, but am going to refactor once it's working):
public class PermissionChecker : IPermissionChecker, ITransientDependency
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PermissionChecker(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<bool> IsGrantedAsync(string permissionName)
{
bool isGranted = false;
// Get user
var user = _httpContextAccessor.HttpContext.User;
// Get claims of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
// Check for applicable permission based on role permissions
foreach (Claim claim in roleClaims)
{
switch (claim.Value)
{
case "TestResults":
// Assign applicable permission
// ...
if(permissionName.ToLowerInvariant() == "TestResults".ToLowerInvariant())
{
isGranted = true;
}
break;
case "About":
// Assign applicable permission
// ...
if (permissionName.ToLowerInvariant() == "AboutView".ToLowerInvariant())
{
isGranted = true;
}
break;
case "Account":
// Assign applicable permission
// ...
if (permissionName.ToLowerInvariant() == "AccountView".ToLowerInvariant())
{
isGranted = true;
}
break;
default:
break;
}
if (isGranted)
{
break;
}
}
//return new Task<bool>
return Task.FromResult(isGranted);
}
public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
{
return IsGrantedAsync(permissionName);
}
}
AuthorizationProvider:
public class MyAuthProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
var about = context.CreatePermission("AboutView");
var account = context.CreatePermission("AccountView");
var testResults = context.CreatePermission("TestResults");
}
}
Module Initialization:
public class CentralPortalCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Auditing.IsEnabledForAnonymousUsers = true;
CentralPortalLocalizationConfigurer.Configure(Configuration.Localization);
IocManager.Register<IPermissionChecker, PermissionChecker>(DependencyLifeStyle.Transient);
Configuration.Authorization.Providers.Add<MyAuthProvider>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(CentralPortalCoreModule).GetAssembly());
}
}
Navigation Provider:
public override void SetNavigation(INavigationProviderContext context)
{
context.Manager.MainMenu
.AddItem(
new MenuItemDefinition(
PageNames.Home,
L("HomePage"),
url: "",
icon: "fa fa-home"
)
).AddItem(
new MenuItemDefinition(
PageNames.About,
L("About"),
url: "Home/About",
icon: "fa fa-info",
requiredPermissionName: "AboutView",
requiresAuthentication: true
)
).AddItem(
new MenuItemDefinition(
"Results",
L("Results"),
url: "Results",
icon: "fa fa-tasks",
requiredPermissionName: "TestResults"
)
)
.AddItem(
new MenuItemDefinition(
PageNames.Account,
L("Account"),
url: "Account",
icon: "fa fa-info",
requiredPermissionName:"AccountView",
requiresAuthentication: true
)
)
.AddItem(
new MenuItemDefinition(
PageNames.Contact,
L("Contact"),
url: "Contact",
icon: "fa fa-info"
)
);
}
Adding breakpoints shows that the PermissionChecker is initialized as is the AuthProvider. Unfortunately, the navigation doesn't show the protected items even though I am authenticated and have valid role claims. The IsGrantedAsync method is never called. I tried setting the nav items with requiresAuthentication = true and false and neither changed anything.
Am I missing something?
Upvotes: 0
Views: 5636
Reputation: 43103
Well, you're referring to Module Zero documentation but not using Module Zero.
If you don't store users, then it may not make sense to store user permissions.
You can implement IPermissionChecker
to check permissions from claims.
public class PermissionChecker : IPermissionChecker, ITransientDependency
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PermissionChecker(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<bool> IsGrantedAsync(string permissionName)
{
// Get user
var user = _httpContextAccessor.HttpContext.User;
// Get claims of type "role"
var roleClaims = user.Claims.Where(claim => claim.Type == "role");
// Check for applicable permission based on role permissions
// ...
}
public Task<bool> IsGrantedAsync(UserIdentifier user, string permissionName)
{
return IsGrantedAsync(permissionName);
}
}
Since AuthorizationHelper
checks AbpSession.UserId
, you'll have to override its method.
public class NonUserAuthorizationHelper : AuthorizationHelper
{
private readonly IAuthorizationConfiguration _authConfiguration
public NonUserAuthorizationHelper(IFeatureChecker featureChecker, IAuthorizationConfiguration authConfiguration)
: base(featureChecker, authConfiguration)
{
_authConfiguration = authConfiguration;
}
public override async Task AuthorizeAsync(IEnumerable<IAbpAuthorizeAttribute> authorizeAttributes)
{
if (!_authConfiguration.IsEnabled)
{
return;
}
// if (!AbpSession.UserId.HasValue)
// {
// throw new AbpAuthorizationException(
// LocalizationManager.GetString(AbpConsts.LocalizationSourceName, "CurrentUserDidNotLoginToTheApplication")
// );
// }
foreach (var authorizeAttribute in authorizeAttributes)
{
await PermissionChecker.AuthorizeAsync(authorizeAttribute.RequireAllPermissions, authorizeAttribute.Permissions);
}
}
}
And then replace it in the PreInitialize
method of your *.Core
module.
// using Abp.Configuration.Startup;
public override void PreInitialize()
{
Configuration.ReplaceService<IAuthorizationHelper, NonUserAuthorizationHelper>();
}
Upvotes: 2