diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 88cfc71256..2f1c5f18fb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -92,6 +92,8 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev **/.dockerignore @bitwarden/team-platform-dev **/Dockerfile @bitwarden/team-platform-dev **/entrypoint.sh @bitwarden/team-platform-dev +# The PushType enum is expected to be editted by anyone without need for Platform review +src/Core/Platform/Push/PushType.cs # Multiple owners - DO NOT REMOVE (BRE) **/packages.lock.json diff --git a/src/Api/Models/Request/DeviceRequestModels.cs b/src/Api/Models/Request/DeviceRequestModels.cs index 397d4e27df..11600a0195 100644 --- a/src/Api/Models/Request/DeviceRequestModels.cs +++ b/src/Api/Models/Request/DeviceRequestModels.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.NotificationHub; +using Bit.Core.Platform.PushRegistration; using Bit.Core.Utilities; namespace Bit.Api.Models.Request; diff --git a/src/Api/Platform/Push/Controllers/PushController.cs b/src/Api/Platform/Push/Controllers/PushController.cs index 88aec18be3..14c0a20636 100644 --- a/src/Api/Platform/Push/Controllers/PushController.cs +++ b/src/Api/Platform/Push/Controllers/PushController.cs @@ -6,9 +6,9 @@ using System.Text.Json; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Api; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs deleted file mode 100644 index 07c40f94a2..0000000000 --- a/src/Core/Enums/PushType.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Bit.Core.Enums; - -public enum PushType : byte -{ - SyncCipherUpdate = 0, - SyncCipherCreate = 1, - SyncLoginDelete = 2, - SyncFolderDelete = 3, - SyncCiphers = 4, - - SyncVault = 5, - SyncOrgKeys = 6, - SyncFolderCreate = 7, - SyncFolderUpdate = 8, - SyncCipherDelete = 9, - SyncSettings = 10, - - LogOut = 11, - - SyncSendCreate = 12, - SyncSendUpdate = 13, - SyncSendDelete = 14, - - AuthRequest = 15, - AuthRequestResponse = 16, - - SyncOrganizations = 17, - SyncOrganizationStatusChanged = 18, - SyncOrganizationCollectionSettingChanged = 19, - - Notification = 20, - NotificationStatus = 21, - - RefreshSecurityTasks = 22 -} diff --git a/src/Core/Models/PushNotification.cs b/src/Core/Models/PushNotification.cs index 83c6f577d4..e235d05b13 100644 --- a/src/Core/Models/PushNotification.cs +++ b/src/Core/Models/PushNotification.cs @@ -1,9 +1,11 @@ -#nullable enable -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.NotificationCenter.Enums; namespace Bit.Core.Models; +// New push notification payload models should not be defined in this file +// they should instead be defined in file owned by your team. + public class PushNotificationData { public PushNotificationData(PushType type, T payload, string? contextId) diff --git a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Engines/AzureQueuePushEngine.cs similarity index 91% rename from src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs rename to src/Core/Platform/Push/Engines/AzureQueuePushEngine.cs index 94a20f1971..e8c8790c64 100644 --- a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Engines/AzureQueuePushEngine.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json; +using System.Text.Json; using Azure.Storage.Queues; using Bit.Core.Context; using Bit.Core.Enums; @@ -13,17 +12,16 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; -public class AzureQueuePushNotificationService : IPushEngine +public class AzureQueuePushEngine : IPushEngine { private readonly QueueClient _queueClient; private readonly IHttpContextAccessor _httpContextAccessor; - public AzureQueuePushNotificationService( + public AzureQueuePushEngine( [FromKeyedServices("notifications")] QueueClient queueClient, IHttpContextAccessor httpContextAccessor, IGlobalSettings globalSettings, - ILogger logger, - TimeProvider timeProvider) + ILogger logger) { _queueClient = queueClient; _httpContextAccessor = httpContextAccessor; diff --git a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Engines/MultiServicePushNotificationService.cs similarity index 91% rename from src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs rename to src/Core/Platform/Push/Engines/MultiServicePushNotificationService.cs index 404b153fa3..1dbd2c83e5 100644 --- a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Engines/MultiServicePushNotificationService.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Settings; using Bit.Core.Vault.Entities; using Microsoft.Extensions.Logging; @@ -8,7 +7,7 @@ namespace Bit.Core.Platform.Push.Internal; public class MultiServicePushNotificationService : IPushNotificationService { - private readonly IEnumerable _services; + private readonly IPushEngine[] _services; public Guid InstallationId { get; } @@ -22,7 +21,8 @@ public class MultiServicePushNotificationService : IPushNotificationService GlobalSettings globalSettings, TimeProvider timeProvider) { - _services = services; + // Filter out any NoopPushEngine's + _services = [.. services.Where(engine => engine is not NoopPushEngine)]; Logger = logger; Logger.LogInformation("Hub services: {Services}", _services.Count()); diff --git a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs b/src/Core/Platform/Push/Engines/NoopPushEngine.cs similarity index 75% rename from src/Core/Platform/Push/Services/NoopPushNotificationService.cs rename to src/Core/Platform/Push/Engines/NoopPushEngine.cs index e6f71de006..029d6dd556 100644 --- a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Engines/NoopPushEngine.cs @@ -1,10 +1,9 @@ -#nullable enable -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Vault.Entities; namespace Bit.Core.Platform.Push.Internal; -internal class NoopPushNotificationService : IPushEngine +internal class NoopPushEngine : IPushEngine { public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) => Task.CompletedTask; diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Engines/NotificationsApiPushEngine.cs similarity index 87% rename from src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs rename to src/Core/Platform/Push/Engines/NotificationsApiPushEngine.cs index 5e0d584ba8..add53278ff 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Engines/NotificationsApiPushEngine.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Core.Context; +using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Services; @@ -8,23 +7,22 @@ using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -// This service is not in the `Internal` namespace because it has direct external references. -namespace Bit.Core.Platform.Push; +namespace Bit.Core.Platform.Push.Internal; /// /// Sends non-mobile push notifications to the Azure Queue Api, later received by Notifications Api. /// Used by Cloud-Hosted environments. /// Received by AzureQueueHostedService message receiver in Notifications project. /// -public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushEngine +public class NotificationsApiPushEngine : BaseIdentityClientService, IPushEngine { private readonly IHttpContextAccessor _httpContextAccessor; - public NotificationsApiPushNotificationService( + public NotificationsApiPushEngine( IHttpClientFactory httpFactory, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger) + ILogger logger) : base( httpFactory, globalSettings.BaseServiceUri.InternalNotifications, diff --git a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs b/src/Core/Platform/Push/Engines/RelayPushEngine.cs similarity index 94% rename from src/Core/Platform/Push/Services/RelayPushNotificationService.cs rename to src/Core/Platform/Push/Engines/RelayPushEngine.cs index 9f2289b864..66b0229315 100644 --- a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Engines/RelayPushEngine.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Core.Context; +using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models; @@ -19,18 +18,18 @@ namespace Bit.Core.Platform.Push.Internal; /// Used by Self-Hosted environments. /// Received by PushController endpoint in Api project. /// -public class RelayPushNotificationService : BaseIdentityClientService, IPushEngine +public class RelayPushEngine : BaseIdentityClientService, IPushEngine { private readonly IDeviceRepository _deviceRepository; private readonly IHttpContextAccessor _httpContextAccessor; - public RelayPushNotificationService( + public RelayPushEngine( IHttpClientFactory httpFactory, IDeviceRepository deviceRepository, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger) + ILogger logger) : base( httpFactory, globalSettings.PushRelayBaseUri, diff --git a/src/Core/Platform/Push/Services/IPushEngine.cs b/src/Core/Platform/Push/IPushEngine.cs similarity index 76% rename from src/Core/Platform/Push/Services/IPushEngine.cs rename to src/Core/Platform/Push/IPushEngine.cs index bde4ddaf4b..ca00dae3ad 100644 --- a/src/Core/Platform/Push/Services/IPushEngine.cs +++ b/src/Core/Platform/Push/IPushEngine.cs @@ -1,8 +1,7 @@ -#nullable enable -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Vault.Entities; -namespace Bit.Core.Platform.Push; +namespace Bit.Core.Platform.Push.Internal; public interface IPushEngine { diff --git a/src/Core/Platform/Push/Services/IPushNotificationService.cs b/src/Core/Platform/Push/IPushNotificationService.cs similarity index 82% rename from src/Core/Platform/Push/Services/IPushNotificationService.cs rename to src/Core/Platform/Push/IPushNotificationService.cs index c6da6cf6b7..339ce5a917 100644 --- a/src/Core/Platform/Push/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/IPushNotificationService.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; using Bit.Core.Models; @@ -10,10 +9,27 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push; +/// +/// Used to Push notifications to end-user devices. +/// +/// +/// New notifications should not be wired up inside this service. You may either directly call the +/// method in your service to send your notification or if you want your notification +/// sent by other teams you can make an extension method on this service with a well typed definition +/// of your notification. You may also make your own service that injects this and exposes methods for each of +/// your notifications. +/// public interface IPushNotificationService { + private const string ServiceDeprecation = "Do not use the services exposed here, instead use your own services injected in your service."; + + [Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")] Guid InstallationId { get; } + + [Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")] TimeProvider TimeProvider { get; } + + [Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")] ILogger Logger { get; } #region Legacy method, to be removed soon. @@ -80,7 +96,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -94,7 +112,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -108,7 +128,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -122,7 +144,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -136,7 +160,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -150,7 +176,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = excludeCurrentContextFromPush, }); @@ -231,7 +259,9 @@ public interface IPushNotificationService ClientType = notification.ClientType, UserId = notification.UserId, OrganizationId = notification.OrganizationId, +#pragma warning disable BWP0001 // Type or member is obsolete InstallationId = notification.Global ? InstallationId : null, +#pragma warning restore BWP0001 // Type or member is obsolete TaskId = notification.TaskId, Title = notification.Title, Body = notification.Body, @@ -246,7 +276,9 @@ public interface IPushNotificationService { // TODO: Think about this a bit more target = NotificationTarget.Installation; +#pragma warning disable BWP0001 // Type or member is obsolete targetId = InstallationId; +#pragma warning restore BWP0001 // Type or member is obsolete } else if (notification.UserId.HasValue) { @@ -260,7 +292,9 @@ public interface IPushNotificationService } else { +#pragma warning disable BWP0001 // Type or member is obsolete Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); +#pragma warning restore BWP0001 // Type or member is obsolete return Task.CompletedTask; } @@ -285,7 +319,9 @@ public interface IPushNotificationService ClientType = notification.ClientType, UserId = notification.UserId, OrganizationId = notification.OrganizationId, +#pragma warning disable BWP0001 // Type or member is obsolete InstallationId = notification.Global ? InstallationId : null, +#pragma warning restore BWP0001 // Type or member is obsolete TaskId = notification.TaskId, Title = notification.Title, Body = notification.Body, @@ -302,7 +338,9 @@ public interface IPushNotificationService { // TODO: Think about this a bit more target = NotificationTarget.Installation; +#pragma warning disable BWP0001 // Type or member is obsolete targetId = InstallationId; +#pragma warning restore BWP0001 // Type or member is obsolete } else if (notification.UserId.HasValue) { @@ -316,7 +354,9 @@ public interface IPushNotificationService } else { +#pragma warning disable BWP0001 // Type or member is obsolete Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); +#pragma warning restore BWP0001 // Type or member is obsolete return Task.CompletedTask; } @@ -398,7 +438,9 @@ public interface IPushNotificationService Payload = new UserPushNotification { UserId = userId, +#pragma warning disable BWP0001 // Type or member is obsolete Date = TimeProvider.GetUtcNow().UtcDateTime, +#pragma warning restore BWP0001 // Type or member is obsolete }, ExcludeCurrentContext = false, }); @@ -406,6 +448,12 @@ public interface IPushNotificationService Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds); + /// + /// Pushes a notification to devices based on the settings given to us in . + /// + /// The type of the payload to be sent along with the notification. + /// + /// A task that is NOT guarunteed to have sent the notification by the time the task resolves. Task PushAsync(PushNotification pushNotification) where T : class; } diff --git a/src/Core/Platform/Push/Services/IPushRelayer.cs b/src/Core/Platform/Push/IPushRelayer.cs similarity index 97% rename from src/Core/Platform/Push/Services/IPushRelayer.cs rename to src/Core/Platform/Push/IPushRelayer.cs index fde0a521f3..1fb75e0dfc 100644 --- a/src/Core/Platform/Push/Services/IPushRelayer.cs +++ b/src/Core/Platform/Push/IPushRelayer.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System.Text.Json; +using System.Text.Json; using Bit.Core.Enums; namespace Bit.Core.Platform.Push.Internal; diff --git a/src/Core/NotificationHub/INotificationHubClientProxy.cs b/src/Core/Platform/Push/NotificationHub/INotificationHubClientProxy.cs similarity index 82% rename from src/Core/NotificationHub/INotificationHubClientProxy.cs rename to src/Core/Platform/Push/NotificationHub/INotificationHubClientProxy.cs index 78eb0206d6..8b765d209b 100644 --- a/src/Core/NotificationHub/INotificationHubClientProxy.cs +++ b/src/Core/Platform/Push/NotificationHub/INotificationHubClientProxy.cs @@ -1,8 +1,6 @@ using Microsoft.Azure.NotificationHubs; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; public interface INotificationHubProxy { diff --git a/src/Core/NotificationHub/INotificationHubPool.cs b/src/Core/Platform/Push/NotificationHub/INotificationHubPool.cs similarity index 81% rename from src/Core/NotificationHub/INotificationHubPool.cs rename to src/Core/Platform/Push/NotificationHub/INotificationHubPool.cs index 25a31d62f4..3d5767623b 100644 --- a/src/Core/NotificationHub/INotificationHubPool.cs +++ b/src/Core/Platform/Push/NotificationHub/INotificationHubPool.cs @@ -1,8 +1,6 @@ using Microsoft.Azure.NotificationHubs; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; public interface INotificationHubPool { diff --git a/src/Core/NotificationHub/NotificationHubClientProxy.cs b/src/Core/Platform/Push/NotificationHub/NotificationHubClientProxy.cs similarity index 94% rename from src/Core/NotificationHub/NotificationHubClientProxy.cs rename to src/Core/Platform/Push/NotificationHub/NotificationHubClientProxy.cs index b47069fe21..026f3179d1 100644 --- a/src/Core/NotificationHub/NotificationHubClientProxy.cs +++ b/src/Core/Platform/Push/NotificationHub/NotificationHubClientProxy.cs @@ -1,8 +1,6 @@ using Microsoft.Azure.NotificationHubs; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; public class NotificationHubClientProxy : INotificationHubProxy { diff --git a/src/Core/NotificationHub/NotificationHubConnection.cs b/src/Core/Platform/Push/NotificationHub/NotificationHubConnection.cs similarity index 99% rename from src/Core/NotificationHub/NotificationHubConnection.cs rename to src/Core/Platform/Push/NotificationHub/NotificationHubConnection.cs index a61f2ded8f..22c1668506 100644 --- a/src/Core/NotificationHub/NotificationHubConnection.cs +++ b/src/Core/Platform/Push/NotificationHub/NotificationHubConnection.cs @@ -6,9 +6,7 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; public class NotificationHubConnection { diff --git a/src/Core/NotificationHub/NotificationHubPool.cs b/src/Core/Platform/Push/NotificationHub/NotificationHubPool.cs similarity index 98% rename from src/Core/NotificationHub/NotificationHubPool.cs rename to src/Core/Platform/Push/NotificationHub/NotificationHubPool.cs index 38192c11fc..c3dc47809f 100644 --- a/src/Core/NotificationHub/NotificationHubPool.cs +++ b/src/Core/Platform/Push/NotificationHub/NotificationHubPool.cs @@ -3,9 +3,7 @@ using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; public class NotificationHubPool : INotificationHubPool { diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/Platform/Push/NotificationHub/NotificationHubPushEngine.cs similarity index 95% rename from src/Core/NotificationHub/NotificationHubPushNotificationService.cs rename to src/Core/Platform/Push/NotificationHub/NotificationHubPushEngine.cs index 81ec82a25d..1d1eb2ef70 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/Platform/Push/NotificationHub/NotificationHubPushEngine.cs @@ -1,28 +1,23 @@ -#nullable enable -using System.Text.Json; +using System.Text.Json; using System.Text.RegularExpressions; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; -using Bit.Core.Platform.Push; -using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.Push.Internal; /// /// Sends mobile push notifications to the Azure Notification Hub. /// Used by Cloud-Hosted environments. /// Received by Firebase for Android or APNS for iOS. /// -public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer +public class NotificationHubPushEngine : IPushEngine, IPushRelayer { private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly IHttpContextAccessor _httpContextAccessor; @@ -30,11 +25,11 @@ public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer private readonly INotificationHubPool _notificationHubPool; private readonly ILogger _logger; - public NotificationHubPushNotificationService( + public NotificationHubPushEngine( IInstallationDeviceRepository installationDeviceRepository, INotificationHubPool notificationHubPool, IHttpContextAccessor httpContextAccessor, - ILogger logger, + ILogger logger, IGlobalSettings globalSettings) { _installationDeviceRepository = installationDeviceRepository; diff --git a/src/Core/Platform/Push/NotificationInfoAttribute.cs b/src/Core/Platform/Push/NotificationInfoAttribute.cs new file mode 100644 index 0000000000..ff134f5fda --- /dev/null +++ b/src/Core/Platform/Push/NotificationInfoAttribute.cs @@ -0,0 +1,44 @@ +using Bit.Core.Enums; + +namespace Bit.Core.Platform.Push; + +/// +/// Used to annotate information about a given . +/// +[AttributeUsage(AttributeTargets.Field)] +public class NotificationInfoAttribute : Attribute +{ + // Once upon a time we can feed this information into a C# analyzer to make sure that we validate + // the callsites of IPushNotificationService.PushAsync uses the correct payload type for the notification type + // for now this only exists as forced documentation to teams who create a push type. + + // It's especially on purpose that we allow ourselves to take a type name via just the string, + // this allows teams to make a push type that is only sent with a payload that exists in a separate assembly than + // this one. + + public NotificationInfoAttribute(string team, Type payloadType) + // It should be impossible to reference an unnamed type for an attributes constructor so this assertion should be safe. + : this(team, payloadType.FullName!) + { + Team = team; + } + + public NotificationInfoAttribute(string team, string payloadTypeName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(team); + ArgumentException.ThrowIfNullOrWhiteSpace(payloadTypeName); + + Team = team; + PayloadTypeName = payloadTypeName; + } + + /// + /// The name of the team that owns this . + /// + public string Team { get; } + + /// + /// The fully qualified type name of the payload that should be used when sending a notification of this type. + /// + public string PayloadTypeName { get; } +} diff --git a/src/Core/Platform/Push/Services/PushNotification.cs b/src/Core/Platform/Push/PushNotification.cs similarity index 96% rename from src/Core/Platform/Push/Services/PushNotification.cs rename to src/Core/Platform/Push/PushNotification.cs index e1d3f44cd8..3150b854a4 100644 --- a/src/Core/Platform/Push/Services/PushNotification.cs +++ b/src/Core/Platform/Push/PushNotification.cs @@ -6,6 +6,9 @@ namespace Bit.Core.Platform.Push; /// /// Contains constants for all the available targets for a given notification. /// +/// +/// Please reach out to the Platform team if you need a new target added. +/// public enum NotificationTarget { /// diff --git a/src/Core/Platform/Push/PushServiceCollectionExtensions.cs b/src/Core/Platform/Push/PushServiceCollectionExtensions.cs new file mode 100644 index 0000000000..b54ae64c08 --- /dev/null +++ b/src/Core/Platform/Push/PushServiceCollectionExtensions.cs @@ -0,0 +1,82 @@ +using Azure.Storage.Queues; +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for adding the Push feature. +/// +public static class PushServiceCollectionExtensions +{ + /// + /// Adds a to the services that can be used to send push notifications to + /// end user devices. This method is safe to be ran multiple time provided does not + /// change between calls. + /// + /// The to add services to. + /// The to use to configure services. + /// The for additional chaining. + public static IServiceCollection AddPush(this IServiceCollection services, GlobalSettings globalSettings) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(globalSettings); + + services.TryAddSingleton(TimeProvider.System); + services.TryAddSingleton(); + + if (globalSettings.SelfHosted) + { + if (globalSettings.Installation.Id == Guid.Empty) + { + throw new InvalidOperationException("Installation Id must be set for self-hosted installations."); + } + + if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) && + CoreHelpers.SettingHasValue(globalSettings.Installation.Key)) + { + // TODO: We should really define the HttpClient we will use here + services.AddHttpClient(); + services.AddHttpContextAccessor(); + // We also depend on IDeviceRepository but don't explicitly add it right now. + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + } + + if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && + CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) + { + // TODO: We should really define the HttpClient we will use here + services.AddHttpClient(); + services.AddHttpContextAccessor(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + } + } + else + { + services.TryAddSingleton(); + services.AddHttpContextAccessor(); + + // We also depend on IInstallationDeviceRepository but don't explicitly add it right now. + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + + services.TryAddSingleton(); + + if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) + { + services.TryAddKeyedSingleton("notifications", static (sp, _) => + { + var gs = sp.GetRequiredService(); + return new QueueClient(gs.Notifications.ConnectionString, "notifications"); + }); + + // We not IHttpContextAccessor will be added above, no need to do it here. + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + } + } + + return services; + } +} diff --git a/src/Core/Platform/Push/PushType.cs b/src/Core/Platform/Push/PushType.cs new file mode 100644 index 0000000000..7fcb60b4ef --- /dev/null +++ b/src/Core/Platform/Push/PushType.cs @@ -0,0 +1,93 @@ +using Bit.Core.Platform.Push; + +// TODO: This namespace should change to `Bit.Core.Platform.Push` +namespace Bit.Core.Enums; + +/// +/// +/// +/// +/// +/// When adding a new enum member you must annotate it with a +/// this is enforced with a unit test. It is preferred that you do NOT add new usings for the type referenced +/// in . +/// +/// +/// You may and are +/// +/// +public enum PushType : byte +{ + // When adding a new enum member you must annotate it with a NotificationInfoAttribute this is enforced with a unit + // test. It is preferred that you do NOT add new usings for the type referenced for the payload. You are also + // encouraged to define the payload type in your own teams owned code. + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncCipherPushNotification))] + SyncCipherUpdate = 0, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncCipherPushNotification))] + SyncCipherCreate = 1, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncCipherPushNotification))] + SyncLoginDelete = 2, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncFolderPushNotification))] + SyncFolderDelete = 3, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.UserPushNotification))] + SyncCiphers = 4, + + [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] + SyncVault = 5, + + [NotificationInfo("@bitwarden/team-admin-console-dev", typeof(Models.UserPushNotification))] + SyncOrgKeys = 6, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncFolderPushNotification))] + SyncFolderCreate = 7, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncFolderPushNotification))] + SyncFolderUpdate = 8, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.SyncCipherPushNotification))] + SyncCipherDelete = 9, + + [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] + SyncSettings = 10, + + [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] + LogOut = 11, + + [NotificationInfo("@bitwarden/team-tools-dev", typeof(Models.SyncSendPushNotification))] + SyncSendCreate = 12, + + [NotificationInfo("@bitwarden/team-tools-dev", typeof(Models.SyncSendPushNotification))] + SyncSendUpdate = 13, + + [NotificationInfo("@bitwarden/team-tools-dev", typeof(Models.SyncSendPushNotification))] + SyncSendDelete = 14, + + [NotificationInfo("@bitwarden/team-auth-dev", typeof(Models.AuthRequestPushNotification))] + AuthRequest = 15, + + [NotificationInfo("@bitwarden/team-auth-dev", typeof(Models.AuthRequestPushNotification))] + AuthRequestResponse = 16, + + [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] + SyncOrganizations = 17, + + [NotificationInfo("@bitwarden/team-billing-dev", typeof(Models.OrganizationStatusPushNotification))] + SyncOrganizationStatusChanged = 18, + + [NotificationInfo("@bitwarden/team-admin-console-dev", typeof(Models.OrganizationCollectionManagementPushNotification))] + SyncOrganizationCollectionSettingChanged = 19, + + [NotificationInfo("not-specified", typeof(Models.NotificationPushNotification))] + Notification = 20, + + [NotificationInfo("not-specified", typeof(Models.NotificationPushNotification))] + NotificationStatus = 21, + + [NotificationInfo("@bitwarden/team-vault-dev", typeof(Models.UserPushNotification))] + RefreshSecurityTasks = 22, +} diff --git a/src/Core/Platform/Push/Services/IPushRegistrationService.cs b/src/Core/Platform/PushRegistration/IPushRegistrationService.cs similarity index 79% rename from src/Core/Platform/Push/Services/IPushRegistrationService.cs rename to src/Core/Platform/PushRegistration/IPushRegistrationService.cs index 8e34e5e316..d650842f32 100644 --- a/src/Core/Platform/Push/Services/IPushRegistrationService.cs +++ b/src/Core/Platform/PushRegistration/IPushRegistrationService.cs @@ -1,10 +1,10 @@ -#nullable enable - -using Bit.Core.Enums; -using Bit.Core.NotificationHub; +using Bit.Core.Enums; +using Bit.Core.Platform.PushRegistration; +// TODO: Change this namespace to `Bit.Core.Platform.PushRegistration namespace Bit.Core.Platform.Push; + public interface IPushRegistrationService { Task CreateOrUpdateRegistrationAsync(PushRegistrationData data, string deviceId, string userId, string identifier, DeviceType type, IEnumerable organizationIds, Guid installationId); diff --git a/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs b/src/Core/Platform/PushRegistration/NoopPushRegistrationService.cs similarity index 86% rename from src/Core/Platform/Push/Services/NoopPushRegistrationService.cs rename to src/Core/Platform/PushRegistration/NoopPushRegistrationService.cs index 32efc95ce6..0aebcbf1f3 100644 --- a/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs +++ b/src/Core/Platform/PushRegistration/NoopPushRegistrationService.cs @@ -1,9 +1,7 @@ -#nullable enable +using Bit.Core.Enums; +using Bit.Core.Platform.Push; -using Bit.Core.Enums; -using Bit.Core.NotificationHub; - -namespace Bit.Core.Platform.Push.Internal; +namespace Bit.Core.Platform.PushRegistration.Internal; public class NoopPushRegistrationService : IPushRegistrationService { diff --git a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs b/src/Core/Platform/PushRegistration/NotificationHubPushRegistrationService.cs similarity index 99% rename from src/Core/NotificationHub/NotificationHubPushRegistrationService.cs rename to src/Core/Platform/PushRegistration/NotificationHubPushRegistrationService.cs index dc494eecd6..ee02e2bdf1 100644 --- a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs +++ b/src/Core/Platform/PushRegistration/NotificationHubPushRegistrationService.cs @@ -6,14 +6,13 @@ using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.PushRegistration.Internal; public class NotificationHubPushRegistrationService : IPushRegistrationService { diff --git a/src/Core/NotificationHub/PushRegistrationData.cs b/src/Core/Platform/PushRegistration/PushRegistrationData.cs similarity index 92% rename from src/Core/NotificationHub/PushRegistrationData.cs rename to src/Core/Platform/PushRegistration/PushRegistrationData.cs index c11ee7be23..844de4e1be 100644 --- a/src/Core/NotificationHub/PushRegistrationData.cs +++ b/src/Core/Platform/PushRegistration/PushRegistrationData.cs @@ -1,6 +1,4 @@ -namespace Bit.Core.NotificationHub; - -#nullable enable +namespace Bit.Core.Platform.PushRegistration; public record struct WebPushRegistrationData { diff --git a/src/Core/Platform/PushRegistration/PushRegistrationServiceCollectionExtensions.cs b/src/Core/Platform/PushRegistration/PushRegistrationServiceCollectionExtensions.cs new file mode 100644 index 0000000000..841902c964 --- /dev/null +++ b/src/Core/Platform/PushRegistration/PushRegistrationServiceCollectionExtensions.cs @@ -0,0 +1,54 @@ +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration.Internal; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for adding the Push Registration feature. +/// +public static class PushRegistrationServiceCollectionExtensions +{ + /// + /// Adds a to the service collection. + /// + /// The to add services to. + /// The for chaining. + public static IServiceCollection AddPushRegistration(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + // TODO: Should add feature that brings in IInstallationDeviceRepository once that is featurized + + // Register all possible variants under there concrete type. + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.AddHttpClient(); + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton(static sp => + { + var globalSettings = sp.GetRequiredService(); + + if (globalSettings.SelfHosted) + { + if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) && + CoreHelpers.SettingHasValue(globalSettings.Installation.Key)) + { + return sp.GetRequiredService(); + } + + return sp.GetRequiredService(); + } + + return sp.GetRequiredService(); + }); + + return services; + } +} diff --git a/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs b/src/Core/Platform/PushRegistration/RelayPushRegistrationService.cs similarity index 95% rename from src/Core/Platform/Push/Services/RelayPushRegistrationService.cs rename to src/Core/Platform/PushRegistration/RelayPushRegistrationService.cs index 20e405935b..96a259ecf8 100644 --- a/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs +++ b/src/Core/Platform/PushRegistration/RelayPushRegistrationService.cs @@ -1,14 +1,12 @@ -#nullable enable - -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models.Api; -using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push; using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.Extensions.Logging; -namespace Bit.Core.Platform.Push.Internal; +namespace Bit.Core.Platform.PushRegistration.Internal; public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService { diff --git a/src/Core/Services/IDeviceService.cs b/src/Core/Services/IDeviceService.cs index 78739e081d..ca13047c77 100644 --- a/src/Core/Services/IDeviceService.cs +++ b/src/Core/Services/IDeviceService.cs @@ -1,6 +1,6 @@ using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Entities; -using Bit.Core.NotificationHub; +using Bit.Core.Platform.PushRegistration; namespace Bit.Core.Services; diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index 931dfccdec..ea6e77aa8c 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -3,8 +3,8 @@ using Bit.Core.Auth.Utilities; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.PushRegistration; using Bit.Core.Repositories; using Bit.Core.Settings; diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 51383d650e..0dd5431dd7 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using AspNetCoreRateLimit; -using Azure.Storage.Queues; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; @@ -35,11 +34,10 @@ using Bit.Core.Identity; using Bit.Core.IdentityServer; using Bit.Core.KeyManagement; using Bit.Core.NotificationCenter; -using Bit.Core.NotificationHub; using Bit.Core.OrganizationFeatures; using Bit.Core.Platform; using Bit.Core.Platform.Push; -using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration.Internal; using Bit.Core.Repositories; using Bit.Core.Resources; using Bit.Core.SecretsManager.Repositories; @@ -279,46 +277,8 @@ public static class ServiceCollectionExtensions services.AddSingleton(); } - services.TryAddSingleton(TimeProvider.System); - - services.AddSingleton(); - if (globalSettings.SelfHosted) - { - if (globalSettings.Installation.Id == Guid.Empty) - { - throw new InvalidOperationException("Installation Id must be set for self-hosted installations."); - } - - if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) && - CoreHelpers.SettingHasValue(globalSettings.Installation.Key)) - { - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.AddSingleton(); - } - else - { - services.AddSingleton(); - } - - if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && - CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) - { - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - } - } - else - { - services.AddSingleton(); - services.AddSingleton(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.TryAddSingleton(); - if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) - { - services.AddKeyedSingleton("notifications", - (_, _) => new QueueClient(globalSettings.Notifications.ConnectionString, "notifications")); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - } - } + services.AddPush(globalSettings); + services.AddPushRegistration(); if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString)) { diff --git a/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs index 32d6389616..1f0ecd4835 100644 --- a/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs +++ b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs @@ -8,8 +8,8 @@ using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Api; using Bit.Core.Models.Data; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Installations; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using NSubstitute; using Xunit; diff --git a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs index d6a26255e9..e74ebc4c03 100644 --- a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs +++ b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs @@ -4,8 +4,8 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Api; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.PushRegistration; using Bit.Core.Settings; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs similarity index 98% rename from test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs index b223ef7252..961d7cd770 100644 --- a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs @@ -22,18 +22,18 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Platform.Push.Services; +namespace Bit.Core.Test.Platform.Push.Engines; [QueueClientCustomize] [SutProviderCustomize] -public class AzureQueuePushNotificationServiceTests +public class AzureQueuePushEngineTests { private static readonly Guid _deviceId = Guid.Parse("c4730f80-caaa-4772-97bd-5c0d23a2baa3"); private static readonly string _deviceIdentifier = "test_device_identifier"; private readonly FakeTimeProvider _fakeTimeProvider; private readonly Core.Settings.GlobalSettings _globalSettings = new(); - public AzureQueuePushNotificationServiceTests() + public AzureQueuePushEngineTests() { _fakeTimeProvider = new(); _fakeTimeProvider.SetUtcNow(DateTime.UtcNow); @@ -771,12 +771,11 @@ public class AzureQueuePushNotificationServiceTests var globalSettings = new Core.Settings.GlobalSettings(); - var sut = new AzureQueuePushNotificationService( + var sut = new AzureQueuePushEngine( queueClient, httpContextAccessor, globalSettings, - NullLogger.Instance, - _fakeTimeProvider + NullLogger.Instance ); await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id)); diff --git a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs similarity index 97% rename from test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs index 5231456d63..c61c2f37d0 100644 --- a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs @@ -2,16 +2,16 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.NotificationCenter.Entities; -using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.Extensions.Logging.Abstractions; -namespace Bit.Core.Test.Platform.Push.Services; +namespace Bit.Core.Test.Platform.Push.Engines; -public class NotificationsApiPushNotificationServiceTests : PushTestBase +public class NotificationsApiPushEngineTests : PushTestBase { - public NotificationsApiPushNotificationServiceTests() + public NotificationsApiPushEngineTests() { GlobalSettings.BaseServiceUri.InternalNotifications = "https://localhost:7777"; GlobalSettings.BaseServiceUri.InternalIdentity = "https://localhost:8888"; @@ -21,11 +21,11 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase protected override IPushEngine CreateService() { - return new NotificationsApiPushNotificationService( + return new NotificationsApiPushEngine( HttpClientFactory, GlobalSettings, HttpContextAccessor, - NullLogger.Instance + NullLogger.Instance ); } diff --git a/test/Core.Test/Platform/Push/Services/PushTestBase.cs b/test/Core.Test/Platform/Push/Engines/PushTestBase.cs similarity index 99% rename from test/Core.Test/Platform/Push/Services/PushTestBase.cs rename to test/Core.Test/Platform/Push/Engines/PushTestBase.cs index 3ff09f1064..9097028370 100644 --- a/test/Core.Test/Platform/Push/Services/PushTestBase.cs +++ b/test/Core.Test/Platform/Push/Engines/PushTestBase.cs @@ -10,6 +10,7 @@ using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -22,6 +23,8 @@ using NSubstitute; using RichardSzalay.MockHttp; using Xunit; +namespace Bit.Core.Test.Platform.Push.Engines; + public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService { public Guid InstallationId { get; } = installationId; diff --git a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs similarity index 98% rename from test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs index ddad05eda0..010ad40d13 100644 --- a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs @@ -5,7 +5,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Entities; using Bit.Core.NotificationCenter.Entities; -using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -15,7 +14,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; -namespace Bit.Core.Test.Platform.Push.Services; +namespace Bit.Core.Test.Platform.Push.Engines; public class RelayPushNotificationServiceTests : PushTestBase { @@ -39,12 +38,12 @@ public class RelayPushNotificationServiceTests : PushTestBase protected override IPushEngine CreateService() { - return new RelayPushNotificationService( + return new RelayPushEngine( HttpClientFactory, _deviceRepository, GlobalSettings, HttpContextAccessor, - NullLogger.Instance + NullLogger.Instance ); } diff --git a/test/Core.Test/Platform/Push/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/MultiServicePushNotificationServiceTests.cs new file mode 100644 index 0000000000..f0143bae51 --- /dev/null +++ b/test/Core.Test/Platform/Push/MultiServicePushNotificationServiceTests.cs @@ -0,0 +1,57 @@ +using Bit.Core.Enums; +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Time.Testing; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Platform.Push; + +public class MultiServicePushNotificationServiceTests +{ + private readonly IPushEngine _fakeEngine1; + private readonly IPushEngine _fakeEngine2; + + private readonly MultiServicePushNotificationService _sut; + + public MultiServicePushNotificationServiceTests() + { + _fakeEngine1 = Substitute.For(); + _fakeEngine2 = Substitute.For(); + + _sut = new MultiServicePushNotificationService( + [_fakeEngine1, _fakeEngine2], + NullLogger.Instance, + new GlobalSettings(), + new FakeTimeProvider() + ); + } + +#if DEBUG // This test requires debug code in the sut to work properly + [Fact] + public async Task PushAsync_CallsAllEngines() + { + var notification = new PushNotification + { + Target = NotificationTarget.User, + TargetId = Guid.NewGuid(), + Type = PushType.AuthRequest, + Payload = new { }, + ExcludeCurrentContext = false, + }; + + await _sut.PushAsync(notification); + + await _fakeEngine1 + .Received(1) + .PushAsync(Arg.Is>(n => ReferenceEquals(n, notification))); + + await _fakeEngine2 + .Received(1) + .PushAsync(Arg.Is>(n => ReferenceEquals(n, notification))); + } + +#endif +} diff --git a/test/Core.Test/NotificationHub/NotificationHubConnectionTests.cs b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubConnectionTests.cs similarity index 98% rename from test/Core.Test/NotificationHub/NotificationHubConnectionTests.cs rename to test/Core.Test/Platform/Push/NotificationHub/NotificationHubConnectionTests.cs index fc76e5c1b7..51ba3a10c6 100644 --- a/test/Core.Test/NotificationHub/NotificationHubConnectionTests.cs +++ b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubConnectionTests.cs @@ -1,9 +1,9 @@ -using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Settings; using Bit.Core.Utilities; using Xunit; -namespace Bit.Core.Test.NotificationHub; +namespace Bit.Core.Test.Platform.Push.NotificationHub; public class NotificationHubConnectionTests { diff --git a/test/Core.Test/NotificationHub/NotificationHubPoolTests.cs b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPoolTests.cs similarity index 98% rename from test/Core.Test/NotificationHub/NotificationHubPoolTests.cs rename to test/Core.Test/Platform/Push/NotificationHub/NotificationHubPoolTests.cs index dd9afb867e..5547ab55dd 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPoolTests.cs +++ b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPoolTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.Extensions.Logging; @@ -6,7 +6,7 @@ using NSubstitute; using Xunit; using static Bit.Core.Settings.GlobalSettings; -namespace Bit.Core.Test.NotificationHub; +namespace Bit.Core.Test.Platform.Push.NotificationHub; public class NotificationHubPoolTests { diff --git a/test/Core.Test/NotificationHub/NotificationHubProxyTests.cs b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubProxyTests.cs similarity index 93% rename from test/Core.Test/NotificationHub/NotificationHubProxyTests.cs rename to test/Core.Test/Platform/Push/NotificationHub/NotificationHubProxyTests.cs index b2e9c4f9f3..846b6e5fc4 100644 --- a/test/Core.Test/NotificationHub/NotificationHubProxyTests.cs +++ b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubProxyTests.cs @@ -1,11 +1,11 @@ using AutoFixture; -using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push.Internal; using Bit.Test.Common.AutoFixture; using Microsoft.Azure.NotificationHubs; using NSubstitute; using Xunit; -namespace Bit.Core.Test.NotificationHub; +namespace Bit.Core.Test.Platform.Push.NotificationHub; public class NotificationHubProxyTests { diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs similarity index 98% rename from test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs index 54a6f84339..a32b112675 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Nodes; using Bit.Core.Auth.Entities; using Bit.Core.Context; @@ -7,10 +6,11 @@ using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Core.Test.Platform.Push.Engines; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Bit.Test.Common.AutoFixture; @@ -22,7 +22,7 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Core.Test.NotificationHub; +namespace Bit.Core.Test.Platform.Push.NotificationHub; [SutProviderCustomize] [NotificationStatusCustomize] @@ -621,11 +621,11 @@ public class NotificationHubPushNotificationServiceTests fakeTimeProvider.SetUtcNow(_now); - var sut = new NotificationHubPushNotificationService( + var sut = new NotificationHubPushEngine( installationDeviceRepository, notificationHubPool, httpContextAccessor, - NullLogger.Instance, + NullLogger.Instance, globalSettings ); @@ -676,7 +676,7 @@ public class NotificationHubPushNotificationServiceTests }; private static async Task AssertSendTemplateNotificationAsync( - SutProvider sutProvider, PushType type, object payload, string tag) + SutProvider sutProvider, PushType type, object payload, string tag) { await sutProvider.GetDependency() .Received(1) diff --git a/test/Core.Test/Platform/Push/PushServiceCollectionExtensionsTests.cs b/test/Core.Test/Platform/Push/PushServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..0ab1a91195 --- /dev/null +++ b/test/Core.Test/Platform/Push/PushServiceCollectionExtensionsTests.cs @@ -0,0 +1,198 @@ +using Bit.Core.Auth.Models.Data; +using Bit.Core.Entities; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; +using Bit.Core.Repositories; +using Bit.Core.Repositories.Noop; +using Bit.Core.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Xunit; + +namespace Bit.Core.Test.Platform.Push; + +public class PushServiceCollectionExtensionsTests +{ + [Fact] + public void AddPush_SelfHosted_NoConfig_NoEngines() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + Assert.Empty(engines); + } + + [Fact] + public void AddPush_SelfHosted_ConfiguredForRelay_RelayEngineAdded() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() }, + { "GlobalSettings:Installation:Key", "some_key"}, + { "GlobalSettings:PushRelayBaseUri", "https://example.com" }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + var engine = Assert.Single(engines); + Assert.IsType(engine); + } + + [Fact] + public void AddPush_SelfHosted_ConfiguredForApi_ApiEngineAdded() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() }, + { "GlobalSettings:InternalIdentityKey", "some_key"}, + { "GlobalSettings:BaseServiceUri", "https://example.com" }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + var engine = Assert.Single(engines); + Assert.IsType(engine); + } + + [Fact] + public void AddPush_SelfHosted_ConfiguredForRelayAndApi_TwoEnginesAdded() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() }, + { "GlobalSettings:Installation:Key", "some_key"}, + { "GlobalSettings:PushRelayBaseUri", "https://example.com" }, + { "GlobalSettings:InternalIdentityKey", "some_key"}, + { "GlobalSettings:BaseServiceUri", "https://example.com" }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + Assert.Collection( + engines, + e => Assert.IsType(e), + e => Assert.IsType(e) + ); + } + + [Fact] + public void AddPush_Cloud_NoConfig_AddsNotificationHub() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "false" }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + var engine = Assert.Single(engines); + Assert.IsType(engine); + } + + [Fact] + public void AddPush_Cloud_HasNotificationConnectionString_TwoEngines() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "false" }, + { "GlobalSettings:Notifications:ConnectionString", "UseDevelopmentStorage=true" }, + }); + + _ = services.GetRequiredService(); + var engines = services.GetServices(); + + Assert.Collection( + engines, + e => Assert.IsType(e), + e => Assert.IsType(e) + ); + } + + [Fact] + public void AddPush_Cloud_CalledTwice_DoesNotAddServicesTwice() + { + var services = new ServiceCollection(); + + var config = new Dictionary + { + { "GlobalSettings:SelfHosted", "false" }, + { "GlobalSettings:Notifications:ConnectionString", "UseDevelopmentStorage=true" }, + }; + + AddServices(services, config); + + var initialCount = services.Count; + + // Add services again + AddServices(services, config); + + Assert.Equal(initialCount, services.Count); + } + + private static ServiceProvider Build(Dictionary initialData) + { + var services = new ServiceCollection(); + + AddServices(services, initialData); + + return services.BuildServiceProvider(); + } + + private static void AddServices(IServiceCollection services, Dictionary initialData) + { + // A minimal service collection is always expected to have logging, config, and global settings + // pre-registered. + + services.AddLogging(); + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(initialData) + .Build(); + + services.TryAddSingleton(config); + var globalSettings = new GlobalSettings(); + config.GetSection("GlobalSettings").Bind(globalSettings); + + services.TryAddSingleton(globalSettings); + services.TryAddSingleton(globalSettings); + + // Temporary until AddPush can add it themselves directly. + services.TryAddSingleton(); + + // Temporary until AddPush can add it themselves directly. + services.TryAddSingleton(); + + services.AddPush(globalSettings); + } + + private class StubDeviceRepository : IDeviceRepository + { + public Task ClearPushTokenAsync(Guid id) => throw new NotImplementedException(); + public Task CreateAsync(Device obj) => throw new NotImplementedException(); + public Task DeleteAsync(Device obj) => throw new NotImplementedException(); + public Task GetByIdAsync(Guid id, Guid userId) => throw new NotImplementedException(); + public Task GetByIdAsync(Guid id) => throw new NotImplementedException(); + public Task GetByIdentifierAsync(string identifier) => throw new NotImplementedException(); + public Task GetByIdentifierAsync(string identifier, Guid userId) => throw new NotImplementedException(); + public Task> GetManyByUserIdAsync(Guid userId) => throw new NotImplementedException(); + public Task> GetManyByUserIdWithDeviceAuth(Guid userId) => throw new NotImplementedException(); + public Task ReplaceAsync(Device obj) => throw new NotImplementedException(); + public UpdateEncryptedDataForKeyRotation UpdateKeysForRotationAsync(Guid userId, IEnumerable devices) => throw new NotImplementedException(); + public Task UpsertAsync(Device obj) => throw new NotImplementedException(); + } +} diff --git a/test/Core.Test/Platform/Push/PushTypeTests.cs b/test/Core.Test/Platform/Push/PushTypeTests.cs new file mode 100644 index 0000000000..0d1e389410 --- /dev/null +++ b/test/Core.Test/Platform/Push/PushTypeTests.cs @@ -0,0 +1,64 @@ +using System.Diagnostics; +using System.Reflection; +using Bit.Core.Enums; +using Bit.Core.Platform.Push; +using Xunit; + +namespace Bit.Core.Test.Platform.Push; + +public class PushTypeTests +{ + [Fact] + public void AllEnumMembersHaveUniqueValue() + { + // No enum member should use the same value as another named member. + + var usedNumbers = new HashSet(); + var enumMembers = Enum.GetValues(); + + foreach (var enumMember in enumMembers) + { + if (!usedNumbers.Add((byte)enumMember)) + { + Assert.Fail($"Enum number value ({(byte)enumMember}) on {enumMember} is already in use."); + } + } + } + + [Fact] + public void AllEnumMembersHaveNotificationInfoAttribute() + { + // Every enum member should be annotated with [NotificationInfo] + + foreach (var member in typeof(PushType).GetMembers(BindingFlags.Public | BindingFlags.Static)) + { + var notificationInfoAttribute = member.GetCustomAttribute(); + if (notificationInfoAttribute is null) + { + Assert.Fail($"PushType.{member.Name} is missing a required [NotificationInfo(\"team-name\", typeof(MyType))] attribute."); + } + } + } + + [Fact] + public void AllEnumValuesAreInSequence() + { + // There should not be any gaps in the numbers defined for an enum, that being if someone last defined 22 + // the next number used should be 23 not 24 or any other number. + + var sortedValues = Enum.GetValues() + .Order() + .ToArray(); + + Debug.Assert(sortedValues.Length > 0); + + var lastValue = sortedValues[0]; + + foreach (var value in sortedValues[1..]) + { + var expectedValue = ++lastValue; + + Assert.Equal(expectedValue, value); + } + } +} diff --git a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs deleted file mode 100644 index a1bc2c6547..0000000000 --- a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable enable - -namespace Bit.Core.Test.Platform.Push.Services; - -public class MultiServicePushNotificationServiceTests -{ - // TODO: Can add a couple tests here -} diff --git a/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs b/test/Core.Test/Platform/PushRegistration/NotificationHubPushRegistrationServiceTests.cs similarity index 99% rename from test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs rename to test/Core.Test/Platform/PushRegistration/NotificationHubPushRegistrationServiceTests.cs index b30cd3dda8..43ac916ed6 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs +++ b/test/Core.Test/Platform/PushRegistration/NotificationHubPushRegistrationServiceTests.cs @@ -1,6 +1,8 @@ #nullable enable using Bit.Core.Enums; -using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration; +using Bit.Core.Platform.PushRegistration.Internal; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/Platform/PushRegistration/PushRegistrationServiceCollectionExtensionsTests.cs b/test/Core.Test/Platform/PushRegistration/PushRegistrationServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..19760f93ab --- /dev/null +++ b/test/Core.Test/Platform/PushRegistration/PushRegistrationServiceCollectionExtensionsTests.cs @@ -0,0 +1,108 @@ +using Bit.Core.Platform.Push; +using Bit.Core.Platform.PushRegistration.Internal; +using Bit.Core.Repositories; +using Bit.Core.Repositories.Noop; +using Bit.Core.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Xunit; + +namespace Bit.Core.Test.Platform.PushRegistration; + +public class PushRegistrationServiceCollectionExtensionsTests +{ + [Fact] + public void AddPushRegistration_Cloud_CreatesNotificationHubRegistrationService() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "false" }, + }); + + var pushRegistrationService = services.GetRequiredService(); + Assert.IsType(pushRegistrationService); + } + + [Fact] + public void AddPushRegistration_SelfHosted_NoOtherConfig_ReturnsNoopRegistrationService() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + }); + + var pushRegistrationService = services.GetRequiredService(); + Assert.IsType(pushRegistrationService); + } + + [Fact] + public void AddPushRegistration_SelfHosted_RelayConfig_ReturnsRelayRegistrationService() + { + var services = Build(new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:PushRelayBaseUri", "https://example.com" }, + { "GlobalSettings:Installation:Key", "some_key" }, + }); + + var pushRegistrationService = services.GetRequiredService(); + Assert.IsType(pushRegistrationService); + } + + [Fact] + public void AddPushRegistration_MultipleTimes_NoAdditionalServices() + { + var services = new ServiceCollection(); + + var config = new Dictionary + { + { "GlobalSettings:SelfHosted", "true" }, + { "GlobalSettings:PushRelayBaseUri", "https://example.com" }, + { "GlobalSettings:Installation:Key", "some_key" }, + }; + + AddServices(services, config); + + // Add services again + services.AddPushRegistration(); + + var provider = services.BuildServiceProvider(); + + Assert.Single(provider.GetServices()); + } + + private static ServiceProvider Build(Dictionary initialData) + { + var services = new ServiceCollection(); + + AddServices(services, initialData); + + return services.BuildServiceProvider(); + } + + private static void AddServices(IServiceCollection services, Dictionary initialData) + { + // A minimal service collection is always expected to have logging, config, and global settings + // pre-registered. + + services.AddLogging(); + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(initialData) + .Build(); + + services.TryAddSingleton(config); + var globalSettings = new GlobalSettings(); + config.GetSection("GlobalSettings").Bind(globalSettings); + + services.TryAddSingleton(globalSettings); + services.TryAddSingleton(globalSettings); + + + // Temporary until AddPushRegistration can add it themselves directly. + services.TryAddSingleton(); + + services.AddPushRegistration(); + } +} diff --git a/test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs b/test/Core.Test/Platform/PushRegistration/RelayPushRegistrationServiceTests.cs similarity index 95% rename from test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs rename to test/Core.Test/Platform/PushRegistration/RelayPushRegistrationServiceTests.cs index 062b4a96a8..1abadacd24 100644 --- a/test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs +++ b/test/Core.Test/Platform/PushRegistration/RelayPushRegistrationServiceTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration.Internal; using Bit.Core.Settings; using Microsoft.Extensions.Logging; using NSubstitute; diff --git a/test/Core.Test/Services/DeviceServiceTests.cs b/test/Core.Test/Services/DeviceServiceTests.cs index b454a0c04b..f34a906404 100644 --- a/test/Core.Test/Services/DeviceServiceTests.cs +++ b/test/Core.Test/Services/DeviceServiceTests.cs @@ -4,8 +4,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations.OrganizationUsers; -using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.PushRegistration; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index 1d39d63ef7..92e80b073d 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -2,7 +2,7 @@ using Bit.Core.Billing.Organizations.Services; using Bit.Core.Billing.Services; using Bit.Core.Platform.Push; -using Bit.Core.Platform.Push.Internal; +using Bit.Core.Platform.PushRegistration.Internal; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Infrastructure.EntityFramework.Repositories;