From 3a604af0a441938e9e21f0ad6835172a537f011a Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Mon, 21 Oct 2024 14:58:57 +0100 Subject: [PATCH] PM-10600: Notification push notification --- src/Core/Enums/PushType.cs | 2 + src/Core/Models/PushNotification.cs | 9 ++ src/Core/Services/IPushNotificationService.cs | 12 ++- .../AzureQueuePushNotificationService.cs | 38 +++++--- .../MultiServicePushNotificationService.cs | 27 +++++- .../NotificationHubPushNotificationService.cs | 95 +++++++++++++----- .../NotificationHubPushRegistrationService.cs | 97 ++++++++----------- ...NotificationsApiPushNotificationService.cs | 38 +++++--- .../RelayPushNotificationService.cs | 80 +++++++++------ .../NoopPushNotificationService.cs | 10 +- src/Notifications/HubHelpers.cs | 36 +++++-- 11 files changed, 291 insertions(+), 153 deletions(-) diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs index 9dbef7b8e..77719cc67 100644 --- a/src/Core/Enums/PushType.cs +++ b/src/Core/Enums/PushType.cs @@ -25,4 +25,6 @@ public enum PushType : byte AuthRequestResponse = 16, SyncOrganizations = 17, + + SyncNotification = 18, } diff --git a/src/Core/Models/PushNotification.cs b/src/Core/Models/PushNotification.cs index 37b3b25c0..e27bea364 100644 --- a/src/Core/Models/PushNotification.cs +++ b/src/Core/Models/PushNotification.cs @@ -45,6 +45,15 @@ public class SyncSendPushNotification public DateTime RevisionDate { get; set; } } +public class SyncNotificationPushNotification +{ + public Guid Id { get; set; } + public bool Global { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public DateTime RevisionDate { get; set; } +} + public class AuthRequestPushNotification { public Guid UserId { get; set; } diff --git a/src/Core/Services/IPushNotificationService.cs b/src/Core/Services/IPushNotificationService.cs index 29a20239d..53744041f 100644 --- a/src/Core/Services/IPushNotificationService.cs +++ b/src/Core/Services/IPushNotificationService.cs @@ -1,5 +1,6 @@ using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -22,9 +23,16 @@ public interface IPushNotificationService Task PushSyncSendCreateAsync(Send send); Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendDeleteAsync(Send send); + Task PushSyncNotificationAsync(Notification notification); Task PushAuthRequestAsync(AuthRequest authRequest); Task PushAuthRequestResponseAsync(AuthRequest authRequest); - Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, string deviceId = 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); + string deviceId = null, ClientType? clientType = null); + + Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null); } diff --git a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs index 1e4a7314c..b5c667706 100644 --- a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs +++ b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs @@ -4,6 +4,7 @@ using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Utilities; @@ -128,11 +129,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendMessageAsync(type, message, excludeCurrentContext); } @@ -149,11 +146,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendMessageAsync(type, message, true); } @@ -173,6 +166,20 @@ public class AzureQueuePushNotificationService : IPushNotificationService await PushSendAsync(send, PushType.SyncSendDelete); } + public async Task PushSyncNotificationAsync(Notification notification) + { + var message = new SyncNotificationPushNotification + { + Id = notification.Id, + Global = notification.Global, + UserId = notification.Id, + OrganizationId = notification.Id, + RevisionDate = notification.RevisionDate + }; + + await SendMessageAsync(PushType.SyncNotification, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -203,22 +210,25 @@ public class AzureQueuePushNotificationService : IPushNotificationService return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _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) + 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) + string deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } + + public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null) => Task.CompletedTask; } diff --git a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs index 92e29908f..a92ba3e3d 100644 --- a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs +++ b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs @@ -1,5 +1,6 @@ using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tools.Entities; @@ -34,6 +35,7 @@ public class MultiServicePushNotificationService : IPushNotificationService _services.Add(new RelayPushNotificationService(httpFactory, deviceRepository, globalSettings, httpContextAccessor, relayLogger)); } + if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) { @@ -43,12 +45,14 @@ public class MultiServicePushNotificationService : IPushNotificationService } else { - var generalHub = globalSettings.NotificationHubs?.FirstOrDefault(h => h.HubType == NotificationHubType.General); + var generalHub = + globalSettings.NotificationHubs?.FirstOrDefault(h => h.HubType == NotificationHubType.General); if (CoreHelpers.SettingHasValue(generalHub?.ConnectionString)) { _services.Add(new NotificationHubPushNotificationService(installationDeviceRepository, globalSettings, httpContextAccessor, hubLogger)); } + if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) { _services.Add(new AzureQueuePushNotificationService(globalSettings, httpContextAccessor)); @@ -161,19 +165,32 @@ public class MultiServicePushNotificationService : IPushNotificationService } public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { - PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId)); + 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) + string deviceId = null, ClientType? clientType = null) { - PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId)); + PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType)); return Task.FromResult(0); } + public Task PushSyncNotificationAsync(Notification notification) + { + PushToServices((s) => s.PushSyncNotificationAsync(notification)); + return Task.CompletedTask; + } + + public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null) + { + PushToServices((s) => s.SendPayloadToEveryoneAsync(type, payload, identifier, deviceId, clientType)); + return Task.CompletedTask; + } + private void PushToServices(Func pushFunc) { if (_services != null) diff --git a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs index 480f0dfa9..8e4ca9767 100644 --- a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs @@ -12,6 +12,7 @@ using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; +using Notification = Bit.Core.NotificationCenter.Entities.Notification; namespace Bit.Core.Services; @@ -148,11 +149,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); } @@ -197,31 +194,65 @@ public class NotificationHubPushNotificationService : IPushNotificationService await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); } + public async Task PushSyncNotificationAsync(Notification notification) + { + var message = new SyncNotificationPushNotification + { + Id = notification.Id, + Global = notification.Global, + UserId = notification.Id, + OrganizationId = notification.Id, + RevisionDate = notification.RevisionDate + }; + + if (notification.Global) + { + await SendPayloadToEveryoneAsync(PushType.SyncNotification, message, true, notification.ClientType); + } + else if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, + true, notification.ClientType); + } + } + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendPayloadToUserAsync(authRequest.UserId, type, message, true); } - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, + ClientType? clientType = null) { - await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext)); + await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext), + clientType: clientType); } - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext) + private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, + bool excludeCurrentContext, ClientType? clientType = null) { - await SendPayloadToUserAsync(orgId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext)); + await SendPayloadToUserAsync(orgId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext), + clientType: clientType); + } + + private async Task SendPayloadToEveryoneAsync(PushType type, object payload, bool excludeCurrentContext, + ClientType? clientType = null) + { + await SendPayloadToEveryoneAsync(type, payload, GetContextIdentifier(excludeCurrentContext), + clientType: clientType); } public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { - var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier); + var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType); await SendPayloadAsync(tag, type, payload); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { @@ -230,9 +261,20 @@ public class NotificationHubPushNotificationService : IPushNotificationService } public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { - var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier); + var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType); + await SendPayloadAsync(tag, type, payload); + if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) + { + await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); + } + } + + public async Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, + string deviceId = null, ClientType? clientType = null) + { + var tag = BuildTag($"template:payload", identifier, clientType); await SendPayloadAsync(tag, type, payload); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { @@ -247,18 +289,23 @@ public class NotificationHubPushNotificationService : IPushNotificationService return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - private string BuildTag(string tag, string identifier) + private string BuildTag(string tag, string identifier, ClientType? clientType) { if (!string.IsNullOrWhiteSpace(identifier)) { tag += $" && !deviceIdentifier:{SanitizeTagInput(identifier)}"; } + if (clientType.HasValue && clientType.Value != ClientType.All) + { + tag += $" && clientType:{clientType}"; + } + return $"({tag})"; } @@ -270,8 +317,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService var task = client.SendTemplateNotificationAsync( new Dictionary { - { "type", ((byte)type).ToString() }, - { "payload", JsonSerializer.Serialize(payload) } + { "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) } }, tag); tasks.Add(task); } @@ -285,7 +331,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService if (_clients[i].EnableTestSend) { var outcome = await tasks[i]; - _logger.LogInformation("Azure Notification Hub Tracking ID: {id} | {type} push notification with {success} successes and {failure} failures with a payload of {@payload} and result of {@results}", + _logger.LogInformation( + "Azure Notification Hub Tracking ID: {id} | {type} push notification with {success} successes and {failure} failures with a payload of {@payload} and result of {@results}", outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results); } } diff --git a/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs index 87df60e8e..d0751f314 100644 --- a/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs @@ -2,6 +2,7 @@ using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; +using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -62,10 +63,9 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService Templates = new Dictionary() }; - installation.Tags = new List - { - $"userId:{userId}" - }; + var clientType = DeviceTypes.ToClientType(type); + + installation.Tags = new List { $"userId:{userId}", $"clientType:{clientType}" }; if (!string.IsNullOrWhiteSpace(identifier)) { @@ -81,24 +81,25 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService { payloadTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\",\"payload\":\"$(payload)\"}}}"; messageTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\"}," + - "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; + "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; installation.Platform = NotificationPlatform.FcmV1; } else { payloadTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}}"; messageTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\"}," + - "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; + "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; installation.Platform = NotificationPlatform.Fcm; } + break; case DeviceType.iOS: payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}," + - "\"aps\":{\"content-available\":1}}"; + "\"aps\":{\"content-available\":1}}"; messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," + - "\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}"; + "\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}"; badgeMessageTemplate = "{\"data\":{\"type\":\"#(type)\"}," + - "\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}"; + "\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}"; installation.Platform = NotificationPlatform.Apns; break; @@ -112,10 +113,10 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService break; } - BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier); - BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier); + BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier, clientType); + BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier, clientType); BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, - userId, identifier); + userId, identifier, clientType); await GetClient(type).CreateOrUpdateInstallationAsync(installation); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) @@ -125,7 +126,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody, - string userId, string identifier) + string userId, string identifier, ClientType clientType) { if (templateBody == null) { @@ -139,8 +140,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService Body = templateBody, Tags = new List { - fullTemplateId, - $"{fullTemplateId}_userId:{userId}" + fullTemplateId, $"{fullTemplateId}_userId:{userId}", $"clientType:{clientType}" } }; @@ -168,7 +168,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } - public async Task AddUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) + public async Task AddUserRegistrationOrganizationAsync(IEnumerable> devices, + string organizationId) { await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Add, $"organizationId:{organizationId}"); if (devices.Any() && InstallationDeviceEntity.IsInstallationDeviceId(devices.First().Key)) @@ -178,7 +179,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } - public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable> devices, string organizationId) + public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable> devices, + string organizationId) { await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Remove, $"organizationId:{organizationId}"); @@ -189,7 +191,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } - private async Task PatchTagsForUserDevicesAsync(IEnumerable> devices, UpdateOperationType op, + private async Task PatchTagsForUserDevicesAsync(IEnumerable> devices, + UpdateOperationType op, string tag) { if (!devices.Any()) @@ -197,11 +200,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService return; } - var operation = new PartialUpdateOperation - { - Operation = op, - Path = "/tags" - }; + var operation = new PartialUpdateOperation { Operation = op, Path = "/tags" }; if (op == UpdateOperationType.Add) { @@ -216,7 +215,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService { try { - await GetClient(device.Value).PatchInstallationAsync(device.Key, new List { operation }); + await GetClient(device.Value) + .PatchInstallationAsync(device.Key, new List { operation }); } catch (Exception e) when (e.InnerException == null || !e.InnerException.Message.Contains("(404) Not Found")) { @@ -227,41 +227,21 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService private NotificationHubClient GetClient(DeviceType deviceType) { - var hubType = NotificationHubType.General; - switch (deviceType) + var clientType = DeviceTypes.ToClientType(deviceType); + + var hubType = clientType switch { - case DeviceType.Android: - hubType = NotificationHubType.Android; - break; - case DeviceType.iOS: - hubType = NotificationHubType.iOS; - break; - case DeviceType.ChromeExtension: - case DeviceType.FirefoxExtension: - case DeviceType.OperaExtension: - case DeviceType.EdgeExtension: - case DeviceType.VivaldiExtension: - case DeviceType.SafariExtension: - hubType = NotificationHubType.GeneralBrowserExtension; - break; - case DeviceType.WindowsDesktop: - case DeviceType.MacOsDesktop: - case DeviceType.LinuxDesktop: - hubType = NotificationHubType.GeneralDesktop; - break; - case DeviceType.ChromeBrowser: - case DeviceType.FirefoxBrowser: - case DeviceType.OperaBrowser: - case DeviceType.EdgeBrowser: - case DeviceType.IEBrowser: - case DeviceType.UnknownBrowser: - case DeviceType.SafariBrowser: - case DeviceType.VivaldiBrowser: - hubType = NotificationHubType.GeneralWeb; - break; - default: - break; - } + ClientType.Web => NotificationHubType.GeneralWeb, + ClientType.Browser => NotificationHubType.GeneralBrowserExtension, + ClientType.Desktop => NotificationHubType.GeneralDesktop, + ClientType.Mobile => deviceType switch + { + DeviceType.Android => NotificationHubType.Android, + DeviceType.iOS => NotificationHubType.iOS, + _ => NotificationHubType.General + }, + _ => NotificationHubType.General + }; if (!_clients.ContainsKey(hubType)) { @@ -272,6 +252,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService throw new Exception("No general hub client found."); } } + return _clients[hubType]; } } diff --git a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs index 9ec1eb31d..a43720c4f 100644 --- a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs @@ -2,6 +2,7 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -135,11 +136,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendMessageAsync(type, message, excludeCurrentContext); } @@ -156,11 +153,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendMessageAsync(type, message, true); } @@ -180,6 +173,20 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await PushSendAsync(send, PushType.SyncSendDelete); } + public async Task PushSyncNotificationAsync(Notification notification) + { + var message = new SyncNotificationPushNotification + { + Id = notification.Id, + Global = notification.Global, + UserId = notification.Id, + OrganizationId = notification.Id, + RevisionDate = notification.RevisionDate + }; + + await SendMessageAsync(PushType.SyncNotification, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -209,22 +216,25 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _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) + 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) + string deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } + + public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null) => Task.CompletedTask; } diff --git a/src/Core/Services/Implementations/RelayPushNotificationService.cs b/src/Core/Services/Implementations/RelayPushNotificationService.cs index 6cfc0c0a6..99f120a80 100644 --- a/src/Core/Services/Implementations/RelayPushNotificationService.cs +++ b/src/Core/Services/Implementations/RelayPushNotificationService.cs @@ -4,6 +4,7 @@ using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models; using Bit.Core.Models.Api; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tools.Entities; @@ -136,11 +137,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); } @@ -187,36 +184,58 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendPayloadToUserAsync(authRequest.UserId, type, message, true); } + public async Task PushSyncNotificationAsync(Notification notification) + { + var message = new SyncNotificationPushNotification + { + Id = notification.Id, + Global = notification.Global, + UserId = notification.Id, + OrganizationId = notification.Id, + RevisionDate = notification.RevisionDate + }; + + if (notification.Global) + { + await SendPayloadToEveryoneAsync(PushType.SyncNotification, message, true); + } + else if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, + true); + } + } + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) { - var request = new PushSendRequestModel - { - UserId = userId.ToString(), - Type = type, - Payload = payload - }; + var request = new PushSendRequestModel { UserId = userId.ToString(), Type = type, Payload = payload }; await AddCurrentContextAsync(request, excludeCurrentContext); await SendAsync(HttpMethod.Post, "push/send", request); } - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext) + private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, + bool excludeCurrentContext) { - var request = new PushSendRequestModel - { - OrganizationId = orgId.ToString(), - Type = type, - Payload = payload - }; + var request = new PushSendRequestModel { OrganizationId = orgId.ToString(), Type = type, Payload = payload }; + + await AddCurrentContextAsync(request, excludeCurrentContext); + await SendAsync(HttpMethod.Post, "push/send", request); + } + + private async Task SendPayloadToEveryoneAsync(PushType type, object payload, bool excludeCurrentContext) + { + // TODO global flag prop to be explicit ? + var request = new PushSendRequestModel { Type = type, Payload = payload }; await AddCurrentContextAsync(request, excludeCurrentContext); await SendAsync(HttpMethod.Post, "push/send", request); @@ -224,8 +243,8 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) { - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) { var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); @@ -233,6 +252,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti { request.DeviceId = device.Id.ToString(); } + if (addIdentifier) { request.Identifier = currentContext.DeviceIdentifier; @@ -241,13 +261,19 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti } public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { throw new NotImplementedException(); } public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) + { + throw new NotImplementedException(); + } + + public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null) { throw new NotImplementedException(); } diff --git a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs b/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs index d4eff93ef..f6bbb22a1 100644 --- a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs +++ b/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs @@ -1,5 +1,6 @@ using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -83,7 +84,7 @@ public class NoopPushNotificationService : IPushNotificationService } public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } @@ -99,8 +100,13 @@ public class NoopPushNotificationService : IPushNotificationService } public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + string deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } + + public Task PushSyncNotificationAsync(Notification notification) => Task.CompletedTask; + + public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null, + ClientType? clientType = null) => Task.CompletedTask; } diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index 53edb7638..8736643e2 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -18,7 +18,8 @@ public static class HubHelpers CancellationToken cancellationToken = default(CancellationToken) ) { - var notification = JsonSerializer.Deserialize>(notificationJson, _deserializerOptions); + var notification = + JsonSerializer.Deserialize>(notificationJson, _deserializerOptions); logger.LogInformation("Sending notification: {NotificationType}", notification.Type); switch (notification.Type) { @@ -37,9 +38,10 @@ public static class HubHelpers else if (cipherNotification.Payload.OrganizationId.HasValue) { await hubContext.Clients.Group( - $"Organization_{cipherNotification.Payload.OrganizationId}") + $"Organization_{cipherNotification.Payload.OrganizationId}") .SendAsync("ReceiveMessage", cipherNotification, cancellationToken); } + break; case PushType.SyncFolderUpdate: case PushType.SyncFolderCreate: @@ -48,7 +50,7 @@ public static class HubHelpers JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); await hubContext.Clients.User(folderNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", folderNotification, cancellationToken); + .SendAsync("ReceiveMessage", folderNotification, cancellationToken); break; case PushType.SyncCiphers: case PushType.SyncVault: @@ -60,31 +62,51 @@ public static class HubHelpers JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); await hubContext.Clients.User(userNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", userNotification, cancellationToken); + .SendAsync("ReceiveMessage", userNotification, cancellationToken); break; case PushType.SyncSendCreate: case PushType.SyncSendUpdate: case PushType.SyncSendDelete: var sendNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await hubContext.Clients.User(sendNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", sendNotification, cancellationToken); break; case PushType.AuthRequestResponse: var authRequestResponseNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString()) .SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken); break; case PushType.AuthRequest: var authRequestNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", authRequestNotification, cancellationToken); break; + case PushType.SyncNotification: + var syncNotification = + JsonSerializer.Deserialize>( + notificationJson, _deserializerOptions); + if (syncNotification.Payload.Global) + { + await hubContext.Clients.All.SendAsync("ReceiveMessage", syncNotification, cancellationToken); + } + else if (syncNotification.Payload.UserId.HasValue) + { + await hubContext.Clients.User(syncNotification.Payload.UserId.ToString()) + .SendAsync("ReceiveMessage", syncNotification, cancellationToken); + } + else if (syncNotification.Payload.OrganizationId.HasValue) + { + await hubContext.Clients.Group( + $"Organization_{syncNotification.Payload.OrganizationId}") + .SendAsync("ReceiveMessage", syncNotification, cancellationToken); + } + break; default: break; }