From 40221f578ff0777b9f5113931cafe6b36e1aa425 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 8 Apr 2024 15:39:44 -0400 Subject: [PATCH] [PM-6339] Shard notification hub clients across multiple accounts (#3812) * WIP registration updates * fix deviceHubs * addHub inline in ctor * adjust setttings for hub reg * send to all clients * fix multiservice push * use notification hub type * feedback --------- Co-authored-by: Matt Bishop --- src/Api/Controllers/PushController.cs | 12 +- .../Implementations/OrganizationService.cs | 14 ++- src/Core/Enums/NotificationHubType.cs | 11 ++ .../Api/Request/PushDeviceRequestModel.cs | 12 ++ .../Api/Request/PushUpdateRequestModel.cs | 7 +- src/Core/Services/IPushRegistrationService.cs | 6 +- .../Services/Implementations/DeviceService.cs | 4 +- .../MultiServicePushNotificationService.cs | 3 +- .../NotificationHubPushNotificationService.cs | 54 ++++++--- .../NotificationHubPushRegistrationService.cs | 111 ++++++++++++++---- .../RelayPushRegistrationService.cs | 23 ++-- .../NoopPushRegistrationService.cs | 6 +- src/Core/Settings/GlobalSettings.cs | 9 +- ...ficationHubPushRegistrationServiceTests.cs | 6 +- 14 files changed, 208 insertions(+), 70 deletions(-) create mode 100644 src/Core/Enums/NotificationHubType.cs create mode 100644 src/Core/Models/Api/Request/PushDeviceRequestModel.cs diff --git a/src/Api/Controllers/PushController.cs b/src/Api/Controllers/PushController.cs index 7312cb7b85..c83eb200b8 100644 --- a/src/Api/Controllers/PushController.cs +++ b/src/Api/Controllers/PushController.cs @@ -42,11 +42,11 @@ public class PushController : Controller Prefix(model.UserId), Prefix(model.Identifier), model.Type); } - [HttpDelete("{id}")] - public async Task Delete(string id) + [HttpPost("delete")] + public async Task PostDelete([FromBody] PushDeviceRequestModel model) { CheckUsage(); - await _pushRegistrationService.DeleteRegistrationAsync(Prefix(id)); + await _pushRegistrationService.DeleteRegistrationAsync(Prefix(model.Id), model.Type); } [HttpPut("add-organization")] @@ -54,7 +54,8 @@ public class PushController : Controller { CheckUsage(); await _pushRegistrationService.AddUserRegistrationOrganizationAsync( - model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId)); + model.Devices.Select(d => new KeyValuePair(Prefix(d.Id), d.Type)), + Prefix(model.OrganizationId)); } [HttpPut("delete-organization")] @@ -62,7 +63,8 @@ public class PushController : Controller { CheckUsage(); await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync( - model.DeviceIds.Select(d => Prefix(d)), Prefix(model.OrganizationId)); + model.Devices.Select(d => new KeyValuePair(Prefix(d.Id), d.Type)), + Prefix(model.OrganizationId)); } [HttpPost("send")] diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index d322add42c..9c87ff40a0 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -681,8 +681,8 @@ public class OrganizationService : IOrganizationService await _organizationUserRepository.CreateAsync(orgUser); - var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value); - await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds, + var devices = await GetUserDeviceIdsAsync(orgUser.UserId.Value); + await _pushRegistrationService.AddUserRegistrationOrganizationAsync(devices, organization.Id.ToString()); await _pushNotificationService.PushSyncOrgKeysAsync(ownerId); } @@ -1932,17 +1932,19 @@ public class OrganizationService : IOrganizationService private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId) { - var deviceIds = await GetUserDeviceIdsAsync(userId); - await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(deviceIds, + var devices = await GetUserDeviceIdsAsync(userId); + await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(devices, organizationId.ToString()); await _pushNotificationService.PushSyncOrgKeysAsync(userId); } - private async Task> GetUserDeviceIdsAsync(Guid userId) + private async Task>> GetUserDeviceIdsAsync(Guid userId) { var devices = await _deviceRepository.GetManyByUserIdAsync(userId); - return devices.Where(d => !string.IsNullOrWhiteSpace(d.PushToken)).Select(d => d.Id.ToString()); + return devices + .Where(d => !string.IsNullOrWhiteSpace(d.PushToken)) + .Select(d => new KeyValuePair(d.Id.ToString(), d.Type)); } public async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null) diff --git a/src/Core/Enums/NotificationHubType.cs b/src/Core/Enums/NotificationHubType.cs new file mode 100644 index 0000000000..d8c31176b9 --- /dev/null +++ b/src/Core/Enums/NotificationHubType.cs @@ -0,0 +1,11 @@ +namespace Bit.Core.Enums; + +public enum NotificationHubType +{ + General = 0, + Android = 1, + iOS = 2, + GeneralWeb = 3, + GeneralBrowserExtension = 4, + GeneralDesktop = 5 +} diff --git a/src/Core/Models/Api/Request/PushDeviceRequestModel.cs b/src/Core/Models/Api/Request/PushDeviceRequestModel.cs new file mode 100644 index 0000000000..e1866b6f27 --- /dev/null +++ b/src/Core/Models/Api/Request/PushDeviceRequestModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; + +namespace Bit.Core.Models.Api; + +public class PushDeviceRequestModel +{ + [Required] + public string Id { get; set; } + [Required] + public DeviceType Type { get; set; } +} diff --git a/src/Core/Models/Api/Request/PushUpdateRequestModel.cs b/src/Core/Models/Api/Request/PushUpdateRequestModel.cs index 2ccbf6eb00..9f7ed5f288 100644 --- a/src/Core/Models/Api/Request/PushUpdateRequestModel.cs +++ b/src/Core/Models/Api/Request/PushUpdateRequestModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; namespace Bit.Core.Models.Api; @@ -7,14 +8,14 @@ public class PushUpdateRequestModel public PushUpdateRequestModel() { } - public PushUpdateRequestModel(IEnumerable deviceIds, string organizationId) + public PushUpdateRequestModel(IEnumerable> devices, string organizationId) { - DeviceIds = deviceIds; + Devices = devices.Select(d => new PushDeviceRequestModel { Id = d.Key, Type = d.Value }); OrganizationId = organizationId; } [Required] - public IEnumerable DeviceIds { get; set; } + public IEnumerable Devices { get; set; } [Required] public string OrganizationId { get; set; } } diff --git a/src/Core/Services/IPushRegistrationService.cs b/src/Core/Services/IPushRegistrationService.cs index 985246de0c..83bbed4854 100644 --- a/src/Core/Services/IPushRegistrationService.cs +++ b/src/Core/Services/IPushRegistrationService.cs @@ -6,7 +6,7 @@ public interface IPushRegistrationService { Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, string identifier, DeviceType type); - Task DeleteRegistrationAsync(string deviceId); - Task AddUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId); - Task DeleteUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId); + Task DeleteRegistrationAsync(string deviceId, DeviceType type); + Task AddUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId); + Task DeleteUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId); } diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index 5b1e4b0f01..9d8315f691 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -38,13 +38,13 @@ public class DeviceService : IDeviceService public async Task ClearTokenAsync(Device device) { await _deviceRepository.ClearPushTokenAsync(device.Id); - await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString()); + await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString(), device.Type); } public async Task DeleteAsync(Device device) { await _deviceRepository.DeleteAsync(device); - await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString()); + await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString(), device.Type); } public async Task UpdateDevicesTrustAsync(string currentDeviceIdentifier, diff --git a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs index b683c05d0f..92e29908f5 100644 --- a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs +++ b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs @@ -43,7 +43,8 @@ public class MultiServicePushNotificationService : IPushNotificationService } else { - if (CoreHelpers.SettingHasValue(globalSettings.NotificationHub.ConnectionString)) + var generalHub = globalSettings.NotificationHubs?.FirstOrDefault(h => h.HubType == NotificationHubType.General); + if (CoreHelpers.SettingHasValue(generalHub?.ConnectionString)) { _services.Add(new NotificationHubPushNotificationService(installationDeviceRepository, globalSettings, httpContextAccessor, hubLogger)); diff --git a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs index 96c50ca93a..480f0dfa9e 100644 --- a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs @@ -20,8 +20,9 @@ public class NotificationHubPushNotificationService : IPushNotificationService private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private NotificationHubClient _client = null; - private ILogger _logger; + private readonly List _clients = []; + private readonly bool _enableTracing = false; + private readonly ILogger _logger; public NotificationHubPushNotificationService( IInstallationDeviceRepository installationDeviceRepository, @@ -32,10 +33,18 @@ public class NotificationHubPushNotificationService : IPushNotificationService _installationDeviceRepository = installationDeviceRepository; _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; - _client = NotificationHubClient.CreateClientFromConnectionString( - _globalSettings.NotificationHub.ConnectionString, - _globalSettings.NotificationHub.HubName, - _globalSettings.NotificationHub.EnableSendTracing); + + foreach (var hub in globalSettings.NotificationHubs) + { + var client = NotificationHubClient.CreateClientFromConnectionString( + hub.ConnectionString, + hub.HubName, + hub.EnableSendTracing); + _clients.Add(client); + + _enableTracing = _enableTracing || hub.EnableSendTracing; + } + _logger = logger; } @@ -255,16 +264,31 @@ public class NotificationHubPushNotificationService : IPushNotificationService private async Task SendPayloadAsync(string tag, PushType type, object payload) { - var outcome = await _client.SendTemplateNotificationAsync( - new Dictionary - { - { "type", ((byte)type).ToString() }, - { "payload", JsonSerializer.Serialize(payload) } - }, tag); - if (_globalSettings.NotificationHub.EnableSendTracing) + var tasks = new List>(); + foreach (var client in _clients) { - _logger.LogInformation("Azure Notification Hub Tracking ID: {id} | {type} push notification with {success} successes and {failure} failures with a payload of {@payload} and result of {@results}", - outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results); + var task = client.SendTemplateNotificationAsync( + new Dictionary + { + { "type", ((byte)type).ToString() }, + { "payload", JsonSerializer.Serialize(payload) } + }, tag); + tasks.Add(task); + } + + await Task.WhenAll(tasks); + + if (_enableTracing) + { + for (var i = 0; i < tasks.Count; i++) + { + if (_clients[i].EnableTestSend) + { + var outcome = await tasks[i]; + _logger.LogInformation("Azure Notification Hub Tracking ID: {id} | {type} push notification with {success} successes and {failure} failures with a payload of {@payload} and result of {@results}", + outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results); + } + } } } diff --git a/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs index 6f09375398..9a31a2a879 100644 --- a/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs @@ -3,6 +3,7 @@ using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; using Microsoft.Azure.NotificationHubs; +using Microsoft.Extensions.Logging; namespace Bit.Core.Services; @@ -10,18 +11,36 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService { private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly GlobalSettings _globalSettings; - - private NotificationHubClient _client = null; + private readonly ILogger _logger; + private Dictionary _clients = []; public NotificationHubPushRegistrationService( IInstallationDeviceRepository installationDeviceRepository, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + ILogger logger) { _installationDeviceRepository = installationDeviceRepository; _globalSettings = globalSettings; - _client = NotificationHubClient.CreateClientFromConnectionString( - _globalSettings.NotificationHub.ConnectionString, - _globalSettings.NotificationHub.HubName); + _logger = logger; + + // Is this dirty to do in the ctor? + void addHub(NotificationHubType type) + { + var hubRegistration = globalSettings.NotificationHubs.FirstOrDefault( + h => h.HubType == type && h.EnableRegistration); + if (hubRegistration != null) + { + var client = NotificationHubClient.CreateClientFromConnectionString( + hubRegistration.ConnectionString, + hubRegistration.HubName, + hubRegistration.EnableSendTracing); + _clients.Add(type, client); + } + } + + addHub(NotificationHubType.General); + addHub(NotificationHubType.iOS); + addHub(NotificationHubType.Android); } public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, @@ -84,7 +103,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, userId, identifier); - await _client.CreateOrUpdateInstallationAsync(installation); + await GetClient(type).CreateOrUpdateInstallationAsync(installation); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); @@ -119,11 +138,11 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService installation.Templates.Add(fullTemplateId, template); } - public async Task DeleteRegistrationAsync(string deviceId) + public async Task DeleteRegistrationAsync(string deviceId, DeviceType deviceType) { try { - await _client.DeleteInstallationAsync(deviceId); + await GetClient(deviceType).DeleteInstallationAsync(deviceId); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { await _installationDeviceRepository.DeleteAsync(new InstallationDeviceEntity(deviceId)); @@ -135,31 +154,31 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } - public async Task AddUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public async Task AddUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) { - await PatchTagsForUserDevicesAsync(deviceIds, UpdateOperationType.Add, $"organizationId:{organizationId}"); - if (deviceIds.Any() && InstallationDeviceEntity.IsInstallationDeviceId(deviceIds.First())) + await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Add, $"organizationId:{organizationId}"); + if (devices.Any() && InstallationDeviceEntity.IsInstallationDeviceId(devices.First().Key)) { - var entities = deviceIds.Select(e => new InstallationDeviceEntity(e)); + var entities = devices.Select(e => new InstallationDeviceEntity(e.Key)); await _installationDeviceRepository.UpsertManyAsync(entities.ToList()); } } - public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) { - await PatchTagsForUserDevicesAsync(deviceIds, UpdateOperationType.Remove, + await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Remove, $"organizationId:{organizationId}"); - if (deviceIds.Any() && InstallationDeviceEntity.IsInstallationDeviceId(deviceIds.First())) + if (devices.Any() && InstallationDeviceEntity.IsInstallationDeviceId(devices.First().Key)) { - var entities = deviceIds.Select(e => new InstallationDeviceEntity(e)); + var entities = devices.Select(e => new InstallationDeviceEntity(e.Key)); await _installationDeviceRepository.UpsertManyAsync(entities.ToList()); } } - private async Task PatchTagsForUserDevicesAsync(IEnumerable deviceIds, UpdateOperationType op, + private async Task PatchTagsForUserDevicesAsync(IEnumerable> devices, UpdateOperationType op, string tag) { - if (!deviceIds.Any()) + if (!devices.Any()) { return; } @@ -179,11 +198,11 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService operation.Path += $"/{tag}"; } - foreach (var id in deviceIds) + foreach (var device in devices) { try { - await _client.PatchInstallationAsync(id, new List { operation }); + await GetClient(device.Value).PatchInstallationAsync(device.Key, new List { operation }); } catch (Exception e) when (e.InnerException == null || !e.InnerException.Message.Contains("(404) Not Found")) { @@ -191,4 +210,54 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } } + + private NotificationHubClient GetClient(DeviceType deviceType) + { + var hubType = NotificationHubType.General; + switch (deviceType) + { + case DeviceType.Android: + hubType = NotificationHubType.Android; + break; + case DeviceType.iOS: + hubType = NotificationHubType.iOS; + break; + case DeviceType.ChromeExtension: + case DeviceType.FirefoxExtension: + case DeviceType.OperaExtension: + case DeviceType.EdgeExtension: + case DeviceType.VivaldiExtension: + case DeviceType.SafariExtension: + hubType = NotificationHubType.GeneralBrowserExtension; + break; + case DeviceType.WindowsDesktop: + case DeviceType.MacOsDesktop: + case DeviceType.LinuxDesktop: + hubType = NotificationHubType.GeneralDesktop; + break; + case DeviceType.ChromeBrowser: + case DeviceType.FirefoxBrowser: + case DeviceType.OperaBrowser: + case DeviceType.EdgeBrowser: + case DeviceType.IEBrowser: + case DeviceType.UnknownBrowser: + case DeviceType.SafariBrowser: + case DeviceType.VivaldiBrowser: + hubType = NotificationHubType.GeneralWeb; + break; + default: + break; + } + + if (!_clients.ContainsKey(hubType)) + { + _logger.LogWarning("No hub client for '{0}'. Using general hub instead.", hubType); + hubType = NotificationHubType.General; + if (!_clients.ContainsKey(hubType)) + { + throw new Exception("No general hub client found."); + } + } + return _clients[hubType]; + } } diff --git a/src/Core/Services/Implementations/RelayPushRegistrationService.cs b/src/Core/Services/Implementations/RelayPushRegistrationService.cs index f661af537d..d9df7d04dc 100644 --- a/src/Core/Services/Implementations/RelayPushRegistrationService.cs +++ b/src/Core/Services/Implementations/RelayPushRegistrationService.cs @@ -38,30 +38,37 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi await SendAsync(HttpMethod.Post, "push/register", requestModel); } - public async Task DeleteRegistrationAsync(string deviceId) + public async Task DeleteRegistrationAsync(string deviceId, DeviceType type) { - await SendAsync(HttpMethod.Delete, string.Concat("push/", deviceId)); + var requestModel = new PushDeviceRequestModel + { + Id = deviceId, + Type = type, + }; + await SendAsync(HttpMethod.Post, "push/delete", requestModel); } - public async Task AddUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public async Task AddUserRegistrationOrganizationAsync( + IEnumerable> devices, string organizationId) { - if (!deviceIds.Any()) + if (!devices.Any()) { return; } - var requestModel = new PushUpdateRequestModel(deviceIds, organizationId); + var requestModel = new PushUpdateRequestModel(devices, organizationId); await SendAsync(HttpMethod.Put, "push/add-organization", requestModel); } - public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public async Task DeleteUserRegistrationOrganizationAsync( + IEnumerable> devices, string organizationId) { - if (!deviceIds.Any()) + if (!devices.Any()) { return; } - var requestModel = new PushUpdateRequestModel(deviceIds, organizationId); + var requestModel = new PushUpdateRequestModel(devices, organizationId); await SendAsync(HttpMethod.Put, "push/delete-organization", requestModel); } } diff --git a/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs b/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs index f6279c9467..fcd0889248 100644 --- a/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs +++ b/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs @@ -4,7 +4,7 @@ namespace Bit.Core.Services; public class NoopPushRegistrationService : IPushRegistrationService { - public Task AddUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public Task AddUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) { return Task.FromResult(0); } @@ -15,12 +15,12 @@ public class NoopPushRegistrationService : IPushRegistrationService return Task.FromResult(0); } - public Task DeleteRegistrationAsync(string deviceId) + public Task DeleteRegistrationAsync(string deviceId, DeviceType deviceType) { return Task.FromResult(0); } - public Task DeleteUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId) + public Task DeleteUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) { return Task.FromResult(0); } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 50b4efe6fb..f883422221 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -1,4 +1,5 @@ using Bit.Core.Auth.Settings; +using Bit.Core.Enums; using Bit.Core.Settings.LoggingSettings; namespace Bit.Core.Settings; @@ -64,7 +65,7 @@ public class GlobalSettings : IGlobalSettings public virtual SentrySettings Sentry { get; set; } = new SentrySettings(); public virtual SyslogSettings Syslog { get; set; } = new SyslogSettings(); public virtual ILogLevelSettings MinLogLevel { get; set; } = new LogLevelSettings(); - public virtual NotificationHubSettings NotificationHub { get; set; } = new NotificationHubSettings(); + public virtual List NotificationHubs { get; set; } = new(); public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings(); public virtual DuoSettings Duo { get; set; } = new DuoSettings(); public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings(); @@ -416,12 +417,16 @@ public class GlobalSettings : IGlobalSettings set => _connectionString = value.Trim('"'); } public string HubName { get; set; } - /// /// Enables TestSend on the Azure Notification Hub, which allows tracing of the request through the hub and to the platform-specific push notification service (PNS). /// Enabling this will result in delayed responses because the Hub must wait on delivery to the PNS. This should ONLY be enabled in a non-production environment, as results are throttled. /// public bool EnableSendTracing { get; set; } = false; + /// + /// At least one hub configuration should have registration enabled, preferably the General hub as a safety net. + /// + public bool EnableRegistration { get; set; } + public NotificationHubType HubType { get; set; } } public class YubicoSettings diff --git a/test/Core.Test/Services/NotificationHubPushRegistrationServiceTests.cs b/test/Core.Test/Services/NotificationHubPushRegistrationServiceTests.cs index 8e2a19d7b9..0b9c64121b 100644 --- a/test/Core.Test/Services/NotificationHubPushRegistrationServiceTests.cs +++ b/test/Core.Test/Services/NotificationHubPushRegistrationServiceTests.cs @@ -1,6 +1,7 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -11,16 +12,19 @@ public class NotificationHubPushRegistrationServiceTests private readonly NotificationHubPushRegistrationService _sut; private readonly IInstallationDeviceRepository _installationDeviceRepository; + private readonly ILogger _logger; private readonly GlobalSettings _globalSettings; public NotificationHubPushRegistrationServiceTests() { _installationDeviceRepository = Substitute.For(); + _logger = Substitute.For>(); _globalSettings = new GlobalSettings(); _sut = new NotificationHubPushRegistrationService( _installationDeviceRepository, - _globalSettings + _globalSettings, + _logger ); }