Files
server/src/Core/Platform/Push/IPushNotificationService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

446 lines
16 KiB
C#
Raw Normal View History

using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
[PM-10600] Push notification creation to affected clients (#4923) * PM-10600: Notification push notification * PM-10600: Sending to specific client types for relay push notifications * PM-10600: Sending to specific client types for other clients * PM-10600: Send push notification on notification creation * PM-10600: Explicit group names * PM-10600: Id typos * PM-10600: Revert global push notifications * PM-10600: Added DeviceType claim * PM-10600: Sent to organization typo * PM-10600: UT coverage * PM-10600: Small refactor, UTs coverage * PM-10600: UTs coverage * PM-10600: Startup fix * PM-10600: Test fix * PM-10600: Required attribute, organization group for push notification fix * PM-10600: UT coverage * PM-10600: Fix Mobile devices not registering to organization push notifications We only register devices for organization push notifications when the organization is being created. This does not work, since we have a use case (Notification Center) of delivering notifications to all users of organization. This fixes it, by adding the organization id tag when device registers for push notifications. * PM-10600: Unit Test coverage for NotificationHubPushRegistrationService Fixed IFeatureService substitute mocking for Android tests. Added user part of organization test with organizationId tags expectation. * PM-10600: Unit Tests fix to NotificationHubPushRegistrationService after merge conflict * PM-10600: Organization push notifications not sending to mobile device from self-hosted. Self-hosted instance uses relay to register the mobile device against Bitwarden Cloud Api. Only the self-hosted server knows client's organization membership, which means it needs to pass in the organization id's information to the relay. Similarly, for Bitwarden Cloud, the organizaton id will come directly from the server. * PM-10600: Fix self-hosted organization notification not being received by mobile device. When mobile device registers on self-hosted through the relay, every single id, like user id, device id and now organization id needs to be prefixed with the installation id. This have been missing in the PushController that handles this for organization id. * PM-10600: Broken NotificationsController integration test Device type is now part of JWT access token, so the notification center results in the integration test are now scoped to client type web and all. * PM-10600: Merge conflicts fix * merge conflict fix
2025-02-12 16:46:30 +01:00
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Platform.Push;
2022-08-29 16:06:55 -04:00
/// <summary>
/// Used to Push notifications to end-user devices.
/// </summary>
/// <remarks>
/// New notifications should not be wired up inside this service. You may either directly call the
/// <see cref="PushAsync"/> method in your service to send your notification or if you want your notification
/// sent by other teams you can make an extension method on this service with a well typed definition
/// of your notification. You may also make your own service that injects this and exposes methods for each of
/// your notifications.
/// </remarks>
2017-05-26 09:44:54 -04:00
public interface IPushNotificationService
{
private const string ServiceDeprecation = "Do not use the services exposed here, instead use your own services injected in your service.";
[Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")]
Guid InstallationId { get; }
[Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")]
TimeProvider TimeProvider { get; }
[Obsolete(ServiceDeprecation, DiagnosticId = "BWP0001")]
ILogger Logger { get; }
#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,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
ExcludeCurrentContext = false,
});
Task PushSyncVaultAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncVault,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
ExcludeCurrentContext = false,
});
Task PushSyncOrganizationsAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncOrganizations,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
ExcludeCurrentContext = false,
});
Task PushSyncOrgKeysAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncOrgKeys,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
ExcludeCurrentContext = false,
});
Task PushSyncSettingsAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.SyncSettings,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
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,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
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,
#pragma warning disable BWP0001 // Type or member is obsolete
InstallationId = notification.Global ? InstallationId : null,
#pragma warning restore BWP0001 // Type or member is obsolete
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;
#pragma warning disable BWP0001 // Type or member is obsolete
targetId = InstallationId;
#pragma warning restore BWP0001 // Type or member is obsolete
}
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
{
#pragma warning disable BWP0001 // Type or member is obsolete
Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id);
#pragma warning restore BWP0001 // Type or member is obsolete
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,
#pragma warning disable BWP0001 // Type or member is obsolete
InstallationId = notification.Global ? InstallationId : null,
#pragma warning restore BWP0001 // Type or member is obsolete
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;
#pragma warning disable BWP0001 // Type or member is obsolete
targetId = InstallationId;
#pragma warning restore BWP0001 // Type or member is obsolete
}
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
{
#pragma warning disable BWP0001 // Type or member is obsolete
Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id);
#pragma warning restore BWP0001 // Type or member is obsolete
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 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 PushRefreshSecurityTasksAsync(Guid userId)
=> PushAsync(new PushNotification<UserPushNotification>
{
Type = PushType.RefreshSecurityTasks,
Target = NotificationTarget.User,
TargetId = userId,
Payload = new UserPushNotification
{
UserId = userId,
#pragma warning disable BWP0001 // Type or member is obsolete
Date = TimeProvider.GetUtcNow().UtcDateTime,
#pragma warning restore BWP0001 // Type or member is obsolete
},
ExcludeCurrentContext = false,
});
#endregion
Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds);
/// <summary>
/// Pushes a notification to devices based on the settings given to us in <see cref="PushNotification{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the payload to be sent along with the notification.</typeparam>
/// <param name="pushNotification"></param>
/// <returns>A task that is NOT guarunteed to have sent the notification by the time the task resolves.</returns>
Task PushAsync<T>(PushNotification<T> pushNotification)
where T : class;
}