1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

[Innovation Sprint] Updated Phishing domains to rely on blob storage (#5517)

* Updated phishing detection data layer to rely on azure blob storage instead of sql server

* dotnet format

* Took rider refactors
This commit is contained in:
Conner Turnbull 2025-03-18 11:31:44 -04:00 committed by GitHub
parent 7baa788484
commit 38ac322edc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 226 additions and 572 deletions

View File

@ -39,7 +39,6 @@ public class UpdatePhishingDomainsJob : BaseJob
return;
}
// Get the remote checksum
var remoteChecksum = await _cloudPhishingDomainQuery.GetRemoteChecksumAsync();
if (string.IsNullOrWhiteSpace(remoteChecksum))
{
@ -47,10 +46,8 @@ public class UpdatePhishingDomainsJob : BaseJob
return;
}
// Get the current checksum from the database
var currentChecksum = await _phishingDomainRepository.GetCurrentChecksumAsync();
// Compare checksums to determine if update is needed
if (string.Equals(currentChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation(Constants.BypassFiltersEventId,

View File

@ -4,6 +4,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
using Bit.Core.IdentityServer;
using Bit.Core.PhishingDomainFeatures;
using Bit.Core.PhishingDomainFeatures.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Repositories.Implementations;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.Core.Vault.Authorization.SecurityTasks;
@ -117,6 +119,9 @@ public static class ServiceCollectionExtensions
client.Timeout = TimeSpan.FromSeconds(30);
});
services.AddSingleton<AzurePhishingDomainStorageService>();
services.AddSingleton<IPhishingDomainRepository, AzurePhishingDomainRepository>();
if (globalSettings.SelfHosted)
{
services.AddScoped<ICloudPhishingDomainQuery, CloudPhishingDomainRelayQuery>();

View File

@ -0,0 +1,92 @@
using System.Text;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
namespace Bit.Core.PhishingDomainFeatures;
public class AzurePhishingDomainStorageService
{
private const string _containerName = "phishingdomains";
private const string _domainsFileName = "domains.txt";
private const string _checksumFileName = "checksum.txt";
private readonly BlobServiceClient _blobServiceClient;
private readonly ILogger<AzurePhishingDomainStorageService> _logger;
private BlobContainerClient _containerClient;
public AzurePhishingDomainStorageService(
GlobalSettings globalSettings,
ILogger<AzurePhishingDomainStorageService> logger)
{
_blobServiceClient = new BlobServiceClient(globalSettings.Storage.ConnectionString);
_logger = logger;
}
public async Task<ICollection<string>> GetDomainsAsync()
{
await InitAsync();
var blobClient = _containerClient.GetBlobClient(_domainsFileName);
if (!await blobClient.ExistsAsync())
{
return [];
}
var response = await blobClient.DownloadAsync();
using var streamReader = new StreamReader(response.Value.Content);
var content = await streamReader.ReadToEndAsync();
return [.. content
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#'))];
}
public async Task<string> GetChecksumAsync()
{
await InitAsync();
var blobClient = _containerClient.GetBlobClient(_checksumFileName);
if (!await blobClient.ExistsAsync())
{
return string.Empty;
}
var response = await blobClient.DownloadAsync();
using var streamReader = new StreamReader(response.Value.Content);
return (await streamReader.ReadToEndAsync()).Trim();
}
public async Task UpdateDomainsAsync(IEnumerable<string> domains, string checksum)
{
await InitAsync();
var domainsContent = string.Join(Environment.NewLine, domains);
var domainsStream = new MemoryStream(Encoding.UTF8.GetBytes(domainsContent));
var domainsBlobClient = _containerClient.GetBlobClient(_domainsFileName);
await domainsBlobClient.UploadAsync(domainsStream, new BlobUploadOptions
{
HttpHeaders = new BlobHttpHeaders { ContentType = "text/plain" }
}, CancellationToken.None);
var checksumStream = new MemoryStream(Encoding.UTF8.GetBytes(checksum));
var checksumBlobClient = _containerClient.GetBlobClient(_checksumFileName);
await checksumBlobClient.UploadAsync(checksumStream, new BlobUploadOptions
{
HttpHeaders = new BlobHttpHeaders { ContentType = "text/plain" }
}, CancellationToken.None);
}
private async Task InitAsync()
{
if (_containerClient is null)
{
_containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
await _containerClient.CreateIfNotExistsAsync();
}
}
}

View File

@ -80,12 +80,8 @@ public class CloudPhishingDomainDirectQuery : ICloudPhishingDomainQuery
// Format is typically "hash *filename"
var parts = checksumContent.Split(' ', 2);
if (parts.Length > 0)
{
return parts[0].Trim();
}
return string.Empty;
return parts.Length > 0 ? parts[0].Trim() : string.Empty;
}
private static List<string> ParseDomains(string content)

View File

@ -0,0 +1,126 @@
using System.Text.Json;
using Bit.Core.PhishingDomainFeatures;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Repositories.Implementations;
public class AzurePhishingDomainRepository : IPhishingDomainRepository
{
private readonly AzurePhishingDomainStorageService _storageService;
private readonly IDistributedCache _cache;
private readonly ILogger<AzurePhishingDomainRepository> _logger;
private const string _domainsCacheKey = "PhishingDomains_v1";
private const string _checksumCacheKey = "PhishingDomains_Checksum_v1";
private static readonly DistributedCacheEntryOptions _cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24),
SlidingExpiration = TimeSpan.FromHours(1)
};
public AzurePhishingDomainRepository(
AzurePhishingDomainStorageService storageService,
IDistributedCache cache,
ILogger<AzurePhishingDomainRepository> logger)
{
_storageService = storageService;
_cache = cache;
_logger = logger;
}
public async Task<ICollection<string>> GetActivePhishingDomainsAsync()
{
try
{
var cachedDomains = await _cache.GetStringAsync(_domainsCacheKey);
if (!string.IsNullOrEmpty(cachedDomains))
{
_logger.LogDebug("Retrieved phishing domains from cache");
return JsonSerializer.Deserialize<ICollection<string>>(cachedDomains) ?? [];
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to retrieve phishing domains from cache");
}
var domains = await _storageService.GetDomainsAsync();
try
{
await _cache.SetStringAsync(
_domainsCacheKey,
JsonSerializer.Serialize(domains),
_cacheOptions);
_logger.LogDebug("Stored {Count} phishing domains in cache", domains.Count);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to store phishing domains in cache");
}
return domains;
}
public async Task<string> GetCurrentChecksumAsync()
{
try
{
var cachedChecksum = await _cache.GetStringAsync(_checksumCacheKey);
if (!string.IsNullOrEmpty(cachedChecksum))
{
_logger.LogDebug("Retrieved phishing domain checksum from cache");
return cachedChecksum;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to retrieve phishing domain checksum from cache");
}
var checksum = await _storageService.GetChecksumAsync();
try
{
if (!string.IsNullOrEmpty(checksum))
{
await _cache.SetStringAsync(
_checksumCacheKey,
checksum,
_cacheOptions);
_logger.LogDebug("Stored phishing domain checksum in cache");
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to store phishing domain checksum in cache");
}
return checksum;
}
public async Task UpdatePhishingDomainsAsync(IEnumerable<string> domains, string checksum)
{
var domainsList = domains.ToList();
await _storageService.UpdateDomainsAsync(domainsList, checksum);
try
{
await _cache.SetStringAsync(
_domainsCacheKey,
JsonSerializer.Serialize(domainsList),
_cacheOptions);
await _cache.SetStringAsync(
_checksumCacheKey,
checksum,
_cacheOptions);
_logger.LogDebug("Updated phishing domains cache after update operation");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to update phishing domains in cache");
}
}
}

View File

@ -44,7 +44,6 @@ public static class DapperServiceCollectionExtensions
services.AddSingleton<IOrganizationRepository, OrganizationRepository>();
services.AddSingleton<IOrganizationSponsorshipRepository, OrganizationSponsorshipRepository>();
services.AddSingleton<IOrganizationUserRepository, OrganizationUserRepository>();
services.AddSingleton<IPhishingDomainRepository, PhishingDomainRepository>();
services.AddSingleton<IPolicyRepository, PolicyRepository>();
services.AddSingleton<IProviderOrganizationRepository, ProviderOrganizationRepository>();
services.AddSingleton<IProviderRepository, ProviderRepository>();

View File

@ -1,164 +0,0 @@
using System.Data;
using System.Text.Json;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
namespace Bit.Infrastructure.Dapper.Repositories;
public class PhishingDomainRepository : IPhishingDomainRepository
{
private readonly string _connectionString;
private readonly IDistributedCache _cache;
private readonly ILogger<PhishingDomainRepository> _logger;
private const string _cacheKey = "PhishingDomains_v1";
private static readonly DistributedCacheEntryOptions _cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24),
SlidingExpiration = TimeSpan.FromHours(1)
};
public PhishingDomainRepository(
GlobalSettings globalSettings,
IDistributedCache cache,
ILogger<PhishingDomainRepository> logger)
: this(globalSettings.SqlServer.ConnectionString, cache, logger)
{ }
public PhishingDomainRepository(
string connectionString,
IDistributedCache cache,
ILogger<PhishingDomainRepository> logger)
{
_connectionString = connectionString;
_cache = cache;
_logger = logger;
}
public async Task<ICollection<string>> GetActivePhishingDomainsAsync()
{
try
{
var cachedDomains = await _cache.GetStringAsync(_cacheKey);
if (!string.IsNullOrEmpty(cachedDomains))
{
_logger.LogDebug("Retrieved phishing domains from cache");
return JsonSerializer.Deserialize<ICollection<string>>(cachedDomains) ?? [];
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to retrieve phishing domains from cache");
}
await using var connection = new SqlConnection(_connectionString);
var results = await connection.QueryAsync<string>(
"[dbo].[PhishingDomain_ReadAll]",
commandType: CommandType.StoredProcedure);
var domains = results.AsList();
try
{
await _cache.SetStringAsync(
_cacheKey,
JsonSerializer.Serialize(domains),
_cacheOptions);
_logger.LogDebug("Stored {Count} phishing domains in cache", domains.Count);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to store phishing domains in cache");
}
return domains;
}
public async Task<string> GetCurrentChecksumAsync()
{
try
{
await using var connection = new SqlConnection(_connectionString);
var checksum = await connection.QueryFirstOrDefaultAsync<string>(
"[dbo].[PhishingDomain_ReadChecksum]",
commandType: CommandType.StoredProcedure);
return checksum ?? string.Empty;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving phishing domain checksum from database");
return string.Empty;
}
}
public async Task UpdatePhishingDomainsAsync(IEnumerable<string> domains, string checksum)
{
var domainsList = domains.ToList();
_logger.LogInformation("Beginning bulk update of {Count} phishing domains with checksum {Checksum}",
domainsList.Count, checksum);
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
await using var transaction = connection.BeginTransaction();
try
{
await connection.ExecuteAsync(
"[dbo].[PhishingDomain_DeleteAll]",
transaction: transaction,
commandType: CommandType.StoredProcedure);
var dataTable = new DataTable();
dataTable.Columns.Add("Id", typeof(Guid));
dataTable.Columns.Add("Domain", typeof(string));
dataTable.Columns.Add("Checksum", typeof(string));
dataTable.PrimaryKey = [dataTable.Columns["Id"]];
foreach (var domain in domainsList)
{
dataTable.Rows.Add(Guid.NewGuid(), domain, checksum);
}
using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction);
bulkCopy.DestinationTableName = "[dbo].[PhishingDomain]";
bulkCopy.BatchSize = 10000;
bulkCopy.ColumnMappings.Add("Id", "Id");
bulkCopy.ColumnMappings.Add("Domain", "Domain");
bulkCopy.ColumnMappings.Add("Checksum", "Checksum");
await bulkCopy.WriteToServerAsync(dataTable);
await transaction.CommitAsync();
_logger.LogInformation("Successfully bulk updated {Count} phishing domains", domainsList.Count);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "Failed to bulk update phishing domains");
throw;
}
try
{
await _cache.SetStringAsync(
_cacheKey,
JsonSerializer.Serialize(domainsList),
_cacheOptions);
_logger.LogDebug("Updated phishing domains cache after update operation");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to update phishing domains in cache");
}
}
}

