1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

PM-10600: Notification push notification

This commit is contained in:
Maciej Zieniuk
2024-10-21 14:58:57 +01:00
parent 7b5e0e4a64
commit 3a604af0a4
11 changed files with 291 additions and 153 deletions

View File

@ -25,4 +25,6 @@ public enum PushType : byte
AuthRequestResponse = 16, AuthRequestResponse = 16,
SyncOrganizations = 17, SyncOrganizations = 17,
SyncNotification = 18,
} }

View File

@ -45,6 +45,15 @@ public class SyncSendPushNotification
public DateTime RevisionDate { get; set; } 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 class AuthRequestPushNotification
{ {
public Guid UserId { get; set; } public Guid UserId { get; set; }

View File

@ -1,5 +1,6 @@
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
@ -22,9 +23,16 @@ public interface IPushNotificationService
Task PushSyncSendCreateAsync(Send send); Task PushSyncSendCreateAsync(Send send);
Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendUpdateAsync(Send send);
Task PushSyncSendDeleteAsync(Send send); Task PushSyncSendDeleteAsync(Send send);
Task PushSyncNotificationAsync(Notification notification);
Task PushAuthRequestAsync(AuthRequest authRequest); Task PushAuthRequestAsync(AuthRequest authRequest);
Task PushAuthRequestResponseAsync(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, 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);
} }

View File

@ -4,6 +4,7 @@ using Bit.Core.Auth.Entities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -128,11 +129,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{ {
var message = new UserPushNotification var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
{
UserId = userId,
Date = DateTime.UtcNow
};
await SendMessageAsync(type, message, excludeCurrentContext); await SendMessageAsync(type, message, excludeCurrentContext);
} }
@ -149,11 +146,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{ {
var message = new AuthRequestPushNotification var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
{
Id = authRequest.Id,
UserId = authRequest.UserId
};
await SendMessageAsync(type, message, true); await SendMessageAsync(type, message, true);
} }
@ -173,6 +166,20 @@ public class AzureQueuePushNotificationService : IPushNotificationService
await PushSendAsync(send, PushType.SyncSendDelete); 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) private async Task PushSendAsync(Send send, PushType type)
{ {
if (send.UserId.HasValue) if (send.UserId.HasValue)
@ -203,22 +210,25 @@ public class AzureQueuePushNotificationService : IPushNotificationService
return null; return null;
} }
var currentContext = _httpContextAccessor?.HttpContext?. var currentContext =
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier; return currentContext?.DeviceIdentifier;
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null,
ClientType? clientType = null) => Task.CompletedTask;
} }

View File

