diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 1ab174cc16..c8c7793f40 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -66,7 +66,7 @@ public class UnitOfWorkDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork) { - var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}"; + var transactionApiKey = GetTransactionApiKey(DbContextCreationContext.Current.ConnectionString); var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi; if (activeTransaction == null) @@ -351,6 +351,16 @@ public class UnitOfWorkDbContextProvider : IDbContextProvider ResolveConnectionStringAsync(string connectionStringName) { // Multi-tenancy unaware contexts should always use the host connection string diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs index 6659bbf970..cc16da3a06 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs @@ -46,7 +46,7 @@ public class UnitOfWorkMemoryDatabaseProvider : IMemoryDatabas } var connectionString = _connectionStringResolver.Resolve(); - var dbContextKey = $"{typeof(TMemoryDbContext).FullName}_{connectionString}"; + var dbContextKey = GetDatabaseApiKey(connectionString); var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, @@ -66,7 +66,7 @@ public class UnitOfWorkMemoryDatabaseProvider : IMemoryDatabas } var connectionString = await _connectionStringResolver.ResolveAsync(); - var dbContextKey = $"{typeof(TMemoryDbContext).FullName}_{connectionString}"; + var dbContextKey = GetDatabaseApiKey(connectionString); var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, @@ -77,6 +77,11 @@ public class UnitOfWorkMemoryDatabaseProvider : IMemoryDatabas return ((MemoryDbDatabaseApi)databaseApi).Database; } + protected virtual string GetDatabaseApiKey(string? connectionString) + { + return $"{typeof(TMemoryDbContext).FullName}_{(connectionString ?? string.Empty).ToSha256()}"; + } + private async Task ResolveConnectionStringAsync() { // Multi-tenancy unaware contexts should always use the host connection string diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index acea595dce..0dc3527756 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -68,7 +68,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext var targetDbContextType = DbContextTypeProvider.GetDbContextType(typeof(TMongoDbContext)); var connectionString = ResolveConnectionString(targetDbContextType); - var dbContextKey = $"{targetDbContextType.FullName}_{connectionString}"; + var dbContextKey = GetDatabaseApiKey(targetDbContextType, connectionString); var mongoUrl = new MongoUrl(connectionString); var databaseName = mongoUrl.DatabaseName; @@ -95,7 +95,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext var targetDbContextType = DbContextTypeProvider.GetDbContextType(typeof(TMongoDbContext)); var connectionString = await ResolveConnectionStringAsync(targetDbContextType); - var dbContextKey = $"{targetDbContextType.FullName}_{connectionString}"; + var dbContextKey = GetDatabaseApiKey(targetDbContextType, connectionString); var mongoUrl = new MongoUrl(connectionString); var databaseName = mongoUrl.DatabaseName; @@ -173,7 +173,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext MongoClient client, IMongoDatabase database) { - var transactionApiKey = $"MongoDb_{url}"; + var transactionApiKey = GetTransactionApiKey(url); var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as MongoDbTransactionApi; var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); @@ -223,7 +223,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext IMongoDatabase database, CancellationToken cancellationToken = default) { - var transactionApiKey = $"MongoDb_{url}"; + var transactionApiKey = GetTransactionApiKey(url); var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as MongoDbTransactionApi; var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); @@ -266,6 +266,16 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext return dbContext; } + protected virtual string GetDatabaseApiKey(Type dbContextType, string? connectionString) + { + return $"{dbContextType.FullName}_{(connectionString ?? string.Empty).ToSha256()}"; + } + + protected virtual string GetTransactionApiKey(MongoUrl url) + { + return $"MongoDb_{url.ToString().ToSha256()}"; + } + protected virtual async Task ResolveConnectionStringAsync(Type dbContextType) { // Multi-tenancy unaware contexts should always use the host connection string diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWorkDbContextProvider_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWorkDbContextProvider_Tests.cs new file mode 100644 index 0000000000..86964f8de2 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWorkDbContextProvider_Tests.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.EntityFrameworkCore; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Uow; + +public class UnitOfWorkDbContextProvider_Tests : EntityFrameworkCoreTestBase +{ + private readonly IPersonRepository _personRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IConnectionStringResolver _connectionStringResolver; + + public UnitOfWorkDbContextProvider_Tests() + { + _personRepository = GetRequiredService(); + _unitOfWorkManager = GetRequiredService(); + _connectionStringResolver = GetRequiredService(); + } + + [Fact] + public async Task Should_Not_Use_Connection_String_As_Database_Api_Key() + { + var connectionString = await _connectionStringResolver.ResolveAsync( + ConnectionStringNameAttribute.GetConnStringName() + ); + + using (var uow = _unitOfWorkManager.Begin()) + { + await _personRepository.GetDbContextAsync(); + + uow.FindDatabaseApi($"{typeof(TestAppDbContext).FullName}_{connectionString}").ShouldBeNull(); + uow.FindDatabaseApi($"{typeof(TestAppDbContext).FullName}_{connectionString.ToSha256()}").ShouldNotBeNull(); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Should_Not_Use_Connection_String_As_Transaction_Api_Key() + { + var connectionString = await _connectionStringResolver.ResolveAsync( + ConnectionStringNameAttribute.GetConnStringName() + ); + + using (var uow = _unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true })) + { + await _personRepository.GetDbContextAsync(); + + uow.FindTransactionApi($"EntityFrameworkCore_{connectionString}").ShouldBeNull(); + uow.FindTransactionApi($"EntityFrameworkCore_{connectionString.ToSha256()}").ShouldNotBeNull(); + + await uow.CompleteAsync(); + } + } +} diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Uow/UnitOfWorkMemoryDatabaseProvider_Tests.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Uow/UnitOfWorkMemoryDatabaseProvider_Tests.cs new file mode 100644 index 0000000000..33fb06c997 --- /dev/null +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Uow/UnitOfWorkMemoryDatabaseProvider_Tests.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.TestApp.MemoryDb; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.MemoryDb.Uow; + +public class UnitOfWorkMemoryDatabaseProvider_Tests : MemoryDbTestBase +{ + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IConnectionStringResolver _connectionStringResolver; + private readonly IMemoryDatabaseProvider _memoryDatabaseProvider; + + public UnitOfWorkMemoryDatabaseProvider_Tests() + { + _unitOfWorkManager = GetRequiredService(); + _connectionStringResolver = GetRequiredService(); + _memoryDatabaseProvider = GetRequiredService>(); + } + + [Fact] + public async Task Should_Not_Use_Connection_String_As_Database_Api_Key() + { + var connectionString = await _connectionStringResolver.ResolveAsync(); + + using (var uow = _unitOfWorkManager.Begin()) + { + await _memoryDatabaseProvider.GetDatabaseAsync(); + + uow.FindDatabaseApi($"{typeof(TestAppMemoryDbContext).FullName}_{connectionString}").ShouldBeNull(); + uow.FindDatabaseApi($"{typeof(TestAppMemoryDbContext).FullName}_{connectionString.ToSha256()}").ShouldNotBeNull(); + + await uow.CompleteAsync(); + } + } +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/UnitOfWorkMongoDbContextProvider_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/UnitOfWorkMongoDbContextProvider_Tests.cs new file mode 100644 index 0000000000..3fe400e9e8 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/UnitOfWorkMongoDbContextProvider_Tests.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; +using MongoDB.Driver; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.TestApp.MongoDB; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.MongoDB.Uow; + +[Collection(MongoTestCollection.Name)] +public class UnitOfWorkMongoDbContextProvider_Tests : MongoDbTestBase +{ + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IConnectionStringResolver _connectionStringResolver; + private readonly IMongoDbContextProvider _mongoDbContextProvider; + + public UnitOfWorkMongoDbContextProvider_Tests() + { + _unitOfWorkManager = GetRequiredService(); + _connectionStringResolver = GetRequiredService(); + _mongoDbContextProvider = GetRequiredService>(); + } + + [Fact] + public async Task Should_Not_Use_Connection_String_As_Database_Api_Key() + { + var connectionString = await _connectionStringResolver.ResolveAsync(); + + using (var uow = _unitOfWorkManager.Begin()) + { + await _mongoDbContextProvider.GetDbContextAsync(); + + uow.FindDatabaseApi($"{typeof(TestAppMongoDbContext).FullName}_{connectionString}").ShouldBeNull(); + uow.FindDatabaseApi($"{typeof(TestAppMongoDbContext).FullName}_{connectionString.ToSha256()}").ShouldNotBeNull(); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Should_Not_Use_Connection_String_As_Transaction_Api_Key() + { + var connectionString = await _connectionStringResolver.ResolveAsync(); + var mongoUrl = new MongoUrl(connectionString); + + using (var uow = _unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true })) + { + await _mongoDbContextProvider.GetDbContextAsync(); + + uow.FindTransactionApi($"MongoDb_{mongoUrl}").ShouldBeNull(); + uow.FindTransactionApi($"MongoDb_{mongoUrl.ToString().ToSha256()}").ShouldNotBeNull(); + + await uow.CompleteAsync(); + } + } +} diff --git a/framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_DatabaseApi_Tests.cs b/framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_DatabaseApi_Tests.cs new file mode 100644 index 0000000000..8493440d47 --- /dev/null +++ b/framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_DatabaseApi_Tests.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Uow; + +public class UnitOfWork_DatabaseApi_Tests : AbpIntegratedTest +{ + private const string KeyWithSecret = "MyDbContext_Server=localhost;User Id=sa;Password=SecretMarker;"; + + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public UnitOfWork_DatabaseApi_Tests() + { + _unitOfWorkManager = GetRequiredService(); + } + + [Fact] + public void Should_Not_Expose_Database_Api_Key_On_Duplicate() + { + using (var uow = _unitOfWorkManager.Begin()) + { + uow.AddDatabaseApi(KeyWithSecret, new FakeDatabaseApi()); + + var exception = Assert.Throws(() => uow.AddDatabaseApi(KeyWithSecret, new FakeDatabaseApi())); + exception.ToString().ShouldNotContain("SecretMarker"); + } + } + + [Fact] + public void Should_Not_Expose_Transaction_Api_Key_On_Duplicate() + { + using (var uow = _unitOfWorkManager.Begin()) + { + uow.AddTransactionApi(KeyWithSecret, new FakeTransactionApi()); + + var exception = Assert.Throws(() => uow.AddTransactionApi(KeyWithSecret, new FakeTransactionApi())); + exception.ToString().ShouldNotContain("SecretMarker"); + } + } + + private class FakeDatabaseApi : IDatabaseApi + { + + } + + private class FakeTransactionApi : ITransactionApi + { + public Task CommitAsync(CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public void Dispose() + { + + } + } +}