View File

@ -101,7 +101,6 @@ public static class EntityFrameworkServiceCollectionExtensions
services.AddSingleton<IPasswordHealthReportApplicationRepository, PasswordHealthReportApplicationRepository>();
services.AddSingleton<ISecurityTaskRepository, SecurityTaskRepository>();
services.AddSingleton<IUserAsymmetricKeysRepository, UserAsymmetricKeysRepository>();
services.AddSingleton<IPhishingDomainRepository, PhishingDomainRepository>();
if (selfHosted)
{

View File

@ -1,16 +0,0 @@
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; }
[MaxLength(64)]
public string Checksum { get; set; }
}

View File

@ -80,7 +80,6 @@ public class DatabaseContext : DbContext
public DbSet<PasswordHealthReportApplication> PasswordHealthReportApplications { get; set; }
public DbSet<SecurityTask> SecurityTasks { get; set; }
public DbSet<OrganizationInstallation> OrganizationInstallations { get; set; }
public DbSet<PhishingDomain> PhishingDomains { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
@ -111,7 +110,6 @@ public class DatabaseContext : DbContext
var eOrganizationConnection = builder.Entity<OrganizationConnection>();
var eOrganizationDomain = builder.Entity<OrganizationDomain>();
var aWebAuthnCredential = builder.Entity<WebAuthnCredential>();
var ePhishingDomain = builder.Entity<PhishingDomain>();
// Shadow property configurations go here
@ -128,7 +126,6 @@ public class DatabaseContext : DbContext
eOrganizationConnection.Property(c => c.Id).ValueGeneratedNever();
eOrganizationDomain.Property(ar => ar.Id).ValueGeneratedNever();
aWebAuthnCredential.Property(ar => ar.Id).ValueGeneratedNever();
ePhishingDomain.Property(ar => ar.Id).ValueGeneratedNever();
eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId });
eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId });
@ -169,7 +166,6 @@ public class DatabaseContext : DbContext
eOrganizationConnection.ToTable(nameof(OrganizationConnection));
eOrganizationDomain.ToTable(nameof(OrganizationDomain));
aWebAuthnCredential.ToTable(nameof(WebAuthnCredential));
ePhishingDomain.ToTable(nameof(PhishingDomain));
ConfigureDateTimeUtcQueries(builder);
}