@ -1,5 +1,6 @@
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
@ -34,6 +35,7 @@ public class MultiServicePushNotificationService : IPushNotificationService
_services.Add(new RelayPushNotificationService(httpFactory, deviceRepository, globalSettings, _services.Add(new RelayPushNotificationService(httpFactory, deviceRepository, globalSettings,
httpContextAccessor, relayLogger)); httpContextAccessor, relayLogger));
} }
if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) &&
CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications))
{ {
@ -43,12 +45,14 @@ public class MultiServicePushNotificationService : IPushNotificationService
} }
else 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)) if (CoreHelpers.SettingHasValue(generalHub?.ConnectionString))
{ {
_services.Add(new NotificationHubPushNotificationService(installationDeviceRepository, _services.Add(new NotificationHubPushNotificationService(installationDeviceRepository,
globalSettings, httpContextAccessor, hubLogger)); globalSettings, httpContextAccessor, hubLogger));
} }
if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString))
{ {
_services.Add(new AzureQueuePushNotificationService(globalSettings, httpContextAccessor)); _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, 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); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, 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); 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<IPushNotificationService, Task> pushFunc) private void PushToServices(Func<IPushNotificationService, Task> pushFunc)
{ {
if (_services != null) if (_services != null)

View File

@ -12,6 +12,7 @@ using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Azure.NotificationHubs; using Microsoft.Azure.NotificationHubs;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Notification = Bit.Core.NotificationCenter.Entities.Notification;
namespace Bit.Core.Services; namespace Bit.Core.Services;
@ -148,11 +149,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{ {
var message = new UserPushNotification var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
{
UserId = userId,
Date = DateTime.UtcNow
};
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
} }
@ -197,31 +194,65 @@ public class NotificationHubPushNotificationService : IPushNotificationService
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); 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) private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{ {
var message = new AuthRequestPushNotification var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
{
Id = authRequest.Id,
UserId = authRequest.UserId
};
await SendPayloadToUserAsync(authRequest.UserId, type, message, true); 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, 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); await SendPayloadAsync(tag, type, payload);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
{ {
@ -230,9 +261,20 @@ public class NotificationHubPushNotificationService : IPushNotificationService
} }
public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, 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); await SendPayloadAsync(tag, type, payload);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
{ {
@ -247,18 +289,23 @@ public class NotificationHubPushNotificationService : IPushNotificationService
return null; return null;
} }
var currentContext = _httpContextAccessor?.HttpContext?. var currentContext =
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier; return currentContext?.DeviceIdentifier;
} }
private string BuildTag(string tag, string identifier) private string BuildTag(string tag, string identifier, ClientType? clientType)
{ {
if (!string.IsNullOrWhiteSpace(identifier)) if (!string.IsNullOrWhiteSpace(identifier))
{ {
tag += $" && !deviceIdentifier:{SanitizeTagInput(identifier)}"; tag += $" && !deviceIdentifier:{SanitizeTagInput(identifier)}";
} }
if (clientType.HasValue && clientType.Value != ClientType.All)
{
tag += $" && clientType:{clientType}";
}
return $"({tag})"; return $"({tag})";
} }
@ -270,8 +317,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
var task = client.SendTemplateNotificationAsync( var task = client.SendTemplateNotificationAsync(
new Dictionary<string, string> new Dictionary<string, string>
{ {
{ "type", ((byte)type).ToString() }, { "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) }
{ "payload", JsonSerializer.Serialize(payload) }
}, tag); }, tag);
tasks.Add(task); tasks.Add(task);
} }
@ -285,7 +331,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService
if (_clients[i].EnableTestSend) if (_clients[i].EnableTestSend)
{ {
var outcome = await tasks[i]; 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); outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results);
} }
} }

View File

