Browse Source

Merge pull request #26047 from abpframework/maliming/uow-api-key-connection-string

Hash the connection string in unit of work api keys
pull/26063/head
Engincan VESKE 3 days ago
committed by GitHub
parent
commit
39a6bad676
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  2. 18
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
  3. 9
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs
  4. 18
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs
  5. 24
      framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs
  6. 61
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWorkDbContextProvider_Tests.cs
  7. 39
      framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Uow/UnitOfWorkMemoryDatabaseProvider_Tests.cs
  8. 58
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/UnitOfWorkMongoDbContextProvider_Tests.cs
  9. 62
      framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_DatabaseApi_Tests.cs

12
framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs

@ -384,6 +384,7 @@ public static class AbpStringExtensions
public static string ToMd5(this string str)
{
#if NETSTANDARD2_0 || NETSTANDARD2_1
using (var md5 = MD5.Create())
{
var inputBytes = Encoding.UTF8.GetBytes(str);
@ -397,10 +398,14 @@ public static class AbpStringExtensions
return sb.ToString();
}
#else
return Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(str)));
#endif
}
public static string ToSha256(this string str)
{
#if NETSTANDARD2_0 || NETSTANDARD2_1
using (var sha = SHA256.Create())
{
var data = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
@ -412,10 +417,14 @@ public static class AbpStringExtensions
}
return sb.ToString();
}
#else
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(str))).ToLowerInvariant();
#endif
}
public static string ToSha512(this string str)
{
#if NETSTANDARD2_0 || NETSTANDARD2_1
using (var sha = SHA512.Create())
{
var data = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
@ -427,6 +436,9 @@ public static class AbpStringExtensions
}
return sb.ToString();
}
#else
return Convert.ToHexString(SHA512.HashData(Encoding.UTF8.GetBytes(str))).ToLowerInvariant();
#endif
}
/// <summary>

18
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs

@ -66,7 +66,7 @@ public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbCon
var targetDbContextType = EfCoreDbContextTypeProvider.GetDbContextType(typeof(TDbContext));
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(targetDbContextType);
var connectionString = ResolveConnectionString(connectionStringName);
var dbContextKey = $"{targetDbContextType.FullName}_{connectionString}";
var dbContextKey = GetDatabaseApiKey(targetDbContextType, connectionString);
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
@ -89,7 +89,7 @@ public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbCon
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(targetDbContextType);
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
var dbContextKey = $"{targetDbContextType.FullName}_{connectionString}";
var dbContextKey = GetDatabaseApiKey(targetDbContextType, connectionString);
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
@ -164,7 +164,7 @@ public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbCon
[Obsolete("Use CreateDbContextWithTransactionAsync.")]
protected virtual TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork)
{
var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}";
var transactionApiKey = GetTransactionApiKey(DbContextCreationContext.Current.ConnectionString);
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi;
if (activeTransaction == null)
@ -256,7 +256,7 @@ public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbCon
protected virtual async Task<TDbContext> 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<TDbContext> : IDbContextProvider<TDbCon
}
}
protected virtual string GetDatabaseApiKey(Type dbContextType, string? connectionString)
{
return $"{dbContextType.FullName}_{(connectionString ?? string.Empty).ToSha256()}";
}
protected virtual string GetTransactionApiKey(string? connectionString)
{
return $"EntityFrameworkCore_{(connectionString ?? string.Empty).ToSha256()}";
}
protected virtual async Task<string> ResolveConnectionStringAsync(string connectionStringName)
{
// Multi-tenancy unaware contexts should always use the host connection string

9
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs

@ -46,7 +46,7 @@ public class UnitOfWorkMemoryDatabaseProvider<TMemoryDbContext> : IMemoryDatabas
}
var connectionString = _connectionStringResolver.Resolve<TMemoryDbContext>();
var dbContextKey = $"{typeof(TMemoryDbContext).FullName}_{connectionString}";
var dbContextKey = GetDatabaseApiKey(connectionString);
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
@ -66,7 +66,7 @@ public class UnitOfWorkMemoryDatabaseProvider<TMemoryDbContext> : IMemoryDatabas
}
var connectionString = await _connectionStringResolver.ResolveAsync<TMemoryDbContext>();
var dbContextKey = $"{typeof(TMemoryDbContext).FullName}_{connectionString}";
var dbContextKey = GetDatabaseApiKey(connectionString);
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
@ -77,6 +77,11 @@ public class UnitOfWorkMemoryDatabaseProvider<TMemoryDbContext> : IMemoryDatabas
return ((MemoryDbDatabaseApi)databaseApi).Database;
}
protected virtual string GetDatabaseApiKey(string? connectionString)
{
return $"{typeof(TMemoryDbContext).FullName}_{(connectionString ?? string.Empty).ToSha256()}";
}
private async Task<string> ResolveConnectionStringAsync()
{
// Multi-tenancy unaware contexts should always use the host connection string

18
framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs

@ -68,7 +68,7 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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<TMongoDbContext> : 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<TMongoDbContext> : 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<TMongoDbContext>();
@ -223,7 +223,7 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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<TMongoDbContext>();
@ -266,6 +266,16 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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<string> ResolveConnectionStringAsync(Type dbContextType)
{
// Multi-tenancy unaware contexts should always use the host connection string

24
framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs

@ -255,6 +255,30 @@ public class StringExtensions_Tests : IDisposable
Encoding.ASCII.GetString(bytes).ShouldBe(str);
}
[Theory]
[InlineData("", "D41D8CD98F00B204E9800998ECF8427E")]
[InlineData("abc", "900150983CD24FB0D6963F7D28E17F72")]
public void ToMd5_Test(string str, string expected)
{
str.ToMd5().ShouldBe(expected);
}
[Theory]
[InlineData("", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")]
[InlineData("abc", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f")]
public void ToSha512_Test(string str, string expected)
{
str.ToSha512().ShouldBe(expected);
}
[Theory]
[InlineData("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")]
[InlineData("abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")]
public void ToSha256_Test(string str, string expected)
{
str.ToSha256().ShouldBe(expected);
}
private enum MyEnum
{
MyValue1,

61
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<IPersonRepository>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
_connectionStringResolver = GetRequiredService<IConnectionStringResolver>();
}
[Fact]
public async Task Should_Not_Use_Connection_String_As_Database_Api_Key()
{
var connectionString = await _connectionStringResolver.ResolveAsync(
ConnectionStringNameAttribute.GetConnStringName<TestAppDbContext>()
);
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<TestAppDbContext>()
);
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();
}
}
}

39
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<TestAppMemoryDbContext> _memoryDatabaseProvider;
public UnitOfWorkMemoryDatabaseProvider_Tests()
{
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
_connectionStringResolver = GetRequiredService<IConnectionStringResolver>();
_memoryDatabaseProvider = GetRequiredService<IMemoryDatabaseProvider<TestAppMemoryDbContext>>();
}
[Fact]
public async Task Should_Not_Use_Connection_String_As_Database_Api_Key()
{
var connectionString = await _connectionStringResolver.ResolveAsync<TestAppMemoryDbContext>();
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();
}
}
}

58
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<TestAppMongoDbContext> _mongoDbContextProvider;
public UnitOfWorkMongoDbContextProvider_Tests()
{
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
_connectionStringResolver = GetRequiredService<IConnectionStringResolver>();
_mongoDbContextProvider = GetRequiredService<IMongoDbContextProvider<TestAppMongoDbContext>>();
}
[Fact]
public async Task Should_Not_Use_Connection_String_As_Database_Api_Key()
{
var connectionString = await _connectionStringResolver.ResolveAsync<TestAppMongoDbContext>();
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<TestAppMongoDbContext>();
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();
}
}
}

62
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<AbpUnitOfWorkModule>
{
private const string KeyWithSecret = "MyDbContext_Server=localhost;User Id=sa;Password=SecretMarker;";
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWork_DatabaseApi_Tests()
{
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
}
[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<AbpException>(() => 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<AbpException>(() => 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()
{
}
}
}
Loading…
Cancel
Save