View File

@ -1,167 +0,0 @@
using System.Data;
using System.Text.Json;
using Bit.Core.Repositories;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Infrastructure.EntityFramework.Repositories;
public class PhishingDomainRepository : IPhishingDomainRepository
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IDistributedCache _cache;
private readonly ILogger<PhishingDomainRepository> _logger;
private const string _cacheKey = "PhishingDomains_v1";
private static readonly DistributedCacheEntryOptions _cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24),
SlidingExpiration = TimeSpan.FromHours(1)
};
public PhishingDomainRepository(
IServiceScopeFactory serviceScopeFactory,
IDistributedCache cache,
ILogger<PhishingDomainRepository> logger)
{
_serviceScopeFactory = serviceScopeFactory;
_cache = cache;
_logger = logger;
}
public async Task<ICollection<string>> GetActivePhishingDomainsAsync()
{
try
{
var cachedDomains = await _cache.GetStringAsync(_cacheKey);
if (!string.IsNullOrEmpty(cachedDomains))
{
_logger.LogDebug("Retrieved phishing domains from cache");
return JsonSerializer.Deserialize<ICollection<string>>(cachedDomains) ?? [];
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to retrieve phishing domains from cache");
}
using var scope = _serviceScopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var domains = await dbContext.PhishingDomains
.Select(d => d.Domain)
.ToListAsync();
try
{
await _cache.SetStringAsync(
_cacheKey,
JsonSerializer.Serialize(domains),
_cacheOptions);
_logger.LogDebug("Stored {Count} phishing domains in cache", domains.Count);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to store phishing domains in cache");
}
return domains;
}
public async Task<string> GetCurrentChecksumAsync()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
// Get the first checksum in the database (there should only be one set of domains with the same checksum)
var checksum = await dbContext.PhishingDomains
.Select(d => d.Checksum)
.FirstOrDefaultAsync();
return checksum ?? string.Empty;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving phishing domain checksum from database");
return string.Empty;
}
}
public async Task UpdatePhishingDomainsAsync(IEnumerable<string> domains, string checksum)
{
var domainsList = domains.ToList();
_logger.LogInformation("Beginning bulk update of {Count} phishing domains with checksum {Checksum}",
domainsList.Count, checksum);
using var scope = _serviceScopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var connection = dbContext.Database.GetDbConnection();
var connectionString = connection.ConnectionString;
await using var sqlConnection = new SqlConnection(connectionString);
await sqlConnection.OpenAsync();
await using var transaction = sqlConnection.BeginTransaction();
try
{
await using var command = sqlConnection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "[dbo].[PhishingDomain_DeleteAll]";
command.CommandType = CommandType.StoredProcedure;
await command.ExecuteNonQueryAsync();
var dataTable = new DataTable();
dataTable.Columns.Add("Id", typeof(Guid));
dataTable.Columns.Add("Domain", typeof(string));
dataTable.Columns.Add("Checksum", typeof(string));
dataTable.PrimaryKey = [dataTable.Columns["Id"]];
foreach (var domain in domainsList)
{
dataTable.Rows.Add(Guid.NewGuid(), domain, checksum);
}
using var bulkCopy = new SqlBulkCopy(sqlConnection, SqlBulkCopyOptions.Default, transaction);
bulkCopy.DestinationTableName = "[dbo].[PhishingDomain]";
bulkCopy.BatchSize = 10000;
bulkCopy.ColumnMappings.Add("Id", "Id");
bulkCopy.ColumnMappings.Add("Domain", "Domain");
bulkCopy.ColumnMappings.Add("Checksum", "Checksum");
await bulkCopy.WriteToServerAsync(dataTable);
await transaction.CommitAsync();
_logger.LogInformation("Successfully bulk updated {Count} phishing domains", domainsList.Count);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "Failed to bulk update phishing domains");
throw;
}
try
{
await _cache.SetStringAsync(
_cacheKey,
JsonSerializer.Serialize(domainsList),
_cacheOptions);
_logger.LogDebug("Updated phishing domains cache after update operation");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to update phishing domains in cache");
}
}
}