@ -2,6 +2,7 @@
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Azure.NotificationHubs; using Microsoft.Azure.NotificationHubs;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -62,10 +63,9 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
Templates = new Dictionary<string, InstallationTemplate>() Templates = new Dictionary<string, InstallationTemplate>()
}; };
installation.Tags = new List<string> var clientType = DeviceTypes.ToClientType(type);
{
$"userId:{userId}" installation.Tags = new List<string> { $"userId:{userId}", $"clientType:{clientType}" };
};
if (!string.IsNullOrWhiteSpace(identifier)) if (!string.IsNullOrWhiteSpace(identifier))
{ {
@ -81,24 +81,25 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
{ {
payloadTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\",\"payload\":\"$(payload)\"}}}"; payloadTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\",\"payload\":\"$(payload)\"}}}";
messageTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\"}," + messageTemplate = "{\"message\":{\"data\":{\"type\":\"$(type)\"}," +
"\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}";
installation.Platform = NotificationPlatform.FcmV1; installation.Platform = NotificationPlatform.FcmV1;
} }
else else
{ {
payloadTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}}"; payloadTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}}";
messageTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\"}," + messageTemplate = "{\"data\":{\"data\":{\"type\":\"#(type)\"}," +
"\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}"; "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}";
installation.Platform = NotificationPlatform.Fcm; installation.Platform = NotificationPlatform.Fcm;
} }
break; break;
case DeviceType.iOS: case DeviceType.iOS:
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}," + payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}," +
"\"aps\":{\"content-available\":1}}"; "\"aps\":{\"content-available\":1}}";
messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," + messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
"\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}"; "\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}";
badgeMessageTemplate = "{\"data\":{\"type\":\"#(type)\"}," + badgeMessageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
"\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}"; "\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}";
installation.Platform = NotificationPlatform.Apns; installation.Platform = NotificationPlatform.Apns;
break; break;
@ -112,10 +113,10 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
break; break;
} }
BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier); BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier, clientType);
BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier); BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier, clientType);
BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate,
userId, identifier); userId, identifier, clientType);
await GetClient(type).CreateOrUpdateInstallationAsync(installation); await GetClient(type).CreateOrUpdateInstallationAsync(installation);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
@ -125,7 +126,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
} }
private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody, private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody,
string userId, string identifier) string userId, string identifier, ClientType clientType)
{ {
if (templateBody == null) if (templateBody == null)
{ {
@ -139,8 +140,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
Body = templateBody, Body = templateBody,
Tags = new List<string> Tags = new List<string>
{ {
fullTemplateId, fullTemplateId, $"{fullTemplateId}_userId:{userId}", $"clientType:{clientType}"
$"{fullTemplateId}_userId:{userId}"
} }
}; };
@ -168,7 +168,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
} }
} }
public async Task AddUserRegistrationOrganizationAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices, string organizationId) public async Task AddUserRegistrationOrganizationAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices,
string organizationId)
{ {
await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Add, $"organizationId:{organizationId}"); await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Add, $"organizationId:{organizationId}");
if (devices.Any() && InstallationDeviceEntity.IsInstallationDeviceId(devices.First().Key)) if (devices.Any() && InstallationDeviceEntity.IsInstallationDeviceId(devices.First().Key))
@ -178,7 +179,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
} }
} }
public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices, string organizationId) public async Task DeleteUserRegistrationOrganizationAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices,
string organizationId)
{ {
await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Remove, await PatchTagsForUserDevicesAsync(devices, UpdateOperationType.Remove,
$"organizationId:{organizationId}"); $"organizationId:{organizationId}");
@ -189,7 +191,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
} }
} }
private async Task PatchTagsForUserDevicesAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices, UpdateOperationType op, private async Task PatchTagsForUserDevicesAsync(IEnumerable<KeyValuePair<string, DeviceType>> devices,
UpdateOperationType op,
string tag) string tag)
{ {
if (!devices.Any()) if (!devices.Any())
@ -197,11 +200,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
return; return;
} }
var operation = new PartialUpdateOperation var operation = new PartialUpdateOperation { Operation = op, Path = "/tags" };
{
Operation = op,
Path = "/tags"
};
if (op == UpdateOperationType.Add) if (op == UpdateOperationType.Add)
{ {
@ -216,7 +215,8 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
{ {
try try
{ {
await GetClient(device.Value).PatchInstallationAsync(device.Key, new List<PartialUpdateOperation> { operation }); await GetClient(device.Value)
.PatchInstallationAsync(device.Key, new List<PartialUpdateOperation> { operation });
} }
catch (Exception e) when (e.InnerException == null || !e.InnerException.Message.Contains("(404) Not Found")) 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) private NotificationHubClient GetClient(DeviceType deviceType)
{ {
var hubType = NotificationHubType.General; var clientType = DeviceTypes.ToClientType(deviceType);
switch (deviceType)
var hubType = clientType switch
{ {
case DeviceType.Android: ClientType.Web => NotificationHubType.GeneralWeb,
hubType = NotificationHubType.Android; ClientType.Browser => NotificationHubType.GeneralBrowserExtension,
break; ClientType.Desktop => NotificationHubType.GeneralDesktop,
case DeviceType.iOS: ClientType.Mobile => deviceType switch
hubType = NotificationHubType.iOS; {
break; DeviceType.Android => NotificationHubType.Android,
case DeviceType.ChromeExtension: DeviceType.iOS => NotificationHubType.iOS,
case DeviceType.FirefoxExtension: _ => NotificationHubType.General
case DeviceType.OperaExtension: },
case DeviceType.EdgeExtension: _ => NotificationHubType.General
case DeviceType.VivaldiExtension: };
case DeviceType.SafariExtension:
hubType = NotificationHubType.GeneralBrowserExtension;
break;
case DeviceType.WindowsDesktop:
case DeviceType.MacOsDesktop:
case DeviceType.LinuxDesktop:
hubType = NotificationHubType.GeneralDesktop;
break;
case DeviceType.ChromeBrowser:
case DeviceType.FirefoxBrowser:
case DeviceType.OperaBrowser:
case DeviceType.EdgeBrowser:
case DeviceType.IEBrowser:
case DeviceType.UnknownBrowser:
case DeviceType.SafariBrowser:
case DeviceType.VivaldiBrowser:
hubType = NotificationHubType.GeneralWeb;
break;
default:
break;
}
if (!_clients.ContainsKey(hubType)) if (!_clients.ContainsKey(hubType))
{ {
@ -272,6 +252,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
throw new Exception("No general hub client found."); throw new Exception("No general hub client found.");
} }
} }
return _clients[hubType]; return _clients[hubType];
} }
} }

