1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-17 07:30:59 -05:00

[PM-18555] Main part of notifications refactor (#5757)

* More tests

* More  tests

* Add non-guid tests

* Introduce slimmer services

* Implement IPushEngine on services

* Implement IPushEngine

* Fix tests

* Format

* Switch to `Guid` on `PushSendRequestModel`

* Remove TODOs
This commit is contained in:
Justin Baur
2025-06-17 13:30:56 -04:00
committed by GitHub
parent 6dc26f4be6
commit 6800bc57f3
23 changed files with 1271 additions and 2408 deletions

View File

@ -4,22 +4,22 @@ using Bit.Core.Enums;
namespace Bit.Core.Models.Api;
public class PushSendRequestModel : IValidatableObject
public class PushSendRequestModel<T> : IValidatableObject
{
public string? UserId { get; set; }
public string? OrganizationId { get; set; }
public string? DeviceId { get; set; }
public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? DeviceId { get; set; }
public string? Identifier { get; set; }
public required PushType Type { get; set; }
public required object Payload { get; set; }
public required T Payload { get; set; }
public ClientType? ClientType { get; set; }
public string? InstallationId { get; set; }
public Guid? InstallationId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(UserId) &&
string.IsNullOrWhiteSpace(OrganizationId) &&
string.IsNullOrWhiteSpace(InstallationId))
if (!UserId.HasValue &&
!OrganizationId.HasValue &&
!InstallationId.HasValue)
{
yield return new ValidationResult(
$"{nameof(UserId)} or {nameof(OrganizationId)} or {nameof(InstallationId)} is required.");

View File

@ -1,21 +1,17 @@
#nullable enable
using System.Text.Json;
using System.Text.RegularExpressions;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
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.Platform.Push.Internal;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Notification = Bit.Core.NotificationCenter.Entities.Notification;
namespace Bit.Core.NotificationHub;
@ -26,52 +22,32 @@ namespace Bit.Core.NotificationHub;
/// Used by Cloud-Hosted environments.
/// Received by Firebase for Android or APNS for iOS.
/// </summary>
public class NotificationHubPushNotificationService : IPushNotificationService
public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer
{
private readonly IInstallationDeviceRepository _installationDeviceRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly bool _enableTracing = false;
private readonly INotificationHubPool _notificationHubPool;
private readonly ILogger _logger;
private readonly IGlobalSettings _globalSettings;
private readonly TimeProvider _timeProvider;
public NotificationHubPushNotificationService(
IInstallationDeviceRepository installationDeviceRepository,
INotificationHubPool notificationHubPool,
IHttpContextAccessor httpContextAccessor,
ILogger<NotificationHubPushNotificationService> logger,
IGlobalSettings globalSettings,
TimeProvider timeProvider)
IGlobalSettings globalSettings)
{
_installationDeviceRepository = installationDeviceRepository;
_httpContextAccessor = httpContextAccessor;
_notificationHubPool = notificationHubPool;
_logger = logger;
_globalSettings = globalSettings;
_timeProvider = timeProvider;
if (globalSettings.Installation.Id == Guid.Empty)
{
logger.LogWarning("Installation ID is not set. Push notifications for installations will not work.");
}
}
public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds);
}
public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds);
}
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
{
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
}
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{
if (cipher.OrganizationId.HasValue)
{
@ -93,311 +69,17 @@ public class NotificationHubPushNotificationService : IPushNotificationService
CollectionIds = collectionIds,
};
await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true);
}
}
public async Task PushSyncFolderCreateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderCreate);
}
public async Task PushSyncFolderUpdateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
}
public async Task PushSyncFolderDeleteAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderDelete);
}
private async Task PushFolderAsync(Folder folder, PushType type)
{
var message = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate
};
await SendPayloadToUserAsync(folder.UserId, type, message, true);
}
public async Task PushSyncCiphersAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncCiphers);
}
public async Task PushSyncVaultAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncVault);
}
public async Task PushSyncOrganizationsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrganizations);
}
public async Task PushSyncOrgKeysAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrgKeys);
}
public async Task PushSyncSettingsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncSettings);
}
public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false)
{
await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext);
}
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{
var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime };
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
}
public async Task PushSyncSendCreateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendCreate);
}
public async Task PushSyncSendUpdateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendUpdate);
}
public async Task PushSyncSendDeleteAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendDelete);
}
private async Task PushSendAsync(Send send, PushType type)
{
if (send.UserId.HasValue)
{
var message = new SyncSendPushNotification
await PushAsync(new PushNotification<SyncCipherPushNotification>
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate
};
await SendPayloadToUserAsync(message.UserId, type, message, true);
Type = type,
Target = NotificationTarget.User,
TargetId = cipher.UserId.Value,
Payload = message,
ExcludeCurrentContext = true,
});
}
}
public async Task PushAuthRequestAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequest);
}
public async Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse);
}
public async Task PushNotificationAsync(Notification notification)
{
Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty
? _globalSettings.Installation.Id
: null;
var message = new NotificationPushNotification
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = installationId,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate
};
if (notification.Global)
{
if (installationId.HasValue)
{
await SendPayloadToInstallationAsync(installationId.Value, PushType.Notification, message, true,
notification.ClientType);
}
else
{
_logger.LogWarning(
"Invalid global notification id {NotificationId} push notification. No installation id provided.",
notification.Id);
}
}
else if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message,
true, notification.ClientType);
}
else
{
_logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
}
}
public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus)
{
Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty
? _globalSettings.Installation.Id
: null;
var message = new NotificationPushNotification
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = installationId,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus.ReadDate,
DeletedDate = notificationStatus.DeletedDate
};
if (notification.Global)
{
if (installationId.HasValue)
{
await SendPayloadToInstallationAsync(installationId.Value, PushType.NotificationStatus, message, true,
notification.ClientType);
}
else
{
_logger.LogWarning(
"Invalid global notification status id {NotificationId} push notification. No installation id provided.",
notification.Id);
}
}
else if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus,
message, true, notification.ClientType);
}
else
{
_logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
}
}
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{
var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
await SendPayloadToUserAsync(authRequest.UserId, type, message, true);
}
private async Task SendPayloadToInstallationAsync(Guid installationId, PushType type, object payload,
bool excludeCurrentContext, ClientType? clientType = null)
{
await SendPayloadToInstallationAsync(installationId.ToString(), type, payload,
GetContextIdentifier(excludeCurrentContext), clientType: clientType);
}
private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext,
ClientType? clientType = null)
{
await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext),
clientType: clientType);
}
private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload,
bool excludeCurrentContext, ClientType? clientType = null)
{
await SendPayloadToOrganizationAsync(orgId.ToString(), type, payload,
GetContextIdentifier(excludeCurrentContext), clientType: clientType);
}
public async Task PushPendingSecurityTasksAsync(Guid userId)
{
await PushUserAsync(userId, PushType.PendingSecurityTasks);
}
public async Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload,
string? identifier, string? deviceId = null, ClientType? clientType = null)
{
var tag = BuildTag($"template:payload && installationId:{installationId}", identifier, clientType);
await SendPayloadAsync(tag, type, payload);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
{
await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId));
}
}
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);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
{
await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId));
}
}
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);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
{
await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId));
}
}
public async Task PushSyncOrganizationStatusAsync(Organization organization)
{
var message = new OrganizationStatusPushNotification
{
OrganizationId = organization.Id,
Enabled = organization.Enabled
};
await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false);
}
public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) =>
await SendPayloadToOrganizationAsync(
organization.Id,
PushType.SyncOrganizationCollectionSettingChanged,
new OrganizationCollectionManagementPushNotification
{
OrganizationId = organization.Id,
LimitCollectionCreation = organization.LimitCollectionCreation,
LimitCollectionDeletion = organization.LimitCollectionDeletion,
LimitItemDeletion = organization.LimitItemDeletion
},
false
);
private string? GetContextIdentifier(bool excludeCurrentContext)
{
if (!excludeCurrentContext)
@ -425,13 +107,73 @@ public class NotificationHubPushNotificationService : IPushNotificationService
return $"({tag})";
}
private async Task SendPayloadAsync(string tag, PushType type, object payload)
public async Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class
{
var initialTag = pushNotification.Target switch
{
NotificationTarget.User => $"template:payload_userId:{pushNotification.TargetId}",
NotificationTarget.Organization => $"template:payload && organizationId:{pushNotification.TargetId}",
NotificationTarget.Installation => $"template:payload && installationId:{pushNotification.TargetId}",
_ => throw new InvalidOperationException($"Push notification target '{pushNotification.Target}' is not valid."),
};
await PushCoreAsync(
initialTag,
GetContextIdentifier(pushNotification.ExcludeCurrentContext),
pushNotification.Type,
pushNotification.ClientType,
pushNotification.Payload
);
}
public async Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification)
{
// Relayed notifications need identifiers prefixed with the installation they are from and a underscore
var initialTag = relayedNotification.Target switch
{
NotificationTarget.User => $"template:payload_userId:{fromInstallation}_{relayedNotification.TargetId}",
NotificationTarget.Organization => $"template:payload && organizationId:{fromInstallation}_{relayedNotification.TargetId}",
NotificationTarget.Installation => $"template:payload && installationId:{fromInstallation}",
_ => throw new InvalidOperationException($"Invalid Notification target {relayedNotification.Target}"),
};
await PushCoreAsync(
initialTag,
relayedNotification.Identifier,
relayedNotification.Type,
relayedNotification.ClientType,
relayedNotification.Payload
);
if (relayedNotification.DeviceId.HasValue)
{
await _installationDeviceRepository.UpsertAsync(
new InstallationDeviceEntity(fromInstallation, relayedNotification.DeviceId.Value)
);
}
else
{
_logger.LogWarning(
"A related notification of type '{Type}' came through without a device id from installation {Installation}",
relayedNotification.Type,
fromInstallation
);
}
}
private async Task PushCoreAsync<T>(string initialTag, string? contextId, PushType pushType, ClientType? clientType, T payload)
{
var finalTag = BuildTag(initialTag, contextId, clientType);
var results = await _notificationHubPool.AllClients.SendTemplateNotificationAsync(
new Dictionary<string, string>
{
{ "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) }
}, tag);
{ "type", ((byte)pushType).ToString() },
{ "payload", JsonSerializer.Serialize(payload) },
},
finalTag
);
if (_enableTracing)
{
@ -444,7 +186,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
_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, pushType, outcome.Success, outcome.Failure, payload, outcome.Results);
}
}
}