View File

@ -1,21 +0,0 @@
CREATE PROCEDURE [dbo].[PhishingDomain_Create]
@Id UNIQUEIDENTIFIER,
@Domain NVARCHAR(255),
@Checksum NVARCHAR(64)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[PhishingDomain]
(
[Id],
[Domain],
[Checksum]
)
VALUES
(
@Id,
@Domain,
@Checksum
)
END

View File

@ -1,8 +0,0 @@
CREATE PROCEDURE [dbo].[PhishingDomain_DeleteAll]
AS
BEGIN
SET NOCOUNT ON
DELETE FROM
[dbo].[PhishingDomain]
END

View File

@ -1,12 +0,0 @@
CREATE PROCEDURE [dbo].[PhishingDomain_ReadAll]
AS
BEGIN
SET NOCOUNT ON
SELECT
[Domain]
FROM
[dbo].[PhishingDomain]
ORDER BY
[Domain] ASC
END

View File

@ -1,10 +0,0 @@
CREATE PROCEDURE [dbo].[PhishingDomain_ReadChecksum]
AS
BEGIN
SET NOCOUNT ON
SELECT TOP 1
[Checksum]
FROM
[dbo].[PhishingDomain]
END

View File

@ -1,11 +0,0 @@
CREATE TABLE [dbo].[PhishingDomain] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Domain] NVARCHAR(255) NOT NULL,
[Checksum] NVARCHAR(64) NULL,
CONSTRAINT [PK_PhishingDomain] PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE NONCLUSTERED INDEX [IX_PhishingDomain_Domain]
ON [dbo].[PhishingDomain]([Domain] ASC);

View File

@ -1,86 +0,0 @@
-- 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

View File

@ -1,61 +0,0 @@
-- Update PhishingDomain table to use Checksum instead of dates
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'PhishingDomain' AND COLUMN_NAME = 'CreationDate')
BEGIN
-- Add Checksum column
ALTER TABLE [dbo].[PhishingDomain]
ADD [Checksum] NVARCHAR(64) NULL;
-- Drop old columns
ALTER TABLE [dbo].[PhishingDomain]
DROP COLUMN [CreationDate], [RevisionDate];
END
GO
-- Update 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),
@Checksum NVARCHAR(64)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[PhishingDomain]
(
[Id],
[Domain],
[Checksum]
)
VALUES
(
@Id,
@Domain,
@Checksum
)
END
GO
-- Create PhishingDomain_ReadChecksum stored procedure
IF OBJECT_ID('[dbo].[PhishingDomain_ReadChecksum]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[PhishingDomain_ReadChecksum]
END
GO
CREATE PROCEDURE [dbo].[PhishingDomain_ReadChecksum]
AS
BEGIN
SET NOCOUNT ON
SELECT TOP 1
[Checksum]
FROM
[dbo].[PhishingDomain]
END
GO