diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs index b656e70601..d4a4caeb9e 100644 --- a/src/Core/Enums/PushType.cs +++ b/src/Core/Enums/PushType.cs @@ -29,4 +29,5 @@ public enum PushType : byte SyncOrganizationCollectionSettingChanged = 19, SyncNotification = 20, + SyncNotificationStatus = 21 } diff --git a/src/Core/Models/PushNotification.cs b/src/Core/Models/PushNotification.cs index a6e8852e95..775c3443f2 100644 --- a/src/Core/Models/PushNotification.cs +++ b/src/Core/Models/PushNotification.cs @@ -1,11 +1,12 @@ -using Bit.Core.Enums; +#nullable enable +using Bit.Core.Enums; using Bit.Core.NotificationCenter.Enums; namespace Bit.Core.Models; public class PushNotificationData { - public PushNotificationData(PushType type, T payload, string contextId) + public PushNotificationData(PushType type, T payload, string? contextId) { Type = type; Payload = payload; @@ -14,7 +15,7 @@ public class PushNotificationData public PushType Type { get; set; } public T Payload { get; set; } - public string ContextId { get; set; } + public string? ContextId { get; set; } } public class SyncCipherPushNotification @@ -22,7 +23,7 @@ public class SyncCipherPushNotification public Guid Id { get; set; } public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } - public IEnumerable CollectionIds { get; set; } + public IEnumerable? CollectionIds { get; set; } public DateTime RevisionDate { get; set; } } @@ -46,7 +47,6 @@ public class SyncSendPushNotification public DateTime RevisionDate { get; set; } } -#nullable enable public class NotificationPushNotification { public Guid Id { get; set; } @@ -59,8 +59,9 @@ public class NotificationPushNotification public string? Body { get; set; } public DateTime CreationDate { get; set; } public DateTime RevisionDate { get; set; } + public DateTime? ReadDate { get; set; } + public DateTime? DeletedDate { get; set; } } -#nullable disable public class AuthRequestPushNotification { diff --git a/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs b/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs index fcd61ceebc..793da22f81 100644 --- a/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs +++ b/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public CreateNotificationStatusCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task CreateAsync(NotificationStatus notificationStatus) @@ -42,6 +46,10 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - return await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); + + return newNotificationStatus; } } diff --git a/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs b/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs index 2ca7aa9051..256702c10c 100644 --- a/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs +++ b/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public MarkNotificationDeletedCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task MarkDeletedAsync(Guid notificationId) @@ -59,7 +63,9 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); } else { @@ -69,6 +75,8 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand notificationStatus.DeletedDate = DateTime.UtcNow; await _notificationStatusRepository.UpdateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, notificationStatus); } } } diff --git a/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs b/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs index 400e44463a..9c9d1d48a2 100644 --- a/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs +++ b/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public MarkNotificationReadCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task MarkReadAsync(Guid notificationId) @@ -59,7 +63,9 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); } else { @@ -69,6 +75,8 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand notificationStatus.ReadDate = DateTime.UtcNow; await _notificationStatusRepository.UpdateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, notificationStatus); } } } diff --git a/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs b/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs index f049478178..471786aac6 100644 --- a/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs +++ b/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -15,14 +16,17 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand private readonly ICurrentContext _currentContext; private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; + private readonly IPushNotificationService _pushNotificationService; public UpdateNotificationCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, - INotificationRepository notificationRepository) + INotificationRepository notificationRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; + _pushNotificationService = pushNotificationService; } public async Task UpdateAsync(Notification notificationToUpdate) @@ -43,5 +47,7 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand notification.RevisionDate = DateTime.UtcNow; await _notificationRepository.ReplaceAsync(notification); + + await _pushNotificationService.PushNotificationAsync(notification); } } diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs index d1c7749d9f..7baf0352ee 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +#nullable enable +using System.Text.Json; using System.Text.RegularExpressions; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; @@ -6,6 +7,7 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Tools.Entities; @@ -51,7 +53,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -209,6 +211,36 @@ public class NotificationHubPushNotificationService : IPushNotificationService } } + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationStatus, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationStatus, message, + true, notification.ClientType); + } + } + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; @@ -230,8 +262,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService GetContextIdentifier(excludeCurrentContext), clientType: clientType); } - public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType); await SendPayloadAsync(tag, type, payload); @@ -241,8 +273,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService } } - public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType); await SendPayloadAsync(tag, type, payload); @@ -277,7 +309,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService false ); - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { @@ -285,11 +317,11 @@ public class NotificationHubPushNotificationService : IPushNotificationService } var currentContext = - _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - private string BuildTag(string tag, string identifier, ClientType? clientType) + private string BuildTag(string tag, string? identifier, ClientType? clientType) { if (!string.IsNullOrWhiteSpace(identifier)) { diff --git a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs index a25a017192..c32212c6b2 100644 --- a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +#nullable enable +using System.Text.Json; using Azure.Storage.Queues; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; @@ -42,7 +43,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -184,6 +185,27 @@ public class AzureQueuePushNotificationService : IPushNotificationService await SendMessageAsync(PushType.SyncNotification, message, true); } + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + await SendMessageAsync(PushType.SyncNotificationStatus, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -207,7 +229,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService await _queueClient.SendMessageAsync(message); } - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { @@ -219,15 +241,15 @@ public class AzureQueuePushNotificationService : IPushNotificationService return currentContext?.DeviceIdentifier; } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); diff --git a/src/Core/Platform/Push/Services/IPushNotificationService.cs b/src/Core/Platform/Push/Services/IPushNotificationService.cs index 7f2b6c90fe..1c7fdc659b 100644 --- a/src/Core/Platform/Push/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/IPushNotificationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; @@ -25,12 +26,13 @@ public interface IPushNotificationService Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendDeleteAsync(Send send); Task PushNotificationAsync(Notification notification); + Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus); Task PushAuthRequestAsync(AuthRequest authRequest); Task PushAuthRequestResponseAsync(AuthRequest authRequest); Task PushSyncOrganizationStatusAsync(Organization organization); Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization); - Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null); - Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null); + Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null); + Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null); } diff --git a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs index db3107b6c3..9b4e66ae1a 100644 --- a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; @@ -24,7 +25,7 @@ public class MultiServicePushNotificationService : IPushNotificationService _logger = logger; _logger.LogInformation("Hub services: {Services}", _services.Count()); - globalSettings?.NotificationHubPool?.NotificationHubs?.ForEach(hub => + globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub => { _logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); }); @@ -150,15 +151,21 @@ public class MultiServicePushNotificationService : IPushNotificationService return Task.CompletedTask; } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + PushToServices((s) => s.PushNotificationStatusAsync(notification, notificationStatus)); + return Task.CompletedTask; + } + + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType)); return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType)); return Task.FromResult(0); @@ -166,12 +173,16 @@ public class MultiServicePushNotificationService : IPushNotificationService private void PushToServices(Func pushFunc) { - if (_services != null) + if (!_services.Any()) { - foreach (var service in _services) - { - pushFunc(service); - } + _logger.LogWarning("No services found to push notification"); + return; + } + + foreach (var service in _services) + { + _logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); + pushFunc(service); } } } diff --git a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs index fb4121179f..57c446c5e5 100644 --- a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; @@ -84,8 +85,8 @@ public class NoopPushNotificationService : IPushNotificationService return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } @@ -107,11 +108,14 @@ public class NoopPushNotificationService : IPushNotificationService return Task.FromResult(0); } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } public Task PushNotificationAsync(Notification notification) => Task.CompletedTask; + + public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) => + Task.CompletedTask; } diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index fb3814fd64..7a557e8978 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; @@ -16,7 +17,6 @@ namespace Bit.Core.Platform.Push; public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService { - private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; public NotificationsApiPushNotificationService( @@ -33,7 +33,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService globalSettings.InternalIdentityKey, logger) { - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; } @@ -52,7 +51,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -203,6 +202,27 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await SendMessageAsync(PushType.SyncNotification, message, true); } + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + await SendMessageAsync(PushType.SyncNotificationStatus, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -225,7 +245,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await SendAsync(HttpMethod.Post, "send", request); } - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { @@ -233,19 +253,19 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService } var currentContext = - _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); diff --git a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs index 4d99f04768..09f42fd0d1 100644 --- a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; @@ -55,7 +56,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -219,6 +220,36 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti } } + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationStatus, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationStatus, message, + true, notification.ClientType); + } + } + public async Task PushSyncOrganizationStatusAsync(Organization organization) { var message = new OrganizationStatusPushNotification @@ -277,7 +308,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) { var currentContext = - _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) { var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); @@ -293,14 +324,14 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti } } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { throw new NotImplementedException(); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null, ClientType? clientType = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { throw new NotImplementedException(); } diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index 9b0164fdc5..af571e48c4 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -104,6 +104,7 @@ public static class HubHelpers .SendAsync("ReceiveMessage", organizationCollectionSettingsChangedNotification, cancellationToken); break; case PushType.SyncNotification: + case PushType.SyncNotificationStatus: var syncNotification = JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); diff --git a/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs index 41efce82ab..3256f2f9cb 100644 --- a/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs @@ -41,6 +41,12 @@ public class CreateNotificationCommandTest Setup(sutProvider, notification, authorized: false); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -59,5 +65,8 @@ public class CreateNotificationCommandTest await sutProvider.GetDependency() .Received(1) .PushNotificationAsync(newNotification); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } } diff --git a/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs index 8dc8524926..78aaaba18f 100644 --- a/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -50,6 +51,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification: null, notificationStatus, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -61,6 +68,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification, notificationStatus, authorizedNotification: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -72,6 +85,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification, notificationStatus, true, authorizedCreate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -85,5 +104,11 @@ public class CreateNotificationStatusCommandTest var newNotificationStatus = await sutProvider.Sut.CreateAsync(notificationStatus); Assert.Equal(notificationStatus, newNotificationStatus); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, notificationStatus); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } } diff --git a/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs index a5bb20423c..f1d23b5f18 100644 --- a/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs @@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -63,6 +64,12 @@ public class MarkNotificationDeletedCommandTest Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -74,6 +81,12 @@ public class MarkNotificationDeletedCommandTest Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -86,6 +99,12 @@ public class MarkNotificationDeletedCommandTest true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -98,6 +117,12 @@ public class MarkNotificationDeletedCommandTest authorizedCreate: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -110,6 +135,12 @@ public class MarkNotificationDeletedCommandTest authorizedUpdate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -119,13 +150,25 @@ public class MarkNotificationDeletedCommandTest Guid notificationId, Guid userId, Notification notification) { Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); + var expectedNotificationStatus = new NotificationStatus + { + NotificationId = notificationId, + UserId = userId, + ReadDate = null, + DeletedDate = DateTime.UtcNow + }; await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(ns => - ns.NotificationId == notificationId && ns.UserId == userId && !ns.ReadDate.HasValue && - ns.DeletedDate.HasValue && DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); + .CreateAsync(Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -134,18 +177,30 @@ public class MarkNotificationDeletedCommandTest SutProvider sutProvider, Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) { - var deletedDate = notificationStatus.DeletedDate; - Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Is(ns => - ns.Equals(notificationStatus) && - ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && - ns.ReadDate == notificationStatus.ReadDate && ns.DeletedDate != deletedDate && - ns.DeletedDate.HasValue && - DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); + .UpdateAsync(Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + } + + private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus, + NotificationStatus? actualNotificationStatus) + { + Assert.NotNull(actualNotificationStatus); + Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId); + Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId); + Assert.Equal(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate); + Assert.NotEqual(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate); + Assert.NotNull(actualNotificationStatus.DeletedDate); + Assert.Equal(DateTime.UtcNow, actualNotificationStatus.DeletedDate.Value, TimeSpan.FromMinutes(1)); } } diff --git a/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs index f80234c075..481a973d32 100644 --- a/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs @@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -63,6 +64,12 @@ public class MarkNotificationReadCommandTest Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -74,6 +81,12 @@ public class MarkNotificationReadCommandTest Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -86,6 +99,12 @@ public class MarkNotificationReadCommandTest true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -98,6 +117,12 @@ public class MarkNotificationReadCommandTest authorizedCreate: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -110,6 +135,12 @@ public class MarkNotificationReadCommandTest authorizedUpdate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -119,13 +150,25 @@ public class MarkNotificationReadCommandTest Guid notificationId, Guid userId, Notification notification) { Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); + var expectedNotificationStatus = new NotificationStatus + { + NotificationId = notificationId, + UserId = userId, + ReadDate = DateTime.UtcNow, + DeletedDate = null + }; await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(ns => - ns.NotificationId == notificationId && ns.UserId == userId && !ns.DeletedDate.HasValue && - ns.ReadDate.HasValue && DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); + .CreateAsync(Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -134,18 +177,30 @@ public class MarkNotificationReadCommandTest SutProvider sutProvider, Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) { - var readDate = notificationStatus.ReadDate; - Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Is(ns => - ns.Equals(notificationStatus) && - ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && - ns.DeletedDate == notificationStatus.DeletedDate && ns.ReadDate != readDate && - ns.ReadDate.HasValue && - DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); + .UpdateAsync(Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + } + + private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus, + NotificationStatus? actualNotificationStatus) + { + Assert.NotNull(actualNotificationStatus); + Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId); + Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId); + Assert.NotEqual(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate); + Assert.NotNull(actualNotificationStatus.ReadDate); + Assert.Equal(DateTime.UtcNow, actualNotificationStatus.ReadDate.Value, TimeSpan.FromMinutes(1)); + Assert.Equal(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate); } } diff --git a/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs index 976d1d77a3..406347e0df 100644 --- a/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs @@ -7,6 +7,7 @@ using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -45,6 +46,12 @@ public class UpdateNotificationCommandTest Setup(sutProvider, notification.Id, notification: null, true); await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -56,6 +63,12 @@ public class UpdateNotificationCommandTest Setup(sutProvider, notification.Id, notification, authorized: false); await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -91,5 +104,11 @@ public class UpdateNotificationCommandTest n.Priority == notificationToUpdate.Priority && n.ClientType == notificationToUpdate.ClientType && n.Title == notificationToUpdate.Title && n.Body == notificationToUpdate.Body && DateTime.UtcNow - n.RevisionDate < TimeSpan.FromMinutes(1))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationAsync(notification); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } } diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index f1cfdc9f85..2b8ff88dc1 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -15,12 +15,13 @@ using Xunit; namespace Bit.Core.Test.NotificationHub; [SutProviderCustomize] +[NotificationStatusCustomize] public class NotificationHubPushNotificationServiceTests { [Theory] [BitAutoData] [NotificationCustomize] - public async void PushNotificationAsync_Global_NotSent( + public async Task PushNotificationAsync_Global_NotSent( SutProvider sutProvider, Notification notification) { await sutProvider.Sut.PushNotificationAsync(notification); @@ -39,7 +40,7 @@ public class NotificationHubPushNotificationServiceTests [BitAutoData(false)] [BitAutoData(true)] [NotificationCustomize(false)] - public async void PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( + public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( bool organizationIdNull, SutProvider sutProvider, Notification notification) { @@ -49,11 +50,12 @@ public class NotificationHubPushNotificationServiceTests } notification.ClientType = ClientType.All; - var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + var expectedNotification = ToNotificationPushNotification(notification, null); await sutProvider.Sut.PushNotificationAsync(notification); - await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, $"(template:payload_userId:{notification.UserId})"); await sutProvider.GetDependency() .Received(0) @@ -61,30 +63,46 @@ public class NotificationHubPushNotificationServiceTests } [Theory] - [BitAutoData(false, ClientType.Browser)] - [BitAutoData(false, ClientType.Desktop)] - [BitAutoData(false, ClientType.Web)] - [BitAutoData(false, ClientType.Mobile)] - [BitAutoData(true, ClientType.Browser)] - [BitAutoData(true, ClientType.Desktop)] - [BitAutoData(true, ClientType.Web)] - [BitAutoData(true, ClientType.Mobile)] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] [NotificationCustomize(false)] - public async void PushNotificationAsync_UserIdProvidedClientTypeNotAll_SentToUser(bool organizationIdNull, + public async Task PushNotificationAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( ClientType clientType, SutProvider sutProvider, Notification notification) { - if (organizationIdNull) - { - notification.OrganizationId = null; - } - + notification.OrganizationId = null; notification.ClientType = clientType; - var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + var expectedNotification = ToNotificationPushNotification(notification, null); await sutProvider.Sut.PushNotificationAsync(notification); - await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); await sutProvider.GetDependency() .Received(0) @@ -94,16 +112,17 @@ public class NotificationHubPushNotificationServiceTests [Theory] [BitAutoData] [NotificationCustomize(false)] - public async void PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( + public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( SutProvider sutProvider, Notification notification) { notification.UserId = null; notification.ClientType = ClientType.All; - var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + var expectedNotification = ToNotificationPushNotification(notification, null); await sutProvider.Sut.PushNotificationAsync(notification); - await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, $"(template:payload && organizationId:{notification.OrganizationId})"); await sutProvider.GetDependency() .Received(0) @@ -116,18 +135,156 @@ public class NotificationHubPushNotificationServiceTests [BitAutoData(ClientType.Web)] [BitAutoData(ClientType.Mobile)] [NotificationCustomize(false)] - public async void PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( + public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( ClientType clientType, SutProvider sutProvider, Notification notification) { notification.UserId = null; notification.ClientType = clientType; - - var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + var expectedNotification = ToNotificationPushNotification(notification, null); await sutProvider.Sut.PushNotificationAsync(notification); - await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize] + public async Task PushNotificationStatusAsync_Global_NotSent( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) + { + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency() + .Received(0) + .AllClients + .Received(0) + .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(false)] + [BitAutoData(true)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedClientTypeAll_SentToUser( + bool organizationIdNull, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + if (organizationIdNull) + { + notification.OrganizationId = null; + } + + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.OrganizationId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) + { + notification.UserId = null; + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task + PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.UserId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); await sutProvider.GetDependency() .Received(0) @@ -137,7 +294,7 @@ public class NotificationHubPushNotificationServiceTests [Theory] [BitAutoData([null])] [BitAutoData(ClientType.All)] - public async void SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, + public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, SutProvider sutProvider, Guid userId, PushType pushType, string payload, string identifier) { @@ -156,7 +313,7 @@ public class NotificationHubPushNotificationServiceTests [BitAutoData(ClientType.Desktop)] [BitAutoData(ClientType.Mobile)] [BitAutoData(ClientType.Web)] - public async void SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, + public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, SutProvider sutProvider, Guid userId, PushType pushType, string payload, string identifier) { @@ -173,7 +330,7 @@ public class NotificationHubPushNotificationServiceTests [Theory] [BitAutoData([null])] [BitAutoData(ClientType.All)] - public async void SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, + public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, SutProvider sutProvider, Guid organizationId, PushType pushType, string payload, string identifier) { @@ -192,7 +349,7 @@ public class NotificationHubPushNotificationServiceTests [BitAutoData(ClientType.Desktop)] [BitAutoData(ClientType.Mobile)] [BitAutoData(ClientType.Web)] - public async void SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( + public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( ClientType clientType, SutProvider sutProvider, Guid organizationId, PushType pushType, string payload, string identifier) { @@ -206,7 +363,8 @@ public class NotificationHubPushNotificationServiceTests .UpsertAsync(Arg.Any()); } - private static NotificationPushNotification ToSyncNotificationPushNotification(Notification notification) => + private static NotificationPushNotification ToNotificationPushNotification(Notification notification, + NotificationStatus? notificationStatus) => new() { Id = notification.Id, @@ -218,7 +376,9 @@ public class NotificationHubPushNotificationServiceTests Title = notification.Title, Body = notification.Body, CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus?.ReadDate, + DeletedDate = notificationStatus?.DeletedDate }; private static async Task AssertSendTemplateNotificationAsync( diff --git a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs index a84a76152a..22161924ea 100644 --- a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs @@ -24,7 +24,7 @@ public class AzureQueuePushNotificationServiceTests [BitAutoData] [NotificationCustomize] [CurrentContextCustomize] - public async void PushNotificationAsync_Notification_Sent( + public async Task PushNotificationAsync_Notification_Sent( SutProvider sutProvider, Notification notification, Guid deviceIdentifier, ICurrentContext currentContext) { @@ -36,7 +36,30 @@ public class AzureQueuePushNotificationServiceTests await sutProvider.GetDependency().Received(1) .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.SyncNotification, message, new SyncNotificationEquals(notification), + MatchMessage(PushType.SyncNotification, message, + new NotificationPushNotificationEquals(notification, null), + deviceIdentifier.ToString()))); + } + + [Theory] + [BitAutoData] + [NotificationCustomize] + [NotificationStatusCustomize] + [CurrentContextCustomize] + public async Task PushNotificationStatusAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, Guid deviceIdentifier, + ICurrentContext currentContext, NotificationStatus notificationStatus) + { + currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); + sutProvider.GetDependency().HttpContext!.RequestServices + .GetService(Arg.Any()).Returns(currentContext); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency().Received(1) + .SendMessageAsync(Arg.Is(message => + MatchMessage(PushType.SyncNotificationStatus, message, + new NotificationPushNotificationEquals(notification, notificationStatus), deviceIdentifier.ToString()))); } @@ -50,7 +73,8 @@ public class AzureQueuePushNotificationServiceTests pushNotificationData.ContextId == contextId; } - private class SyncNotificationEquals(Notification notification) : IEquatable + private class NotificationPushNotificationEquals(Notification notification, NotificationStatus? notificationStatus) + : IEquatable { public bool Equals(NotificationPushNotification? other) { @@ -66,7 +90,9 @@ public class AzureQueuePushNotificationServiceTests other.Title == notification.Title && other.Body == notification.Body && other.CreationDate == notification.CreationDate && - other.RevisionDate == notification.RevisionDate; + other.RevisionDate == notification.RevisionDate && + other.ReadDate == notificationStatus?.ReadDate && + other.DeletedDate == notificationStatus?.DeletedDate; } } } diff --git a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs index edbd297708..08dfd0a5c0 100644 --- a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs @@ -26,6 +26,22 @@ public class MultiServicePushNotificationServiceTests .PushNotificationAsync(notification); } + [Theory] + [BitAutoData] + [NotificationCustomize] + [NotificationStatusCustomize] + public async Task PushNotificationStatusAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) + { + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .PushNotificationStatusAsync(notification, notificationStatus); + } + [Theory] [BitAutoData([null, null])] [BitAutoData(ClientType.All, null)]