mirror of https://github.com/abpframework/abp.git
committed by
GitHub
12 changed files with 678 additions and 6 deletions
@ -0,0 +1,135 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
public class GetTwoFactorAuthenticationUser_Tests : SharedAbpIdentityAspNetCoreTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Resolve_Tenant_User_By_Id_When_Current_Tenant_Is_Host() |
|||
{ |
|||
var userRepository = GetRequiredService<IIdentityUserRepository>(); |
|||
var currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
var tenantId = Guid.NewGuid(); |
|||
Guid tenantUserId; |
|||
|
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), "shared-2fa-tenant-user", "shared-2fa-tenant-user@abp.io", tenantId); |
|||
await userRepository.InsertAsync(user); |
|||
tenantUserId = user.Id; |
|||
} |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
await WriteTwoFactorCookieAsync(tenantUserId); |
|||
|
|||
var getResponse = await Client.GetAsync("/api/signin-test/get-two-factor-user"); |
|||
getResponse.EnsureSuccessStatusCode(); |
|||
var content = await getResponse.Content.ReadAsStringAsync(); |
|||
|
|||
content.ShouldBe(tenantUserId.ToString()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Return_Null_When_No_Two_Factor_Cookie() |
|||
{ |
|||
var getResponse = await Client.GetAsync("/api/signin-test/get-two-factor-user"); |
|||
getResponse.EnsureSuccessStatusCode(); |
|||
var content = await getResponse.Content.ReadAsStringAsync(); |
|||
|
|||
content.ShouldBe("null"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task TwoFactorSignInAsync_Should_Resolve_Tenant_User_In_Shared_Mode() |
|||
{ |
|||
// If the tenant switch inside AbpSignInManager.TwoFactorSignInAsync regresses,
|
|||
// the base implementation would not find the user and return SignInResult.Failed.
|
|||
// An inactive tenant user lets PreSignInCheck return NotAllowed, proving the user
|
|||
// was actually located and inspected under its own tenant context.
|
|||
var tenantUserId = await CreateInactiveTenantUserAsync("shared-2fa-signin", "shared-2fa-signin@abp.io"); |
|||
|
|||
await WriteTwoFactorCookieAsync(tenantUserId); |
|||
|
|||
var response = await Client.GetAsync("/api/signin-test/two-factor-signin?provider=Email&code=invalid"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("NotAllowed"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task TwoFactorRecoveryCodeSignInAsync_Should_Resolve_Tenant_User_In_Shared_Mode() |
|||
{ |
|||
// AbpSignInManager.TwoFactorRecoveryCodeSignInAsync runs PreSignInCheck after locating
|
|||
// the user under its own tenant context. An inactive tenant user therefore produces
|
|||
// NotAllowed only when the override actually resolves the user; a regression would
|
|||
// return Failed instead.
|
|||
var tenantUserId = await CreateInactiveTenantUserAsync("shared-2fa-recovery", "shared-2fa-recovery@abp.io"); |
|||
|
|||
await WriteTwoFactorCookieAsync(tenantUserId); |
|||
|
|||
var response = await Client.GetAsync("/api/signin-test/two-factor-recovery-signin?recoveryCode=invalid"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("NotAllowed"); |
|||
} |
|||
|
|||
private async Task<Guid> CreateInactiveTenantUserAsync(string userName, string email) |
|||
{ |
|||
return await CreateTenantUserAsync(userName, email, isActive: false); |
|||
} |
|||
|
|||
private async Task<Guid> CreateTenantUserAsync(string userName, string email, bool isActive) |
|||
{ |
|||
var userRepository = GetRequiredService<IIdentityUserRepository>(); |
|||
var currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
var tenantId = Guid.NewGuid(); |
|||
Guid userId; |
|||
|
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), userName, email, tenantId); |
|||
if (!isActive) |
|||
{ |
|||
user.SetIsActive(false); |
|||
} |
|||
await userRepository.InsertAsync(user); |
|||
userId = user.Id; |
|||
} |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
return userId; |
|||
} |
|||
|
|||
private async Task WriteTwoFactorCookieAsync(Guid userId) |
|||
{ |
|||
var writeResponse = await Client.GetAsync($"/api/signin-test/write-two-factor-cookie?userId={userId}"); |
|||
writeResponse.EnsureSuccessStatusCode(); |
|||
|
|||
if (writeResponse.Headers.TryGetValues("Set-Cookie", out var setCookies)) |
|||
{ |
|||
Client.DefaultRequestHeaders.Remove("Cookie"); |
|||
foreach (var cookie in setCookies) |
|||
{ |
|||
Client.DefaultRequestHeaders.Add("Cookie", cookie.Split(';').First()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
public class Isolated_TwoFactor_Tests : AbpIdentityAspNetCoreTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task TwoFactorRecoveryCodeSignInAsync_Should_Return_NotAllowed_For_Inactive_User() |
|||
{ |
|||
// The AbpSignInManager override adds PreSignInCheck to the recovery-code path (the base
|
|||
// AspNetCore Identity implementation does not). This test asserts that behavior also works
|
|||
// in the default (isolated) configuration so the new invariant is protected across modes.
|
|||
var userManager = GetRequiredService<IdentityUserManager>(); |
|||
var userRepository = GetRequiredService<IIdentityUserRepository>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
Guid userId; |
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), "iso-recovery-inactive", "iso-recovery-inactive@abp.io"); |
|||
(await userManager.CreateAsync(user, "Iso!9Aa")).Succeeded.ShouldBeTrue(); |
|||
user.SetIsActive(false); |
|||
await userRepository.UpdateAsync(user); |
|||
userId = user.Id; |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
var writeResponse = await Client.GetAsync($"/api/signin-test/write-two-factor-cookie?userId={userId}"); |
|||
writeResponse.EnsureSuccessStatusCode(); |
|||
if (writeResponse.Headers.TryGetValues("Set-Cookie", out var setCookies)) |
|||
{ |
|||
Client.DefaultRequestHeaders.Remove("Cookie"); |
|||
foreach (var cookie in setCookies) |
|||
{ |
|||
Client.DefaultRequestHeaders.Add("Cookie", cookie.Split(';').First()); |
|||
} |
|||
} |
|||
|
|||
var response = await Client.GetAsync("/api/signin-test/two-factor-recovery-signin?recoveryCode=invalid"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("NotAllowed"); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
using Volo.Abp.AspNetCore.TestBase; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
public abstract class SharedAbpIdentityAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase<SharedAbpIdentityAspNetCoreTestStartup> |
|||
{ |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
[DependsOn(typeof(AbpIdentityAspNetCoreTestModule))] |
|||
public class SharedAbpIdentityAspNetCoreTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = true; |
|||
options.UserSharingStrategy = TenantUserSharingStrategy.Shared; |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
public class SharedAbpIdentityAspNetCoreTestStartup |
|||
{ |
|||
public void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddApplication<SharedAbpIdentityAspNetCoreTestModule>(); |
|||
} |
|||
|
|||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) |
|||
{ |
|||
app.InitializeApplication(); |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Shouldly; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity.AspNetCore; |
|||
|
|||
public class Shared_SignIn_Tests : SharedAbpIdentityAspNetCoreTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task PasswordSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context() |
|||
{ |
|||
// In shared mode, calling PasswordSignInAsync with a username while CurrentTenant is host
|
|||
// must resolve the tenant user via FindSharedUserByNameAsync, apply the user's tenant
|
|||
// IdentityOptions (the fix added in AbpSignInManager), and complete the sign-in.
|
|||
var userManager = GetRequiredService<IdentityUserManager>(); |
|||
var currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
var tenantId = Guid.NewGuid(); |
|||
const string userName = "shared-password-signin"; |
|||
const string password = "Shared!9Aa"; |
|||
|
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId); |
|||
(await userManager.CreateAsync(user, password)).Succeeded.ShouldBeTrue(); |
|||
} |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password={password}"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("Succeeded"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task PasswordSignInAsync_Should_Fail_For_Wrong_Password_In_Shared_Mode() |
|||
{ |
|||
var userManager = GetRequiredService<IdentityUserManager>(); |
|||
var currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
var tenantId = Guid.NewGuid(); |
|||
const string userName = "shared-password-wrong"; |
|||
|
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId); |
|||
(await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue(); |
|||
} |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password=wrong"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("Failed"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ExternalLoginSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context() |
|||
{ |
|||
// Covers the AbpSignInManager.ExternalLoginSignInAsync override: finds the user via
|
|||
// FindSharedUserByLoginAsync, switches CurrentTenant to user.TenantId, applies tenant
|
|||
// IdentityOptions, then calls PreSignInCheck + SignInOrTwoFactorAsync.
|
|||
const string loginProvider = "test-provider"; |
|||
var providerKey = "ext-" + Guid.NewGuid().ToString("N").Substring(0, 8); |
|||
|
|||
var userManager = GetRequiredService<IdentityUserManager>(); |
|||
var currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
var unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
|
|||
var tenantId = Guid.NewGuid(); |
|||
|
|||
using (var uow = unitOfWorkManager.Begin()) |
|||
{ |
|||
using (currentTenant.Change(tenantId)) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), "shared-external-signin", "shared-external-signin@abp.io", tenantId); |
|||
(await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue(); |
|||
(await userManager.AddLoginAsync(user, new UserLoginInfo(loginProvider, providerKey, "Test Provider"))).Succeeded.ShouldBeTrue(); |
|||
} |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider={loginProvider}&providerKey={providerKey}"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("Succeeded"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task ExternalLoginSignInAsync_Should_Fail_For_Unknown_Provider_Key_In_Shared_Mode() |
|||
{ |
|||
var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider=unknown&providerKey=none"); |
|||
response.EnsureSuccessStatusCode(); |
|||
var result = await response.Content.ReadAsStringAsync(); |
|||
|
|||
result.ShouldBe("Failed"); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue