1
0
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:
Kyle Spearrin 2019-06-13 00:10:37 -04:00
parent 2d63732085
commit 6f0d64119a
16 changed files with 276 additions and 9 deletions

View File

@ -36,6 +36,10 @@
"events": {
"connectionString": "SECRET"
},
"serviceBus": {
"connectionString": "SECRET",
"applicationCacheTopicName": "SECRET"
},
"documentDb": {
"uri": "SECRET",
"key": "SECRET"

View File

@ -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(

View File

@ -51,6 +51,10 @@
"connectionString": "SECRET",
"hubName": "SECRET"
},
"serviceBus": {
"connectionString": "SECRET",
"applicationCacheTopicName": "SECRET"
},
"yubico": {
"clientid": "SECRET",
"key": "SECRET"

View File

@ -36,6 +36,10 @@
"events": {
"connectionString": "SECRET"
},
"serviceBus": {
"connectionString": "SECRET",
"applicationCacheTopicName": "SECRET"
},
"documentDb": {
"uri": "SECRET",
"key": "SECRET"

View File

@ -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" />

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum ApplicationCacheMessageType : byte
{
UpsertOrganizationAbility = 0,
DeleteOrganizationAbility = 1
}
}

View File

@ -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; }
}
}
}

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

View File

@ -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))
{

View File

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

View File

@ -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;
}
}
}

View File

@ -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))
{

View File

@ -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(

View File

@ -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(

View File

@ -47,6 +47,10 @@
"connectionString": "SECRET",
"hubName": "SECRET"
},
"serviceBus": {
"connectionString": "SECRET",
"applicationCacheTopicName": "SECRET"
},
"yubico": {
"clientid": "SECRET",
"key": "SECRET"

View File

@ -26,6 +26,10 @@
"events": {
"connectionString": "SECRET"
},
"serviceBus": {
"connectionString": "SECRET",
"applicationCacheTopicName": "SECRET"
},
"documentDb": {
"uri": "SECRET",
"key": "SECRET"