View File

@ -1,14 +1,10 @@
#nullable enable
using System.Text.Json;
using Azure.Storage.Queues;
using Bit.Core.AdminConsole.Entities;
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;
using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http;
@ -17,12 +13,10 @@ using Microsoft.Extensions.Logging;
namespace Bit.Core.Platform.Push.Internal;
public class AzureQueuePushNotificationService : IPushNotificationService
public class AzureQueuePushNotificationService : IPushEngine
{
private readonly QueueClient _queueClient;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IGlobalSettings _globalSettings;
private readonly TimeProvider _timeProvider;
public AzureQueuePushNotificationService(
[FromKeyedServices("notifications")] QueueClient queueClient,
@ -33,30 +27,13 @@ public class AzureQueuePushNotificationService : IPushNotificationService
{
_queueClient = queueClient;
_httpContextAccessor = httpContextAccessor;
_globalSettings = globalSettings;
_timeProvider = timeProvider;
if (globalSettings.Installation.Id == Guid.Empty)
{
logger.LogWarning("Installation ID is not set. Push notifications for installations will not work.");
}
}
public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds);
}
public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds);
}
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
{
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
}
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{
if (cipher.OrganizationId.HasValue)
{
@ -83,166 +60,6 @@ public class AzureQueuePushNotificationService : IPushNotificationService
}
}
public async Task PushSyncFolderCreateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderCreate);
}
public async Task PushSyncFolderUpdateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
}
public async Task PushSyncFolderDeleteAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderDelete);
}
private async Task PushFolderAsync(Folder folder, PushType type)
{
var message = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate
};
await SendMessageAsync(type, message, true);
}
public async Task PushSyncCiphersAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncCiphers);
}
public async Task PushSyncVaultAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncVault);
}
public async Task PushSyncOrganizationsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrganizations);
}
public async Task PushSyncOrgKeysAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrgKeys);
}
public async Task PushSyncSettingsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncSettings);
}
public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false)
{
await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext);
}
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{
var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime };
await SendMessageAsync(type, message, excludeCurrentContext);
}
public async Task PushAuthRequestAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequest);
}
public async Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse);
}
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{
var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
await SendMessageAsync(type, message, true);
}
public async Task PushSyncSendCreateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendCreate);
}
public async Task PushSyncSendUpdateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendUpdate);
}
public async Task PushSyncSendDeleteAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendDelete);
}
public async Task PushNotificationAsync(Notification notification)
{
var message = new NotificationPushNotification
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate
};
await SendMessageAsync(PushType.Notification, 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,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus.ReadDate,
DeletedDate = notificationStatus.DeletedDate
};
await SendMessageAsync(PushType.NotificationStatus, message, true);
}
public async Task PushPendingSecurityTasksAsync(Guid userId)
{
await PushUserAsync(userId, PushType.PendingSecurityTasks);
}
private async Task PushSendAsync(Send send, PushType type)
{
if (send.UserId.HasValue)
{
var message = new SyncSendPushNotification
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate
};
await SendMessageAsync(type, message, true);
}
}
private async Task SendMessageAsync<T>(PushType type, T payload, bool excludeCurrentContext)
{
var contextId = GetContextIdentifier(excludeCurrentContext);
@ -263,42 +80,9 @@ public class AzureQueuePushNotificationService : IPushNotificationService
return currentContext?.DeviceIdentifier;
}
public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null) =>
// Noop
Task.CompletedTask;
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
public async Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class
{
// Noop
return Task.FromResult(0);
await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext);
}
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
{
// Noop
return Task.FromResult(0);
}
public async Task PushSyncOrganizationStatusAsync(Organization organization)
{
var message = new OrganizationStatusPushNotification
{
OrganizationId = organization.Id,
Enabled = organization.Enabled
};
await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false);
}
public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) =>
await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged,
new OrganizationCollectionManagementPushNotification
{
OrganizationId = organization.Id,
LimitCollectionCreation = organization.LimitCollectionCreation,
LimitCollectionDeletion = organization.LimitCollectionDeletion,
LimitItemDeletion = organization.LimitItemDeletion
}, false);
}

