Browse Source

Merge pull request #221 from aspnetzero/ids4-multitenancy

Ids4 multitenancy
pull/272/head
Halil İbrahim Kalkan 9 years ago
committed by GitHub
parent
commit
975fe50f45
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      src/MicroserviceDemo/MicroserviceDemo.AuthServer/Controllers/MultiTenancyController.cs
  2. 5
      src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs
  3. 2
      src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs
  4. 1
      src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj
  5. 9
      src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs
  6. 2
      src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs
  7. 2
      src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs
  8. 18
      src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs
  9. 2
      src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs
  10. 2
      src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml
  11. 47
      src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs
  12. 3
      src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs
  13. 9
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs
  14. 8
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancyOptions.cs
  15. 24
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/ClaimsHttpTenantResolveContributer.cs
  16. 8
      src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyOptionsExtensions.cs
  17. 48
      src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs
  18. 7
      src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs
  19. 38
      src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs
  20. 10
      src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs
  21. 11
      src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  22. 30
      src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs
  23. 13
      src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpClaimsService.cs
  24. 17
      src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs
  25. 1
      src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj
  26. 21
      src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentClaimsPrincipalTenantResolveContributer.cs
  27. 5
      src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs
  28. 7
      src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs
  29. 67
      src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs
  30. 5
      src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs
  31. 4
      src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderExtensions.cs
  32. 69
      test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs

66
src/MicroserviceDemo/MicroserviceDemo.AuthServer/Controllers/MultiTenancyController.cs

@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Ui;
namespace MicroserviceDemo.AuthServer.Controllers
{
/* TODO: This is temporary solution to switch tenant.
*/
public class MultiTenancyController : AbpController
{
private readonly ITenantStore _tenantStore;
private readonly AspNetCoreMultiTenancyOptions _options;
public MultiTenancyController(ITenantStore tenantStore, IOptions<AspNetCoreMultiTenancyOptions> options)
{
_tenantStore = tenantStore;
_options = options.Value;
}
public async Task<ActionResult> SwitchTenant(string tenant = "")
{
if (tenant.IsNullOrEmpty())
{
HttpContext.Response.Cookies.Delete(_options.TenantKey);
}
else
{
var tenantInfo = await FindTenantAsync(tenant);
if (tenantInfo == null)
{
throw new UserFriendlyException("Unknown tenant: " + tenant);
}
HttpContext.Response.Cookies.Append(
_options.TenantKey,
tenantInfo.Id.ToString(),
new CookieOptions
{
Expires = DateTimeOffset.Now.AddYears(1)
}
);
}
return Redirect("/");
}
private async Task<TenantInfo> FindTenantAsync(string tenantIdOrName)
{
if (Guid.TryParse(tenantIdOrName, out var parsedTenantId))
{
return await _tenantStore.FindAsync(parsedTenantId);
}
else
{
return await _tenantStore.FindAsync(tenantIdOrName);
}
}
}
}

5
src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs

@ -97,11 +97,12 @@ namespace MicroserviceDemo.AuthServer
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
} }
app.UseMultiTenancy();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseVirtualFiles(); app.UseVirtualFiles();
app.UseAuthentication(); //TODO: This makes it twice since UseIdentityServer also internally adds UseAuthentication!
app.UseMultiTenancy();
app.UseIdentityServer(); //This internally adds .UseAuthentication() (we should be carefully about that) app.UseIdentityServer(); //This internally adds .UseAuthentication() (we should be carefully about that)
app.UseMvc(routes => app.UseMvc(routes =>

2
src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs

@ -26,7 +26,7 @@ namespace MicroserviceDemo.AuthServer
.AddDebug() .AddDebug()
.AddSerilog(new LoggerConfiguration() .AddSerilog(new LoggerConfiguration()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.RollingFile("Logs/logs.txt") .WriteTo.File("Logs/logs.txt")
.CreateLogger() .CreateLogger()
); );

1
src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj

@ -20,6 +20,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Volo.Abp.AspNetCore.MultiTenancy\Volo.Abp.AspNetCore.MultiTenancy.csproj" />
<ProjectReference Include="..\..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> <ProjectReference Include="..\..\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\Volo.Abp.MultiTenancy.Application\Volo.Abp.MultiTenancy.Application.csproj" /> <ProjectReference Include="..\..\Volo.Abp.MultiTenancy.Application\Volo.Abp.MultiTenancy.Application.csproj" />
<ProjectReference Include="..\..\Volo.Abp.MultiTenancy.EntityFrameworkCore\Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj" /> <ProjectReference Include="..\..\Volo.Abp.MultiTenancy.EntityFrameworkCore\Volo.Abp.MultiTenancy.EntityFrameworkCore.csproj" />

9
src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs

@ -6,7 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.Swagger;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Modularity; using Volo.Abp.AspNetCore.Modularity;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
@ -15,7 +15,6 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.MultiTenancy.EntityFrameworkCore; using Volo.Abp.MultiTenancy.EntityFrameworkCore;
using Volo.Abp.Permissions.EntityFrameworkCore; using Volo.Abp.Permissions.EntityFrameworkCore;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using Volo.Abp.Threading;
namespace MicroserviceDemo.TenancyService namespace MicroserviceDemo.TenancyService
{ {
@ -24,6 +23,7 @@ namespace MicroserviceDemo.TenancyService
[DependsOn(typeof(AbpMultiTenancyHttpApiModule))] [DependsOn(typeof(AbpMultiTenancyHttpApiModule))]
[DependsOn(typeof(AbpMultiTenancyApplicationModule))] [DependsOn(typeof(AbpMultiTenancyApplicationModule))]
[DependsOn(typeof(AbpPermissionsEntityFrameworkCoreModule))] [DependsOn(typeof(AbpPermissionsEntityFrameworkCoreModule))]
[DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))]
public class MicroservicesDemoTenancyServiceModule : AbpModule public class MicroservicesDemoTenancyServiceModule : AbpModule
{ {
public override void ConfigureServices(IServiceCollection services) public override void ConfigureServices(IServiceCollection services)
@ -83,9 +83,6 @@ namespace MicroserviceDemo.TenancyService
{ {
var app = context.GetApplicationBuilder(); var app = context.GetApplicationBuilder();
var store = context.ServiceProvider.GetRequiredService<IPermissionStore>();
var xx = AsyncHelper.RunSync(() => store.IsGrantedAsync("AbpTenantManagement.Tenants", "Role", "admin"));
app.UseStaticFiles(); app.UseStaticFiles();
app.UseSwagger(); app.UseSwagger();
@ -96,6 +93,8 @@ namespace MicroserviceDemo.TenancyService
app.UseAuthentication(); app.UseAuthentication();
app.UseMultiTenancy();
app.UseMvcWithDefaultRoute(); app.UseMvcWithDefaultRoute();
} }

2
src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs

@ -26,7 +26,7 @@ namespace MicroserviceDemo.TenancyService
.AddDebug() .AddDebug()
.AddSerilog(new LoggerConfiguration() .AddSerilog(new LoggerConfiguration()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.RollingFile("Logs/logs.txt") .WriteTo.File("Logs/logs.txt")
.CreateLogger() .CreateLogger()
); );

2
src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs

@ -8,7 +8,7 @@ using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Ui; using Volo.Abp.Ui;
namespace AbpDesk.Web.Mvc.Controllers namespace MicroserviceDemo.Web.Controllers
{ {
/* TODO: This is temporary solution to switch tenant. /* TODO: This is temporary solution to switch tenant.
*/ */

18
src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -69,9 +70,9 @@ namespace MicroserviceDemo.Web
{ {
"/Abp/ApplicationConfigurationScript?_v=" + DateTime.Now.Ticks, "/Abp/ApplicationConfigurationScript?_v=" + DateTime.Now.Ticks,
"/Abp/ServiceProxyScript?_v=" + DateTime.Now.Ticks "/Abp/ServiceProxyScript?_v=" + DateTime.Now.Ticks
}); });
}); });
services.AddAuthentication(options => services.AddAuthentication(options =>
{ {
options.DefaultChallengeScheme = "oidc"; options.DefaultChallengeScheme = "oidc";
@ -97,6 +98,19 @@ namespace MicroserviceDemo.Web
options.Scope.Add("multi-tenancy-api"); options.Scope.Add("multi-tenancy-api");
options.ClaimActions.MapAbpClaimTypes(); options.ClaimActions.MapAbpClaimTypes();
options.Events.OnRedirectToIdentityProvider += context =>
{
//TODO: ...
var tenantId = context.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>().Id?.ToString();
if (!tenantId.IsNullOrEmpty())
{
context.ProtocolMessage.Parameters[TenantResolverConsts.DefaultTenantKey] = tenantId;
}
return Task.CompletedTask;
};
}); });
services.Configure<RemoteServiceOptions>(configuration); services.Configure<RemoteServiceOptions>(configuration);

