mirror of
https://github.com/bitwarden/server
synced 2025-12-15 15:53:59 +00:00
[PM-19659] Clean up Notifications code (#6244)
* Move PushType to Platform Folder - Move the PushType next to the rest of push notification code - Specifically exclude it from needing Platform code review - Add tests establishing rules Platform has for usage of this enum, making it safe to have no owner * Move NotificationHub code into Platform/Push directory * Update NotificationHub namespace imports * Add attribute for storing push type metadata * Rename Push Engines to have PushEngine suffix * Move Push Registration items to their own directory * Push code move * Add expected usage comment * Add Push feature registration method - Make method able to be called multipes times with no ill effects * Add Push Registration service entrypoint and tests * Use new service entrypoints * Test changes
This commit is contained in:
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -92,6 +92,8 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev
|
|||||||
**/.dockerignore @bitwarden/team-platform-dev
|
**/.dockerignore @bitwarden/team-platform-dev
|
||||||
**/Dockerfile @bitwarden/team-platform-dev
|
**/Dockerfile @bitwarden/team-platform-dev
|
||||||
**/entrypoint.sh @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)
|
# Multiple owners - DO NOT REMOVE (BRE)
|
||||||
**/packages.lock.json
|
**/packages.lock.json
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Api.Models.Request;
|
namespace Bit.Api.Models.Request;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ using System.Text.Json;
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Platform.Push.Internal;
|
using Bit.Core.Platform.Push.Internal;
|
||||||
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.NotificationCenter.Enums;
|
using Bit.Core.NotificationCenter.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Models;
|
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<T>
|
public class PushNotificationData<T>
|
||||||
{
|
{
|
||||||
public PushNotificationData(PushType type, T payload, string? contextId)
|
public PushNotificationData(PushType type, T payload, string? contextId)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using System.Text.Json;
|
||||||
using System.Text.Json;
|
|
||||||
using Azure.Storage.Queues;
|
using Azure.Storage.Queues;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@@ -13,17 +12,16 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Bit.Core.Platform.Push.Internal;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class AzureQueuePushNotificationService : IPushEngine
|
public class AzureQueuePushEngine : IPushEngine
|
||||||
{
|
{
|
||||||
private readonly QueueClient _queueClient;
|
private readonly QueueClient _queueClient;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
public AzureQueuePushNotificationService(
|
public AzureQueuePushEngine(
|
||||||
[FromKeyedServices("notifications")] QueueClient queueClient,
|
[FromKeyedServices("notifications")] QueueClient queueClient,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
ILogger<AzureQueuePushNotificationService> logger,
|
ILogger<AzureQueuePushEngine> logger)
|
||||||
TimeProvider timeProvider)
|
|
||||||
{
|
{
|
||||||
_queueClient = queueClient;
|
_queueClient = queueClient;
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -8,7 +7,7 @@ namespace Bit.Core.Platform.Push.Internal;
|
|||||||
|
|
||||||
public class MultiServicePushNotificationService : IPushNotificationService
|
public class MultiServicePushNotificationService : IPushNotificationService
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IPushEngine> _services;
|
private readonly IPushEngine[] _services;
|
||||||
|
|
||||||
public Guid InstallationId { get; }
|
public Guid InstallationId { get; }
|
||||||
|
|
||||||
@@ -22,7 +21,8 @@ public class MultiServicePushNotificationService : IPushNotificationService
|
|||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
TimeProvider timeProvider)
|
TimeProvider timeProvider)
|
||||||
{
|
{
|
||||||
_services = services;
|
// Filter out any NoopPushEngine's
|
||||||
|
_services = [.. services.Where(engine => engine is not NoopPushEngine)];
|
||||||
|
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
Logger.LogInformation("Hub services: {Services}", _services.Count());
|
Logger.LogInformation("Hub services: {Services}", _services.Count());
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
|
||||||
namespace Bit.Core.Platform.Push.Internal;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
internal class NoopPushNotificationService : IPushEngine
|
internal class NoopPushEngine : IPushEngine
|
||||||
{
|
{
|
||||||
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds) => Task.CompletedTask;
|
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds) => Task.CompletedTask;
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@@ -8,23 +7,22 @@ using Bit.Core.Vault.Entities;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
// This service is not in the `Internal` namespace because it has direct external references.
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
namespace Bit.Core.Platform.Push;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends non-mobile push notifications to the Azure Queue Api, later received by Notifications Api.
|
/// Sends non-mobile push notifications to the Azure Queue Api, later received by Notifications Api.
|
||||||
/// Used by Cloud-Hosted environments.
|
/// Used by Cloud-Hosted environments.
|
||||||
/// Received by AzureQueueHostedService message receiver in Notifications project.
|
/// Received by AzureQueueHostedService message receiver in Notifications project.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushEngine
|
public class NotificationsApiPushEngine : BaseIdentityClientService, IPushEngine
|
||||||
{
|
{
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
public NotificationsApiPushNotificationService(
|
public NotificationsApiPushEngine(
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ILogger<NotificationsApiPushNotificationService> logger)
|
ILogger<NotificationsApiPushEngine> logger)
|
||||||
: base(
|
: base(
|
||||||
httpFactory,
|
httpFactory,
|
||||||
globalSettings.BaseServiceUri.InternalNotifications,
|
globalSettings.BaseServiceUri.InternalNotifications,
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
@@ -19,18 +18,18 @@ namespace Bit.Core.Platform.Push.Internal;
|
|||||||
/// Used by Self-Hosted environments.
|
/// Used by Self-Hosted environments.
|
||||||
/// Received by PushController endpoint in Api project.
|
/// Received by PushController endpoint in Api project.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RelayPushNotificationService : BaseIdentityClientService, IPushEngine
|
public class RelayPushEngine : BaseIdentityClientService, IPushEngine
|
||||||
{
|
{
|
||||||
private readonly IDeviceRepository _deviceRepository;
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
|
|
||||||
public RelayPushNotificationService(
|
public RelayPushEngine(
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
IDeviceRepository deviceRepository,
|
IDeviceRepository deviceRepository,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ILogger<RelayPushNotificationService> logger)
|
ILogger<RelayPushEngine> logger)
|
||||||
: base(
|
: base(
|
||||||
httpFactory,
|
httpFactory,
|
||||||
globalSettings.PushRelayBaseUri,
|
globalSettings.PushRelayBaseUri,
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
|
||||||
namespace Bit.Core.Platform.Push;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public interface IPushEngine
|
public interface IPushEngine
|
||||||
{
|
{
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
@@ -10,10 +9,27 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Bit.Core.Platform.Push;
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to Push notifications to end-user devices.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// New notifications should not be wired up inside this service. You may either directly call the
|
||||||
|
/// <see cref="PushAsync"/> 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.
|
||||||
|
/// </remarks>
|
||||||
public interface IPushNotificationService
|
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; }
|
Guid InstallationId { get; }
|
||||||
|
|
||||||
|
[Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")]
|
||||||
TimeProvider TimeProvider { get; }
|
TimeProvider TimeProvider { get; }
|
||||||
|
|
||||||
|
[Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")]
|
||||||
ILogger Logger { get; }
|
ILogger Logger { get; }
|
||||||
|
|
||||||
#region Legacy method, to be removed soon.
|
#region Legacy method, to be removed soon.
|
||||||
@@ -80,7 +96,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -94,7 +112,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -108,7 +128,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -122,7 +144,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -136,7 +160,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -150,7 +176,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = excludeCurrentContextFromPush,
|
ExcludeCurrentContext = excludeCurrentContextFromPush,
|
||||||
});
|
});
|
||||||
@@ -231,7 +259,9 @@ public interface IPushNotificationService
|
|||||||
ClientType = notification.ClientType,
|
ClientType = notification.ClientType,
|
||||||
UserId = notification.UserId,
|
UserId = notification.UserId,
|
||||||
OrganizationId = notification.OrganizationId,
|
OrganizationId = notification.OrganizationId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
InstallationId = notification.Global ? InstallationId : null,
|
InstallationId = notification.Global ? InstallationId : null,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
TaskId = notification.TaskId,
|
TaskId = notification.TaskId,
|
||||||
Title = notification.Title,
|
Title = notification.Title,
|
||||||
Body = notification.Body,
|
Body = notification.Body,
|
||||||
@@ -246,7 +276,9 @@ public interface IPushNotificationService
|
|||||||
{
|
{
|
||||||
// TODO: Think about this a bit more
|
// TODO: Think about this a bit more
|
||||||
target = NotificationTarget.Installation;
|
target = NotificationTarget.Installation;
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
targetId = InstallationId;
|
targetId = InstallationId;
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
else if (notification.UserId.HasValue)
|
else if (notification.UserId.HasValue)
|
||||||
{
|
{
|
||||||
@@ -260,7 +292,9 @@ public interface IPushNotificationService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
|
Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +319,9 @@ public interface IPushNotificationService
|
|||||||
ClientType = notification.ClientType,
|
ClientType = notification.ClientType,
|
||||||
UserId = notification.UserId,
|
UserId = notification.UserId,
|
||||||
OrganizationId = notification.OrganizationId,
|
OrganizationId = notification.OrganizationId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
InstallationId = notification.Global ? InstallationId : null,
|
InstallationId = notification.Global ? InstallationId : null,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
TaskId = notification.TaskId,
|
TaskId = notification.TaskId,
|
||||||
Title = notification.Title,
|
Title = notification.Title,
|
||||||
Body = notification.Body,
|
Body = notification.Body,
|
||||||
@@ -302,7 +338,9 @@ public interface IPushNotificationService
|
|||||||
{
|
{
|
||||||
// TODO: Think about this a bit more
|
// TODO: Think about this a bit more
|
||||||
target = NotificationTarget.Installation;
|
target = NotificationTarget.Installation;
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
targetId = InstallationId;
|
targetId = InstallationId;
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
else if (notification.UserId.HasValue)
|
else if (notification.UserId.HasValue)
|
||||||
{
|
{
|
||||||
@@ -316,7 +354,9 @@ public interface IPushNotificationService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
|
Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +438,9 @@ public interface IPushNotificationService
|
|||||||
Payload = new UserPushNotification
|
Payload = new UserPushNotification
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
|
#pragma warning disable BWP0001 // Type or member is obsolete
|
||||||
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
Date = TimeProvider.GetUtcNow().UtcDateTime,
|
||||||
|
#pragma warning restore BWP0001 // Type or member is obsolete
|
||||||
},
|
},
|
||||||
ExcludeCurrentContext = false,
|
ExcludeCurrentContext = false,
|
||||||
});
|
});
|
||||||
@@ -406,6 +448,12 @@ public interface IPushNotificationService
|
|||||||
|
|
||||||
Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds);
|
Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes a notification to devices based on the settings given to us in <see cref="PushNotification{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the payload to be sent along with the notification.</typeparam>
|
||||||
|
/// <param name="pushNotification"></param>
|
||||||
|
/// <returns>A task that is NOT guarunteed to have sent the notification by the time the task resolves.</returns>
|
||||||
Task PushAsync<T>(PushNotification<T> pushNotification)
|
Task PushAsync<T>(PushNotification<T> pushNotification)
|
||||||
where T : class;
|
where T : class;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
#nullable enable
|
using System.Text.Json;
|
||||||
|
|
||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Platform.Push.Internal;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public interface INotificationHubProxy
|
public interface INotificationHubProxy
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public interface INotificationHubPool
|
public interface INotificationHubPool
|
||||||
{
|
{
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class NotificationHubClientProxy : INotificationHubProxy
|
public class NotificationHubClientProxy : INotificationHubProxy
|
||||||
{
|
{
|
||||||
@@ -6,9 +6,7 @@ using Bit.Core.Settings;
|
|||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class NotificationHubConnection
|
public class NotificationHubConnection
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class NotificationHubPool : INotificationHubPool
|
public class NotificationHubPool : INotificationHubPool
|
||||||
{
|
{
|
||||||
@@ -1,28 +1,23 @@
|
|||||||
#nullable enable
|
using System.Text.Json;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Platform.Push;
|
|
||||||
using Bit.Core.Platform.Push.Internal;
|
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends mobile push notifications to the Azure Notification Hub.
|
/// Sends mobile push notifications to the Azure Notification Hub.
|
||||||
/// Used by Cloud-Hosted environments.
|
/// Used by Cloud-Hosted environments.
|
||||||
/// Received by Firebase for Android or APNS for iOS.
|
/// Received by Firebase for Android or APNS for iOS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer
|
public class NotificationHubPushEngine : IPushEngine, IPushRelayer
|
||||||
{
|
{
|
||||||
private readonly IInstallationDeviceRepository _installationDeviceRepository;
|
private readonly IInstallationDeviceRepository _installationDeviceRepository;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
@@ -30,11 +25,11 @@ public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer
|
|||||||
private readonly INotificationHubPool _notificationHubPool;
|
private readonly INotificationHubPool _notificationHubPool;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public NotificationHubPushNotificationService(
|
public NotificationHubPushEngine(
|
||||||
IInstallationDeviceRepository installationDeviceRepository,
|
IInstallationDeviceRepository installationDeviceRepository,
|
||||||
INotificationHubPool notificationHubPool,
|
INotificationHubPool notificationHubPool,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ILogger<NotificationHubPushNotificationService> logger,
|
ILogger<NotificationHubPushEngine> logger,
|
||||||
IGlobalSettings globalSettings)
|
IGlobalSettings globalSettings)
|
||||||
{
|
{
|
||||||
_installationDeviceRepository = installationDeviceRepository;
|
_installationDeviceRepository = installationDeviceRepository;
|
||||||
44
src/Core/Platform/Push/NotificationInfoAttribute.cs
Normal file
44
src/Core/Platform/Push/NotificationInfoAttribute.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to annotate information about a given <see cref="PushType"/>.
|
||||||
|
/// </summary>
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the team that owns this <see cref="PushType"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string Team { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fully qualified type name of the payload that should be used when sending a notification of this type.
|
||||||
|
/// </summary>
|
||||||
|
public string PayloadTypeName { get; }
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ namespace Bit.Core.Platform.Push;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains constants for all the available targets for a given notification.
|
/// Contains constants for all the available targets for a given notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Please reach out to the Platform team if you need a new target added.
|
||||||
|
/// </remarks>
|
||||||
public enum NotificationTarget
|
public enum NotificationTarget
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
82
src/Core/Platform/Push/PushServiceCollectionExtensions.cs
Normal file
82
src/Core/Platform/Push/PushServiceCollectionExtensions.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for adding the Push feature.
|
||||||
|
/// </summary>
|
||||||
|
public static class PushServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="IPushNotificationService"/> 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 <see cref="GlobalSettings"/> does not
|
||||||
|
/// change between calls.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
||||||
|
/// <param name="globalSettings">The <see cref="GlobalSettings"/> to use to configure services.</param>
|
||||||
|
/// <returns>The <see cref="IServiceCollection"/> for additional chaining.</returns>
|
||||||
|
public static IServiceCollection AddPush(this IServiceCollection services, GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
ArgumentNullException.ThrowIfNull(globalSettings);
|
||||||
|
|
||||||
|
services.TryAddSingleton(TimeProvider.System);
|
||||||
|
services.TryAddSingleton<IPushNotificationService, MultiServicePushNotificationService>();
|
||||||
|
|
||||||
|
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<IPushEngine, RelayPushEngine>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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<IPushEngine, NotificationsApiPushEngine>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.TryAddSingleton<INotificationHubPool, NotificationHubPool>();
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
// We also depend on IInstallationDeviceRepository but don't explicitly add it right now.
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPushEngine, NotificationHubPushEngine>());
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPushRelayer, NotificationHubPushEngine>();
|
||||||
|
|
||||||
|
if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString))
|
||||||
|
{
|
||||||
|
services.TryAddKeyedSingleton("notifications", static (sp, _) =>
|
||||||
|
{
|
||||||
|
var gs = sp.GetRequiredService<GlobalSettings>();
|
||||||
|
return new QueueClient(gs.Notifications.ConnectionString, "notifications");
|
||||||
|
});
|
||||||
|
|
||||||
|
// We not IHttpContextAccessor will be added above, no need to do it here.
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPushEngine, AzureQueuePushEngine>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/Core/Platform/Push/PushType.cs
Normal file
93
src/Core/Platform/Push/PushType.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using Bit.Core.Platform.Push;
|
||||||
|
|
||||||
|
// TODO: This namespace should change to `Bit.Core.Platform.Push`
|
||||||
|
namespace Bit.Core.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// When adding a new enum member you must annotate it with a <see cref="NotificationInfoAttribute"/>
|
||||||
|
/// this is enforced with a unit test. It is preferred that you do NOT add new usings for the type referenced
|
||||||
|
/// in <see cref="NotificationInfoAttribute"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// You may and are
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
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,
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
|
|
||||||
|
// TODO: Change this namespace to `Bit.Core.Platform.PushRegistration
|
||||||
namespace Bit.Core.Platform.Push;
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
|
|
||||||
public interface IPushRegistrationService
|
public interface IPushRegistrationService
|
||||||
{
|
{
|
||||||
Task CreateOrUpdateRegistrationAsync(PushRegistrationData data, string deviceId, string userId, string identifier, DeviceType type, IEnumerable<string> organizationIds, Guid installationId);
|
Task CreateOrUpdateRegistrationAsync(PushRegistrationData data, string deviceId, string userId, string identifier, DeviceType type, IEnumerable<string> organizationIds, Guid installationId);
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
|
|
||||||
using Bit.Core.Enums;
|
namespace Bit.Core.Platform.PushRegistration.Internal;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
|
|
||||||
namespace Bit.Core.Platform.Push.Internal;
|
|
||||||
|
|
||||||
public class NoopPushRegistrationService : IPushRegistrationService
|
public class NoopPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
||||||
@@ -6,14 +6,13 @@ using System.Text.Json;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.PushRegistration.Internal;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
namespace Bit.Core.NotificationHub;
|
namespace Bit.Core.Platform.PushRegistration;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public record struct WebPushRegistrationData
|
public record struct WebPushRegistrationData
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for adding the Push Registration feature.
|
||||||
|
/// </summary>
|
||||||
|
public static class PushRegistrationServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="IPushRegistrationService"/> to the service collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
||||||
|
/// <returns>The <see cref="IServiceCollection"/> for chaining.</returns>
|
||||||
|
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<RelayPushRegistrationService>();
|
||||||
|
services.TryAddSingleton<NoopPushRegistrationService>();
|
||||||
|
|
||||||
|
services.AddHttpClient();
|
||||||
|
services.TryAddSingleton<INotificationHubPool, NotificationHubPool>();
|
||||||
|
services.TryAddSingleton<NotificationHubPushRegistrationService>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPushRegistrationService>(static sp =>
|
||||||
|
{
|
||||||
|
var globalSettings = sp.GetRequiredService<GlobalSettings>();
|
||||||
|
|
||||||
|
if (globalSettings.SelfHosted)
|
||||||
|
{
|
||||||
|
if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) &&
|
||||||
|
CoreHelpers.SettingHasValue(globalSettings.Installation.Key))
|
||||||
|
{
|
||||||
|
return sp.GetRequiredService<RelayPushRegistrationService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp.GetRequiredService<NoopPushRegistrationService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp.GetRequiredService<NotificationHubPushRegistrationService>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
#nullable enable
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Platform.Push.Internal;
|
namespace Bit.Core.Platform.PushRegistration.Internal;
|
||||||
|
|
||||||
public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService
|
public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Bit.Core.Auth.Models.Api.Request;
|
using Bit.Core.Auth.Models.Api.Request;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.PushRegistration;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Bit.Core.Auth.Utilities;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.Reflection;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using AspNetCoreRateLimit;
|
using AspNetCoreRateLimit;
|
||||||
using Azure.Storage.Queues;
|
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||||
@@ -35,11 +34,10 @@ using Bit.Core.Identity;
|
|||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
using Bit.Core.KeyManagement;
|
using Bit.Core.KeyManagement;
|
||||||
using Bit.Core.NotificationCenter;
|
using Bit.Core.NotificationCenter;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.OrganizationFeatures;
|
using Bit.Core.OrganizationFeatures;
|
||||||
using Bit.Core.Platform;
|
using Bit.Core.Platform;
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Platform.Push.Internal;
|
using Bit.Core.Platform.PushRegistration.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Resources;
|
using Bit.Core.Resources;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
@@ -279,46 +277,8 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
|
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
services.TryAddSingleton(TimeProvider.System);
|
services.AddPush(globalSettings);
|
||||||
|
services.AddPushRegistration();
|
||||||
services.AddSingleton<IPushNotificationService, MultiServicePushNotificationService>();
|
|
||||||
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<IPushEngine, RelayPushNotificationService>());
|
|
||||||
services.AddSingleton<IPushRegistrationService, RelayPushRegistrationService>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) &&
|
|
||||||
CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications))
|
|
||||||
{
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPushEngine, NotificationsApiPushNotificationService>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<INotificationHubPool, NotificationHubPool>();
|
|
||||||
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPushEngine, NotificationHubPushNotificationService>());
|
|
||||||
services.TryAddSingleton<IPushRelayer, NotificationHubPushNotificationService>();
|
|
||||||
if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString))
|
|
||||||
{
|
|
||||||
services.AddKeyedSingleton("notifications",
|
|
||||||
(_, _) => new QueueClient(globalSettings.Notifications.ConnectionString, "notifications"));
|
|
||||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPushEngine, AzureQueuePushNotificationService>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString))
|
if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Installations;
|
using Bit.Core.Platform.Installations;
|
||||||
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ using Microsoft.Extensions.Time.Testing;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Platform.Push.Services;
|
namespace Bit.Core.Test.Platform.Push.Engines;
|
||||||
|
|
||||||
[QueueClientCustomize]
|
[QueueClientCustomize]
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class AzureQueuePushNotificationServiceTests
|
public class AzureQueuePushEngineTests
|
||||||
{
|
{
|
||||||
private static readonly Guid _deviceId = Guid.Parse("c4730f80-caaa-4772-97bd-5c0d23a2baa3");
|
private static readonly Guid _deviceId = Guid.Parse("c4730f80-caaa-4772-97bd-5c0d23a2baa3");
|
||||||
private static readonly string _deviceIdentifier = "test_device_identifier";
|
private static readonly string _deviceIdentifier = "test_device_identifier";
|
||||||
private readonly FakeTimeProvider _fakeTimeProvider;
|
private readonly FakeTimeProvider _fakeTimeProvider;
|
||||||
private readonly Core.Settings.GlobalSettings _globalSettings = new();
|
private readonly Core.Settings.GlobalSettings _globalSettings = new();
|
||||||
|
|
||||||
public AzureQueuePushNotificationServiceTests()
|
public AzureQueuePushEngineTests()
|
||||||
{
|
{
|
||||||
_fakeTimeProvider = new();
|
_fakeTimeProvider = new();
|
||||||
_fakeTimeProvider.SetUtcNow(DateTime.UtcNow);
|
_fakeTimeProvider.SetUtcNow(DateTime.UtcNow);
|
||||||
@@ -771,12 +771,11 @@ public class AzureQueuePushNotificationServiceTests
|
|||||||
|
|
||||||
var globalSettings = new Core.Settings.GlobalSettings();
|
var globalSettings = new Core.Settings.GlobalSettings();
|
||||||
|
|
||||||
var sut = new AzureQueuePushNotificationService(
|
var sut = new AzureQueuePushEngine(
|
||||||
queueClient,
|
queueClient,
|
||||||
httpContextAccessor,
|
httpContextAccessor,
|
||||||
globalSettings,
|
globalSettings,
|
||||||
NullLogger<AzureQueuePushNotificationService>.Instance,
|
NullLogger<AzureQueuePushEngine>.Instance
|
||||||
_fakeTimeProvider
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id));
|
await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id));
|
||||||
@@ -2,16 +2,16 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.NotificationCenter.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.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
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.InternalNotifications = "https://localhost:7777";
|
||||||
GlobalSettings.BaseServiceUri.InternalIdentity = "https://localhost:8888";
|
GlobalSettings.BaseServiceUri.InternalIdentity = "https://localhost:8888";
|
||||||
@@ -21,11 +21,11 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
|
|||||||
|
|
||||||
protected override IPushEngine CreateService()
|
protected override IPushEngine CreateService()
|
||||||
{
|
{
|
||||||
return new NotificationsApiPushNotificationService(
|
return new NotificationsApiPushEngine(
|
||||||
HttpClientFactory,
|
HttpClientFactory,
|
||||||
GlobalSettings,
|
GlobalSettings,
|
||||||
HttpContextAccessor,
|
HttpContextAccessor,
|
||||||
NullLogger<NotificationsApiPushNotificationService>.Instance
|
NullLogger<NotificationsApiPushEngine>.Instance
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.NotificationCenter.Entities;
|
using Bit.Core.NotificationCenter.Entities;
|
||||||
using Bit.Core.NotificationCenter.Enums;
|
using Bit.Core.NotificationCenter.Enums;
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
@@ -22,6 +23,8 @@ using NSubstitute;
|
|||||||
using RichardSzalay.MockHttp;
|
using RichardSzalay.MockHttp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Platform.Push.Engines;
|
||||||
|
|
||||||
public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService
|
public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService
|
||||||
{
|
{
|
||||||
public Guid InstallationId { get; } = installationId;
|
public Guid InstallationId { get; } = installationId;
|
||||||
@@ -5,7 +5,6 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.NotificationCenter.Entities;
|
using Bit.Core.NotificationCenter.Entities;
|
||||||
using Bit.Core.Platform.Push;
|
|
||||||
using Bit.Core.Platform.Push.Internal;
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
@@ -15,7 +14,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
|||||||
using Microsoft.Extensions.Time.Testing;
|
using Microsoft.Extensions.Time.Testing;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Platform.Push.Services;
|
namespace Bit.Core.Test.Platform.Push.Engines;
|
||||||
|
|
||||||
public class RelayPushNotificationServiceTests : PushTestBase
|
public class RelayPushNotificationServiceTests : PushTestBase
|
||||||
{
|
{
|
||||||
@@ -39,12 +38,12 @@ public class RelayPushNotificationServiceTests : PushTestBase
|
|||||||
|
|
||||||
protected override IPushEngine CreateService()
|
protected override IPushEngine CreateService()
|
||||||
{
|
{
|
||||||
return new RelayPushNotificationService(
|
return new RelayPushEngine(
|
||||||
HttpClientFactory,
|
HttpClientFactory,
|
||||||
_deviceRepository,
|
_deviceRepository,
|
||||||
GlobalSettings,
|
GlobalSettings,
|
||||||
HttpContextAccessor,
|
HttpContextAccessor,
|
||||||
NullLogger<RelayPushNotificationService>.Instance
|
NullLogger<RelayPushEngine>.Instance
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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<IPushEngine>();
|
||||||
|
_fakeEngine2 = Substitute.For<IPushEngine>();
|
||||||
|
|
||||||
|
_sut = new MultiServicePushNotificationService(
|
||||||
|
[_fakeEngine1, _fakeEngine2],
|
||||||
|
NullLogger<MultiServicePushNotificationService>.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<object>
|
||||||
|
{
|
||||||
|
Target = NotificationTarget.User,
|
||||||
|
TargetId = Guid.NewGuid(),
|
||||||
|
Type = PushType.AuthRequest,
|
||||||
|
Payload = new { },
|
||||||
|
ExcludeCurrentContext = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await _sut.PushAsync(notification);
|
||||||
|
|
||||||
|
await _fakeEngine1
|
||||||
|
.Received(1)
|
||||||
|
.PushAsync(Arg.Is<PushNotification<object>>(n => ReferenceEquals(n, notification)));
|
||||||
|
|
||||||
|
await _fakeEngine2
|
||||||
|
.Received(1)
|
||||||
|
.PushAsync(Arg.Is<PushNotification<object>>(n => ReferenceEquals(n, notification)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Core.Test.NotificationHub;
|
namespace Bit.Core.Test.Platform.Push.NotificationHub;
|
||||||
|
|
||||||
public class NotificationHubConnectionTests
|
public class NotificationHubConnectionTests
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -6,7 +6,7 @@ using NSubstitute;
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
using static Bit.Core.Settings.GlobalSettings;
|
using static Bit.Core.Settings.GlobalSettings;
|
||||||
|
|
||||||
namespace Bit.Core.Test.NotificationHub;
|
namespace Bit.Core.Test.Platform.Push.NotificationHub;
|
||||||
|
|
||||||
public class NotificationHubPoolTests
|
public class NotificationHubPoolTests
|
||||||
{
|
{
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using AutoFixture;
|
using AutoFixture;
|
||||||
using Bit.Core.NotificationHub;
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Core.Test.NotificationHub;
|
namespace Bit.Core.Test.Platform.Push.NotificationHub;
|
||||||
|
|
||||||
public class NotificationHubProxyTests
|
public class NotificationHubProxyTests
|
||||||
{
|
{
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using System.Text.Json;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@@ -7,10 +6,11 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.NotificationCenter.Entities;
|
using Bit.Core.NotificationCenter.Entities;
|
||||||
using Bit.Core.NotificationCenter.Enums;
|
using Bit.Core.NotificationCenter.Enums;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.Push.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||||
|
using Bit.Core.Test.Platform.Push.Engines;
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@@ -22,7 +22,7 @@ using Microsoft.Extensions.Time.Testing;
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Core.Test.NotificationHub;
|
namespace Bit.Core.Test.Platform.Push.NotificationHub;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
[NotificationStatusCustomize]
|
[NotificationStatusCustomize]
|
||||||
@@ -621,11 +621,11 @@ public class NotificationHubPushNotificationServiceTests
|
|||||||
|
|
||||||
fakeTimeProvider.SetUtcNow(_now);
|
fakeTimeProvider.SetUtcNow(_now);
|
||||||
|
|
||||||
var sut = new NotificationHubPushNotificationService(
|
var sut = new NotificationHubPushEngine(
|
||||||
installationDeviceRepository,
|
installationDeviceRepository,
|
||||||
notificationHubPool,
|
notificationHubPool,
|
||||||
httpContextAccessor,
|
httpContextAccessor,
|
||||||
NullLogger<NotificationHubPushNotificationService>.Instance,
|
NullLogger<NotificationHubPushEngine>.Instance,
|
||||||
globalSettings
|
globalSettings
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -676,7 +676,7 @@ public class NotificationHubPushNotificationServiceTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static async Task AssertSendTemplateNotificationAsync(
|
private static async Task AssertSendTemplateNotificationAsync(
|
||||||
SutProvider<NotificationHubPushNotificationService> sutProvider, PushType type, object payload, string tag)
|
SutProvider<NotificationHubPushEngine> sutProvider, PushType type, object payload, string tag)
|
||||||
{
|
{
|
||||||
await sutProvider.GetDependency<INotificationHubPool>()
|
await sutProvider.GetDependency<INotificationHubPool>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
@@ -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<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "true" },
|
||||||
|
{ "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() },
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = services.GetRequiredService<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
Assert.Empty(engines);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_SelfHosted_ConfiguredForRelay_RelayEngineAdded()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "true" },
|
||||||
|
{ "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() },
|
||||||
|
{ "GlobalSettings:Installation:Key", "some_key"},
|
||||||
|
{ "GlobalSettings:PushRelayBaseUri", "https://example.com" },
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = services.GetRequiredService<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
var engine = Assert.Single(engines);
|
||||||
|
Assert.IsType<RelayPushEngine>(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_SelfHosted_ConfiguredForApi_ApiEngineAdded()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "true" },
|
||||||
|
{ "GlobalSettings:Installation:Id", Guid.NewGuid().ToString() },
|
||||||
|
{ "GlobalSettings:InternalIdentityKey", "some_key"},
|
||||||
|
{ "GlobalSettings:BaseServiceUri", "https://example.com" },
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = services.GetRequiredService<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
var engine = Assert.Single(engines);
|
||||||
|
Assert.IsType<NotificationsApiPushEngine>(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_SelfHosted_ConfiguredForRelayAndApi_TwoEnginesAdded()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "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<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
engines,
|
||||||
|
e => Assert.IsType<RelayPushEngine>(e),
|
||||||
|
e => Assert.IsType<NotificationsApiPushEngine>(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_Cloud_NoConfig_AddsNotificationHub()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "false" },
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = services.GetRequiredService<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
var engine = Assert.Single(engines);
|
||||||
|
Assert.IsType<NotificationHubPushEngine>(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_Cloud_HasNotificationConnectionString_TwoEngines()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "false" },
|
||||||
|
{ "GlobalSettings:Notifications:ConnectionString", "UseDevelopmentStorage=true" },
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = services.GetRequiredService<IPushNotificationService>();
|
||||||
|
var engines = services.GetServices<IPushEngine>();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
engines,
|
||||||
|
e => Assert.IsType<NotificationHubPushEngine>(e),
|
||||||
|
e => Assert.IsType<AzureQueuePushEngine>(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPush_Cloud_CalledTwice_DoesNotAddServicesTwice()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
var config = new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "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<string, string?> initialData)
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
AddServices(services, initialData);
|
||||||
|
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddServices(IServiceCollection services, Dictionary<string, string?> 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<IGlobalSettings>(globalSettings);
|
||||||
|
|
||||||
|
// Temporary until AddPush can add it themselves directly.
|
||||||
|
services.TryAddSingleton<IDeviceRepository, StubDeviceRepository>();
|
||||||
|
|
||||||
|
// Temporary until AddPush can add it themselves directly.
|
||||||
|
services.TryAddSingleton<IInstallationDeviceRepository, InstallationDeviceRepository>();
|
||||||
|
|
||||||
|
services.AddPush(globalSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StubDeviceRepository : IDeviceRepository
|
||||||
|
{
|
||||||
|
public Task ClearPushTokenAsync(Guid id) => throw new NotImplementedException();
|
||||||
|
public Task<Device> CreateAsync(Device obj) => throw new NotImplementedException();
|
||||||
|
public Task DeleteAsync(Device obj) => throw new NotImplementedException();
|
||||||
|
public Task<Device?> GetByIdAsync(Guid id, Guid userId) => throw new NotImplementedException();
|
||||||
|
public Task<Device?> GetByIdAsync(Guid id) => throw new NotImplementedException();
|
||||||
|
public Task<Device?> GetByIdentifierAsync(string identifier) => throw new NotImplementedException();
|
||||||
|
public Task<Device?> GetByIdentifierAsync(string identifier, Guid userId) => throw new NotImplementedException();
|
||||||
|
public Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId) => throw new NotImplementedException();
|
||||||
|
public Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId) => throw new NotImplementedException();
|
||||||
|
public Task ReplaceAsync(Device obj) => throw new NotImplementedException();
|
||||||
|
public UpdateEncryptedDataForKeyRotation UpdateKeysForRotationAsync(Guid userId, IEnumerable<Device> devices) => throw new NotImplementedException();
|
||||||
|
public Task UpsertAsync(Device obj) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
test/Core.Test/Platform/Push/PushTypeTests.cs
Normal file
64
test/Core.Test/Platform/Push/PushTypeTests.cs
Normal file
@@ -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<byte>();
|
||||||
|
var enumMembers = Enum.GetValues<PushType>();
|
||||||
|
|
||||||
|
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<NotificationInfoAttribute>();
|
||||||
|
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<PushType>()
|
||||||
|
.Order()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Debug.Assert(sortedValues.Length > 0);
|
||||||
|
|
||||||
|
var lastValue = sortedValues[0];
|
||||||
|
|
||||||
|
foreach (var value in sortedValues[1..])
|
||||||
|
{
|
||||||
|
var expectedValue = ++lastValue;
|
||||||
|
|
||||||
|
Assert.Equal(expectedValue, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace Bit.Core.Test.Platform.Push.Services;
|
|
||||||
|
|
||||||
public class MultiServicePushNotificationServiceTests
|
|
||||||
{
|
|
||||||
// TODO: Can add a couple tests here
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Bit.Core.Enums;
|
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.Core.Utilities;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
@@ -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<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "false" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var pushRegistrationService = services.GetRequiredService<IPushRegistrationService>();
|
||||||
|
Assert.IsType<NotificationHubPushRegistrationService>(pushRegistrationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPushRegistration_SelfHosted_NoOtherConfig_ReturnsNoopRegistrationService()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "true" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var pushRegistrationService = services.GetRequiredService<IPushRegistrationService>();
|
||||||
|
Assert.IsType<NoopPushRegistrationService>(pushRegistrationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPushRegistration_SelfHosted_RelayConfig_ReturnsRelayRegistrationService()
|
||||||
|
{
|
||||||
|
var services = Build(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "GlobalSettings:SelfHosted", "true" },
|
||||||
|
{ "GlobalSettings:PushRelayBaseUri", "https://example.com" },
|
||||||
|
{ "GlobalSettings:Installation:Key", "some_key" },
|
||||||
|
});
|
||||||
|
|
||||||
|
var pushRegistrationService = services.GetRequiredService<IPushRegistrationService>();
|
||||||
|
Assert.IsType<RelayPushRegistrationService>(pushRegistrationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPushRegistration_MultipleTimes_NoAdditionalServices()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
var config = new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "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<IPushRegistrationService>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServiceProvider Build(Dictionary<string, string?> initialData)
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
AddServices(services, initialData);
|
||||||
|
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddServices(IServiceCollection services, Dictionary<string, string?> 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<IGlobalSettings>(globalSettings);
|
||||||
|
|
||||||
|
|
||||||
|
// Temporary until AddPushRegistration can add it themselves directly.
|
||||||
|
services.TryAddSingleton<IInstallationDeviceRepository, InstallationDeviceRepository>();
|
||||||
|
|
||||||
|
services.AddPushRegistration();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.Platform.Push.Internal;
|
using Bit.Core.Platform.PushRegistration.Internal;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@@ -4,8 +4,8 @@ using Bit.Core.Entities;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.NotificationHub;
|
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Platform.PushRegistration;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using Bit.Core.Billing.Organizations.Services;
|
using Bit.Core.Billing.Organizations.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Platform.Push.Internal;
|
using Bit.Core.Platform.PushRegistration.Internal;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
|||||||
Reference in New Issue
Block a user