View File

@ -0,0 +1,13 @@
#nullable enable
using Bit.Core.Enums;
using Bit.Core.Vault.Entities;
namespace Bit.Core.Platform.Push;
public interface IPushEngine
{
Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds);
Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class;
}

View File

@ -2,41 +2,410 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Platform.Push;
public interface IPushNotificationService
{
Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds);
Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds);
Task PushSyncCipherDeleteAsync(Cipher cipher);
Task PushSyncFolderCreateAsync(Folder folder);
Task PushSyncFolderUpdateAsync(Folder folder);
Task PushSyncFolderDeleteAsync(Folder folder);
Task PushSyncCiphersAsync(Guid userId);
Task PushSyncVaultAsync(Guid userId);
Task PushSyncOrganizationsAsync(Guid userId);
Task PushSyncOrgKeysAsync(Guid userId);
Task PushSyncSettingsAsync(Guid userId);
Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false);
Task PushSyncSendCreateAsync(Send send);
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);
Guid InstallationId { get; }
TimeProvider TimeProvider { get; }
ILogger Logger { get; }
Task SendPayloadToInstallationAsync(string installationId, 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);
Task PushPendingSecurityTasksAsync(Guid userId);
#region Legacy method, to be removed soon.
Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
=> PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds);
Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
=> PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds);
Task PushSyncCipherDeleteAsync(Cipher cipher)
=> PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
Task PushSyncFolderCreateAsync(Folder folder)
=> PushAsync(new PushNotification<SyncFolderPushNotification>
{
Type = PushType.SyncFolderCreate,
Target = NotificationTarget.User,
TargetId = folder.UserId,
Payload = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate,
},
ExcludeCurrentContext = true,
});
Task PushSyncFolderUpdateAsync(Folder folder)
=> PushAsync(new PushNotification<SyncFolderPushNotification>
{
Type = PushType.SyncFolderUpdate,
Target = NotificationTarget.User,
TargetId = folder.UserId,
Payload = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate,
},
ExcludeCurrentContext = true,
});
Task PushSyncFolderDeleteAsync(Folder folder)
=> PushAsync(new PushNotification<SyncFolderPushNotification>
{
Type = PushType.SyncFolderDelete,
Target = NotificationTarget.User,
TargetId = folder.UserId,
Payload = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate,
},
ExcludeCurrentContext = true,
});
Task PushSyncCiphersAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncCiphers,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
Task PushSyncVaultAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncVault,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
Task PushSyncOrganizationsAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncOrganizations,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
Task PushSyncOrgKeysAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncOrgKeys,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
Task PushSyncSettingsAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncSettings,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.LogOut,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = excludeCurrentContextFromPush,
});
Task PushSyncSendCreateAsync(Send send)
{
if (send.UserId.HasValue)
{
return PushAsync(new PushNotification<SyncSendPushNotification>
{
Type = PushType.SyncSendCreate,
Target = NotificationTarget.User,
TargetId = send.UserId.Value,
Payload = new SyncSendPushNotification
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate,
},
ExcludeCurrentContext = true,
});
}
return Task.CompletedTask;
}
Task PushSyncSendUpdateAsync(Send send)
{
if (send.UserId.HasValue)
{
return PushAsync(new PushNotification<SyncSendPushNotification>
{
Type = PushType.SyncSendUpdate,
Target = NotificationTarget.User,
TargetId = send.UserId.Value,
Payload = new SyncSendPushNotification
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate,
},
ExcludeCurrentContext = true,
});
}
return Task.CompletedTask;
}
Task PushSyncSendDeleteAsync(Send send)
{
if (send.UserId.HasValue)
{
return PushAsync(new PushNotification<SyncSendPushNotification>
{
Type = PushType.SyncSendDelete,
Target = NotificationTarget.User,
TargetId = send.UserId.Value,
Payload = new SyncSendPushNotification
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate,
},
ExcludeCurrentContext = true,
});
}
return Task.CompletedTask;
}
Task PushNotificationAsync(Notification notification)
{
var message = new NotificationPushNotification
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = notification.Global ? InstallationId : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
};
NotificationTarget target;
Guid targetId;
if (notification.Global)
{
// TODO: Think about this a bit more
target = NotificationTarget.Installation;
targetId = InstallationId;
}
else if (notification.UserId.HasValue)
{
target = NotificationTarget.User;
targetId = notification.UserId.Value;
}
else if (notification.OrganizationId.HasValue)
{
target = NotificationTarget.Organization;
targetId = notification.OrganizationId.Value;
}
else
{
Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
return Task.CompletedTask;
}
return PushAsync(new PushNotification<NotificationPushNotification>
{
Type = PushType.Notification,
Target = target,
TargetId = targetId,
Payload = message,
ExcludeCurrentContext = true,
ClientType = notification.ClientType,
});
}
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,
InstallationId = notification.Global ? InstallationId : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus.ReadDate,
DeletedDate = notificationStatus.DeletedDate,
};
NotificationTarget target;
Guid targetId;
if (notification.Global)
{
// TODO: Think about this a bit more
target = NotificationTarget.Installation;
targetId = InstallationId;
}
else if (notification.UserId.HasValue)
{
target = NotificationTarget.User;
targetId = notification.UserId.Value;
}
else if (notification.OrganizationId.HasValue)
{
target = NotificationTarget.Organization;
targetId = notification.OrganizationId.Value;
}
else
{
Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
return Task.CompletedTask;
}
return PushAsync(new PushNotification<NotificationPushNotification>
{
Type = PushType.NotificationStatus,
Target = target,
TargetId = targetId,
Payload = message,
ExcludeCurrentContext = true,
ClientType = notification.ClientType,
});
}
Task PushAuthRequestAsync(AuthRequest authRequest)
=> PushAsync(new PushNotification<AuthRequestPushNotification>
{
Type = PushType.AuthRequest,
Target = NotificationTarget.User,
TargetId = authRequest.UserId,
Payload = new AuthRequestPushNotification
{
Id = authRequest.Id,
UserId = authRequest.UserId,
},
ExcludeCurrentContext = true,
});
Task PushAuthRequestResponseAsync(AuthRequest authRequest)
=> PushAsync(new PushNotification<AuthRequestPushNotification>
{
Type = PushType.AuthRequestResponse,
Target = NotificationTarget.User,
TargetId = authRequest.UserId,
Payload = new AuthRequestPushNotification
{
Id = authRequest.Id,
UserId = authRequest.UserId,
},
ExcludeCurrentContext = true,
});
Task PushSyncOrganizationStatusAsync(Organization organization)
=> PushAsync(new PushNotification<OrganizationStatusPushNotification>
{
Type = PushType.SyncOrganizationStatusChanged,
Target = NotificationTarget.Organization,
TargetId = organization.Id,
Payload = new OrganizationStatusPushNotification
{
OrganizationId = organization.Id,
Enabled = organization.Enabled,
},
ExcludeCurrentContext = false,
});
Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization)
=> PushAsync(new PushNotification<OrganizationCollectionManagementPushNotification>
{
Type = PushType.SyncOrganizationCollectionSettingChanged,
Target = NotificationTarget.Organization,
TargetId = organization.Id,
Payload = new OrganizationCollectionManagementPushNotification
{
OrganizationId = organization.Id,
LimitCollectionCreation = organization.LimitCollectionCreation,
LimitCollectionDeletion = organization.LimitCollectionDeletion,
LimitItemDeletion = organization.LimitItemDeletion,
},
ExcludeCurrentContext = false,
});
Task PushPendingSecurityTasksAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.PendingSecurityTasks,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
Date = TimeProvider.GetUtcNow().UtcDateTime,
},
ExcludeCurrentContext = false,
});
#endregion
Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds);
Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class;
}