2
src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs

@ -26,7 +26,7 @@ namespace MicroserviceDemo.Web
.AddDebug() .AddDebug()
.AddSerilog(new LoggerConfiguration() .AddSerilog(new LoggerConfiguration()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.RollingFile("Logs/logs.txt") .WriteTo.File("Logs/logs.txt")
.CreateLogger() .CreateLogger()
); );

2
src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml

@ -1,5 +1,5 @@
@page @page
@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel @model Volo.Abp.Account.Web.Pages.Account.IdentityServerLoginModel
<div class="row"> <div class="row">
@if (Model.EnableLocalLogin) @if (Model.EnableLocalLogin)

47
src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs

@ -18,13 +18,14 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Account.Web.Auth; using Volo.Abp.Account.Web.Auth;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Ui; using Volo.Abp.Ui;
using Volo.Abp.Uow; using Volo.Abp.Uow;
namespace Volo.Abp.Account.Web.Pages.Account namespace Volo.Abp.Account.Web.Pages.Account
{ {
//TODO: Inherit from LoginModel of Account.Web project. We should design it as extensible. //TODO: Inherit from LoginModel of Account.Web project. We should design it as extensible.
public class IdsLoginModel : AccountModelBase public class IdentityServerLoginModel : AccountModelBase
{ {
[HiddenInput] [HiddenInput]
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
@ -54,13 +55,13 @@ namespace Volo.Abp.Account.Web.Pages.Account
private readonly IClientStore _clientStore; private readonly IClientStore _clientStore;
private readonly IEventService _identityServerEvents; private readonly IEventService _identityServerEvents;
public IdsLoginModel( public IdentityServerLoginModel(
SignInManager<IdentityUser> signInManager, SignInManager<IdentityUser> signInManager,
IdentityUserManager userManager, IdentityUserManager userManager,
IIdentityServerInteractionService interaction, IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider, IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions, IOptions<AbpAccountOptions> accountOptions,
IClientStore clientStore, IClientStore clientStore,
IEventService identityServerEvents) IEventService identityServerEvents)
{ {
_signInManager = signInManager; _signInManager = signInManager;
@ -77,12 +78,32 @@ namespace Volo.Abp.Account.Web.Pages.Account
LoginInput = new LoginInputModel(); LoginInput = new LoginInputModel();
var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl);
LoginInput.UserNameOrEmailAddress = context?.LoginHint;
if (context != null)
{
LoginInput.UserNameOrEmailAddress = context.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey];
if (tenant.IsNullOrEmpty())
{
if (Request.Cookies.ContainsKey(TenantResolverConsts.DefaultTenantKey))
{
CurrentTenant.Change(null);
Response.Cookies.Delete(TenantResolverConsts.DefaultTenantKey);
}
}
else
{
CurrentTenant.Change(Guid.Parse(tenant));
Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant);
}
}
if (context?.IdP != null) if (context?.IdP != null)
{ {
LoginInput.UserNameOrEmailAddress = context.LoginHint; LoginInput.UserNameOrEmailAddress = context.LoginHint;
ExternalProviders = new[] {new ExternalProviderModel {AuthenticationScheme = context.IdP}}; ExternalProviders = new[] { new ExternalProviderModel { AuthenticationScheme = context.IdP } };
return; return;
} }
@ -111,7 +132,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
} }
} }
} }
ExternalProviders = providers.ToArray(); ExternalProviders = providers.ToArray();
if (IsExternalLoginOnly) if (IsExternalLoginOnly)
@ -131,7 +152,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
{ {
return Redirect("~/"); return Redirect("~/");
} }
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
return Redirect(ReturnUrl); return Redirect(ReturnUrl);
} }
@ -165,7 +186,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
return Redirect(ReturnUrl); //ReturnUrlHash? return Redirect(ReturnUrl); //ReturnUrlHash?
} }
[UnitOfWork] [UnitOfWork]
public virtual async Task<IActionResult> OnPostExternalLogin(string provider) public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{ {

3
src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs

@ -51,8 +51,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
await _signInManager.SignInAsync(user, isPersistent: false); await _signInManager.SignInAsync(user, isPersistent: false);
//TODO: Use LocalRedirect and Url.GetLocalUrl methods instead of a custom one! return Redirect(returnUrl); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
return RedirectSafely(returnUrl, returnUrlHash);
} }
public class PostInput public class PostInput

