mirror of
https://github.com/bitwarden/server.git
synced 2025-12-11 13:53:48 -06:00
PM-23358 removing phishing blocker code
This commit is contained in:
parent
b3573c15fd
commit
b6830f65f3
@ -1,34 +0,0 @@
|
|||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Bit.Api.Controllers;
|
|
||||||
|
|
||||||
[Route("phishing-domains")]
|
|
||||||
public class PhishingDomainsController(IPhishingDomainRepository phishingDomainRepository, IFeatureService featureService) : Controller
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<ActionResult<ICollection<string>>> GetPhishingDomainsAsync()
|
|
||||||
{
|
|
||||||
if (!featureService.IsEnabled(FeatureFlagKeys.PhishingDetection))
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var domains = await phishingDomainRepository.GetActivePhishingDomainsAsync();
|
|
||||||
return Ok(domains);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("checksum")]
|
|
||||||
public async Task<ActionResult<string>> GetChecksumAsync()
|
|
||||||
{
|
|
||||||
if (!featureService.IsEnabled(FeatureFlagKeys.PhishingDetection))
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var checksum = await phishingDomainRepository.GetCurrentChecksumAsync();
|
|
||||||
return Ok(checksum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -59,13 +59,6 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
.StartNow()
|
.StartNow()
|
||||||
.WithCronSchedule("0 0 * * * ?")
|
.WithCronSchedule("0 0 * * * ?")
|
||||||
.Build();
|
.Build();
|
||||||
var updatePhishingDomainsTrigger = TriggerBuilder.Create()
|
|
||||||
.WithIdentity("UpdatePhishingDomainsTrigger")
|
|
||||||
.StartNow()
|
|
||||||
.WithSimpleSchedule(x => x
|
|
||||||
.WithIntervalInHours(24)
|
|
||||||
.RepeatForever())
|
|
||||||
.Build();
|
|
||||||
var updateOrgSubscriptionsTrigger = TriggerBuilder.Create()
|
var updateOrgSubscriptionsTrigger = TriggerBuilder.Create()
|
||||||
.WithIdentity("UpdateOrgSubscriptionsTrigger")
|
.WithIdentity("UpdateOrgSubscriptionsTrigger")
|
||||||
.StartNow()
|
.StartNow()
|
||||||
@ -81,7 +74,6 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
|
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
|
||||||
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger),
|
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger),
|
||||||
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger),
|
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger),
|
||||||
new Tuple<Type, ITrigger>(typeof(UpdatePhishingDomainsJob), updatePhishingDomainsTrigger),
|
|
||||||
new (typeof(OrganizationSubscriptionUpdateJob), updateOrgSubscriptionsTrigger),
|
new (typeof(OrganizationSubscriptionUpdateJob), updateOrgSubscriptionsTrigger),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,7 +103,6 @@ public class JobsHostedService : BaseJobsHostedService
|
|||||||
services.AddTransient<ValidateUsersJob>();
|
services.AddTransient<ValidateUsersJob>();
|
||||||
services.AddTransient<ValidateOrganizationsJob>();
|
services.AddTransient<ValidateOrganizationsJob>();
|
||||||
services.AddTransient<ValidateOrganizationDomainJob>();
|
services.AddTransient<ValidateOrganizationDomainJob>();
|
||||||
services.AddTransient<UpdatePhishingDomainsJob>();
|
|
||||||
services.AddTransient<OrganizationSubscriptionUpdateJob>();
|
services.AddTransient<OrganizationSubscriptionUpdateJob>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Jobs;
|
|
||||||
using Bit.Core.PhishingDomainFeatures.Interfaces;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace Bit.Api.Jobs;
|
|
||||||
|
|
||||||
public class UpdatePhishingDomainsJob : BaseJob
|
|
||||||
{
|
|
||||||
private readonly GlobalSettings _globalSettings;
|
|
||||||
private readonly IPhishingDomainRepository _phishingDomainRepository;
|
|
||||||
private readonly ICloudPhishingDomainQuery _cloudPhishingDomainQuery;
|
|
||||||
private readonly IFeatureService _featureService;
|
|
||||||
public UpdatePhishingDomainsJob(
|
|
||||||
GlobalSettings globalSettings,
|
|
||||||
IPhishingDomainRepository phishingDomainRepository,
|
|
||||||
ICloudPhishingDomainQuery cloudPhishingDomainQuery,
|
|
||||||
IFeatureService featureService,
|
|
||||||
ILogger<UpdatePhishingDomainsJob> logger)
|
|
||||||
: base(logger)
|
|
||||||
{
|
|
||||||
_globalSettings = globalSettings;
|
|
||||||
_phishingDomainRepository = phishingDomainRepository;
|
|
||||||
_cloudPhishingDomainQuery = cloudPhishingDomainQuery;
|
|
||||||
_featureService = featureService;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
if (!_featureService.IsEnabled(FeatureFlagKeys.PhishingDetection))
|
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Skipping phishing domain update. Feature flag is disabled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_globalSettings.PhishingDomain?.UpdateUrl))
|
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Skipping phishing domain update. No URL configured.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_globalSettings.SelfHosted && !_globalSettings.EnableCloudCommunication)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Skipping phishing domain update. Cloud communication is disabled in global settings.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remoteChecksum = await _cloudPhishingDomainQuery.GetRemoteChecksumAsync();
|
|
||||||
if (string.IsNullOrWhiteSpace(remoteChecksum))
|
|
||||||
{
|
|
||||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Could not retrieve remote checksum. Skipping update.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentChecksum = await _phishingDomainRepository.GetCurrentChecksumAsync();
|
|
||||||
|
|
||||||
if (string.Equals(currentChecksum, remoteChecksum, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId,
|
|
||||||
"Phishing domains list is up to date (checksum: {Checksum}). Skipping update.",
|
|
||||||
currentChecksum);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId,
|
|
||||||
"Checksums differ (current: {CurrentChecksum}, remote: {RemoteChecksum}). Fetching updated domains from {Source}.",
|
|
||||||
currentChecksum, remoteChecksum, _globalSettings.SelfHosted ? "Bitwarden cloud API" : "external source");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var domains = await _cloudPhishingDomainQuery.GetPhishingDomainsAsync();
|
|
||||||
if (!domains.Contains("phishing.testcategory.com", StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
domains.Add("phishing.testcategory.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domains.Count > 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Updating {Count} phishing domains with checksum {Checksum}.",
|
|
||||||
domains.Count, remoteChecksum);
|
|
||||||
await _phishingDomainRepository.UpdatePhishingDomainsAsync(domains, remoteChecksum);
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated phishing domains.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning(Constants.BypassFiltersEventId, "No valid domains found in the response. Skipping update.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(Constants.BypassFiltersEventId, ex, "Error updating phishing domains.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -187,7 +187,6 @@ public class Startup
|
|||||||
services.AddBillingOperations();
|
services.AddBillingOperations();
|
||||||
services.AddReportingServices();
|
services.AddReportingServices();
|
||||||
services.AddImportServices();
|
services.AddImportServices();
|
||||||
services.AddPhishingDomainServices(globalSettings);
|
|
||||||
|
|
||||||
services.AddSendServices();
|
services.AddSendServices();
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
using Bit.Api.AdminConsole.Authorization;
|
using Bit.Api.AdminConsole.Authorization;
|
||||||
using Bit.Api.Tools.Authorization;
|
using Bit.Api.Tools.Authorization;
|
||||||
using Bit.Core.Auth.IdentityServer;
|
using Bit.Core.Auth.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.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Authorization.SecurityTasks;
|
using Bit.Core.Vault.Authorization.SecurityTasks;
|
||||||
@ -114,25 +110,4 @@ public static class ServiceCollectionExtensions
|
|||||||
// Admin Console authorization handlers
|
// Admin Console authorization handlers
|
||||||
services.AddAdminConsoleAuthorizationHandlers();
|
services.AddAdminConsoleAuthorizationHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddPhishingDomainServices(this IServiceCollection services, GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
services.AddHttpClient("PhishingDomains", client =>
|
|
||||||
{
|
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", globalSettings.SelfHosted ? "Bitwarden Self-Hosted" : "Bitwarden");
|
|
||||||
client.Timeout = TimeSpan.FromSeconds(1000); // the source list is very slow
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddSingleton<AzurePhishingDomainStorageService>();
|
|
||||||
services.AddSingleton<IPhishingDomainRepository, AzurePhishingDomainRepository>();
|
|
||||||
|
|
||||||
if (globalSettings.SelfHosted)
|
|
||||||
{
|
|
||||||
services.AddScoped<ICloudPhishingDomainQuery, CloudPhishingDomainRelayQuery>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddScoped<ICloudPhishingDomainQuery, CloudPhishingDomainDirectQuery>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,10 +38,6 @@
|
|||||||
"storage": {
|
"storage": {
|
||||||
"connectionString": "UseDevelopmentStorage=true"
|
"connectionString": "UseDevelopmentStorage=true"
|
||||||
},
|
},
|
||||||
"phishingDomain": {
|
|
||||||
"updateUrl": "https://phish.co.za/latest/phishing-domains-ACTIVE.txt",
|
|
||||||
"checksumUrl": "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-domains-ACTIVE.txt.sha256"
|
|
||||||
},
|
|
||||||
"pricingUri": "https://billingpricing.qa.bitwarden.pw"
|
"pricingUri": "https://billingpricing.qa.bitwarden.pw"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,9 +69,6 @@
|
|||||||
"accessKeySecret": "SECRET",
|
"accessKeySecret": "SECRET",
|
||||||
"region": "SECRET"
|
"region": "SECRET"
|
||||||
},
|
},
|
||||||
"phishingDomain": {
|
|
||||||
"updateUrl": "SECRET"
|
|
||||||
},
|
|
||||||
"distributedIpRateLimiting": {
|
"distributedIpRateLimiting": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"maxRedisTimeoutsThreshold": 10,
|
"maxRedisTimeoutsThreshold": 10,
|
||||||
|
|||||||
@ -1,95 +0,0 @@
|
|||||||
// FIXME: Update this file to be null safe and then delete the line below
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
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(new[] { '\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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
using Bit.Core.PhishingDomainFeatures.Interfaces;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Core.PhishingDomainFeatures;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of ICloudPhishingDomainQuery for cloud environments
|
|
||||||
/// that directly calls the external phishing domain source
|
|
||||||
/// </summary>
|
|
||||||
public class CloudPhishingDomainDirectQuery : ICloudPhishingDomainQuery
|
|
||||||
{
|
|
||||||
private readonly IGlobalSettings _globalSettings;
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
|
||||||
private readonly ILogger<CloudPhishingDomainDirectQuery> _logger;
|
|
||||||
|
|
||||||
public CloudPhishingDomainDirectQuery(
|
|
||||||
IGlobalSettings globalSettings,
|
|
||||||
IHttpClientFactory httpClientFactory,
|
|
||||||
ILogger<CloudPhishingDomainDirectQuery> logger)
|
|
||||||
{
|
|
||||||
_globalSettings = globalSettings;
|
|
||||||
_httpClientFactory = httpClientFactory;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<string>> GetPhishingDomainsAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_globalSettings.PhishingDomain?.UpdateUrl))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Phishing domain update URL is not configured.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient("PhishingDomains");
|
|
||||||
var response = await httpClient.GetAsync(_globalSettings.PhishingDomain.UpdateUrl);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
|
||||||
return ParseDomains(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SHA256 checksum of the remote phishing domains list
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The SHA256 checksum as a lowercase hex string</returns>
|
|
||||||
public async Task<string> GetRemoteChecksumAsync()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_globalSettings.PhishingDomain?.ChecksumUrl))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Phishing domain checksum URL is not configured.");
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var httpClient = _httpClientFactory.CreateClient("PhishingDomains");
|
|
||||||
var response = await httpClient.GetAsync(_globalSettings.PhishingDomain.ChecksumUrl);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
|
||||||
return ParseChecksumResponse(content);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error retrieving phishing domain checksum from {Url}",
|
|
||||||
_globalSettings.PhishingDomain.ChecksumUrl);
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses a checksum response in the format "hash *filename"
|
|
||||||
/// </summary>
|
|
||||||
private static string ParseChecksumResponse(string checksumContent)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(checksumContent))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format is typically "hash *filename"
|
|
||||||
var parts = checksumContent.Split(' ', 2);
|
|
||||||
|
|
||||||
return parts.Length > 0 ? parts[0].Trim() : string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> ParseDomains(string content)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(content))
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(line => line.Trim())
|
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
// FIXME: Update this file to be null safe and then delete the line below
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Bit.Core.PhishingDomainFeatures.Interfaces;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Core.PhishingDomainFeatures;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of ICloudPhishingDomainQuery for self-hosted environments
|
|
||||||
/// that relays the request to the Bitwarden cloud API
|
|
||||||
/// </summary>
|
|
||||||
public class CloudPhishingDomainRelayQuery : BaseIdentityClientService, ICloudPhishingDomainQuery
|
|
||||||
{
|
|
||||||
private readonly IGlobalSettings _globalSettings;
|
|
||||||
|
|
||||||
public CloudPhishingDomainRelayQuery(
|
|
||||||
IHttpClientFactory httpFactory,
|
|
||||||
IGlobalSettings globalSettings,
|
|
||||||
ILogger<CloudPhishingDomainRelayQuery> logger)
|
|
||||||
: base(
|
|
||||||
httpFactory,
|
|
||||||
globalSettings.Installation.ApiUri,
|
|
||||||
globalSettings.Installation.IdentityUri,
|
|
||||||
"api.licensing",
|
|
||||||
$"installation.{globalSettings.Installation.Id}",
|
|
||||||
globalSettings.Installation.Key,
|
|
||||||
logger)
|
|
||||||
{
|
|
||||||
_globalSettings = globalSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<string>> GetPhishingDomainsAsync()
|
|
||||||
{
|
|
||||||
if (!_globalSettings.SelfHosted || !_globalSettings.EnableCloudCommunication)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("This query is only for self-hosted installations with cloud communication enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await SendAsync<object, string[]>(HttpMethod.Get, "phishing-domains", null, true);
|
|
||||||
return result?.ToList() ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SHA256 checksum of the remote phishing domains list
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The SHA256 checksum as a lowercase hex string</returns>
|
|
||||||
public async Task<string> GetRemoteChecksumAsync()
|
|
||||||
{
|
|
||||||
if (!_globalSettings.SelfHosted || !_globalSettings.EnableCloudCommunication)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("This query is only for self-hosted installations with cloud communication enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// For self-hosted environments, we get the checksum from the Bitwarden cloud API
|
|
||||||
var result = await SendAsync<object, string>(HttpMethod.Get, "phishing-domains/checksum", null, true);
|
|
||||||
return result ?? string.Empty;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error retrieving phishing domain checksum from Bitwarden cloud API");
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Core.PhishingDomainFeatures.Interfaces;
|
|
||||||
|
|
||||||
public interface ICloudPhishingDomainQuery
|
|
||||||
{
|
|
||||||
Task<List<string>> GetPhishingDomainsAsync();
|
|
||||||
Task<string> GetRemoteChecksumAsync();
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
namespace Bit.Core.Repositories;
|
|
||||||
|
|
||||||
public interface IPhishingDomainRepository
|
|
||||||
{
|
|
||||||
Task<ICollection<string>> GetActivePhishingDomainsAsync();
|
|
||||||
Task UpdatePhishingDomainsAsync(IEnumerable<string> domains, string checksum);
|
|
||||||
Task<string> GetCurrentChecksumAsync();
|
|
||||||
}
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -81,7 +81,6 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings();
|
public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings();
|
||||||
public virtual string DevelopmentDirectory { get; set; }
|
public virtual string DevelopmentDirectory { get; set; }
|
||||||
public virtual IWebPushSettings WebPush { get; set; } = new WebPushSettings();
|
public virtual IWebPushSettings WebPush { get; set; } = new WebPushSettings();
|
||||||
public virtual IPhishingDomainSettings PhishingDomain { get; set; } = new PhishingDomainSettings();
|
|
||||||
|
|
||||||
public virtual int SendAccessTokenLifetimeInMinutes { get; set; } = 5;
|
public virtual int SendAccessTokenLifetimeInMinutes { get; set; } = 5;
|
||||||
public virtual bool EnableEmailVerification { get; set; }
|
public virtual bool EnableEmailVerification { get; set; }
|
||||||
@ -672,12 +671,6 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
public int MaxNetworkRetries { get; set; } = 2;
|
public int MaxNetworkRetries { get; set; } = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PhishingDomainSettings : IPhishingDomainSettings
|
|
||||||
{
|
|
||||||
public string UpdateUrl { get; set; }
|
|
||||||
public string ChecksumUrl { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DistributedIpRateLimitingSettings
|
public class DistributedIpRateLimitingSettings
|
||||||
{
|
{
|
||||||
public string RedisConnectionString { get; set; }
|
public string RedisConnectionString { get; set; }
|
||||||
|
|||||||
@ -28,5 +28,4 @@ public interface IGlobalSettings
|
|||||||
string DevelopmentDirectory { get; set; }
|
string DevelopmentDirectory { get; set; }
|
||||||
IWebPushSettings WebPush { get; set; }
|
IWebPushSettings WebPush { get; set; }
|
||||||
GlobalSettings.EventLoggingSettings EventLogging { get; set; }
|
GlobalSettings.EventLoggingSettings EventLogging { get; set; }
|
||||||
IPhishingDomainSettings PhishingDomain { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Core.Settings;
|
|
||||||
|
|
||||||
public interface IPhishingDomainSettings
|
|
||||||
{
|
|
||||||
string UpdateUrl { get; set; }
|
|
||||||
string ChecksumUrl { get; set; }
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user