View File

@ -0,0 +1,44 @@
#nullable enable
using System.Text.Json;
using Bit.Core.Enums;
namespace Bit.Core.Platform.Push.Internal;
/// <summary>
/// An object encapsulating the information that is available in a notification
/// given to us from a self-hosted installation.
/// </summary>
public class RelayedNotification
{
/// <inheritdoc cref="PushNotification{T}.Type"/>
public required PushType Type { get; init; }
/// <inheritdoc cref="PushNotification{T}.Target"/>
public required NotificationTarget Target { get; init; }
/// <inheritdoc cref="PushNotification{T}.TargetId"/>
public required Guid TargetId { get; init; }
/// <inheritdoc cref="PushNotification{T}.Payload"/>
public required JsonElement Payload { get; init; }
/// <inheritdoc cref="PushNotification{T}.ClientType"/>
public required ClientType? ClientType { get; init; }
public required Guid? DeviceId { get; init; }
public required string? Identifier { get; init; }
}
/// <summary>
/// A service for taking a notification that was relayed to us from a self-hosted installation and
/// will be injested into our infrastructure so that we can get the notification to devices that require
/// cloud interaction.
/// </summary>
/// <remarks>
/// This interface should be treated as internal and not consumed by other teams.
/// </remarks>
public interface IPushRelayer
{
/// <summary>
/// Relays a notification that was received from an authenticated installation into our cloud push notification infrastructure.
/// </summary>
/// <param name="fromInstallation">The authenticated installation this notification came from.</param>
/// <param name="relayedNotification">The information received from the self-hosted installation.</param>
Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification);
}