9
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs

@ -14,11 +14,10 @@ namespace Volo.Abp.AspNetCore.MultiTenancy
{ {
services.Configure<TenantResolveOptions>(options => services.Configure<TenantResolveOptions>(options =>
{ {
options.TenantResolvers.Insert(0, new ClaimsHttpTenantResolveContributer()); options.TenantResolvers.Add(new QueryStringTenantResolveContributer());
options.TenantResolvers.Insert(1, new QueryStringTenantResolveContributer()); options.TenantResolvers.Add(new RouteTenantResolveContributer());
options.TenantResolvers.Insert(2, new RouteTenantResolveContributer()); options.TenantResolvers.Add(new HeaderTenantResolveContributer());
options.TenantResolvers.Insert(3, new HeaderTenantResolveContributer()); options.TenantResolvers.Add(new CookieTenantResolveContributer());
options.TenantResolvers.Insert(4, new CookieTenantResolveContributer());
}); });
services.AddAssemblyOf<AbpAspNetCoreMultiTenancyModule>(); services.AddAssemblyOf<AbpAspNetCoreMultiTenancyModule>();

8
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancyOptions.cs

@ -1,15 +1,17 @@
namespace Volo.Abp.AspNetCore.MultiTenancy using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy
{ {
public class AspNetCoreMultiTenancyOptions public class AspNetCoreMultiTenancyOptions
{ {
/// <summary> /// <summary>
/// Default: "__tenant". /// Default: <see cref="TenantResolverConsts.DefaultTenantKey"/>.
/// </summary> /// </summary>
public string TenantKey { get; set; } public string TenantKey { get; set; }
public AspNetCoreMultiTenancyOptions() public AspNetCoreMultiTenancyOptions()
{ {
TenantKey = "__tenant"; TenantKey = TenantResolverConsts.DefaultTenantKey;
} }
} }
} }

24
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/ClaimsHttpTenantResolveContributer.cs

@ -1,24 +0,0 @@
using System.Linq;
using Microsoft.AspNetCore.Http;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.MultiTenancy
{
public class ClaimsHttpTenantResolveContributer : HttpTenantResolveContributerBase
{
protected override void ResolveFromHttpContext(ITenantResolveContext context, HttpContext httpContext)
{
if (httpContext.User?.Identity?.IsAuthenticated == true)
{
base.ResolveFromHttpContext(context, httpContext);
context.Handled = true;
}
}
protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext)
{
var tenantKey = context.GetAspNetCoreMultiTenancyOptions().TenantKey;
return httpContext.User.Claims.FirstOrDefault(c => c.Type == tenantKey)?.Value;
}
}
}

8
src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyOptionsExtensions.cs

@ -1,4 +1,5 @@
using Volo.Abp.AspNetCore.MultiTenancy; using System.Collections.Generic;
using Volo.Abp.AspNetCore.MultiTenancy;
namespace Volo.Abp.MultiTenancy namespace Volo.Abp.MultiTenancy
{ {
@ -6,7 +7,10 @@ namespace Volo.Abp.MultiTenancy
{ {
public static void AddDomainTenantResolver(this TenantResolveOptions options, string domainFormat) public static void AddDomainTenantResolver(this TenantResolveOptions options, string domainFormat)
{ {
options.TenantResolvers.Insert(0, new DomainTenantResolveContributer(domainFormat)); options.TenantResolvers.InsertAfter(
r => r is CurrentClaimsPrincipalTenantResolveContributer,
new DomainTenantResolveContributer(domainFormat)
);
} }
} }
} }

48
src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs

@ -9,6 +9,54 @@ namespace System.Collections.Generic
/// </summary> /// </summary>
public static class AbpListExtensions public static class AbpListExtensions
{ {
public static int FindIndex<T>(this IList<T> source, Predicate<T> selector)
{
for (var i = 0; i < source.Count; ++i)
{
if (selector(source[i]))
{
return i;
}
}
return -1;
}
public static void AddFirst<T>(this IList<T> source, T item)
{
source.Insert(0, item);
}
public static void AddLast<T>(this IList<T> source, T item)
{
source.Insert(source.Count, item);
}
public static void InsertAfter<T>(this IList<T> source, Predicate<T> selector, T item)
{
var index = source.FindIndex(selector);
if (index < 0)
{
source.AddFirst(item);
return;
}
source.Insert(index + 1, item);
}
public static void InsertBefore<T>(this IList<T> source, Predicate<T> selector, T item)
{
var index = source.FindIndex(selector);
if (index < 0)
{
source.AddLast(item);
return;
}
source.Insert(index, item);
}
public static void MoveItem<T>(this List<T> source, Predicate<T> selector, int targetIndex) public static void MoveItem<T>(this List<T> source, Predicate<T> selector, int targetIndex)
{ {
if (!targetIndex.IsBetween(0, source.Count - 1)) if (!targetIndex.IsBetween(0, source.Count - 1))

7
src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs

@ -5,8 +5,6 @@ using Volo.Abp.Identity;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
//TODO: AspNetUserManager overrides CancellationToken so we can make same functionality available!
public static class AbpIdentityServiceCollectionExtensions public static class AbpIdentityServiceCollectionExtensions
{ {
public static IdentityBuilder AddAbpIdentity(this IServiceCollection services) public static IdentityBuilder AddAbpIdentity(this IServiceCollection services)
@ -36,9 +34,10 @@ namespace Microsoft.Extensions.DependencyInjection
//AbpRoleStore //AbpRoleStore
services.TryAddScoped<IdentityRoleStore>(); services.TryAddScoped<IdentityRoleStore>();
services.TryAddScoped(typeof(IRoleStore<IdentityRole>), provider => provider.GetService(typeof(IdentityRoleStore))); services.TryAddScoped(typeof(IRoleStore<IdentityRole>), provider => provider.GetService(typeof(IdentityRoleStore)));
return services.AddIdentity<IdentityUser, IdentityRole>(setupAction) return services.AddIdentity<IdentityUser, IdentityRole>(setupAction)
.AddDefaultTokenProviders(); .AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>();
//return services.AddIdentityCore<IdentityUser>(setupAction); //return services.AddIdentityCore<IdentityUser>(setupAction);
} }
} }

38
src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs

@ -0,0 +1,38 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
namespace Volo.Abp.Identity
{
public class AbpUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser, IdentityRole>, ITransientDependency
{
public AbpUserClaimsPrincipalFactory(
UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> options)
: base(
userManager,
roleManager,
options)
{
}
[UnitOfWork]
public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
var principal = await base.CreateAsync(user);
if (user.TenantId.HasValue)
{
principal.Identities.First().AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString()));
}
return principal;
}
}
}

