Hi, switching to a Domain Service and getting a bit more guidance on the Authorization, i managed to fix my issues.
Thank you!
Test Project, but same issue:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
using Microsoft.AspNetCore.Authentication.Twitter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Volo.Abp.PermissionManagement;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using HangfireAuthTest.EntityFrameworkCore;
using HangfireAuthTest.MultiTenancy;
using StackExchange.Redis;
using Microsoft.OpenApi.Models;
using HangfireAuthTest.HealthChecks;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.DistributedLocking;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Autofac;
using Volo.Abp.Caching;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Modularity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Swashbuckle;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.Hangfire;
using Hangfire;
using Hangfire.PostgreSql;
using HangfireAuthTest.Permissions;
namespace HangfireAuthTest;
[DependsOn(
typeof(HangfireAuthTestHttpApiModule),
typeof(AbpAutofacModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpDistributedLockingModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpIdentityAspNetCoreModule),
typeof(HangfireAuthTestApplicationModule),
typeof(HangfireAuthTestEntityFrameworkCoreModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAspNetCoreSerilogModule)
)]
[DependsOn(
typeof(AbpBackgroundJobsHangfireModule),
typeof(AbpHangfireModule)
)]
public class HangfireAuthTestHttpApiHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (!configuration.GetValue<bool>("App:DisablePII"))
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}
ConfigureHangfire(context, configuration);
ConfigureUrls(configuration);
ConfigureConventionalControllers();
ConfigureAuthentication(context, configuration);
ConfigureSwagger(context, configuration);
ConfigureCache(configuration);
ConfigureVirtualFileSystem(context);
ConfigureDataProtection(context, configuration, hostingEnvironment);
ConfigureDistributedLocking(context, configuration);
ConfigureCors(context, configuration);
ConfigureExternalProviders(context);
ConfigureHealthChecks(context);
Configure<PermissionManagementOptions>(options =>
{
options.IsDynamicPermissionStoreEnabled = true;
});
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UsePostgreSqlStorage(options =>
{
options.UseNpgsqlConnection(configuration.GetConnectionString("Default"));
});
});
context.Services.AddHangfireServer(options =>
{
options.Queues = new[] { "jobs", "default" };
});
}
private void ConfigureHealthChecks(ServiceConfigurationContext context)
{
context.Services.AddHangfireAuthTestHealthChecks();
}
private void ConfigureUrls(IConfiguration configuration)
{
Configure<AppUrlOptions>(options =>
{
options.Applications["Angular"].RootUrl = configuration["App:AngularUrl"];
options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
options.Applications["Angular"].Urls[AccountUrlNames.EmailConfirmation] = "account/email-confirmation";
});
}
private void ConfigureCache(IConfiguration configuration)
{
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "HangfireAuthTest:";
});
}
private void ConfigureVirtualFileSystem(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
if (hostingEnvironment.IsDevelopment())
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Domain.Shared", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Domain", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Application.Contracts", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Application", Path.DirectorySeparatorChar)));
options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestHttpApiModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.HttpApi", Path.DirectorySeparatorChar)));
});
}
}
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(HangfireAuthTestApplicationModule).Assembly);
});
}
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
options.Audience = "HangfireAuthTest";
});
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true;
});
}
private static void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAbpSwaggerGenWithOidc(
configuration["AuthServer:Authority"]!,
["HangfireAuthTest"],
[AbpSwaggerOidcFlows.AuthorizationCode],
configuration["AuthServer:MetaAddress"],
options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "HangfireAuthTest API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
}
private void ConfigureDataProtection(
ServiceConfigurationContext context,
IConfiguration configuration,
IWebHostEnvironment hostingEnvironment)
{
var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("HangfireAuthTest");
if (!hostingEnvironment.IsDevelopment())
{
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "HangfireAuthTest-Protection-Keys");
}
}
private void ConfigureDistributedLocking(
ServiceConfigurationContext context,
IConfiguration configuration)
{
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
}
private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.WithOrigins(
configuration["App:CorsOrigins"]?
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.Trim().RemovePostFix("/"))
.ToArray() ?? Array.Empty<string>()
)
.WithAbpExposedHeaders()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
private void ConfigureExternalProviders(ServiceConfigurationContext context)
{
context.Services
.AddDynamicExternalLoginProviderOptions<GoogleOptions>(
GoogleDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ClientId);
options.WithProperty(x => x.ClientSecret, isSecret: true);
}
)
.AddDynamicExternalLoginProviderOptions<MicrosoftAccountOptions>(
MicrosoftAccountDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ClientId);
options.WithProperty(x => x.ClientSecret, isSecret: true);
}
)
.AddDynamicExternalLoginProviderOptions<TwitterOptions>(
TwitterDefaults.AuthenticationScheme,
options =>
{
options.WithProperty(x => x.ConsumerKey);
options.WithProperty(x => x.ConsumerSecret, isSecret: true);
}
);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAbpRequestLocalization();
app.UseStaticFiles();
app.UseAbpSecurityHeaders();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
if (MultiTenancyConsts.IsEnabled)
{
app.UseMultiTenancy();
}
app.UseUnitOfWork();
app.UseDynamicClaims();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "HangfireAuthTest API");
var configuration = context.GetConfiguration();
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseAbpHangfireDashboard("/hangfire", options =>
{
options.AsyncAuthorization = new[]
{
new AbpHangfireAuthorizationFilter(false, requiredPermissionName: HangfireAuthTestPermissions.Hangfire)
};
});
app.UseConfiguredEndpoints();
}
}
HTTP Error 401 when trying to access /hangfire
https://www.abp.io/support/questions/5167/Hangfire-Authorization-Problem
I want to Authorize the Dashboard, so it can only be accessed by Login. EngincanV mentioned in this ticket we have to redirect from Auth Server to "/hangfire" Right now i get Error 401
"Hi, you can do this easily by redirecting to auth server and specifying the ReturnUrl as /hangfire (https://localhost:5000/Account/Login?ReturnUrl=https://localhost:5001/hangfire), however, even if you do that, you should pass the authorization header from auth server to your host application somehow, so a simple redirection would not be enough."
But how would i do that?
This is the same code as in the ABP-Hangfire Documentation, but it does not work. As it was mentioned in old ticket, i dont know how to redirect from Auth Server to "/hangfire".
<br> HttpApiHostModule
[DependsOn(
typeof(HangfireAuthTestHttpApiModule),
typeof(AbpAutofacModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpDistributedLockingModule),
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpIdentityAspNetCoreModule),
typeof(HangfireAuthTestApplicationModule),
typeof(HangfireAuthTestEntityFrameworkCoreModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAspNetCoreSerilogModule)
)]
[DependsOn(
typeof(AbpBackgroundJobsHangfireModule),
typeof(AbpHangfireModule)
)]
...
public override void ConfigureServices(ServiceConfigurationContext context)
{
...
ConfigureHangfire(context, configuration);
...
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UsePostgreSqlStorage(options =>
{
options.UseNpgsqlConnection(configuration.GetConnectionString("Default"));
});
});
context.Services.AddHangfireServer(options =>
{
options.Queues = new[] { "jobs", "default" };
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
...
app.UseAbpHangfireDashboard("/hangfire", options =>
{
options.AsyncAuthorization = new[]
{
new AbpHangfireAuthorizationFilter(false, requiredPermissionName: HangfireAuthTestPermissions.Hangfire)
};
});
app.UseConfiguredEndpoints();
}
I did not think about that, since i was using a lot of DTOs. this solution helped, Thank you!
But what about the Dashboard Authorization?
The only issue i found similiar to this, was this Ticket: https://www.abp.io/support/questions/5167/Hangfire-Authorization-Problem
But the only issue we have in common is that we cannot Authorize the Dashboard too. It was explained that we should redirect from Auth Server to the /hangfire URL, i followed the Documentation and the ticket but found no solution, how do i redirect from Auth Server to /hangfire? To Authorize the Dashboard?
Wow, it was a checkbox all along, anyways thank you!
I have sent the project via. WeTransfer.
The things i added are:
Hello, i tried clearing the cache multiple times and restarting but i did not change anything. I have still no access to email-settings or the option to give permission on tenant.
ABP Framework version: v8.1.4 UI type: Blazor Server DB provider: PostgresSQL Tiered: yes
I found a similar post, but it's three years old, and the suggested code changes didn't help.: https://abp.io/support/questions/1622/Email-settings-not-visible-for-tenant
Steps to reproduce the issue:
I basically copied the code that liangshiwei provided in his old answer, i implemented the service and tested it on the Host Side and it works fine, i also edited the Permission:
public class YourProjectNamePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
......
var settingManagement = context.GetPermissionOrNull(SettingManagementPermissions.Emailing);
settingManagement.MultiTenancySide = MultiTenancySides.Both;
.....
}
.....
}
But when im on a tenant as an admin, I do not have the option to grant permission for the email settings, there is no checkbox. Is the old method outdated? If so, what is a better method to do this?
I also tried creating my own Setting-UI just to check if i could enter and im getting this Error:
2024-07-26 17:16:53.806 +02:00 [INF] Executing endpoint 'Volo.Abp.SettingManagement.EmailSettingsController.GetAsync (Volo.Abp.SettingManagement.HttpApi)' 2024-07-26 17:16:53.808 +02:00 [INF] Route matched with {area = "settingManagement", action = "Get", controller = "EmailSettings", page = ""}. Executing controller action with signature System.Threading.Tasks.Task`1[Volo.Abp.SettingManagement.EmailSettingsDto] GetAsync() on controller Volo.Abp.SettingManagement.EmailSettingsController (Volo.Abp.SettingManagement.HttpApi).
2024-07-26 17:16:53.850 +02:00 [INF] Authorization failed. These requirements were not met:
PermissionRequirement: SettingManagement.Emailing
2024-07-26 17:16:53.864 +02:00 [WRN] ---------- RemoteServiceErrorInfo ----------
{
"code": "Volo.Authorization:010001",
"message": "Autorisation fehlgeschlagen! Die entsprechende Richtlinie wurde nicht erfüllt.",
"details": null,
"data": {},
"validationErrors": null
}
2024-07-26 17:16:53.864 +02:00 [WRN] Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown.
Volo.Abp.Authorization.AbpAuthorizationException: Exception of type 'Volo.Abp.Authorization.AbpAuthorizationException' was thrown.
at Microsoft.AspNetCore.Authorization.AbpAuthorizationServiceExtensions.CheckAsync(IAuthorizationService authorizationService, AuthorizationPolicy policy)
at Volo.Abp.Authorization.MethodInvocationAuthorizationService.CheckAsync(MethodInvocationAuthorizationContext context)
at Volo.Abp.Authorization.AuthorizationInterceptor.AuthorizeAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Authorization.AuthorizationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Castle\.DynamicProxy\.AsyncInterceptorBase\.ProceedAsynchronous\[TResult\]\(IInvocation invocation\, IInvocationProceedInfo proceedInfo\)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync() at Volo.Abp.GlobalFeatures.GlobalFeatureInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Auditing.AuditingInterceptor.ProceedByLoggingAsync(IAbpMethodInvocation invocation, AbpAuditingOptions options, IAuditingHelper auditingHelper, IAuditLogScope auditLogScope)
at Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at Castle\.DynamicProxy\.AsyncInterceptorBase\.ProceedAsynchronous\[TResult\]\(IInvocation invocation\, IInvocationProceedInfo proceedInfo\)
at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync() at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync()
at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
at lambda\_method3516(Closure, Object)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft\.AspNetCore\.Mvc\.Infrastructure\.ControllerActionInvoker\.g\_\_Awaited\|12\_0\(ControllerActionInvoker invoker\, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) 2024-07-26 17:16:53.865 +02:00 [WRN] Code:Volo.Authorization:010001 2024-07-26 17:16:53.869 +02:00 [INF] AuthenticationScheme: Bearer was forbidden. 2024-07-26 17:16:53.869 +02:00 [INF] Executed action Volo.Abp.SettingManagement.EmailSettingsController.GetAsync (Volo.Abp.SettingManagement.HttpApi) in 61.7528ms 2024-07-26 17:16:53.870 +02:00 [INF] Executed endpoint 'Volo.Abp.SettingManagement.EmailSettingsController.GetAsync (Volo.Abp.SettingManagement.HttpApi)' 2024-07-26 17:16:54.057 +02:00 [DBG] Added 0 entity changes to the current audit log 2024-07-26 17:16:54.064 +02:00 [DBG] Added 0 entity changes to the current audit log 2024-07-26 17:16:54.065 +02:00 [INF] Request finished HTTP/1.1 GET https://localhost:44392/api/setting-management/emailing?api-version=1.0 - 403 null null 266.8626ms