View File

@ -1,202 +1,77 @@
#nullable enable
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Platform.Push.Internal;
public class MultiServicePushNotificationService : IPushNotificationService
{
private readonly IEnumerable<IPushNotificationService> _services;
private readonly ILogger<MultiServicePushNotificationService> _logger;
private readonly IEnumerable<IPushEngine> _services;
public Guid InstallationId { get; }
public TimeProvider TimeProvider { get; }
public ILogger Logger { get; }
public MultiServicePushNotificationService(
[FromKeyedServices("implementation")] IEnumerable<IPushNotificationService> services,
IEnumerable<IPushEngine> services,
ILogger<MultiServicePushNotificationService> logger,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
TimeProvider timeProvider)
{
_services = services;
_logger = logger;
_logger.LogInformation("Hub services: {Services}", _services.Count());
Logger = logger;
Logger.LogInformation("Hub services: {Services}", _services.Count());
globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub =>
{
_logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate);
Logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate);
});
InstallationId = globalSettings.Installation.Id;
TimeProvider = timeProvider;
}
public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
PushToServices((s) => s.PushSyncCipherCreateAsync(cipher, collectionIds));
return Task.FromResult(0);
}
public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
PushToServices((s) => s.PushSyncCipherUpdateAsync(cipher, collectionIds));
return Task.FromResult(0);
}
public Task PushSyncCipherDeleteAsync(Cipher cipher)
{
PushToServices((s) => s.PushSyncCipherDeleteAsync(cipher));
return Task.FromResult(0);
}
public Task PushSyncFolderCreateAsync(Folder folder)
{
PushToServices((s) => s.PushSyncFolderCreateAsync(folder));
return Task.FromResult(0);
}
public Task PushSyncFolderUpdateAsync(Folder folder)
{
PushToServices((s) => s.PushSyncFolderUpdateAsync(folder));
return Task.FromResult(0);
}
public Task PushSyncFolderDeleteAsync(Folder folder)
{
PushToServices((s) => s.PushSyncFolderDeleteAsync(folder));
return Task.FromResult(0);
}
public Task PushSyncCiphersAsync(Guid userId)
{
PushToServices((s) => s.PushSyncCiphersAsync(userId));
return Task.FromResult(0);
}
public Task PushSyncVaultAsync(Guid userId)
{
PushToServices((s) => s.PushSyncVaultAsync(userId));
return Task.FromResult(0);
}
public Task PushSyncOrganizationsAsync(Guid userId)
{
PushToServices((s) => s.PushSyncOrganizationsAsync(userId));
return Task.FromResult(0);
}
public Task PushSyncOrgKeysAsync(Guid userId)
{
PushToServices((s) => s.PushSyncOrgKeysAsync(userId));
return Task.FromResult(0);
}
public Task PushSyncSettingsAsync(Guid userId)
{
PushToServices((s) => s.PushSyncSettingsAsync(userId));
return Task.FromResult(0);
}
public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false)
{
PushToServices((s) => s.PushLogOutAsync(userId, excludeCurrentContext));
return Task.FromResult(0);
}
public Task PushSyncSendCreateAsync(Send send)
{
PushToServices((s) => s.PushSyncSendCreateAsync(send));
return Task.FromResult(0);
}
public Task PushSyncSendUpdateAsync(Send send)
{
PushToServices((s) => s.PushSyncSendUpdateAsync(send));
return Task.FromResult(0);
}
public Task PushAuthRequestAsync(AuthRequest authRequest)
{
PushToServices((s) => s.PushAuthRequestAsync(authRequest));
return Task.FromResult(0);
}
public Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
PushToServices((s) => s.PushAuthRequestResponseAsync(authRequest));
return Task.FromResult(0);
}
public Task PushSyncSendDeleteAsync(Send send)
{
PushToServices((s) => s.PushSyncSendDeleteAsync(send));
return Task.FromResult(0);
}
public Task PushSyncOrganizationStatusAsync(Organization organization)
{
PushToServices((s) => s.PushSyncOrganizationStatusAsync(organization));
return Task.FromResult(0);
}
public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization)
{
PushToServices(s => s.PushSyncOrganizationCollectionManagementSettingsAsync(organization));
return Task.CompletedTask;
}
public Task PushNotificationAsync(Notification notification)
{
PushToServices((s) => s.PushNotificationAsync(notification));
return Task.CompletedTask;
}
public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus)
{
PushToServices((s) => s.PushNotificationStatusAsync(notification, notificationStatus));
return Task.CompletedTask;
}
public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
{
PushToServices((s) =>
s.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType));
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)
{
PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType));
return Task.FromResult(0);
}
public Task PushPendingSecurityTasksAsync(Guid userId)
{
PushToServices((s) => s.PushPendingSecurityTasksAsync(userId));
return Task.CompletedTask;
}
private void PushToServices(Func<IPushNotificationService, Task> pushFunc)
private Task PushToServices(Func<IPushEngine, Task> pushFunc)
{
if (!_services.Any())
{
_logger.LogWarning("No services found to push notification");
return;
Logger.LogWarning("No services found to push notification");
return Task.CompletedTask;
}
#if DEBUG
var tasks = new List<Task>();
#endif
foreach (var service in _services)
{
_logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name);
Logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name);
#if DEBUG
var task =
#endif
pushFunc(service);
#if DEBUG
tasks.Add(task);
#endif
}
#if DEBUG
return Task.WhenAll(tasks);
#else
return Task.CompletedTask;
#endif
}
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds)
{
return PushToServices((s) => s.PushCipherAsync(cipher, pushType, collectionIds));
}
public Task PushAsync<T>(PushNotification<T> pushNotification) where T : class
{
return PushToServices((s) => s.PushAsync(pushNotification));
}
}