10
src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs

@ -1,21 +1,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.Threading;
namespace Volo.Abp.Identity namespace Volo.Abp.Identity
{ {
public class IdentityRoleManager : RoleManager<IdentityRole>, IDomainService public class IdentityRoleManager : RoleManager<IdentityRole>, IDomainService
{ {
protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token;
private readonly ICancellationTokenProvider _cancellationTokenProvider;
public IdentityRoleManager( public IdentityRoleManager(
IdentityRoleStore store, IdentityRoleStore store,
IEnumerable<IRoleValidator<IdentityRole>> roleValidators, IEnumerable<IRoleValidator<IdentityRole>> roleValidators,
ILookupNormalizer keyNormalizer, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IdentityErrorDescriber errors,
ILogger<IdentityRoleManager> logger) ILogger<IdentityRoleManager> logger,
ICancellationTokenProvider cancellationTokenProvider)
: base( : base(
store, store,
roleValidators, roleValidators,
@ -23,6 +30,7 @@ namespace Volo.Abp.Identity
errors, errors,
logger) logger)
{ {
_cancellationTokenProvider = cancellationTokenProvider;
} }
public async Task<IdentityRole> GetByIdAsync(Guid id) public async Task<IdentityRole> GetByIdAsync(Guid id)

11
src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -8,11 +9,16 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.Threading;
namespace Volo.Abp.Identity namespace Volo.Abp.Identity
{ {
public class IdentityUserManager : UserManager<IdentityUser>, IDomainService public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
{ {
protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token;
private readonly ICancellationTokenProvider _cancellationTokenProvider;
public IdentityUserManager( public IdentityUserManager(
IdentityUserStore store, IdentityUserStore store,
IOptions<IdentityOptions> optionsAccessor, IOptions<IdentityOptions> optionsAccessor,
@ -21,7 +27,8 @@ namespace Volo.Abp.Identity
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
IServiceProvider services, IServiceProvider services,
ILogger<IdentityUserManager> logger) ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider)
: base( : base(
store, store,
optionsAccessor, optionsAccessor,
@ -33,7 +40,7 @@ namespace Volo.Abp.Identity
services, services,
logger) logger)
{ {
_cancellationTokenProvider = cancellationTokenProvider;
} }
public async Task<IdentityUser> GetByIdAsync(Guid id) public async Task<IdentityUser> GetByIdAsync(Guid id)

30
src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.IdentityServer
{
public class AbpClaimsService : DefaultClaimsService
{
public AbpClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger)
: base(profile, logger)
{
}
protected override IEnumerable<Claim> GetOptionalClaims(ClaimsPrincipal subject)
{
var tenantClaim = subject.FindFirst(AbpClaimTypes.TenantId);
if (tenantClaim == null)
{
return base.GetOptionalClaims(subject);
}
else
{
return base.GetOptionalClaims(subject).Union(new[] { tenantClaim });
}
}
}
}

13
src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpClaimsService.cs

@ -1,13 +0,0 @@
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.IdentityServer.AspNetIdentity
{
public class AbpClaimsService : DefaultClaimsService
{
public AbpClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger)
: base(profile, logger)
{
}
}
}

17
src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs

@ -1,44 +1,41 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Security.Principal;
using IdentityServer4.AspNetIdentity; using IdentityServer4.AspNetIdentity;
using IdentityServer4.Models; using IdentityServer4.Models;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow; using Volo.Abp.Uow;
namespace Volo.Abp.IdentityServer.AspNetIdentity namespace Volo.Abp.IdentityServer.AspNetIdentity
{ {
//TODO: Implement multi-tenancy as like in old ABP
public class AbpProfileService : ProfileService<IdentityUser> public class AbpProfileService : ProfileService<IdentityUser>
{ {
private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly ICurrentTenant _currentTenant;
public AbpProfileService( public AbpProfileService(
IdentityUserManager userManager, IdentityUserManager userManager,
IUserClaimsPrincipalFactory<IdentityUser> claimsFactory, IUserClaimsPrincipalFactory<IdentityUser> claimsFactory,
IUnitOfWorkManager unitOfWorkManager) ICurrentTenant currentTenant)
: base(userManager, claimsFactory) : base(userManager, claimsFactory)
{ {
_unitOfWorkManager = unitOfWorkManager; _currentTenant = currentTenant;
} }
[UnitOfWork] [UnitOfWork]
public override async Task GetProfileDataAsync(ProfileDataRequestContext context) public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{ {
using (var uow = _unitOfWorkManager.Begin()) using (_currentTenant.Change(context.Subject.FindTenantId()))
{ {
await base.GetProfileDataAsync(context); await base.GetProfileDataAsync(context);
await uow.CompleteAsync();
} }
} }
[UnitOfWork] [UnitOfWork]
public override async Task IsActiveAsync(IsActiveContext context) public override async Task IsActiveAsync(IsActiveContext context)
{ {
using (var uow = _unitOfWorkManager.Begin()) using (_currentTenant.Change(context.Subject.FindTenantId()))
{ {
await base.IsActiveAsync(context); await base.IsActiveAsync(context);
await uow.CompleteAsync();
} }
} }
} }

1
src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> <ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Data\Volo.Abp.Data.csproj" /> <ProjectReference Include="..\Volo.Abp.Data\Volo.Abp.Data.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" /> <ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
</ItemGroup> </ItemGroup>

21
src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentClaimsPrincipalTenantResolveContributer.cs

@ -0,0 +1,21 @@
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.MultiTenancy
{
public class CurrentClaimsPrincipalTenantResolveContributer : ITenantResolveContributer
{
public void Resolve(ITenantResolveContext context)
{
var principal = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>().Principal;
if (principal?.Identity?.IsAuthenticated != true)
{
return;
}
context.TenantIdOrName = principal.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value;
context.Handled = true;
}
}
}

5
src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs

@ -10,7 +10,10 @@ namespace Volo.Abp.MultiTenancy
public TenantResolveOptions() public TenantResolveOptions()
{ {
TenantResolvers = new List<ITenantResolveContributer>(); TenantResolvers = new List<ITenantResolveContributer>
{
new CurrentClaimsPrincipalTenantResolveContributer()
};
} }
} }
} }

7
src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.MultiTenancy
{
public class TenantResolverConsts
{
public const string DefaultTenantKey = "__tenant";
}
}

67
src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs

@ -0,0 +1,67 @@
using System.Linq;
using System.Security.Claims;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Security.Claims;
namespace System.Security.Principal
{
public static class AbpClaimsIdentityExtensions
{
public static Guid? FindUserId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
var userIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId);
if (userIdOrNull == null || userIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(userIdOrNull.Value);
}
public static Guid? FindTenantId([NotNull] this ClaimsPrincipal principal)
{
Check.NotNull(principal, nameof(principal));
var tenantIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId);
if (tenantIdOrNull == null || tenantIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(tenantIdOrNull.Value);
}
public static Guid? FindUserId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var userIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId);
if (userIdOrNull == null || userIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(userIdOrNull.Value);
}
public static Guid? FindTenantId([NotNull] this IIdentity identity)
{
Check.NotNull(identity, nameof(identity));
var claimsIdentity = identity as ClaimsIdentity;
var tenantIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId);
if (tenantIdOrNull == null || tenantIdOrNull.Value.IsNullOrWhiteSpace())
{
return null;
}
return Guid.Parse(tenantIdOrNull.Value);
}
}
}

5
src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs

@ -42,5 +42,10 @@ namespace Volo.Abp.Security.Claims
/// Default: "phone_number_verified". /// Default: "phone_number_verified".
/// </summary> /// </summary>
public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; public static string PhoneNumberVerified { get; set; } = "phone_number_verified";
/// <summary>
/// Default: "phone_number_verified".
/// </summary>
public static string TenantId { get; set; } = "tenantid";
} }
} }

