Files
server/src/Core/PhishingDomainFeatures/AzurePhishingDomainStorageService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

93 lines
3.2 KiB
C#
Raw Normal View History

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