View File

@ -1,129 +1,12 @@
#nullable enable
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
namespace Bit.Core.Platform.Push.Internal;
public class NoopPushNotificationService : IPushNotificationService
internal class NoopPushNotificationService : IPushEngine
{
public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
return Task.FromResult(0);
}
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds) => Task.CompletedTask;
public Task PushSyncCipherDeleteAsync(Cipher cipher)
{
return Task.FromResult(0);
}
public Task PushSyncCiphersAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
return Task.FromResult(0);
}
public Task PushSyncFolderCreateAsync(Folder folder)
{
return Task.FromResult(0);
}
public Task PushSyncFolderDeleteAsync(Folder folder)
{
return Task.FromResult(0);
}
public Task PushSyncFolderUpdateAsync(Folder folder)
{
return Task.FromResult(0);
}
public Task PushSyncOrganizationsAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushSyncOrgKeysAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushSyncSettingsAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushSyncVaultAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false)
{
return Task.FromResult(0);
}
public Task PushSyncSendCreateAsync(Send send)
{
return Task.FromResult(0);
}
public Task PushSyncSendDeleteAsync(Send send)
{
return Task.FromResult(0);
}
public Task PushSyncSendUpdateAsync(Send send)
{
return Task.FromResult(0);
}
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
{
return Task.FromResult(0);
}
public Task PushSyncOrganizationStatusAsync(Organization organization)
{
return Task.FromResult(0);
}
public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => Task.CompletedTask;
public Task PushAuthRequestAsync(AuthRequest authRequest)
{
return Task.FromResult(0);
}
public Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
return Task.FromResult(0);
}
public Task PushNotificationAsync(Notification notification) => Task.CompletedTask;
public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) =>
Task.CompletedTask;
public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null) => Task.CompletedTask;
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
{
return Task.FromResult(0);
}
public Task PushPendingSecurityTasksAsync(Guid userId)
{
return Task.FromResult(0);
}
public Task PushAsync<T>(PushNotification<T> pushNotification) where T : class => Task.CompletedTask;
}

View File

@ -1,13 +1,9 @@
#nullable enable
using Bit.Core.AdminConsole.Entities;
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.Services;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@ -20,18 +16,15 @@ namespace Bit.Core.Platform.Push;
/// Used by Cloud-Hosted environments.
/// Received by AzureQueueHostedService message receiver in Notifications project.
/// </summary>
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushEngine
{
private readonly IGlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly TimeProvider _timeProvider;
public NotificationsApiPushNotificationService(
IHttpClientFactory httpFactory,
GlobalSettings globalSettings,
IHttpContextAccessor httpContextAccessor,
ILogger<NotificationsApiPushNotificationService> logger,
TimeProvider timeProvider)
ILogger<NotificationsApiPushNotificationService> logger)
: base(
httpFactory,
globalSettings.BaseServiceUri.InternalNotifications,
@ -41,27 +34,10 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
globalSettings.InternalIdentityKey,
logger)
{
_globalSettings = globalSettings;
_httpContextAccessor = httpContextAccessor;
_timeProvider = timeProvider;
}
public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds);
}
public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds);
}
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
{
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
}
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{
if (cipher.OrganizationId.HasValue)
{
@ -89,174 +65,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
}
}
public async Task PushSyncFolderCreateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderCreate);
}
public async Task PushSyncFolderUpdateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
}
public async Task PushSyncFolderDeleteAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderDelete);
}
private async Task PushFolderAsync(Folder folder, PushType type)
{
var message = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate
};
await SendMessageAsync(type, message, true);
}
public async Task PushSyncCiphersAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncCiphers);
}
public async Task PushSyncVaultAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncVault);
}
public async Task PushSyncOrganizationsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrganizations);
}
public async Task PushSyncOrgKeysAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrgKeys);
}
public async Task PushSyncSettingsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncSettings);
}
public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext)
{
await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext);
}
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{
var message = new UserPushNotification
{
UserId = userId,
Date = _timeProvider.GetUtcNow().UtcDateTime,
};
await SendMessageAsync(type, message, excludeCurrentContext);
}
public async Task PushAuthRequestAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequest);
}
public async Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse);
}
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{
var message = new AuthRequestPushNotification
{
Id = authRequest.Id,
UserId = authRequest.UserId
};
await SendMessageAsync(type, message, true);
}
public async Task PushSyncSendCreateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendCreate);
}
public async Task PushSyncSendUpdateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendUpdate);
}
public async Task PushSyncSendDeleteAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendDelete);
}
public async Task PushNotificationAsync(Notification notification)
{
var message = new NotificationPushNotification
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate
};
await SendMessageAsync(PushType.Notification, 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,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus.ReadDate,
DeletedDate = notificationStatus.DeletedDate
};
await SendMessageAsync(PushType.NotificationStatus, message, true);
}
public async Task PushPendingSecurityTasksAsync(Guid userId)
{
await PushUserAsync(userId, PushType.PendingSecurityTasks);
}
private async Task PushSendAsync(Send send, PushType type)
{
if (send.UserId.HasValue)
{
var message = new SyncSendPushNotification
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate
};
await SendMessageAsync(type, message, false);
}
}
private async Task SendMessageAsync<T>(PushType type, T payload, bool excludeCurrentContext)
{
var contextId = GetContextIdentifier(excludeCurrentContext);
@ -276,43 +84,8 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
return currentContext?.DeviceIdentifier;
}
public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null) =>
// Noop
Task.CompletedTask;
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
public async Task PushAsync<T>(PushNotification<T> pushNotification) where T : class
{
// Noop
return Task.FromResult(0);
await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext);
}
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null)
{
// Noop
return Task.FromResult(0);
}
public async Task PushSyncOrganizationStatusAsync(Organization organization)
{
var message = new OrganizationStatusPushNotification
{
OrganizationId = organization.Id,
Enabled = organization.Enabled
};
await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false);
}
public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) =>
await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged,
new OrganizationCollectionManagementPushNotification
{
OrganizationId = organization.Id,
LimitCollectionCreation = organization.LimitCollectionCreation,
LimitCollectionDeletion = organization.LimitCollectionDeletion,
LimitItemDeletion = organization.LimitItemDeletion
}, false);
}

