Files
server/src/Core/PhishingDomainFeatures/AzurePhishingDomainStorageService.cs
Conner Turnbull cf7a59c077 [Innovation Sprint] Phishing Detection (#5516)
* Initial stubbing out of the phishing service

* Add the phishing domain controller

* Add changes for the phishing domain get

* Add distributed cache to the phishing domain

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* Rename the variable name

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>

* Removed IPhishingDomainService

* Feature/phishing detection cronjob (#5512)

* Added caching to EF implementation. Added error handling and logging

* Refactored update method to use sqlbulkcopy instead of performing a round trip for each new insert

* Initial implementation for quartz job to get list of phishing domains

* Updated phishing domain settings to be its own interface

* Add phishing domain detection with checksum-based updates

* Updated auth for phishing domain endpoints to either require api, or licensing claims to support both web and browser clients, and selfhost api clients

* [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

* Ensuring phishing.testcategory.com exists to test against

* Added redis to dev's docker-compose

* Removed redis from cloud profile

* Remove the Authorize attribute

* error whitespace fix whitespace formatting

* error WHITESPACE: Fix whitespace formatting

* Wrapped phishing detection feature behind feature flag (#5532)

* Increased timeout for fetching source list a bunch

* Removed PhishingDomains policy

---------

Signed-off-by: Cy Okeke <cokeke@bitwarden.com>
Co-authored-by: Cy Okeke <cokeke@bitwarden.com>
2025-04-30 11:03:59 -04:00

93 lines
3.2 KiB
C#

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();
}
}
}