View File

@ -2,6 +2,7 @@
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
@ -135,11 +136,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{ {
var message = new UserPushNotification var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
{
UserId = userId,
Date = DateTime.UtcNow
};
await SendMessageAsync(type, message, excludeCurrentContext); await SendMessageAsync(type, message, excludeCurrentContext);
} }
@ -156,11 +153,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{ {
var message = new AuthRequestPushNotification var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
{
Id = authRequest.Id,
UserId = authRequest.UserId
};
await SendMessageAsync(type, message, true); await SendMessageAsync(type, message, true);
} }
@ -180,6 +173,20 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
await PushSendAsync(send, PushType.SyncSendDelete); 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) private async Task PushSendAsync(Send send, PushType type)
{ {
if (send.UserId.HasValue) if (send.UserId.HasValue)
@ -209,22 +216,25 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
return null; return null;
} }
var currentContext = _httpContextAccessor?.HttpContext?. var currentContext =
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier; return currentContext?.DeviceIdentifier;
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToEveryoneAsync(PushType type, object payload, string identifier, string deviceId = null,
ClientType? clientType = null) => Task.CompletedTask;
} }

View File

@ -4,6 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Entities; 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) private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{ {
var message = new UserPushNotification var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
{
UserId = userId,
Date = DateTime.UtcNow
};
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
} }
@ -187,36 +184,58 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{ {
var message = new AuthRequestPushNotification var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
{
Id = authRequest.Id,
UserId = authRequest.UserId
};
await SendPayloadToUserAsync(authRequest.UserId, type, message, true); 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) private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext)
{ {
var request = new PushSendRequestModel var request = new PushSendRequestModel { UserId = userId.ToString(), Type = type, Payload = payload };
{
UserId = userId.ToString(),
Type = type,
Payload = payload
};
await AddCurrentContextAsync(request, excludeCurrentContext); await AddCurrentContextAsync(request, excludeCurrentContext);
await SendAsync(HttpMethod.Post, "push/send", request); 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 var request = new PushSendRequestModel { OrganizationId = orgId.ToString(), Type = type, Payload = payload };
{
OrganizationId = orgId.ToString(), await AddCurrentContextAsync(request, excludeCurrentContext);
Type = type, await SendAsync(HttpMethod.Post, "push/send", request);
Payload = payload }
};
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 AddCurrentContextAsync(request, excludeCurrentContext);
await SendAsync(HttpMethod.Post, "push/send", request); await SendAsync(HttpMethod.Post, "push/send", request);
@ -224,8 +243,8 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier)
{ {
var currentContext = _httpContextAccessor?.HttpContext?. var currentContext =
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
{ {
var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier);
@ -233,6 +252,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
{ {
request.DeviceId = device.Id.ToString(); request.DeviceId = device.Id.ToString();
} }
if (addIdentifier) if (addIdentifier)
{ {
request.Identifier = currentContext.DeviceIdentifier; request.Identifier = currentContext.DeviceIdentifier;
@ -241,13 +261,19 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, 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(); throw new NotImplementedException();
} }

View File

@ -1,5 +1,6 @@
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Vault.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, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
@ -99,8 +100,13 @@ public class NoopPushNotificationService : IPushNotificationService
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null) string deviceId = null, ClientType? clientType = null)
{ {
return Task.FromResult(0); 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;
} }

View File

@ -18,7 +18,8 @@ public static class HubHelpers
CancellationToken cancellationToken = default(CancellationToken) CancellationToken cancellationToken = default(CancellationToken)
) )
{ {
var notification = JsonSerializer.Deserialize<PushNotificationData<object>>(notificationJson, _deserializerOptions); var notification =
JsonSerializer.Deserialize<PushNotificationData<object>>(notificationJson, _deserializerOptions);
logger.LogInformation("Sending notification: {NotificationType}", notification.Type); logger.LogInformation("Sending notification: {NotificationType}", notification.Type);
switch (notification.Type) switch (notification.Type)
{ {
@ -37,9 +38,10 @@ public static class HubHelpers
else if (cipherNotification.Payload.OrganizationId.HasValue) else if (cipherNotification.Payload.OrganizationId.HasValue)
{ {
await hubContext.Clients.Group( await hubContext.Clients.Group(
$"Organization_{cipherNotification.Payload.OrganizationId}") $"Organization_{cipherNotification.Payload.OrganizationId}")
.SendAsync("ReceiveMessage", cipherNotification, cancellationToken); .SendAsync("ReceiveMessage", cipherNotification, cancellationToken);
} }
break; break;
case PushType.SyncFolderUpdate: case PushType.SyncFolderUpdate:
case PushType.SyncFolderCreate: case PushType.SyncFolderCreate:
@ -48,7 +50,7 @@ public static class HubHelpers
JsonSerializer.Deserialize<PushNotificationData<SyncFolderPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<SyncFolderPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);
await hubContext.Clients.User(folderNotification.Payload.UserId.ToString()) await hubContext.Clients.User(folderNotification.Payload.UserId.ToString())
.SendAsync("ReceiveMessage", folderNotification, cancellationToken); .SendAsync("ReceiveMessage", folderNotification, cancellationToken);
break; break;
case PushType.SyncCiphers: case PushType.SyncCiphers:
case PushType.SyncVault: case PushType.SyncVault:
@ -60,31 +62,51 @@ public static class HubHelpers
JsonSerializer.Deserialize<PushNotificationData<UserPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<UserPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);
await hubContext.Clients.User(userNotification.Payload.UserId.ToString()) await hubContext.Clients.User(userNotification.Payload.UserId.ToString())
.SendAsync("ReceiveMessage", userNotification, cancellationToken); .SendAsync("ReceiveMessage", userNotification, cancellationToken);
break; break;
case PushType.SyncSendCreate: case PushType.SyncSendCreate:
case PushType.SyncSendUpdate: case PushType.SyncSendUpdate:
case PushType.SyncSendDelete: case PushType.SyncSendDelete:
var sendNotification = var sendNotification =
JsonSerializer.Deserialize<PushNotificationData<SyncSendPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<SyncSendPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);
await hubContext.Clients.User(sendNotification.Payload.UserId.ToString()) await hubContext.Clients.User(sendNotification.Payload.UserId.ToString())
.SendAsync("ReceiveMessage", sendNotification, cancellationToken); .SendAsync("ReceiveMessage", sendNotification, cancellationToken);
break; break;
case PushType.AuthRequestResponse: case PushType.AuthRequestResponse:
var authRequestResponseNotification = var authRequestResponseNotification =
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);
await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString()) await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString())
.SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken); .SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken);
break; break;
case PushType.AuthRequest: case PushType.AuthRequest:
var authRequestNotification = var authRequestNotification =
JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<AuthRequestPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);
await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString()) await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString())
.SendAsync("ReceiveMessage", authRequestNotification, cancellationToken); .SendAsync("ReceiveMessage", authRequestNotification, cancellationToken);
break; break;
case PushType.SyncNotification:
var syncNotification =
JsonSerializer.Deserialize<PushNotificationData<SyncNotificationPushNotification>>(
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: default:
break; break;
} }