View File

@ -0,0 +1,78 @@
#nullable enable
using Bit.Core.Enums;
namespace Bit.Core.Platform.Push;
/// <summary>
/// Contains constants for all the available targets for a given notification.
/// </summary>
public enum NotificationTarget
{
/// <summary>
/// The target for the notification is a single user.
/// </summary>
User,
/// <summary>
/// The target for the notification are all the users in an organization.
/// </summary>
Organization,
/// <summary>
/// The target for the notification are all the organizations,
/// and all the users in that organization for a installation.
/// </summary>
Installation,
}
/// <summary>
/// An object containing all the information required for getting a notification
/// to an end users device and the information you want available to that device.
/// </summary>
/// <typeparam name="T">The type of the payload. This type is expected to be able to be roundtripped as JSON.</typeparam>
public record PushNotification<T>
where T : class
{
/// <summary>
/// The <see cref="PushType"/> to be associated with the notification. This is used to route
/// the notification to the correct handler on the client side. Be sure to use the correct payload
/// type for the associated <see cref="PushType"/>.
/// </summary>
public required PushType Type { get; init; }
/// <summary>
/// The target entity type for the notification.
/// </summary>
/// <remarks>
/// When the target type is <see cref="NotificationTarget.User"/> the <see cref="TargetId"/>
/// property is expected to be a users ID. When it is <see cref="NotificationTarget.Organization"/>
/// it should be an organizations id. When it is a <see cref="NotificationTarget.Installation"/>
/// it should be an installation id.
/// </remarks>
public required NotificationTarget Target { get; init; }
/// <summary>
/// The indentifier for the given <see cref="Target"/>.
/// </summary>
public required Guid TargetId { get; init; }
/// <summary>
/// The payload to be sent with the notification. This object will be JSON serialized.
/// </summary>
public required T Payload { get; init; }
/// <summary>
/// When <see langword="true"/> the notification will not include the current context identifier on it, this
/// means that the notification may get handled on the device that this notification could have originated from.
/// </summary>
public required bool ExcludeCurrentContext { get; init; }
/// <summary>
/// The type of clients the notification should be sent to, if <see langword="null"/> then
/// <see cref="ClientType.All"/> is inferred.
/// </summary>
public ClientType? ClientType { get; init; }
internal Guid? GetTargetWhen(NotificationTarget notificationTarget)
{
return Target == notificationTarget ? TargetId : null;
}
}

View File

