mirror of
https://github.com/bitwarden/server.git
synced 2025-05-27 22:34:54 -05:00
keep application cache in sync with service bus
This commit is contained in:
parent
2d63732085
commit
6f0d64119a
@ -36,6 +36,10 @@
|
||||
"events": {
|
||||
"connectionString": "SECRET"
|
||||
},
|
||||
"serviceBus": {
|
||||
"connectionString": "SECRET",
|
||||
"applicationCacheTopicName": "SECRET"
|
||||
},
|
||||
"documentDb": {
|
||||
"uri": "SECRET",
|
||||
"key": "SECRET"
|
||||
|
@ -129,6 +129,11 @@ namespace Bit.Api
|
||||
Jobs.JobsHostedService.AddJobsServices(services);
|
||||
services.AddHostedService<Jobs.JobsHostedService>();
|
||||
}
|
||||
if(CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
|
||||
{
|
||||
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
|
@ -51,6 +51,10 @@
|
||||
"connectionString": "SECRET",
|
||||
"hubName": "SECRET"
|
||||
},
|
||||
"serviceBus": {
|
||||
"connectionString": "SECRET",
|
||||
"applicationCacheTopicName": "SECRET"
|
||||
},
|
||||
"yubico": {
|
||||
"clientid": "SECRET",
|
||||
"key": "SECRET"
|
||||
|
@ -36,6 +36,10 @@
|
||||
"events": {
|
||||
"connectionString": "SECRET"
|
||||
},
|
||||
"serviceBus": {
|
||||
"connectionString": "SECRET",
|
||||
"applicationCacheTopicName": "SECRET"
|
||||
},
|
||||
"documentDb": {
|
||||
"uri": "SECRET",
|
||||
"key": "SECRET"
|
||||
|
@ -33,6 +33,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="2.1.6" />
|
||||
|
8
src/Core/Enums/ApplicationCacheMessageType.cs
Normal file
8
src/Core/Enums/ApplicationCacheMessageType.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum ApplicationCacheMessageType : byte
|
||||
{
|
||||
UpsertOrganizationAbility = 0,
|
||||
DeleteOrganizationAbility = 1
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ namespace Bit.Core
|
||||
public virtual SqlSettings SqlServer { get; set; } = new SqlSettings();
|
||||
public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings();
|
||||
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
||||
public virtual StorageSettings Storage { get; set; } = new StorageSettings();
|
||||
public virtual StorageSettings Events { get; set; } = new StorageSettings();
|
||||
public virtual ConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings();
|
||||
public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings();
|
||||
public virtual NotificationsSettings Notifications { get; set; } = new NotificationsSettings();
|
||||
public virtual AttachmentSettings Attachment { get; set; } = new AttachmentSettings();
|
||||
public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings();
|
||||
@ -36,6 +36,7 @@ namespace Bit.Core
|
||||
public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings();
|
||||
public virtual BitPaySettings BitPay { get; set; } = new BitPaySettings();
|
||||
public virtual AmazonSettings Amazon { get; set; } = new AmazonSettings();
|
||||
public virtual ServiceBusSettings ServiceBus { get; set; } = new ServiceBusSettings();
|
||||
|
||||
public class BaseServiceUriSettings
|
||||
{
|
||||
@ -77,7 +78,7 @@ namespace Bit.Core
|
||||
}
|
||||
}
|
||||
|
||||
public class StorageSettings
|
||||
public class ConnectionStringSettings
|
||||
{
|
||||
private string _connectionString;
|
||||
|
||||
@ -154,7 +155,7 @@ namespace Bit.Core
|
||||
public string Dsn { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationsSettings : StorageSettings
|
||||
public class NotificationsSettings : ConnectionStringSettings
|
||||
{
|
||||
public string AzureSignalRConnectionString { get; set; }
|
||||
}
|
||||
@ -213,5 +214,11 @@ namespace Bit.Core
|
||||
public string AccessKeySecret { get; set; }
|
||||
public string Region { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceBusSettings : ConnectionStringSettings
|
||||
{
|
||||
public string ApplicationCacheTopicName { get; set; }
|
||||
public string ApplicationCacheSubscriptionName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
109
src/Core/HostedServices/ApplicationCacheHostedService.cs
Normal file
109
src/Core/HostedServices/ApplicationCacheHostedService.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
using Microsoft.Azure.ServiceBus.Management;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.HostedServices
|
||||
{
|
||||
public class ApplicationCacheHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly InMemoryServiceBusApplicationCacheService _applicationCacheService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
protected readonly ILogger<ApplicationCacheHostedService> _logger;
|
||||
private readonly SubscriptionClient _subscriptionClient;
|
||||
private readonly ManagementClient _managementClient;
|
||||
private readonly string _subName;
|
||||
private readonly string _topicName;
|
||||
|
||||
public ApplicationCacheHostedService(
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ILogger<ApplicationCacheHostedService> logger,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_topicName = globalSettings.ServiceBus.ApplicationCacheTopicName;
|
||||
_subName = CoreHelpers.GetApplicationCacheServiceBusSubcriptionName(globalSettings);
|
||||
_applicationCacheService = applicationCacheService as InMemoryServiceBusApplicationCacheService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_logger = logger;
|
||||
_managementClient = new ManagementClient(globalSettings.ServiceBus.ConnectionString);
|
||||
_subscriptionClient = new SubscriptionClient(globalSettings.ServiceBus.ConnectionString,
|
||||
_topicName, _subName);
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _managementClient.CreateSubscriptionAsync(new SubscriptionDescription(_topicName, _subName)
|
||||
{
|
||||
DefaultMessageTimeToLive = TimeSpan.FromDays(14),
|
||||
LockDuration = TimeSpan.FromSeconds(30),
|
||||
EnableDeadLetteringOnFilterEvaluationExceptions = true,
|
||||
EnableDeadLetteringOnMessageExpiration = true,
|
||||
}, new RuleDescription("default", new SqlFilter($"sys.Label != '{_subName}'")));
|
||||
}
|
||||
catch(MessagingEntityAlreadyExistsException) { }
|
||||
_subscriptionClient.RegisterMessageHandler(ProcessMessageAsync,
|
||||
new MessageHandlerOptions(ExceptionReceivedHandlerAsync)
|
||||
{
|
||||
MaxConcurrentCalls = 2,
|
||||
AutoComplete = false,
|
||||
});
|
||||
}
|
||||
|
||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _subscriptionClient.CloseAsync();
|
||||
try
|
||||
{
|
||||
await _managementClient.DeleteSubscriptionAsync(_topicName, _subName, cancellationToken);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
|
||||
private async Task ProcessMessageAsync(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
if(message.Label != _subName && _applicationCacheService != null)
|
||||
{
|
||||
switch((ApplicationCacheMessageType)message.UserProperties["type"])
|
||||
{
|
||||
case ApplicationCacheMessageType.UpsertOrganizationAbility:
|
||||
var upsertedOrgId = (Guid)message.UserProperties["id"];
|
||||
var upsertedOrg = await _organizationRepository.GetByIdAsync(upsertedOrgId);
|
||||
if(upsertedOrg != null)
|
||||
{
|
||||
await _applicationCacheService.BaseUpsertOrganizationAbilityAsync(upsertedOrg);
|
||||
}
|
||||
break;
|
||||
case ApplicationCacheMessageType.DeleteOrganizationAbility:
|
||||
await _applicationCacheService.BaseDeleteOrganizationAbilityAsync(
|
||||
(Guid)message.UserProperties["id"]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await _subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ExceptionReceivedHandlerAsync(ExceptionReceivedEventArgs args)
|
||||
{
|
||||
_logger.LogError(args.Exception, "Message handler encountered an exception.");
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,13 +21,13 @@ namespace Bit.Core.Services
|
||||
_organizationRepository = organizationRepository;
|
||||
}
|
||||
|
||||
public async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync()
|
||||
public virtual async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync()
|
||||
{
|
||||
await InitOrganizationAbilitiesAsync();
|
||||
return _orgAbilities;
|
||||
}
|
||||
|
||||
public async Task UpsertOrganizationAbilityAsync(Organization organization)
|
||||
public virtual async Task UpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
await InitOrganizationAbilitiesAsync();
|
||||
var newAbility = new OrganizationAbility(organization);
|
||||
@ -42,7 +42,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
public virtual Task DeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
{
|
||||
if(_orgAbilities != null && _orgAbilities.ContainsKey(organizationId))
|
||||
{
|
||||
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Azure.ServiceBus;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCacheService, IApplicationCacheService
|
||||
{
|
||||
private readonly TopicClient _topicClient;
|
||||
private readonly string _subName;
|
||||
|
||||
public InMemoryServiceBusApplicationCacheService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
GlobalSettings globalSettings)
|
||||
: base(organizationRepository)
|
||||
{
|
||||
_subName = CoreHelpers.GetApplicationCacheServiceBusSubcriptionName(globalSettings);
|
||||
_topicClient = new TopicClient(globalSettings.ServiceBus.ConnectionString,
|
||||
globalSettings.ServiceBus.ApplicationCacheTopicName);
|
||||
}
|
||||
|
||||
public override async Task UpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
await base.UpsertOrganizationAbilityAsync(organization);
|
||||
var message = new Message
|
||||
{
|
||||
Label = _subName,
|
||||
UserProperties =
|
||||
{
|
||||
{ "type", (byte)ApplicationCacheMessageType.UpsertOrganizationAbility },
|
||||
{ "id", organization.Id },
|
||||
}
|
||||
};
|
||||
var task = _topicClient.SendAsync(message);
|
||||
}
|
||||
|
||||
public override async Task DeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
{
|
||||
await base.DeleteOrganizationAbilityAsync(organizationId);
|
||||
var message = new Message
|
||||
{
|
||||
Label = _subName,
|
||||
UserProperties =
|
||||
{
|
||||
{ "type", (byte)ApplicationCacheMessageType.DeleteOrganizationAbility },
|
||||
{ "id", organizationId },
|
||||
}
|
||||
};
|
||||
var task = _topicClient.SendAsync(message);
|
||||
}
|
||||
|
||||
public async Task BaseUpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
await base.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
|
||||
public async Task BaseDeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
{
|
||||
await base.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
}
|
||||
}
|
@ -501,5 +501,27 @@ namespace Bit.Core.Utilities
|
||||
|
||||
return !invalid;
|
||||
}
|
||||
|
||||
public static string GetApplicationCacheServiceBusSubcriptionName(GlobalSettings globalSettings)
|
||||
{
|
||||
var subName = globalSettings.ServiceBus.ApplicationCacheSubscriptionName;
|
||||
if(string.IsNullOrWhiteSpace(subName))
|
||||
{
|
||||
var websiteInstanceId = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
|
||||
if(string.IsNullOrWhiteSpace(websiteInstanceId))
|
||||
{
|
||||
throw new Exception("No service bus subscription name available.");
|
||||
}
|
||||
else
|
||||
{
|
||||
subName = $"{globalSettings.ProjectName.ToLower()}_{websiteInstanceId}";
|
||||
if(subName.Length > 50)
|
||||
{
|
||||
subName = subName.Substring(0, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
return subName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,16 @@ namespace Bit.Core.Utilities
|
||||
services.AddSingleton<IPaymentService, StripePaymentService>();
|
||||
services.AddSingleton<IMailService, HandlebarsMailService>();
|
||||
services.AddSingleton<ILicensingService, LicensingService>();
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryApplicationCacheService>();
|
||||
|
||||
if(CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
|
||||
{
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryServiceBusApplicationCacheService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryApplicationCacheService>();
|
||||
}
|
||||
|
||||
if(CoreHelpers.SettingHasValue(globalSettings.Mail.SendGridApiKey))
|
||||
{
|
||||
|
@ -48,7 +48,16 @@ namespace Bit.Events
|
||||
});
|
||||
|
||||
// Services
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryApplicationCacheService>();
|
||||
var usingServiceBusAppCache = CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName);
|
||||
if(usingServiceBusAppCache)
|
||||
{
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryServiceBusApplicationCacheService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IApplicationCacheService, InMemoryApplicationCacheService>();
|
||||
}
|
||||
services.AddScoped<IEventService, EventService>();
|
||||
if(!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
|
||||
{
|
||||
@ -61,6 +70,11 @@ namespace Bit.Events
|
||||
|
||||
// Mvc
|
||||
services.AddMvc();
|
||||
|
||||
if(usingServiceBusAppCache)
|
||||
{
|
||||
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
|
@ -63,6 +63,12 @@ namespace Bit.Identity
|
||||
// Services
|
||||
services.AddBaseServices();
|
||||
services.AddDefaultServices(globalSettings);
|
||||
|
||||
if(CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
|
||||
{
|
||||
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
|
@ -47,6 +47,10 @@
|
||||
"connectionString": "SECRET",
|
||||
"hubName": "SECRET"
|
||||
},
|
||||
"serviceBus": {
|
||||
"connectionString": "SECRET",
|
||||
"applicationCacheTopicName": "SECRET"
|
||||
},
|
||||
"yubico": {
|
||||
"clientid": "SECRET",
|
||||
"key": "SECRET"
|
||||
|
@ -26,6 +26,10 @@
|
||||
"events": {
|
||||
"connectionString": "SECRET"
|
||||
},
|
||||
"serviceBus": {
|
||||
"connectionString": "SECRET",
|
||||
"applicationCacheTopicName": "SECRET"
|
||||
},
|
||||
"documentDb": {
|
||||
"uri": "SECRET",
|
||||
"key": "SECRET"
|
||||
|
Loading…
x
Reference in New Issue
Block a user