mirror of https://github.com/abpframework/abp.git
9 changed files with 616 additions and 232 deletions
@ -0,0 +1,7 @@ |
|||
using Volo.Abp.Identity.EntityFrameworkCore; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityUserManager_SharedUser_SeparateDatabase_Tests : IdentityUserManager_SharedUser_SeparateDatabase_Tests<AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule> |
|||
{ |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace Volo.Abp.Identity; |
|||
|
|||
public class IdentityUserManager_SharedUser_Tests : IdentityUserManager_SharedUser_Tests<AbpIdentityDomainTestModule> |
|||
{ |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using Microsoft.Data.Sqlite; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Storage; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore.Sqlite; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.MultiTenancy.ConfigurationStore; |
|||
using Volo.Abp.PermissionManagement.EntityFrameworkCore; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.Identity.EntityFrameworkCore; |
|||
|
|||
// EF/SQLite equivalent of the MongoDB separate-database test module: each predefined tenant
|
|||
// has its own keep-alive in-memory SQLite connection. Each test method (and therefore each
|
|||
// AbpApplication) gets a unique connection-string suffix so the test-data seeder runs into
|
|||
// fresh databases instead of duplicating into shared cache.
|
|||
[DependsOn( |
|||
typeof(AbpIdentityTestBaseModule), |
|||
typeof(AbpPermissionManagementEntityFrameworkCoreModule), |
|||
typeof(AbpIdentityEntityFrameworkCoreModule), |
|||
typeof(AbpEntityFrameworkCoreSqliteModule))] |
|||
public class AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule : AbpModule |
|||
{ |
|||
public static readonly Guid TenantAId = |
|||
IdentityUserManager_SharedUser_SeparateDatabase_Tests<AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule>.TenantAId; |
|||
public static readonly Guid TenantBId = |
|||
IdentityUserManager_SharedUser_SeparateDatabase_Tests<AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule>.TenantBId; |
|||
|
|||
// Static cache so the in-memory SQLite databases survive for the full process lifetime
|
|||
// (without an open connection, in-memory shared-cache databases are discarded). One entry
|
|||
// per unique connection string.
|
|||
private static readonly ConcurrentDictionary<string, SqliteConnection> _keepAlive = new(); |
|||
|
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<AbpSqliteOptions>(x => x.BusyTimeout = null); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Unique-per-app suffix so each test method gets a fresh trio of databases (the seeder
|
|||
// in AbpIdentityTestBaseModule.OnApplicationInitialization expects to write into empty
|
|||
// tables, which would fail if test methods reused the same shared-cache database).
|
|||
var suffix = Guid.NewGuid().ToString("N"); |
|||
var hostConnection = $"Data Source=AbpIdentity_SeparateDb_Host_{suffix};Mode=Memory;Cache=Shared"; |
|||
var tenantAConnection = $"Data Source=AbpIdentity_SeparateDb_TenantA_{suffix};Mode=Memory;Cache=Shared"; |
|||
var tenantBConnection = $"Data Source=AbpIdentity_SeparateDb_TenantB_{suffix};Mode=Memory;Cache=Shared"; |
|||
|
|||
EnsureDatabase(hostConnection); |
|||
EnsureDatabase(tenantAConnection); |
|||
EnsureDatabase(tenantBConnection); |
|||
|
|||
Configure<AbpDbConnectionOptions>(options => |
|||
{ |
|||
options.ConnectionStrings.Default = hostConnection; |
|||
}); |
|||
|
|||
Configure<AbpDbContextOptions>(options => |
|||
{ |
|||
options.Configure(ctx => |
|||
{ |
|||
ctx.DbContextOptions.UseSqlite(ctx.ConnectionString); |
|||
}); |
|||
}); |
|||
|
|||
Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = true; |
|||
options.UserSharingStrategy = TenantUserSharingStrategy.Shared; |
|||
}); |
|||
|
|||
Configure<AbpDefaultTenantStoreOptions>(options => |
|||
{ |
|||
options.Tenants = new[] |
|||
{ |
|||
new TenantConfiguration(TenantAId, "tenant-a") |
|||
{ |
|||
ConnectionStrings = new ConnectionStrings |
|||
{ |
|||
{ ConnectionStrings.DefaultConnectionStringName, tenantAConnection } |
|||
} |
|||
}, |
|||
new TenantConfiguration(TenantBId, "tenant-b") |
|||
{ |
|||
ConnectionStrings = new ConnectionStrings |
|||
{ |
|||
{ ConnectionStrings.DefaultConnectionStringName, tenantBConnection } |
|||
} |
|||
} |
|||
}; |
|||
}); |
|||
|
|||
context.Services.AddAlwaysDisableUnitOfWorkTransaction(); |
|||
} |
|||
|
|||
private static void EnsureDatabase(string connectionString) |
|||
{ |
|||
if (_keepAlive.ContainsKey(connectionString)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var keepAlive = new SqliteConnection(connectionString); |
|||
keepAlive.Open(); |
|||
_keepAlive[connectionString] = keepAlive; |
|||
|
|||
new IdentityDbContext( |
|||
new DbContextOptionsBuilder<IdentityDbContext>().UseSqlite(connectionString).Options) |
|||
.GetService<IRelationalDatabaseCreator>().CreateTables(); |
|||
|
|||
new PermissionManagementDbContext( |
|||
new DbContextOptionsBuilder<PermissionManagementDbContext>().UseSqlite(connectionString).Options) |
|||
.GetService<IRelationalDatabaseCreator>().CreateTables(); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.MultiTenancy.ConfigurationStore; |
|||
|
|||
namespace Volo.Abp.Identity.MongoDB; |
|||
|
|||
// Registers two predefined tenants, each pointing at its own physical MongoDB database.
|
|||
// Used by SharedUser tests that need true cross-database isolation (the only place where
|
|||
// AbpIdentityUserValidator's shared-mode owner lookup and FindSharedUserBy*Async deviate from
|
|||
// single-DB behavior).
|
|||
[DependsOn(typeof(AbpIdentityMongoDbTestModule))] |
|||
public class AbpIdentitySharedUserSeparateDbMongoDbTestModule : AbpModule |
|||
{ |
|||
public static readonly Guid TenantAId = Guid.Parse("11111111-1111-1111-1111-111111111111"); |
|||
public static readonly Guid TenantBId = Guid.Parse("22222222-2222-2222-2222-222222222222"); |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Fixed db names so MongoSandbox's embedded mongod does not accumulate one db per test
|
|||
// method. Each test method generates unique email / userName so cross-test data does not
|
|||
// collide on those fields.
|
|||
var tenantAConnection = MongoDbFixture.GetConnectionString("AbpIdentity_SharedSeparateDb_TenantA"); |
|||
var tenantBConnection = MongoDbFixture.GetConnectionString("AbpIdentity_SharedSeparateDb_TenantB"); |
|||
|
|||
Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = true; |
|||
options.UserSharingStrategy = TenantUserSharingStrategy.Shared; |
|||
}); |
|||
|
|||
Configure<AbpDefaultTenantStoreOptions>(options => |
|||
{ |
|||
options.Tenants = new[] |
|||
{ |
|||
new TenantConfiguration(TenantAId, "tenant-a") |
|||
{ |
|||
ConnectionStrings = new ConnectionStrings |
|||
{ |
|||
{ ConnectionStrings.DefaultConnectionStringName, tenantAConnection } |
|||
} |
|||
}, |
|||
new TenantConfiguration(TenantBId, "tenant-b") |
|||
{ |
|||
ConnectionStrings = new ConnectionStrings |
|||
{ |
|||
{ ConnectionStrings.DefaultConnectionStringName, tenantBConnection } |
|||
} |
|||
} |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity.MongoDB; |
|||
|
|||
[Collection(MongoTestCollection.Name)] |
|||
public class IdentityUserManager_SharedUser_SeparateDatabase_Tests : IdentityUserManager_SharedUser_SeparateDatabase_Tests<AbpIdentitySharedUserSeparateDbMongoDbTestModule> |
|||
{ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity.MongoDB; |
|||
|
|||
[Collection(MongoTestCollection.Name)] |
|||
public class IdentityUserManager_SharedUser_Tests : IdentityUserManager_SharedUser_Tests<AbpIdentityMongoDbTestModule> |
|||
{ |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
// Multi-tenant separate-database isolation tests, runnable on any storage backend that lets
|
|||
// each predefined tenant resolve to its own physical connection. EF (SQLite per-tenant
|
|||
// keep-alive) and MongoDB (per-tenant database) implementations both work.
|
|||
//
|
|||
// NOTE on scope: open-source IdentityUserManager.FindSharedUserBy* assumes a single shared
|
|||
// database; cross-DB shared-user resolution is the Pro UserSharingManager's responsibility.
|
|||
// These tests therefore only assert the layer the open-source framework owns: tenant
|
|||
// connection routing and IMultiTenant filter behavior.
|
|||
//
|
|||
// Concrete subclasses must register two predefined tenants (TenantAId / TenantBId from this
|
|||
// class) each with its own connection string, and enable shared-user mode in the test module.
|
|||
public abstract class IdentityUserManager_SharedUser_SeparateDatabase_Tests<TStartupModule> : AbpIdentityTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
public static readonly Guid TenantAId = Guid.Parse("11111111-1111-1111-1111-111111111111"); |
|||
public static readonly Guid TenantBId = Guid.Parse("22222222-2222-2222-2222-222222222222"); |
|||
|
|||
protected IdentityUserManager IdentityUserManager { get; } |
|||
protected IIdentityUserRepository IdentityUserRepository { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
|
|||
protected IdentityUserManager_SharedUser_SeparateDatabase_Tests() |
|||
{ |
|||
IdentityUserManager = GetRequiredService<IdentityUserManager>(); |
|||
IdentityUserRepository = GetRequiredService<IIdentityUserRepository>(); |
|||
CurrentTenant = GetRequiredService<ICurrentTenant>(); |
|||
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task Tenant_Connection_Should_Not_See_Host_Rows() |
|||
{ |
|||
var probeEmail = $"infra-host-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
var hostUser = new IdentityUser(Guid.NewGuid(), $"infra-host-{Guid.NewGuid():N}", probeEmail, null); |
|||
await IdentityUserRepository.InsertAsync(hostUser); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
{ |
|||
var foundInTenantA = await IdentityUserManager.FindByEmailAsync(probeEmail); |
|||
foundInTenantA.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task Host_Connection_Should_Not_See_Tenant_Rows() |
|||
{ |
|||
var probeEmail = $"infra-tenant-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
var tenantUser = new IdentityUser(Guid.NewGuid(), $"infra-t-{Guid.NewGuid():N}", probeEmail, TenantAId); |
|||
await IdentityUserRepository.InsertAsync(tenantUser); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var foundInHost = await IdentityUserManager.FindByEmailAsync(probeEmail); |
|||
foundInHost.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task TenantA_Connection_Should_Not_See_TenantB_Rows() |
|||
{ |
|||
var probeEmail = $"infra-cross-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
var u = new IdentityUser(Guid.NewGuid(), $"a-{Guid.NewGuid():N}", probeEmail, TenantAId); |
|||
await IdentityUserRepository.InsertAsync(u); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(TenantBId)) |
|||
{ |
|||
var foundInB = await IdentityUserManager.FindByEmailAsync(probeEmail); |
|||
foundInB.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task Different_Tenants_Should_Allow_Same_Email_With_Their_Own_Rows() |
|||
{ |
|||
var email = $"same-email-{Guid.NewGuid():N}@abp.io"; |
|||
var nameA = $"a-{Guid.NewGuid():N}"; |
|||
var nameB = $"b-{Guid.NewGuid():N}"; |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), nameA, email, TenantAId)); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
using (CurrentTenant.Change(TenantBId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), nameB, email, TenantBId)); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
{ |
|||
(await IdentityUserManager.FindByEmailAsync(email)).UserName.ShouldBe(nameA); |
|||
} |
|||
using (CurrentTenant.Change(TenantBId)) |
|||
{ |
|||
(await IdentityUserManager.FindByEmailAsync(email)).UserName.ShouldBe(nameB); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task Different_Tenants_Should_Allow_Same_UserName_With_Their_Own_Rows() |
|||
{ |
|||
var userName = $"same-name-{Guid.NewGuid():N}"; |
|||
var emailA = $"a-{Guid.NewGuid():N}@abp.io"; |
|||
var emailB = $"b-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), userName, emailA, TenantAId)); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
using (CurrentTenant.Change(TenantBId)) |
|||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
|||
{ |
|||
await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), userName, emailB, TenantBId)); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(TenantAId)) |
|||
{ |
|||
(await IdentityUserManager.FindByNameAsync(userName)).Email.ShouldBe(emailA); |
|||
} |
|||
using (CurrentTenant.Change(TenantBId)) |
|||
{ |
|||
(await IdentityUserManager.FindByNameAsync(userName)).Email.ShouldBe(emailB); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,254 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Identity; |
|||
|
|||
// Abstract test suite for IdentityUserManager.FindSharedUserBy*Async under
|
|||
// TenantUserSharingStrategy.Shared. Concrete subclasses in Domain.Tests (EF) and
|
|||
// MongoDB.Tests pick the storage backend by passing the corresponding TStartupModule.
|
|||
public abstract class IdentityUserManager_SharedUser_Tests<TStartupModule> : AbpIdentityTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
protected IdentityUserManager IdentityUserManager { get; } |
|||
protected IIdentityUserRepository IdentityUserRepository { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
|||
|
|||
protected IdentityUserManager_SharedUser_Tests() |
|||
{ |
|||
IdentityUserManager = GetRequiredService<IdentityUserManager>(); |
|||
IdentityUserRepository = GetRequiredService<IIdentityUserRepository>(); |
|||
CurrentTenant = GetRequiredService<ICurrentTenant>(); |
|||
UnitOfWorkManager = GetRequiredService<IUnitOfWorkManager>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
services.Configure<AbpMultiTenancyOptions>(options => |
|||
{ |
|||
options.IsEnabled = true; |
|||
options.UserSharingStrategy = TenantUserSharingStrategy.Shared; |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByEmailAsync_Should_Return_Host_User() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
var email = $"shared-email-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(null, $"shared-host-email-{Guid.NewGuid():N}", email); |
|||
await CreateUserAsync(tenantId, $"shared-tenant-email-{Guid.NewGuid():N}", email); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByEmailAsync(email); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByEmailAsync_Should_Find_Tenant_User_When_No_Host_User() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
var email = $"shared-tenant-only-{Guid.NewGuid():N}@abp.io"; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(tenantId, $"shared-tenant-only-{Guid.NewGuid():N}", email); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByEmailAsync(email); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBe(tenantId); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByEmailAsync_Should_Return_Null_For_Unknown_Email() |
|||
{ |
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByEmailAsync($"missing-{Guid.NewGuid():N}@abp.io"); |
|||
user.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByNameAsync_Should_Return_Host_User() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
var userName = $"shared-name-{Guid.NewGuid():N}"; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(null, userName, $"host-{Guid.NewGuid():N}@abp.io"); |
|||
await CreateUserAsync(tenantId, userName, $"tenant-{Guid.NewGuid():N}@abp.io"); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByNameAsync(userName); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByLoginAsync_Should_Return_Host_User() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
var loginProvider = "github"; |
|||
var providerKey = $"shared-login-{Guid.NewGuid():N}"; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(null, $"host-login-{Guid.NewGuid():N}", $"host-login-{Guid.NewGuid():N}@abp.io", |
|||
u => u.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login"))); |
|||
await CreateUserAsync(tenantId, $"tenant-login-{Guid.NewGuid():N}", $"tenant-login-{Guid.NewGuid():N}@abp.io", |
|||
u => u.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login"))); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByLoginAsync(loginProvider, providerKey); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByPasskeyIdAsync_Should_Return_Host_User() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
var credentialId = Guid.NewGuid().ToByteArray(); |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(null, $"shared-host-passkey-{Guid.NewGuid():N}", $"shared-host-passkey-{Guid.NewGuid():N}@abp.io", |
|||
u => u.AddPasskey(credentialId, new IdentityPasskeyData())); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByPasskeyIdAsync(credentialId); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByIdAsync_Should_Find_Tenant_User_From_Host_Context() |
|||
{ |
|||
// Core 2FA shared-mode bug condition: a tenant-only user must be reachable by id from
|
|||
// a host context (CurrentTenant=null). The IMultiTenant filter would otherwise hide it.
|
|||
var tenantId = Guid.NewGuid(); |
|||
IdentityUser tenantUser; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
tenantUser = await CreateUserAsync(tenantId, $"shared-id-tenant-{Guid.NewGuid():N}", $"shared-id-tenant-{Guid.NewGuid():N}@abp.io"); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByIdAsync(tenantUser.Id.ToString()); |
|||
user.ShouldNotBeNull(); |
|||
user.Id.ShouldBe(tenantUser.Id); |
|||
user.TenantId.ShouldBe(tenantId); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByIdAsync_Should_Find_Host_User_From_Tenant_Context() |
|||
{ |
|||
var tenantId = Guid.NewGuid(); |
|||
IdentityUser hostUser; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
hostUser = await CreateUserAsync(null, $"shared-id-host-{Guid.NewGuid():N}", $"shared-id-host-{Guid.NewGuid():N}@abp.io"); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByIdAsync(hostUser.Id.ToString()); |
|||
user.ShouldNotBeNull(); |
|||
user.TenantId.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task FindSharedUserByIdAsync_Should_Return_Null_For_Unknown_Id() |
|||
{ |
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var user = await IdentityUserManager.FindSharedUserByIdAsync(Guid.NewGuid().ToString()); |
|||
user.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public virtual async Task Login_Then_TwoFactor_MidFlow_Should_Resolve_Same_Tenant_User() |
|||
{ |
|||
// End-to-end shape of the 2FA shared-mode regression: a host-context lookup-by-name
|
|||
// followed by lookup-by-id must return the same tenant row both times.
|
|||
var tenantId = Guid.NewGuid(); |
|||
var userName = $"shared-2fa-{Guid.NewGuid():N}"; |
|||
|
|||
using (var uow = UnitOfWorkManager.Begin()) |
|||
{ |
|||
await CreateUserAsync(tenantId, userName, $"{userName}@abp.io"); |
|||
await uow.CompleteAsync(); |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var loginUser = await IdentityUserManager.FindSharedUserByNameAsync(userName); |
|||
loginUser.ShouldNotBeNull(); |
|||
loginUser.TenantId.ShouldBe(tenantId); |
|||
|
|||
var twoFactorUser = await IdentityUserManager.FindSharedUserByIdAsync(loginUser.Id.ToString()); |
|||
twoFactorUser.ShouldNotBeNull(); |
|||
twoFactorUser.Id.ShouldBe(loginUser.Id); |
|||
twoFactorUser.TenantId.ShouldBe(tenantId); |
|||
} |
|||
} |
|||
|
|||
protected async Task<IdentityUser> CreateUserAsync( |
|||
Guid? tenantId, |
|||
string userName, |
|||
string email, |
|||
Action<IdentityUser> configureUser = null) |
|||
{ |
|||
var user = new IdentityUser(Guid.NewGuid(), userName, email, tenantId); |
|||
configureUser?.Invoke(user); |
|||
|
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
await IdentityUserRepository.InsertAsync(user); |
|||
} |
|||
|
|||
return user; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue