using Bit.Core; using Bit.Infrastructure.EntityFramework.Converters; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using DP = Microsoft.AspNetCore.DataProtection; namespace Bit.Infrastructure.EntityFramework.Repositories; public class DatabaseContext : DbContext { public const string postgresIndetermanisticCollation = "postgresIndetermanisticCollation"; public DatabaseContext(DbContextOptions options) : base(options) { } public DbSet AccessPolicies { get; set; } public DbSet UserProjectAccessPolicy { get; set; } public DbSet GroupProjectAccessPolicy { get; set; } public DbSet ServiceAccountProjectAccessPolicy { get; set; } public DbSet UserServiceAccountAccessPolicy { get; set; } public DbSet GroupServiceAccountAccessPolicy { get; set; } public DbSet ApiKeys { get; set; } public DbSet Ciphers { get; set; } public DbSet Collections { get; set; } public DbSet CollectionCiphers { get; set; } public DbSet CollectionGroups { get; set; } public DbSet CollectionUsers { get; set; } public DbSet Devices { get; set; } public DbSet EmergencyAccesses { get; set; } public DbSet Events { get; set; } public DbSet Folders { get; set; } public DbSet Grants { get; set; } public DbSet Groups { get; set; } public DbSet GroupUsers { get; set; } public DbSet Installations { get; set; } public DbSet Organizations { get; set; } public DbSet OrganizationApiKeys { get; set; } public DbSet OrganizationSponsorships { get; set; } public DbSet OrganizationConnections { get; set; } public DbSet OrganizationUsers { get; set; } public DbSet Policies { get; set; } public DbSet Providers { get; set; } public DbSet Secret { get; set; } public DbSet ServiceAccount { get; set; } public DbSet Project { get; set; } public DbSet ProviderUsers { get; set; } public DbSet ProviderOrganizations { get; set; } public DbSet Sends { get; set; } public DbSet SsoConfigs { get; set; } public DbSet SsoUsers { get; set; } public DbSet TaxRates { get; set; } public DbSet Transactions { get; set; } public DbSet Users { get; set; } public DbSet AuthRequests { get; set; } public DbSet OrganizationDomains { get; set; } protected override void OnModelCreating(ModelBuilder builder) { // Scans and loads all configurations implementing the `IEntityTypeConfiguration` from the // `Infrastructure.EntityFramework` Module. Note to get the assembly we can use a random class // from this module. builder.ApplyConfigurationsFromAssembly(typeof(DatabaseContext).Assembly); // Going forward use `IEntityTypeConfiguration` in the Configurations folder for managing // Entity Framework code first database configurations. var eCipher = builder.Entity(); var eCollection = builder.Entity(); var eCollectionCipher = builder.Entity(); var eCollectionUser = builder.Entity(); var eCollectionGroup = builder.Entity(); var eDevice = builder.Entity(); var eEmergencyAccess = builder.Entity(); var eEvent = builder.Entity(); var eFolder = builder.Entity(); var eGrant = builder.Entity(); var eGroup = builder.Entity(); var eGroupUser = builder.Entity(); var eInstallation = builder.Entity(); var eOrganization = builder.Entity(); var eOrganizationSponsorship = builder.Entity(); var eOrganizationUser = builder.Entity(); var ePolicy = builder.Entity(); var eProvider = builder.Entity(); var eProviderUser = builder.Entity(); var eProviderOrganization = builder.Entity(); var eSend = builder.Entity(); var eSsoConfig = builder.Entity(); var eSsoUser = builder.Entity(); var eTaxRate = builder.Entity(); var eTransaction = builder.Entity(); var eUser = builder.Entity(); var eOrganizationApiKey = builder.Entity(); var eOrganizationConnection = builder.Entity(); var eAuthRequest = builder.Entity(); var eOrganizationDomain = builder.Entity(); eCipher.Property(c => c.Id).ValueGeneratedNever(); eCollection.Property(c => c.Id).ValueGeneratedNever(); eEmergencyAccess.Property(c => c.Id).ValueGeneratedNever(); eEvent.Property(c => c.Id).ValueGeneratedNever(); eFolder.Property(c => c.Id).ValueGeneratedNever(); eGroup.Property(c => c.Id).ValueGeneratedNever(); eInstallation.Property(c => c.Id).ValueGeneratedNever(); eOrganization.Property(c => c.Id).ValueGeneratedNever(); eOrganizationSponsorship.Property(c => c.Id).ValueGeneratedNever(); eOrganizationUser.Property(c => c.Id).ValueGeneratedNever(); ePolicy.Property(c => c.Id).ValueGeneratedNever(); eProvider.Property(c => c.Id).ValueGeneratedNever(); eProviderUser.Property(c => c.Id).ValueGeneratedNever(); eProviderOrganization.Property(c => c.Id).ValueGeneratedNever(); eSend.Property(c => c.Id).ValueGeneratedNever(); eTransaction.Property(c => c.Id).ValueGeneratedNever(); eUser.Property(c => c.Id).ValueGeneratedNever(); eOrganizationApiKey.Property(c => c.Id).ValueGeneratedNever(); eOrganizationConnection.Property(c => c.Id).ValueGeneratedNever(); eAuthRequest.Property(ar => ar.Id).ValueGeneratedNever(); eOrganizationDomain.Property(ar => ar.Id).ValueGeneratedNever(); eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId }); eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId }); eCollectionGroup.HasKey(cg => new { cg.CollectionId, cg.GroupId }); eGrant.HasKey(x => x.Key); eGroupUser.HasKey(gu => new { gu.GroupId, gu.OrganizationUserId }); var dataProtector = this.GetService().CreateProtector( Constants.DatabaseFieldProtectorPurpose); var dataProtectionConverter = new DataProtectionConverter(dataProtector); eUser.Property(c => c.Key).HasConversion(dataProtectionConverter); eUser.Property(c => c.MasterPassword).HasConversion(dataProtectionConverter); if (Database.IsNpgsql()) { // the postgres provider doesn't currently support database level non-deterministic collations. // see https://www.npgsql.org/efcore/misc/collations-and-case-sensitivity.html#database-collation builder.HasCollation(postgresIndetermanisticCollation, locale: "en-u-ks-primary", provider: "icu", deterministic: false); eUser.Property(e => e.Email).UseCollation(postgresIndetermanisticCollation); eSsoUser.Property(e => e.ExternalId).UseCollation(postgresIndetermanisticCollation); eOrganization.Property(e => e.Identifier).UseCollation(postgresIndetermanisticCollation); // } eCipher.ToTable(nameof(Cipher)); eCollection.ToTable(nameof(Collection)); eCollectionCipher.ToTable(nameof(CollectionCipher)); eDevice.ToTable(nameof(Device)); eEmergencyAccess.ToTable(nameof(EmergencyAccess)); eEvent.ToTable(nameof(Event)); eFolder.ToTable(nameof(Folder)); eGrant.ToTable(nameof(Grant)); eGroup.ToTable(nameof(Group)); eGroupUser.ToTable(nameof(GroupUser)); eInstallation.ToTable(nameof(Installation)); eOrganization.ToTable(nameof(Organization)); eOrganizationSponsorship.ToTable(nameof(OrganizationSponsorship)); eOrganizationUser.ToTable(nameof(OrganizationUser)); ePolicy.ToTable(nameof(Policy)); eProvider.ToTable(nameof(Provider)); eProviderUser.ToTable(nameof(ProviderUser)); eProviderOrganization.ToTable(nameof(ProviderOrganization)); eSend.ToTable(nameof(Send)); eSsoConfig.ToTable(nameof(SsoConfig)); eSsoUser.ToTable(nameof(SsoUser)); eTaxRate.ToTable(nameof(TaxRate)); eTransaction.ToTable(nameof(Transaction)); eUser.ToTable(nameof(User)); eOrganizationApiKey.ToTable(nameof(OrganizationApiKey)); eOrganizationConnection.ToTable(nameof(OrganizationConnection)); eAuthRequest.ToTable(nameof(AuthRequest)); eOrganizationDomain.ToTable(nameof(OrganizationDomain)); ConfigureDateTimeUtcQueries(builder); } // Make sure this is called after configuring all the entities as it iterates through all setup entities. private void ConfigureDateTimeUtcQueries(ModelBuilder builder) { foreach (var entityType in builder.Model.GetEntityTypes()) { if (entityType.IsKeyless) { continue; } foreach (var property in entityType.GetProperties()) { if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) { property.SetValueConverter( new ValueConverter( v => v, v => new DateTime(v.Ticks, DateTimeKind.Utc))); } } } } }