Browse Source

Merge pull request #221 from aspnetzero/ids4-multitenancy

Ids4 multitenancy
pull/272/head
Halil İbrahim Kalkan 8 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.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 =>

2
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()
);

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

@ -20,6 +20,7 @@
</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.MultiTenancy.Application\Volo.Abp.MultiTenancy.Application.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 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<IPermissionStore>();
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();
}

2
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()
);

2
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.
*/

18
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<ICurrentTenant>().Id?.ToString();
if (!tenantId.IsNullOrEmpty())
{
context.ProtocolMessage.Parameters[TenantResolverConsts.DefaultTenantKey] = tenantId;
}
return Task.CompletedTask;
};
});
services.Configure<RemoteServiceOptions>(configuration);

2
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()
);

2
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
<div class="row">
@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 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<IdentityUser> signInManager,
IdentityUserManager userManager,
IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IClientStore clientStore,
public IdentityServerLoginModel(
SignInManager<IdentityUser> signInManager,
IdentityUserManager userManager,
IIdentityServerInteractionService interaction,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> 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<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);
//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

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 =>
{
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<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
{
/// <summary>
/// Default: "__tenant".
/// Default: <see cref="TenantResolverConsts.DefaultTenantKey"/>.
/// </summary>
public string TenantKey { get; set; }
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
{
@ -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)
);
}
}
}

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

@ -9,6 +9,54 @@ namespace System.Collections.Generic
/// </summary>
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)
{
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
{
//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<IdentityRoleStore>();
services.TryAddScoped(typeof(IRoleStore<IdentityRole>), provider => provider.GetService(typeof(IdentityRoleStore)));
return services.AddIdentity<IdentityUser, IdentityRole>(setupAction)
.AddDefaultTokenProviders();
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<AbpUserClaimsPrincipalFactory>();
//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.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<IdentityRole>, IDomainService
{
protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token;
private readonly ICancellationTokenProvider _cancellationTokenProvider;
public IdentityRoleManager(
IdentityRoleStore store,
IEnumerable<IRoleValidator<IdentityRole>> roleValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
ILogger<IdentityRoleManager> logger)
ILogger<IdentityRoleManager> logger,
ICancellationTokenProvider cancellationTokenProvider)
: base(
store,
roleValidators,
@ -23,6 +30,7 @@ namespace Volo.Abp.Identity
errors,
logger)
{
_cancellationTokenProvider = cancellationTokenProvider;
}
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.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<IdentityUser>, IDomainService
{
protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token;
private readonly ICancellationTokenProvider _cancellationTokenProvider;
public IdentityUserManager(
IdentityUserStore store,
IOptions<IdentityOptions> optionsAccessor,
@ -21,7 +27,8 @@ namespace Volo.Abp.Identity
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger)
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider)
: base(
store,
optionsAccessor,
@ -33,7 +40,7 @@ namespace Volo.Abp.Identity
services,
logger)
{
_cancellationTokenProvider = cancellationTokenProvider;
}
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.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<IdentityUser>
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly ICurrentTenant _currentTenant;
public AbpProfileService(
IdentityUserManager userManager,
IUserClaimsPrincipalFactory<IdentityUser> 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();
}
}
}

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

@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.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" />
</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()
{
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".
/// </summary>
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)
{
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