Browse Source

Hash the connection string in unit of work api keys

The api key can be written to logs as a part of an exception message
pull/26047/head
maliming 2 months ago
parent
commit
206e4a9027
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 18
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
  2. 9
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs
  3. 18
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs
  4. 61
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Uow/UnitOfWorkDbContextProvider_Tests.cs
  5. 39
      framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Uow/UnitOfWorkMemoryDatabaseProvider_Tests.cs
  6. 58
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Uow/UnitOfWorkMongoDbContextProvider_Tests.cs
  7. 62
      framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_DatabaseApi_Tests.cs

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

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