I'm trying to configure domain/subdomain tenant resolving. I'd like to do it on local environment and test it. I saw documentation about that: https://abp.io/docs/latest/framework/architecture/multi-tenancy#domain-subdomain-tenant-resolver and I did all needed changes but with no success.
Let's start to configure that on a new project.
abp new MainPortal -t app-pro -u blazor-server -d ef -csf --tiered
127.0.0.1 mydomain.com
127.0.0.1 tenant1.mydomain.com
public override void PreConfigureServices(ServiceConfigurationContext context)
...
if (context.Services.GetHostingEnvironment().IsDevelopment())
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
}
});
});
});
}
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
...
.AddAbpOpenIdConnect("oidc", options =>
{
...
if (context.Services.GetHostingEnvironment().IsDevelopment())
{
options.BackchannelHttpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
}
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddAbpJwtBearer(options =>
...
options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
options.TokenValidationParameters.ValidIssuers = new[]
{
"https://{0}.mydomain.com:44320/",
"https://mydomain.com:44320/" // i had to add this line in order to make it work even though I set TokenWildcardIssuerValidator
};
if (context.Services.GetHostingEnvironment().IsDevelopment())
{
options.TokenValidationParameters.ValidateIssuerSigningKey = false;
options.TokenValidationParameters.SignatureValidator = delegate (string token, TokenValidationParameters parameters)
{
return new JsonWebToken(token);
};
}
I have no idea why TokenWildcardIssuerValidator does not correctly valid issuers and I had to add it manually - that's the important thing that is different than docs said.
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
...
.AddAbpOpenIdConnect("oidc", options =>
...
options.Events.OnRedirectToIdentityProviderForSignOut = redirectContext =>
{
var currentTenant = redirectContext.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>();
if (currentTenant.IsAvailable)
{
redirectContext.ProtocolMessage.IssuerAddress =
redirectContext.ProtocolMessage.IssuerAddress.Replace("https://", $"https://{currentTenant.Name}.");
}
return Task.CompletedTask;
};
options.Events.OnRedirectToIdentityProvider = options.Events.OnRedirectToIdentityProviderForSignOut;
SecurityTokenInvalidIssuerException: IDX10205: Issuer validation failed. Issuer: 'https://tenant1.mydomain.com:44320/'. Did not match: validationParameters.ValidIssuer: 'null' or validationParameters.ValidIssuers: 'null' or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer: 'https://mydomain.com:44320/'
Port 44320 is assigned to the auth server. In my opinion there is something wrong with configuration - auth server probably should set issuer always as "'https://mydomain.com:44320/'" so without tenant name. In the mvc example (https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver/MVC-TIERED) in PortalAuthServerModule.cs there is a configuration which is probably responsible for this:
Configure<IdentityServerOptions>(options =>
{
options.IssuerUri = configuration["App:SelfUrl"];
});
But as I said example is for mvc app and identity server and i've got blazor server and openid.
In my opinion something important is missing in the docs: https://abp.io/docs/latest/framework/architecture/multi-tenancy#domain-subdomain-tenant-resolver especially when I looked on example: https://github.com/abpframework/abp-samples/tree/master/DomainTenantResolver/MVC-TIERED
I would be really grateful for any help to solve that issue and make Domain/Subdomain Tenant Resolver work :)
CLI (ABP CLI 8.2.0): abp new MainPortal -t app-pro -u blazor-server-d ef -csf --tiered
I added a new "TestPermission" in MainPortalPermissionDefinitionProvider.cs
public class MainPortalPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(MainPortalPermissions.GroupName);
myGroup.AddPermission(MainPortalPermissions.Dashboard.Host, L("Permission:Dashboard"), MultiTenancySides.Host);
myGroup.AddPermission(MainPortalPermissions.Dashboard.Tenant, L("Permission:Dashboard"), MultiTenancySides.Tenant);
myGroup.AddPermission("TestPermission");
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<MainPortalResource>(name);
}
}
The permission is assigned to admin role:
I'd like to return user permissions in AccessToken. As I've got separate AuthServer I added there a code presented below:
public class PermissionsClaimsPrincipalContributor : IAbpClaimsPrincipalContributor, ITransientDependency
{
public async Task ContributeAsync(AbpClaimsPrincipalContributorContext context)
{
var identity = context.ClaimsPrincipal.Identities.FirstOrDefault();
var userId = identity?.FindUserId();
if (userId.HasValue)
{
var permissionManager = context.ServiceProvider.GetRequiredService<IPermissionManager>();
var userPermissions = await permissionManager.GetAllForUserAsync(userId.Value);
// only 46 permissions returned - without "TestPermission"
var rolePermissions = await permissionManager.GetAllForRoleAsync("admin");
// only 46 permissions returned - without "TestPermission"
var permissionDefinitionManager = context.ServiceProvider.GetRequiredService<IPermissionDefinitionManager>();
var allPermissions = await permissionDefinitionManager.GetPermissionsAsync();
// only 46 permissions returned - without "TestPermission"
var allGroups = await permissionDefinitionManager.GetGroupsAsync();
// only 3 permission groups returned - without "MainPortal" group
// AbpIdentity, FeatureManagement, Saas
}
}
}
As comments say there is no way to get my TestPermission assigned to admin role. I can only get 3 groups and all permissions assigned to those groups.
I've tried to use
Configure<PermissionManagementOptions>(options =>
{
options.IsDynamicPermissionStoreEnabled = true;
});
And this way I was able to get all permissions (including TestPermission):
var allPermissions = await permissionDefinitionManager.GetPermissionsAsync();
All 75 permissions were returned.
But when I try to get permissions assigned to current user:
var userPermissions = await permissionManager.GetAllForUserAsync(userId.Value);
I've got an exception:
AbpException: Undefined feature: AuditLogging.Enable
Volo.Abp.Features.FeatureDefinitionManager.GetAsync(string name)
Volo.Abp.Features.FeatureChecker.GetOrNullAsync(string name)
Volo.Abp.Features.FeatureCheckerBase.IsEnabledAsync(string name)
Volo.Abp.Features.FeatureCheckerExtensions.IsEnabledAsync(IFeatureChecker featureChecker, bool requiresAll, string[] featureNames)
Volo.Abp.Features.RequireFeaturesSimpleStateChecker<TState>.IsEnabledAsync(SimpleStateCheckerContext<TState> context)
Volo.Abp.SimpleStateChecking.SimpleStateCheckerManager<TState>.InternalIsEnabledAsync(TState state, bool useBatchChecker)
Volo.Abp.SimpleStateChecking.SimpleStateCheckerManager<TState>.IsEnabledAsync(TState state)
Volo.Abp.PermissionManagement.PermissionManager.GetInternalAsync(PermissionDefinition[] permissions, string providerName, string providerKey)
Volo.Abp.PermissionManagement.PermissionManager.GetAllAsync(string providerName, string providerKey)
MainPortal.PermissionsClaimsPrincipalContributor.ContributeAsync(AbpClaimsPrincipalContributorContext context) in MainPortalAuthServerModule.cs
var userPermissions = await permissionManager.GetAllForUserAsync(userId.Value);
Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal existsClaimsPrincipal, bool isDynamic)
Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.CreateAsync(ClaimsPrincipal existsClaimsPrincipal)
Volo.Abp.Identity.AbpUserClaimsPrincipalFactory.CreateAsync(IdentityUser user)
Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo)
Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue<TResult>.ProceedAsync()
Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter<TInterceptor>.InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache+<>c__DisplayClass23_0+<<GetAsync>b__0>d.MoveNext()
Volo.Abp.Caching.DistributedCache<TCacheItem, TCacheKey>.GetOrAddAsync(TCacheKey key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions> optionsFactory, Nullable<bool> hideErrors, bool considerUow, CancellationToken token)
Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.GetAsync(Guid userId, Nullable<Guid> tenantId)
Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributor.ContributeAsync(AbpClaimsPrincipalContributorContext context)
Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.InternalCreateAsync(AbpClaimsPrincipalFactoryOptions options, ClaimsPrincipal existsClaimsPrincipal, bool isDynamic)
Volo.Abp.Security.Claims.AbpClaimsPrincipalFactory.CreateDynamicAsync(ClaimsPrincipal existsClaimsPrincipal)
Volo.Abp.AspNetCore.Security.Claims.AbpDynamicClaimsMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Volo.Abp.AspNetCore.Uow.AbpUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Volo.Abp.AspNetCore.MultiTenancy.MultiTenancyMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Microsoft.AspNetCore.Builder.ApplicationBuilderAbpOpenIddictMiddlewareExtension+<>c__DisplayClass0_0+<<UseAbpOpenIddictValidation>b__0>d.MoveNext()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Volo.Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+InterfaceMiddlewareBinder+<>c__DisplayClass2_0+<<CreateMiddleware>b__0>d.MoveNext()
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
I would be grateful for your help and analyze whether it's a bug or I missed something.