4
src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderExtensions.cs

@ -6,7 +6,9 @@ namespace Volo.Abp.Threading
{ {
public static CancellationToken FallbackToProvider(this ICancellationTokenProvider provider, CancellationToken prefferedValue = default) public static CancellationToken FallbackToProvider(this ICancellationTokenProvider provider, CancellationToken prefferedValue = default)
{ {
return prefferedValue == default ? provider.Token : prefferedValue; return prefferedValue == default || prefferedValue == CancellationToken.None
? provider.Token
: prefferedValue;
} }
} }
} }

69
test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs

@ -0,0 +1,69 @@
using System.Linq;
using Shouldly;
using Xunit;
namespace System.Collections.Generic
{
public class AbpListExtensions_Tests
{
[Fact]
public void InsertAfter()
{
var list = Enumerable.Range(1, 3).ToList();
list.InsertAfter(i => i == 2, 42);
list.Count.ShouldBe(4);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
list[2].ShouldBe(42);
list[3].ShouldBe(3);
list.InsertAfter(i => i == 3, 43);
list.Count.ShouldBe(5);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
list[2].ShouldBe(42);
list[3].ShouldBe(3);
list[4].ShouldBe(43);
}
[Fact]
public void InsertAfter_Should_Insert_To_First_If_Not_Found()
{
var list = Enumerable.Range(1, 3).ToList();
list.InsertAfter(i => i == 999, 42);
list.Count.ShouldBe(4);
list[0].ShouldBe(42);
list[1].ShouldBe(1);
list[2].ShouldBe(2);
list[3].ShouldBe(3);
}
[Fact]
public void InsertBefore()
{
var list = Enumerable.Range(1, 3).ToList();
list.InsertBefore(i => i == 2, 42);
list.Count.ShouldBe(4);
list[0].ShouldBe(1);
list[1].ShouldBe(42);
list[2].ShouldBe(2);
list[3].ShouldBe(3);
list.InsertBefore(i => i == 1, 43);
list.Count.ShouldBe(5);
list[0].ShouldBe(43);
list[1].ShouldBe(1);
list[2].ShouldBe(42);
list[3].ShouldBe(2);
list[4].ShouldBe(3);
}
}
}
Loading…
Cancel
Save