diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Controllers/MultiTenancyController.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Controllers/MultiTenancyController.cs new file mode 100644 index 0000000000..ac5a453177 --- /dev/null +++ b/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 options) + { + _tenantStore = tenantStore; + _options = options.Value; + } + + public async Task 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 FindTenantAsync(string tenantIdOrName) + { + if (Guid.TryParse(tenantIdOrName, out var parsedTenantId)) + { + return await _tenantStore.FindAsync(parsedTenantId); + } + else + { + return await _tenantStore.FindAsync(tenantIdOrName); + } + } + } +} diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index bb424816a9..f40c7ba0df 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -97,11 +97,12 @@ namespace MicroserviceDemo.AuthServer app.UseDeveloperExceptionPage(); } - app.UseMultiTenancy(); - app.UseStaticFiles(); 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.UseMvc(routes => diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs index e976a59d57..bab82c960f 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Startup.cs @@ -26,7 +26,7 @@ namespace MicroserviceDemo.AuthServer .AddDebug() .AddSerilog(new LoggerConfiguration() .Enrich.FromLogContext() - .WriteTo.RollingFile("Logs/logs.txt") + .WriteTo.File("Logs/logs.txt") .CreateLogger() ); diff --git a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj index 4e28bfadb6..10c93f1e64 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj +++ b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroserviceDemo.TenancyService.csproj @@ -20,6 +20,7 @@ + diff --git a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs index b8f96c327b..d6568e6a28 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/MicroservicesDemoTenancyServiceModule.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.AspNetCore.Modularity; -using Volo.Abp.Authorization.Permissions; +using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -15,7 +15,6 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy.EntityFrameworkCore; using Volo.Abp.Permissions.EntityFrameworkCore; using Volo.Abp.Security.Claims; -using Volo.Abp.Threading; namespace MicroserviceDemo.TenancyService { @@ -24,6 +23,7 @@ namespace MicroserviceDemo.TenancyService [DependsOn(typeof(AbpMultiTenancyHttpApiModule))] [DependsOn(typeof(AbpMultiTenancyApplicationModule))] [DependsOn(typeof(AbpPermissionsEntityFrameworkCoreModule))] + [DependsOn(typeof(AbpAspNetCoreMultiTenancyModule))] public class MicroservicesDemoTenancyServiceModule : AbpModule { public override void ConfigureServices(IServiceCollection services) @@ -83,9 +83,6 @@ namespace MicroserviceDemo.TenancyService { var app = context.GetApplicationBuilder(); - var store = context.ServiceProvider.GetRequiredService(); - var xx = AsyncHelper.RunSync(() => store.IsGrantedAsync("AbpTenantManagement.Tenants", "Role", "admin")); - app.UseStaticFiles(); app.UseSwagger(); @@ -96,6 +93,8 @@ namespace MicroserviceDemo.TenancyService app.UseAuthentication(); + app.UseMultiTenancy(); + app.UseMvcWithDefaultRoute(); } diff --git a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs index de33f56420..795f768579 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.TenancyService/Startup.cs @@ -26,7 +26,7 @@ namespace MicroserviceDemo.TenancyService .AddDebug() .AddSerilog(new LoggerConfiguration() .Enrich.FromLogContext() - .WriteTo.RollingFile("Logs/logs.txt") + .WriteTo.File("Logs/logs.txt") .CreateLogger() ); diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs index 9da448980f..e4a8e3a6ad 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/MultiTenancyController.cs @@ -8,7 +8,7 @@ using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.MultiTenancy; using Volo.Abp.Ui; -namespace AbpDesk.Web.Mvc.Controllers +namespace MicroserviceDemo.Web.Controllers { /* TODO: This is temporary solution to switch tenant. */ diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs index 412332d08a..72f9b841b1 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -69,9 +70,9 @@ namespace MicroserviceDemo.Web { "/Abp/ApplicationConfigurationScript?_v=" + DateTime.Now.Ticks, "/Abp/ServiceProxyScript?_v=" + DateTime.Now.Ticks - }); + }); }); - + services.AddAuthentication(options => { options.DefaultChallengeScheme = "oidc"; @@ -97,6 +98,19 @@ namespace MicroserviceDemo.Web options.Scope.Add("multi-tenancy-api"); options.ClaimActions.MapAbpClaimTypes(); + + options.Events.OnRedirectToIdentityProvider += context => + { + //TODO: ... + + var tenantId = context.HttpContext.RequestServices.GetRequiredService().Id?.ToString(); + if (!tenantId.IsNullOrEmpty()) + { + context.ProtocolMessage.Parameters[TenantResolverConsts.DefaultTenantKey] = tenantId; + } + + return Task.CompletedTask; + }; }); services.Configure(configuration); diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs index c904f49d1a..5f798df736 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/Startup.cs @@ -26,7 +26,7 @@ namespace MicroserviceDemo.Web .AddDebug() .AddSerilog(new LoggerConfiguration() .Enrich.FromLogContext() - .WriteTo.RollingFile("Logs/logs.txt") + .WriteTo.File("Logs/logs.txt") .CreateLogger() ); diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml index 61364f038d..c5aac37e46 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -1,5 +1,5 @@ @page -@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel +@model Volo.Abp.Account.Web.Pages.Account.IdentityServerLoginModel
@if (Model.EnableLocalLogin) diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs index a1db7969f1..2303e2b13f 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -18,13 +18,14 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Account.Web.Auth; using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Ui; using Volo.Abp.Uow; namespace Volo.Abp.Account.Web.Pages.Account { //TODO: Inherit from LoginModel of Account.Web project. We should design it as extensible. - public class IdsLoginModel : AccountModelBase + public class IdentityServerLoginModel : AccountModelBase { [HiddenInput] [BindProperty(SupportsGet = true)] @@ -54,13 +55,13 @@ namespace Volo.Abp.Account.Web.Pages.Account private readonly IClientStore _clientStore; private readonly IEventService _identityServerEvents; - public IdsLoginModel( - SignInManager signInManager, - IdentityUserManager userManager, - IIdentityServerInteractionService interaction, - IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions, - IClientStore clientStore, + public IdentityServerLoginModel( + SignInManager signInManager, + IdentityUserManager userManager, + IIdentityServerInteractionService interaction, + IAuthenticationSchemeProvider schemeProvider, + IOptions accountOptions, + IClientStore clientStore, IEventService identityServerEvents) { _signInManager = signInManager; @@ -77,12 +78,32 @@ namespace Volo.Abp.Account.Web.Pages.Account LoginInput = new LoginInputModel(); 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) { LoginInput.UserNameOrEmailAddress = context.LoginHint; - ExternalProviders = new[] {new ExternalProviderModel {AuthenticationScheme = context.IdP}}; + ExternalProviders = new[] { new ExternalProviderModel { AuthenticationScheme = context.IdP } }; return; } @@ -111,7 +132,7 @@ namespace Volo.Abp.Account.Web.Pages.Account } } } - + ExternalProviders = providers.ToArray(); if (IsExternalLoginOnly) @@ -131,7 +152,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { return Redirect("~/"); } - + await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); return Redirect(ReturnUrl); } @@ -165,7 +186,7 @@ namespace Volo.Abp.Account.Web.Pages.Account return Redirect(ReturnUrl); //ReturnUrlHash? } - + [UnitOfWork] public virtual async Task OnPostExternalLogin(string provider) { diff --git a/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index eedf6ae26b..d091f5d1c2 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/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); - //TODO: Use LocalRedirect and Url.GetLocalUrl methods instead of a custom one! - return RedirectSafely(returnUrl, returnUrlHash); + return Redirect(returnUrl); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! } public class PostInput diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs index 14f196f27f..a5a10f6528 100644 --- a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs +++ b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs @@ -14,11 +14,10 @@ namespace Volo.Abp.AspNetCore.MultiTenancy { services.Configure(options => { - options.TenantResolvers.Insert(0, new ClaimsHttpTenantResolveContributer()); - options.TenantResolvers.Insert(1, new QueryStringTenantResolveContributer()); - options.TenantResolvers.Insert(2, new RouteTenantResolveContributer()); - options.TenantResolvers.Insert(3, new HeaderTenantResolveContributer()); - options.TenantResolvers.Insert(4, new CookieTenantResolveContributer()); + options.TenantResolvers.Add(new QueryStringTenantResolveContributer()); + options.TenantResolvers.Add(new RouteTenantResolveContributer()); + options.TenantResolvers.Add(new HeaderTenantResolveContributer()); + options.TenantResolvers.Add(new CookieTenantResolveContributer()); }); services.AddAssemblyOf(); diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancyOptions.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancyOptions.cs index fda1971eb1..b985aae73e 100644 --- a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancyOptions.cs +++ b/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 { /// - /// Default: "__tenant". + /// Default: . /// public string TenantKey { get; set; } public AspNetCoreMultiTenancyOptions() { - TenantKey = "__tenant"; + TenantKey = TenantResolverConsts.DefaultTenantKey; } } } \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/ClaimsHttpTenantResolveContributer.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/ClaimsHttpTenantResolveContributer.cs deleted file mode 100644 index a320a1fbee..0000000000 --- a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/ClaimsHttpTenantResolveContributer.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyOptionsExtensions.cs index 79623ff56e..8e6d3ae7ea 100644 --- a/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyOptionsExtensions.cs +++ b/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 { @@ -6,7 +7,10 @@ namespace Volo.Abp.MultiTenancy { 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) + ); } } } \ No newline at end of file diff --git a/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs b/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs index 864c50acdc..d4711ad7fc 100644 --- a/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs +++ b/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs @@ -9,6 +9,54 @@ namespace System.Collections.Generic /// public static class AbpListExtensions { + public static int FindIndex(this IList source, Predicate selector) + { + for (var i = 0; i < source.Count; ++i) + { + if (selector(source[i])) + { + return i; + } + } + + return -1; + } + + public static void AddFirst(this IList source, T item) + { + source.Insert(0, item); + } + + public static void AddLast(this IList source, T item) + { + source.Insert(source.Count, item); + } + + public static void InsertAfter(this IList source, Predicate selector, T item) + { + var index = source.FindIndex(selector); + if (index < 0) + { + source.AddFirst(item); + return; + } + + source.Insert(index + 1, item); + } + + + public static void InsertBefore(this IList source, Predicate selector, T item) + { + var index = source.FindIndex(selector); + if (index < 0) + { + source.AddLast(item); + return; + } + + source.Insert(index, item); + } + public static void MoveItem(this List source, Predicate selector, int targetIndex) { if (!targetIndex.IsBetween(0, source.Count - 1)) diff --git a/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs b/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs index d9ee732225..5a59253205 100644 --- a/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs +++ b/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs @@ -5,8 +5,6 @@ using Volo.Abp.Identity; namespace Microsoft.Extensions.DependencyInjection { - //TODO: AspNetUserManager overrides CancellationToken so we can make same functionality available! - public static class AbpIdentityServiceCollectionExtensions { public static IdentityBuilder AddAbpIdentity(this IServiceCollection services) @@ -36,9 +34,10 @@ namespace Microsoft.Extensions.DependencyInjection //AbpRoleStore services.TryAddScoped(); services.TryAddScoped(typeof(IRoleStore), provider => provider.GetService(typeof(IdentityRoleStore))); - + return services.AddIdentity(setupAction) - .AddDefaultTokenProviders(); + .AddDefaultTokenProviders() + .AddClaimsPrincipalFactory(); //return services.AddIdentityCore(setupAction); } } diff --git a/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs b/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs new file mode 100644 index 0000000000..10e7ae3c3f --- /dev/null +++ b/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, ITransientDependency + { + public AbpUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IOptions options) + : base( + userManager, + roleManager, + options) + { + } + + [UnitOfWork] + public override async Task 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; + } + } +} diff --git a/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs b/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs index d1da955666..e59a056566 100644 --- a/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs +++ b/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs @@ -1,21 +1,28 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Services; +using Volo.Abp.Threading; namespace Volo.Abp.Identity { public class IdentityRoleManager : RoleManager, IDomainService { + protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; + + private readonly ICancellationTokenProvider _cancellationTokenProvider; + public IdentityRoleManager( IdentityRoleStore store, IEnumerable> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, - ILogger logger) + ILogger logger, + ICancellationTokenProvider cancellationTokenProvider) : base( store, roleValidators, @@ -23,6 +30,7 @@ namespace Volo.Abp.Identity errors, logger) { + _cancellationTokenProvider = cancellationTokenProvider; } public async Task GetByIdAsync(Guid id) diff --git a/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs index 4bfb31154c..dc4b2fe4b2 100644 --- a/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs +++ b/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Identity; @@ -8,11 +9,16 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Services; +using Volo.Abp.Threading; namespace Volo.Abp.Identity { public class IdentityUserManager : UserManager, IDomainService { + protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; + + private readonly ICancellationTokenProvider _cancellationTokenProvider; + public IdentityUserManager( IdentityUserStore store, IOptions optionsAccessor, @@ -21,7 +27,8 @@ namespace Volo.Abp.Identity IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, - ILogger logger) + ILogger logger, + ICancellationTokenProvider cancellationTokenProvider) : base( store, optionsAccessor, @@ -33,7 +40,7 @@ namespace Volo.Abp.Identity services, logger) { - + _cancellationTokenProvider = cancellationTokenProvider; } public async Task GetByIdAsync(Guid id) diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs new file mode 100644 index 0000000000..60453bc927 --- /dev/null +++ b/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 logger) + : base(profile, logger) + { + } + + protected override IEnumerable GetOptionalClaims(ClaimsPrincipal subject) + { + var tenantClaim = subject.FindFirst(AbpClaimTypes.TenantId); + if (tenantClaim == null) + { + return base.GetOptionalClaims(subject); + } + else + { + return base.GetOptionalClaims(subject).Union(new[] { tenantClaim }); + } + } + } +} diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpClaimsService.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpClaimsService.cs deleted file mode 100644 index c516f45e6a..0000000000 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpClaimsService.cs +++ /dev/null @@ -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 logger) - : base(profile, logger) - { - } - } -} diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs index 2c0dcafa47..7b411221df 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs @@ -1,44 +1,41 @@ using System.Threading.Tasks; +using System.Security.Principal; using IdentityServer4.AspNetIdentity; using IdentityServer4.Models; using Microsoft.AspNetCore.Identity; using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; namespace Volo.Abp.IdentityServer.AspNetIdentity { - //TODO: Implement multi-tenancy as like in old ABP - public class AbpProfileService : ProfileService { - private readonly IUnitOfWorkManager _unitOfWorkManager; - + private readonly ICurrentTenant _currentTenant; public AbpProfileService( IdentityUserManager userManager, IUserClaimsPrincipalFactory claimsFactory, - IUnitOfWorkManager unitOfWorkManager) + ICurrentTenant currentTenant) : base(userManager, claimsFactory) { - _unitOfWorkManager = unitOfWorkManager; + _currentTenant = currentTenant; } [UnitOfWork] public override async Task GetProfileDataAsync(ProfileDataRequestContext context) { - using (var uow = _unitOfWorkManager.Begin()) + using (_currentTenant.Change(context.Subject.FindTenantId())) { await base.GetProfileDataAsync(context); - await uow.CompleteAsync(); } } [UnitOfWork] public override async Task IsActiveAsync(IsActiveContext context) { - using (var uow = _unitOfWorkManager.Begin()) + using (_currentTenant.Change(context.Subject.FindTenantId())) { await base.IsActiveAsync(context); - await uow.CompleteAsync(); } } } diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj b/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj index 07f95cb1ee..3b155ac986 100644 --- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj +++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentClaimsPrincipalTenantResolveContributer.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentClaimsPrincipalTenantResolveContributer.cs new file mode 100644 index 0000000000..4d9beb36e6 --- /dev/null +++ b/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().Principal; + if (principal?.Identity?.IsAuthenticated != true) + { + return; + } + + context.TenantIdOrName = principal.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId)?.Value; + context.Handled = true; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs index 1ea7c0a0b1..6cee735e84 100644 --- a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs +++ b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveOptions.cs @@ -10,7 +10,10 @@ namespace Volo.Abp.MultiTenancy public TenantResolveOptions() { - TenantResolvers = new List(); + TenantResolvers = new List + { + new CurrentClaimsPrincipalTenantResolveContributer() + }; } } } \ No newline at end of file diff --git a/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs b/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs new file mode 100644 index 0000000000..e4bf2141ca --- /dev/null +++ b/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"; + } +} diff --git a/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs new file mode 100644 index 0000000000..321aa22914 --- /dev/null +++ b/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); + } + } +} diff --git a/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index be363b8df4..0edabc957d 100644 --- a/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -42,5 +42,10 @@ namespace Volo.Abp.Security.Claims /// Default: "phone_number_verified". /// public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; + + /// + /// Default: "phone_number_verified". + /// + public static string TenantId { get; set; } = "tenantid"; } } diff --git a/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderExtensions.cs b/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderExtensions.cs index b1ad755db2..bdae019b66 100644 --- a/src/Volo.Abp.Threading/Volo/Abp/Threading/CancellationTokenProviderExtensions.cs +++ b/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) { - return prefferedValue == default ? provider.Token : prefferedValue; + return prefferedValue == default || prefferedValue == CancellationToken.None + ? provider.Token + : prefferedValue; } } } \ No newline at end of file diff --git a/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs b/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs new file mode 100644 index 0000000000..e8ed194314 --- /dev/null +++ b/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); + } + } +}