diff --git a/src/Core/Repositories/IPhishingDomainRepository.cs b/src/Core/Repositories/IPhishingDomainRepository.cs new file mode 100644 index 0000000000..aea6626ab4 --- /dev/null +++ b/src/Core/Repositories/IPhishingDomainRepository.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Repositories; + +public interface IPhishingDomainRepository +{ + Task> GetActivePhishingDomainsAsync(); + Task UpdatePhishingDomainsAsync(IEnumerable domains); +} diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 26abf5632c..228e62b9fa 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -44,6 +44,7 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -71,4 +72,10 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); } } + + public static void AddDapper(this IServiceCollection services) + { + // Register repositories + services.AddSingleton(); + } } diff --git a/src/Infrastructure.Dapper/Repositories/PhishingDomainRepository.cs b/src/Infrastructure.Dapper/Repositories/PhishingDomainRepository.cs new file mode 100644 index 0000000000..10fe50e34d --- /dev/null +++ b/src/Infrastructure.Dapper/Repositories/PhishingDomainRepository.cs @@ -0,0 +1,56 @@ +using System.Data; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Repositories; + +public class PhishingDomainRepository : IPhishingDomainRepository +{ + private readonly string _connectionString; + + public PhishingDomainRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString) + { } + + public PhishingDomainRepository(string connectionString) + { + _connectionString = connectionString; + } + + public async Task> GetActivePhishingDomainsAsync() + { + using (var connection = new SqlConnection(_connectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[PhishingDomain_ReadAll]", + commandType: CommandType.StoredProcedure); + return results.AsList(); + } + } + + public async Task UpdatePhishingDomainsAsync(IEnumerable domains) + { + using (var connection = new SqlConnection(_connectionString)) + { + await connection.ExecuteAsync( + "[dbo].[PhishingDomain_DeleteAll]", + commandType: CommandType.StoredProcedure); + + foreach (var domain in domains) + { + await connection.ExecuteAsync( + "[dbo].[PhishingDomain_Create]", + new + { + Id = Guid.NewGuid(), + Domain = domain, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow + }, + commandType: CommandType.StoredProcedure); + } + } + } +} diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 3f805bbe2c..1a5791ab95 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -101,6 +101,7 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.EntityFramework/Models/PhishingDomain.cs b/src/Infrastructure.EntityFramework/Models/PhishingDomain.cs new file mode 100644 index 0000000000..842939d022 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Models/PhishingDomain.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class PhishingDomain +{ + [Key] + public Guid Id { get; set; } + + [Required] + [MaxLength(255)] + public string Domain { get; set; } + + public DateTime CreationDate { get; set; } + + public DateTime RevisionDate { get; set; } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index dd1b97b4f2..17237c5207 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -80,6 +80,7 @@ public class DatabaseContext : DbContext public DbSet PasswordHealthReportApplications { get; set; } public DbSet SecurityTasks { get; set; } public DbSet OrganizationInstallations { get; set; } + public DbSet PhishingDomains { get; set; } protected override void OnModelCreating(ModelBuilder builder) { diff --git a/src/Infrastructure.EntityFramework/Repositories/PhishingDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/PhishingDomainRepository.cs new file mode 100644 index 0000000000..7266f01e7c --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/PhishingDomainRepository.cs @@ -0,0 +1,50 @@ +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Repositories; + +public class PhishingDomainRepository : IPhishingDomainRepository +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + + public PhishingDomainRepository(IServiceScopeFactory serviceScopeFactory) + { + _serviceScopeFactory = serviceScopeFactory; + } + + public async Task> GetActivePhishingDomainsAsync() + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + var domains = await dbContext.PhishingDomains + .Select(d => d.Domain) + .ToListAsync(); + return domains; + } + } + + public async Task UpdatePhishingDomainsAsync(IEnumerable domains) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + + // Clear existing domains + await dbContext.PhishingDomains.ExecuteDeleteAsync(); + + // Add new domains + var phishingDomains = domains.Select(d => new PhishingDomain + { + Id = Guid.NewGuid(), + Domain = d, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow + }); + await dbContext.PhishingDomains.AddRangeAsync(phishingDomains); + await dbContext.SaveChangesAsync(); + } + } +} diff --git a/src/Sql/dbo/Stored Procedures/PhishingDomain_Create.sql b/src/Sql/dbo/Stored Procedures/PhishingDomain_Create.sql new file mode 100644 index 0000000000..acb2da8876 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PhishingDomain_Create.sql @@ -0,0 +1,24 @@ +CREATE PROCEDURE [dbo].[PhishingDomain_Create] + @Id UNIQUEIDENTIFIER, + @Domain NVARCHAR(255), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[PhishingDomain] + ( + [Id], + [Domain], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @Domain, + @CreationDate, + @RevisionDate + ) +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/PhishingDomain_DeleteAll.sql b/src/Sql/dbo/Stored Procedures/PhishingDomain_DeleteAll.sql new file mode 100644 index 0000000000..94302074e3 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PhishingDomain_DeleteAll.sql @@ -0,0 +1,8 @@ +CREATE PROCEDURE [dbo].[PhishingDomain_DeleteAll] +AS +BEGIN + SET NOCOUNT ON + + DELETE FROM + [dbo].[PhishingDomain] +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/PhishingDomain_ReadAll.sql b/src/Sql/dbo/Stored Procedures/PhishingDomain_ReadAll.sql new file mode 100644 index 0000000000..7b8e18060e --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PhishingDomain_ReadAll.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[PhishingDomain_ReadAll] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Domain] + FROM + [dbo].[PhishingDomain] + ORDER BY + [Domain] ASC +END \ No newline at end of file diff --git a/src/Sql/dbo/Tables/PhishingDomain.sql b/src/Sql/dbo/Tables/PhishingDomain.sql new file mode 100644 index 0000000000..fe5686ee68 --- /dev/null +++ b/src/Sql/dbo/Tables/PhishingDomain.sql @@ -0,0 +1,12 @@ +CREATE TABLE [dbo].[PhishingDomain] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Domain] NVARCHAR(255) NOT NULL, + [CreationDate] DATETIME2(7) NOT NULL, + [RevisionDate] DATETIME2(7) NOT NULL, + CONSTRAINT [PK_PhishingDomain] PRIMARY KEY CLUSTERED ([Id] ASC) +); + +GO + +CREATE NONCLUSTERED INDEX [IX_PhishingDomain_Domain] + ON [dbo].[PhishingDomain]([Domain] ASC); \ No newline at end of file diff --git a/util/Migrator/DbScripts/2024-03-19_00_PhishingDomain.sql b/util/Migrator/DbScripts/2024-03-19_00_PhishingDomain.sql new file mode 100644 index 0000000000..1ec4e9efe7 --- /dev/null +++ b/util/Migrator/DbScripts/2024-03-19_00_PhishingDomain.sql @@ -0,0 +1,86 @@ +-- Create PhishingDomain table +IF OBJECT_ID('[dbo].[PhishingDomain]') IS NULL +BEGIN + CREATE TABLE [dbo].[PhishingDomain] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Domain] NVARCHAR(255) NOT NULL, + [CreationDate] DATETIME2(7) NOT NULL, + [RevisionDate] DATETIME2(7) NOT NULL, + CONSTRAINT [PK_PhishingDomain] PRIMARY KEY CLUSTERED ([Id] ASC) + ); + + CREATE NONCLUSTERED INDEX [IX_PhishingDomain_Domain] + ON [dbo].[PhishingDomain]([Domain] ASC); +END +GO + +-- Create PhishingDomain_ReadAll stored procedure +IF OBJECT_ID('[dbo].[PhishingDomain_ReadAll]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[PhishingDomain_ReadAll] +END +GO + +CREATE PROCEDURE [dbo].[PhishingDomain_ReadAll] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Domain] + FROM + [dbo].[PhishingDomain] + ORDER BY + [Domain] ASC +END +GO + +-- Create PhishingDomain_DeleteAll stored procedure +IF OBJECT_ID('[dbo].[PhishingDomain_DeleteAll]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[PhishingDomain_DeleteAll] +END +GO + +CREATE PROCEDURE [dbo].[PhishingDomain_DeleteAll] +AS +BEGIN + SET NOCOUNT ON + + DELETE FROM + [dbo].[PhishingDomain] +END +GO + +-- Create PhishingDomain_Create stored procedure +IF OBJECT_ID('[dbo].[PhishingDomain_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[PhishingDomain_Create] +END +GO + +CREATE PROCEDURE [dbo].[PhishingDomain_Create] + @Id UNIQUEIDENTIFIER, + @Domain NVARCHAR(255), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[PhishingDomain] + ( + [Id], + [Domain], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @Domain, + @CreationDate, + @RevisionDate + ) +END +GO \ No newline at end of file