@ -1,18 +1,15 @@
#nullable enable
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Context;
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.Services;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Platform.Push.Internal;
@ -22,20 +19,18 @@ namespace Bit.Core.Platform.Push.Internal;
/// Used by Self-Hosted environments.
/// Received by PushController endpoint in Api project.
/// </summary>
public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService
public class RelayPushNotificationService : BaseIdentityClientService, IPushEngine
{
private readonly IDeviceRepository _deviceRepository;
private readonly IGlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly TimeProvider _timeProvider;
public RelayPushNotificationService(
IHttpClientFactory httpFactory,
IDeviceRepository deviceRepository,
GlobalSettings globalSettings,
IHttpContextAccessor httpContextAccessor,
ILogger<RelayPushNotificationService> logger,
TimeProvider timeProvider)
ILogger<RelayPushNotificationService> logger)
: base(
httpFactory,
globalSettings.PushRelayBaseUri,
@ -46,27 +41,10 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
logger)
{
_deviceRepository = deviceRepository;
_globalSettings = globalSettings;
_httpContextAccessor = httpContextAccessor;
_timeProvider = timeProvider;
}
public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds);
}
public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable<Guid> collectionIds)
{
await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds);
}
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
{
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
}
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{
if (cipher.OrganizationId.HasValue)
{
@ -87,306 +65,45 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
RevisionDate = cipher.RevisionDate,
};
await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true);
}
}
public async Task PushSyncFolderCreateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderCreate);
}
public async Task PushSyncFolderUpdateAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
}
public async Task PushSyncFolderDeleteAsync(Folder folder)
{
await PushFolderAsync(folder, PushType.SyncFolderDelete);
}
private async Task PushFolderAsync(Folder folder, PushType type)
{
var message = new SyncFolderPushNotification
{
Id = folder.Id,
UserId = folder.UserId,
RevisionDate = folder.RevisionDate
};
await SendPayloadToUserAsync(folder.UserId, type, message, true);
}
public async Task PushSyncCiphersAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncCiphers);
}
public async Task PushSyncVaultAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncVault);
}
public async Task PushSyncOrganizationsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrganizations);
}
public async Task PushSyncOrgKeysAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncOrgKeys);
}
public async Task PushSyncSettingsAsync(Guid userId)
{
await PushUserAsync(userId, PushType.SyncSettings);
}
public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false)
{
await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext);
}
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{
var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime };
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
}
public async Task PushSyncSendCreateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendCreate);
}
public async Task PushSyncSendUpdateAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendUpdate);
}
public async Task PushSyncSendDeleteAsync(Send send)
{
await PushSendAsync(send, PushType.SyncSendDelete);
}
private async Task PushSendAsync(Send send, PushType type)
{
if (send.UserId.HasValue)
{
var message = new SyncSendPushNotification
await PushAsync(new PushNotification<SyncCipherPushNotification>
{
Id = send.Id,
UserId = send.UserId.Value,
RevisionDate = send.RevisionDate
};
await SendPayloadToUserAsync(message.UserId, type, message, true);
Type = type,
Target = NotificationTarget.User,
TargetId = cipher.UserId.Value,
Payload = message,
ExcludeCurrentContext = true,
});
}
}
public async Task PushAuthRequestAsync(AuthRequest authRequest)
public async Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequest);
}
var deviceIdentifier = _httpContextAccessor.HttpContext
?.RequestServices.GetService<ICurrentContext>()
?.DeviceIdentifier;
public async Task PushAuthRequestResponseAsync(AuthRequest authRequest)
{
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse);
}
Guid? deviceId = null;
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
{
var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
await SendPayloadToUserAsync(authRequest.UserId, type, message, true);
}
public async Task PushNotificationAsync(Notification notification)
{
var message = new NotificationPushNotification
if (!string.IsNullOrEmpty(deviceIdentifier))
{
Id = notification.Id,
Priority = notification.Priority,
Global = notification.Global,
ClientType = notification.ClientType,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate
var device = await _deviceRepository.GetByIdentifierAsync(deviceIdentifier);
deviceId = device?.Id;
}
var payload = new PushSendRequestModel<T>
{
Type = pushNotification.Type,
UserId = pushNotification.GetTargetWhen(NotificationTarget.User),
OrganizationId = pushNotification.GetTargetWhen(NotificationTarget.Organization),
InstallationId = pushNotification.GetTargetWhen(NotificationTarget.Installation),
Payload = pushNotification.Payload,
Identifier = pushNotification.ExcludeCurrentContext ? deviceIdentifier : null,
// We set the device id regardless of if they want to exclude the current context or not
DeviceId = deviceId,
ClientType = pushNotification.ClientType,
};
if (notification.Global)
{
await SendPayloadToInstallationAsync(PushType.Notification, message, true, notification.ClientType);
}
else if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message,
true, notification.ClientType);
}
else
{
_logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
}
}
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,
InstallationId = notification.Global ? _globalSettings.Installation.Id : null,
TaskId = notification.TaskId,
Title = notification.Title,
Body = notification.Body,
CreationDate = notification.CreationDate,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus.ReadDate,
DeletedDate = notificationStatus.DeletedDate
};
if (notification.Global)
{
await SendPayloadToInstallationAsync(PushType.NotificationStatus, message, true, notification.ClientType);
}
else if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus, message,
true, notification.ClientType);
}
else
{
_logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
}
}
public async Task PushSyncOrganizationStatusAsync(Organization organization)
{
var message = new OrganizationStatusPushNotification
{
OrganizationId = organization.Id,
Enabled = organization.Enabled
};
await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false);
}
public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) =>
await SendPayloadToOrganizationAsync(
organization.Id,
PushType.SyncOrganizationCollectionSettingChanged,
new OrganizationCollectionManagementPushNotification
{
OrganizationId = organization.Id,
LimitCollectionCreation = organization.LimitCollectionCreation,
LimitCollectionDeletion = organization.LimitCollectionDeletion,
LimitItemDeletion = organization.LimitItemDeletion
},
false
);
public async Task PushPendingSecurityTasksAsync(Guid userId)
{
await PushUserAsync(userId, PushType.PendingSecurityTasks);
}
private async Task SendPayloadToInstallationAsync(PushType type, object payload, bool excludeCurrentContext,
ClientType? clientType = null)
{
var request = new PushSendRequestModel
{
InstallationId = _globalSettings.Installation.Id.ToString(),
Type = type,
Payload = payload,
ClientType = clientType
};
await AddCurrentContextAsync(request, excludeCurrentContext);
await SendAsync(HttpMethod.Post, "push/send", request);
}
private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext,
ClientType? clientType = null)
{
var request = new PushSendRequestModel
{
UserId = userId.ToString(),
Type = type,
Payload = payload,
ClientType = clientType
};
await AddCurrentContextAsync(request, excludeCurrentContext);
await SendAsync(HttpMethod.Post, "push/send", request);
}
private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload,
bool excludeCurrentContext, ClientType? clientType = null)
{
var request = new PushSendRequestModel
{
OrganizationId = orgId.ToString(),
Type = type,
Payload = payload,
ClientType = clientType
};
await AddCurrentContextAsync(request, excludeCurrentContext);
await SendAsync(HttpMethod.Post, "push/send", request);
}
private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier)
{
var currentContext =
_httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
{
var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier);
if (device != null)
{
request.DeviceId = device.Id.ToString();
}
if (addIdentifier)
{
request.Identifier = currentContext.DeviceIdentifier;
}
}
}
public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier,
string? deviceId = null, ClientType? clientType = null) =>
throw new NotImplementedException();
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)
{
throw new NotImplementedException();
await SendAsync(HttpMethod.Post, "push/send", payload);
}
}