mirror of
https://github.com/bitwarden/server
synced 2026-01-04 17:43:53 +00:00
[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
This commit is contained in:
@@ -5,26 +5,25 @@ using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.Platform.Push.Internal;
|
||||
|
||||
public class AzureQueuePushNotificationService : IPushNotificationService
|
||||
{
|
||||
private readonly QueueClient _queueClient;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AzureQueuePushNotificationService(
|
||||
GlobalSettings globalSettings,
|
||||
[FromKeyedServices("notifications")] QueueClient queueClient,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_queueClient = new QueueClient(globalSettings.Notifications.ConnectionString, "notifications");
|
||||
_globalSettings = globalSettings;
|
||||
_queueClient = queueClient;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
@@ -129,11 +128,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
|
||||
|
||||
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
|
||||
{
|
||||
var message = new UserPushNotification
|
||||
{
|
||||
UserId = userId,
|
||||
Date = DateTime.UtcNow
|
||||
};
|
||||
var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
|
||||
|
||||
await SendMessageAsync(type, message, excludeCurrentContext);
|
||||
}
|
||||
@@ -150,11 +145,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
|
||||
|
||||
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
|
||||
{
|
||||
var message = new AuthRequestPushNotification
|
||||
{
|
||||
Id = authRequest.Id,
|
||||
UserId = authRequest.UserId
|
||||
};
|
||||
var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
|
||||
|
||||
await SendMessageAsync(type, message, true);
|
||||
}
|
||||
@@ -174,6 +165,20 @@ public class AzureQueuePushNotificationService : IPushNotificationService
|
||||
await PushSendAsync(send, PushType.SyncSendDelete);
|
||||
}
|
||||
|
||||
public async Task PushSyncNotificationAsync(Notification notification)
|
||||
{
|
||||
var message = new SyncNotificationPushNotification
|
||||
{
|
||||
Id = notification.Id,
|
||||
UserId = notification.UserId,
|
||||
OrganizationId = notification.OrganizationId,
|
||||
ClientType = notification.ClientType,
|
||||
RevisionDate = notification.RevisionDate
|
||||
};
|
||||
|
||||
await SendMessageAsync(PushType.SyncNotification, message, true);
|
||||
}
|
||||
|
||||
private async Task PushSendAsync(Send send, PushType type)
|
||||
{
|
||||
if (send.UserId.HasValue)
|
||||
@@ -204,20 +209,20 @@ public class AzureQueuePushNotificationService : IPushNotificationService
|
||||
return null;
|
||||
}
|
||||
|
||||
var currentContext = _httpContextAccessor?.HttpContext?.
|
||||
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
|
||||
var currentContext =
|
||||
_httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
|
||||
return currentContext?.DeviceIdentifier;
|
||||
}
|
||||
|
||||
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
// Noop
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
// Noop
|
||||
return Task.FromResult(0);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
|
||||
@@ -23,11 +24,13 @@ public interface IPushNotificationService
|
||||
Task PushSyncSendCreateAsync(Send send);
|
||||
Task PushSyncSendUpdateAsync(Send send);
|
||||
Task PushSyncSendDeleteAsync(Send send);
|
||||
Task PushSyncNotificationAsync(Notification notification);
|
||||
Task PushAuthRequestAsync(AuthRequest authRequest);
|
||||
Task PushAuthRequestResponseAsync(AuthRequest authRequest);
|
||||
Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, string deviceId = null);
|
||||
Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null);
|
||||
Task PushSyncOrganizationStatusAsync(Organization organization);
|
||||
Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization);
|
||||
Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null, ClientType? clientType = null);
|
||||
Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null, ClientType? clientType = null);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Bit.Core.Platform.Push;
|
||||
public interface IPushRegistrationService
|
||||
{
|
||||
Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type);
|
||||
string identifier, DeviceType type, IEnumerable<string> organizationIds);
|
||||
Task DeleteRegistrationAsync(string deviceId);
|
||||
Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);
|
||||
Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -131,20 +132,6 @@ public class MultiServicePushNotificationService : IPushNotificationService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
{
|
||||
PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId));
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
{
|
||||
PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId));
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task PushSyncOrganizationStatusAsync(Organization organization)
|
||||
{
|
||||
PushToServices((s) => s.PushSyncOrganizationStatusAsync(organization));
|
||||
@@ -157,6 +144,26 @@ public class MultiServicePushNotificationService : IPushNotificationService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PushSyncNotificationAsync(Notification notification)
|
||||
{
|
||||
PushToServices((s) => s.PushSyncNotificationAsync(notification));
|
||||
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);
|
||||
}
|
||||
|
||||
private void PushToServices(Func<IPushNotificationService, Task> pushFunc)
|
||||
{
|
||||
if (_services != null)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
|
||||
@@ -84,7 +85,7 @@ public class NoopPushNotificationService : IPushNotificationService
|
||||
}
|
||||
|
||||
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
@@ -107,8 +108,10 @@ public class NoopPushNotificationService : IPushNotificationService
|
||||
}
|
||||
|
||||
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task PushSyncNotificationAsync(Notification notification) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class NoopPushRegistrationService : IPushRegistrationService
|
||||
}
|
||||
|
||||
public Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type)
|
||||
string identifier, DeviceType type, IEnumerable<string> organizationIds)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tools.Entities;
|
||||
@@ -183,6 +184,20 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
|
||||
await PushSendAsync(send, PushType.SyncSendDelete);
|
||||
}
|
||||
|
||||
public async Task PushSyncNotificationAsync(Notification notification)
|
||||
{
|
||||
var message = new SyncNotificationPushNotification
|
||||
{
|
||||
Id = notification.Id,
|
||||
UserId = notification.UserId,
|
||||
OrganizationId = notification.OrganizationId,
|
||||
ClientType = notification.ClientType,
|
||||
RevisionDate = notification.RevisionDate
|
||||
};
|
||||
|
||||
await SendMessageAsync(PushType.SyncNotification, message, true);
|
||||
}
|
||||
|
||||
private async Task PushSendAsync(Send send, PushType type)
|
||||
{
|
||||
if (send.UserId.HasValue)
|
||||
@@ -212,20 +227,20 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
|
||||
return null;
|
||||
}
|
||||
|
||||
var currentContext = _httpContextAccessor?.HttpContext?.
|
||||
RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
|
||||
var currentContext =
|
||||
_httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
|
||||
return currentContext?.DeviceIdentifier;
|
||||
}
|
||||
|
||||
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
// Noop
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
string deviceId = null, ClientType? clientType = null)
|
||||
{
|
||||
// Noop
|
||||
return Task.FromResult(0);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@@ -138,11 +139,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
|
||||
|
||||
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
|
||||
{
|
||||
var message = new UserPushNotification
|
||||
{
|
||||
UserId = userId,
|
||||
Date = DateTime.UtcNow
|
||||
};
|
||||
var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
|
||||
|
||||
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
|
||||
}
|
||||
@@ -189,69 +186,32 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
|
||||
|
||||
private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type)
|
||||
{
|
||||
var message = new AuthRequestPushNotification
|
||||
{
|
||||
Id = authRequest.Id,
|
||||
UserId = authRequest.UserId
|
||||
};
|
||||
var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId };
|
||||
|
||||
await SendPayloadToUserAsync(authRequest.UserId, type, message, true);
|
||||
}
|
||||
|
||||
private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext)
|
||||
public async Task PushSyncNotificationAsync(Notification notification)
|
||||
{
|
||||
var request = new PushSendRequestModel
|
||||
var message = new SyncNotificationPushNotification
|
||||
{
|
||||
UserId = userId.ToString(),
|
||||
Type = type,
|
||||
Payload = payload
|
||||
Id = notification.Id,
|
||||
UserId = notification.UserId,
|
||||
OrganizationId = notification.OrganizationId,
|
||||
ClientType = notification.ClientType,
|
||||
RevisionDate = notification.RevisionDate
|
||||
};
|
||||
|
||||
await AddCurrentContextAsync(request, excludeCurrentContext);
|
||||
await SendAsync(HttpMethod.Post, "push/send", request);
|
||||
}
|
||||
|
||||
private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext)
|
||||
{
|
||||
var request = new PushSendRequestModel
|
||||
if (notification.UserId.HasValue)
|
||||
{
|
||||
OrganizationId = orgId.ToString(),
|
||||
Type = type,
|
||||
Payload = payload
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
|
||||
string deviceId = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task PushSyncOrganizationStatusAsync(Organization organization)
|
||||
@@ -278,4 +238,65 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi
|
||||
}
|
||||
|
||||
public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
|
||||
string identifier, DeviceType type)
|
||||
string identifier, DeviceType type, IEnumerable<string> organizationIds)
|
||||
{
|
||||
var requestModel = new PushRegistrationRequestModel
|
||||
{
|
||||
@@ -33,7 +33,8 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi
|
||||
Identifier = identifier,
|
||||
PushToken = pushToken,
|
||||
Type = type,
|
||||
UserId = userId
|
||||
UserId = userId,
|
||||
OrganizationIds = organizationIds
|
||||
};
|
||||
await SendAsync(HttpMethod.Post, "push/register", requestModel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user