1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

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.
This commit is contained in:
Maciej Zieniuk 2024-11-20 11:39:41 +00:00
parent 0aa38bed5a
commit 35fab48ad9
No known key found for this signature in database
GPG Key ID: 9CACE59F1272ACD9
9 changed files with 162 additions and 118 deletions

View File

@ -39,7 +39,7 @@ public class PushController : Controller
{ {
CheckUsage(); CheckUsage();
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId), await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId),
Prefix(model.UserId), Prefix(model.Identifier), model.Type); Prefix(model.UserId), Prefix(model.Identifier), model.Type, model.OrganizationIds);
} }
[HttpPost("delete")] [HttpPost("delete")]

View File

@ -15,4 +15,5 @@ public class PushRegistrationRequestModel
public DeviceType Type { get; set; } public DeviceType Type { get; set; }
[Required] [Required]
public string Identifier { get; set; } public string Identifier { get; set; }
public IEnumerable<string> OrganizationIds { get; set; }
} }

View File

@ -11,20 +11,17 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
{ {
private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly IInstallationDeviceRepository _installationDeviceRepository;
private readonly INotificationHubPool _notificationHubPool; private readonly INotificationHubPool _notificationHubPool;
private readonly IOrganizationUserRepository _organizationUserRepository;
public NotificationHubPushRegistrationService( public NotificationHubPushRegistrationService(
IInstallationDeviceRepository installationDeviceRepository, IInstallationDeviceRepository installationDeviceRepository,
INotificationHubPool notificationHubPool, INotificationHubPool notificationHubPool)
IOrganizationUserRepository organizationUserRepository)
{ {
_installationDeviceRepository = installationDeviceRepository; _installationDeviceRepository = installationDeviceRepository;
_notificationHubPool = notificationHubPool; _notificationHubPool = notificationHubPool;
_organizationUserRepository = organizationUserRepository;
} }
public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
string identifier, DeviceType type) string identifier, DeviceType type, IEnumerable<string> organizationIds)
{ {
if (string.IsNullOrWhiteSpace(pushToken)) if (string.IsNullOrWhiteSpace(pushToken))
{ {
@ -47,10 +44,10 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
installation.Tags.Add("deviceIdentifier:" + identifier); installation.Tags.Add("deviceIdentifier:" + identifier);
} }
foreach (var organizationUserDetails in await _organizationUserRepository.GetManyDetailsByUserAsync( var organizationIdsList = organizationIds.ToList();
Guid.Parse(userId), OrganizationUserStatusType.Confirmed)) foreach (var organizationId in organizationIdsList)
{ {
installation.Tags.Add($"organizationId:{organizationUserDetails.OrganizationId}"); installation.Tags.Add($"organizationId:{organizationId}");
} }
string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null; string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null;
@ -82,10 +79,12 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
break; break;
} }
BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier, clientType); BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier, clientType,
BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier, clientType); organizationIdsList);
BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier, clientType,
organizationIdsList);
BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate,
userId, identifier, clientType); userId, identifier, clientType, organizationIdsList);
await ClientFor(GetComb(deviceId)).CreateOrUpdateInstallationAsync(installation); await ClientFor(GetComb(deviceId)).CreateOrUpdateInstallationAsync(installation);
if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId))
@ -95,7 +94,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
} }
private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody, private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody,
string userId, string identifier, ClientType clientType) string userId, string identifier, ClientType clientType, List<string> organizationIds)
{ {
if (templateBody == null) if (templateBody == null)
{ {
@ -118,6 +117,11 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{identifier}"); template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{identifier}");
} }
foreach (var organizationId in organizationIds)
{
template.Tags.Add($"organizationId:{organizationId}");
}
installation.Templates.Add(fullTemplateId, template); installation.Templates.Add(fullTemplateId, template);
} }

View File

@ -5,7 +5,7 @@ namespace Bit.Core.Services;
public interface IPushRegistrationService public interface IPushRegistrationService
{ {
Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
string identifier, DeviceType type); string identifier, DeviceType type, IEnumerable<string> organizationIds);
Task DeleteRegistrationAsync(string deviceId); Task DeleteRegistrationAsync(string deviceId);
Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId); Task AddUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);
Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId); Task DeleteUserRegistrationOrganizationAsync(IEnumerable<string> deviceIds, string organizationId);

View File

@ -1,6 +1,7 @@
using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Auth.Models.Api.Request;
using Bit.Core.Auth.Utilities; using Bit.Core.Auth.Utilities;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -10,13 +11,16 @@ public class DeviceService : IDeviceService
{ {
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly IPushRegistrationService _pushRegistrationService; private readonly IPushRegistrationService _pushRegistrationService;
private readonly IOrganizationUserRepository _organizationUserRepository;
public DeviceService( public DeviceService(
IDeviceRepository deviceRepository, IDeviceRepository deviceRepository,
IPushRegistrationService pushRegistrationService) IPushRegistrationService pushRegistrationService,
IOrganizationUserRepository organizationUserRepository)
{ {
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_pushRegistrationService = pushRegistrationService; _pushRegistrationService = pushRegistrationService;
_organizationUserRepository = organizationUserRepository;
} }
public async Task SaveAsync(Device device) public async Task SaveAsync(Device device)
@ -31,8 +35,13 @@ public class DeviceService : IDeviceService
await _deviceRepository.ReplaceAsync(device); await _deviceRepository.ReplaceAsync(device);
} }
var organizationIdsString =
(await _organizationUserRepository.GetManyDetailsByUserAsync(device.UserId,
OrganizationUserStatusType.Confirmed))
.Select(ou => ou.OrganizationId.ToString());
await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device.PushToken, device.Id.ToString(), await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device.PushToken, device.Id.ToString(),
device.UserId.ToString(), device.Identifier, device.Type); device.UserId.ToString(), device.Identifier, device.Type, organizationIdsString);
} }
public async Task ClearTokenAsync(Device device) public async Task ClearTokenAsync(Device device)

View File

@ -25,7 +25,7 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi
} }
public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, 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 var requestModel = new PushRegistrationRequestModel
{ {
@ -33,7 +33,8 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi
Identifier = identifier, Identifier = identifier,
PushToken = pushToken, PushToken = pushToken,
Type = type, Type = type,
UserId = userId UserId = userId,
OrganizationIds = organizationIds
}; };
await SendAsync(HttpMethod.Post, "push/register", requestModel); await SendAsync(HttpMethod.Post, "push/register", requestModel);
} }

View File

@ -10,7 +10,7 @@ public class NoopPushRegistrationService : IPushRegistrationService
} }
public Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, public Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId,
string identifier, DeviceType type) string identifier, DeviceType type, IEnumerable<string> organizationIds)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }

View File

@ -1,8 +1,6 @@
#nullable enable #nullable enable
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.NotificationHub; using Bit.Core.NotificationHub;
using Bit.Core.Repositories;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
@ -19,11 +17,12 @@ public class NotificationHubPushRegistrationServiceTests
[BitAutoData([null])] [BitAutoData([null])]
[BitAutoData("")] [BitAutoData("")]
[BitAutoData(" ")] [BitAutoData(" ")]
public async void CreateOrUpdateRegistrationAsync_PushTokenNullOrEmpty_InstallationNotCreated(string? pushToken, public async Task CreateOrUpdateRegistrationAsync_PushTokenNullOrEmpty_InstallationNotCreated(string? pushToken,
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier) SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier,
Guid organizationId)
{ {
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifier.ToString(), DeviceType.Android); identifier.ToString(), DeviceType.Android, [organizationId.ToString()]);
sutProvider.GetDependency<INotificationHubPool>() sutProvider.GetDependency<INotificationHubPool>()
.Received(0) .Received(0)
@ -31,10 +30,13 @@ public class NotificationHubPushRegistrationServiceTests
} }
[Theory] [Theory]
[BitAutoData(false)] [BitAutoData(false, false)]
[BitAutoData(true)] [BitAutoData(false, true)]
public async void CreateOrUpdateRegistrationAsync_DeviceTypeAndroid_InstallationCreated(bool identifierNull, [BitAutoData(true, false)]
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid? identifier) [BitAutoData(true, true)]
public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroid_InstallationCreated(bool identifierNull,
bool partOfOrganizationId, SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId,
Guid userId, Guid? identifier, Guid organizationId)
{ {
var notificationHubClient = Substitute.For<INotificationHubClient>(); var notificationHubClient = Substitute.For<INotificationHubClient>();
sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient); sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient);
@ -42,7 +44,8 @@ public class NotificationHubPushRegistrationServiceTests
var pushToken = "test push token"; var pushToken = "test push token";
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifierNull ? null : identifier.ToString(), DeviceType.Android); identifierNull ? null : identifier.ToString(), DeviceType.Android,
partOfOrganizationId ? [organizationId.ToString()] : []);
sutProvider.GetDependency<INotificationHubPool>() sutProvider.GetDependency<INotificationHubPool>()
.Received(1) .Received(1)
@ -53,10 +56,10 @@ public class NotificationHubPushRegistrationServiceTests
installation.InstallationId == deviceId.ToString() && installation.InstallationId == deviceId.ToString() &&
installation.PushChannel == pushToken && installation.PushChannel == pushToken &&
installation.Platform == NotificationPlatform.FcmV1 && installation.Platform == NotificationPlatform.FcmV1 &&
installation.Tags.Count == (identifierNull ? 2 : 3) &&
installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains($"userId:{userId}") &&
installation.Tags.Contains("clientType:Mobile") && installation.Tags.Contains("clientType:Mobile") &&
(identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) &&
(!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) &&
installation.Templates.Count == 3)); installation.Templates.Count == 3));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -68,7 +71,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:payload", "template:payload",
$"template:payload_userId:{userId}", $"template:payload_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}" identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -80,7 +84,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:message", "template:message",
$"template:message_userId:{userId}", $"template:message_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:message_deviceIdentifier:{identifier}" identifierNull ? null : $"template:message_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -92,15 +97,19 @@ public class NotificationHubPushRegistrationServiceTests
"template:badgeMessage", "template:badgeMessage",
$"template:badgeMessage_userId:{userId}", $"template:badgeMessage_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}" identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
} }
[Theory] [Theory]
[BitAutoData(false)] [BitAutoData(false, false)]
[BitAutoData(true)] [BitAutoData(false, true)]
public async void CreateOrUpdateRegistrationAsync_DeviceTypeIOS_InstallationCreated(bool identifierNull, [BitAutoData(true, false)]
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier) [BitAutoData(true, true)]
public async Task CreateOrUpdateRegistrationAsync_DeviceTypeIOS_InstallationCreated(bool identifierNull,
bool partOfOrganizationId, SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId,
Guid userId, Guid identifier, Guid organizationId)
{ {
var notificationHubClient = Substitute.For<INotificationHubClient>(); var notificationHubClient = Substitute.For<INotificationHubClient>();
sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient); sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient);
@ -108,7 +117,8 @@ public class NotificationHubPushRegistrationServiceTests
var pushToken = "test push token"; var pushToken = "test push token";
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifierNull ? null : identifier.ToString(), DeviceType.iOS); identifierNull ? null : identifier.ToString(), DeviceType.iOS,
partOfOrganizationId ? [organizationId.ToString()] : []);
sutProvider.GetDependency<INotificationHubPool>() sutProvider.GetDependency<INotificationHubPool>()
.Received(1) .Received(1)
@ -119,10 +129,10 @@ public class NotificationHubPushRegistrationServiceTests
installation.InstallationId == deviceId.ToString() && installation.InstallationId == deviceId.ToString() &&
installation.PushChannel == pushToken && installation.PushChannel == pushToken &&
installation.Platform == NotificationPlatform.Apns && installation.Platform == NotificationPlatform.Apns &&
installation.Tags.Count == (identifierNull ? 2 : 3) &&
installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains($"userId:{userId}") &&
installation.Tags.Contains("clientType:Mobile") && installation.Tags.Contains("clientType:Mobile") &&
(identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) &&
(!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) &&
installation.Templates.Count == 3)); installation.Templates.Count == 3));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -134,7 +144,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:payload", "template:payload",
$"template:payload_userId:{userId}", $"template:payload_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}" identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -146,7 +157,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:message", "template:message",
$"template:message_userId:{userId}", $"template:message_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:message_deviceIdentifier:{identifier}" identifierNull ? null : $"template:message_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -158,15 +170,19 @@ public class NotificationHubPushRegistrationServiceTests
"template:badgeMessage", "template:badgeMessage",
$"template:badgeMessage_userId:{userId}", $"template:badgeMessage_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}" identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
} }
[Theory] [Theory]
[BitAutoData(false)] [BitAutoData(false, false)]
[BitAutoData(true)] [BitAutoData(false, true)]
public async void CreateOrUpdateRegistrationAsync_DeviceTypeAndroidAmazon_InstallationCreated(bool identifierNull, [BitAutoData(true, false)]
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier) [BitAutoData(true, true)]
public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroidAmazon_InstallationCreated(bool identifierNull,
bool partOfOrganizationId, SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId,
Guid userId, Guid identifier, Guid organizationId)
{ {
var notificationHubClient = Substitute.For<INotificationHubClient>(); var notificationHubClient = Substitute.For<INotificationHubClient>();
sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient); sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient);
@ -174,7 +190,8 @@ public class NotificationHubPushRegistrationServiceTests
var pushToken = "test push token"; var pushToken = "test push token";
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifierNull ? null : identifier.ToString(), DeviceType.AndroidAmazon); identifierNull ? null : identifier.ToString(), DeviceType.AndroidAmazon,
partOfOrganizationId ? [organizationId.ToString()] : []);
sutProvider.GetDependency<INotificationHubPool>() sutProvider.GetDependency<INotificationHubPool>()
.Received(1) .Received(1)
@ -185,10 +202,10 @@ public class NotificationHubPushRegistrationServiceTests
installation.InstallationId == deviceId.ToString() && installation.InstallationId == deviceId.ToString() &&
installation.PushChannel == pushToken && installation.PushChannel == pushToken &&
installation.Platform == NotificationPlatform.Adm && installation.Platform == NotificationPlatform.Adm &&
installation.Tags.Count == (identifierNull ? 2 : 3) &&
installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains($"userId:{userId}") &&
installation.Tags.Contains("clientType:Mobile") && installation.Tags.Contains("clientType:Mobile") &&
(identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) &&
(!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) &&
installation.Templates.Count == 3)); installation.Templates.Count == 3));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -200,7 +217,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:payload", "template:payload",
$"template:payload_userId:{userId}", $"template:payload_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}" identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -212,7 +230,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:message", "template:message",
$"template:message_userId:{userId}", $"template:message_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:message_deviceIdentifier:{identifier}" identifierNull ? null : $"template:message_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
await notificationHubClient await notificationHubClient
.Received(1) .Received(1)
@ -224,7 +243,8 @@ public class NotificationHubPushRegistrationServiceTests
"template:badgeMessage", "template:badgeMessage",
$"template:badgeMessage_userId:{userId}", $"template:badgeMessage_userId:{userId}",
"clientType:Mobile", "clientType:Mobile",
identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}" identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}",
partOfOrganizationId ? $"organizationId:{organizationId}" : null,
}))); })));
} }
@ -232,8 +252,9 @@ public class NotificationHubPushRegistrationServiceTests
[BitAutoData(DeviceType.ChromeBrowser)] [BitAutoData(DeviceType.ChromeBrowser)]
[BitAutoData(DeviceType.ChromeExtension)] [BitAutoData(DeviceType.ChromeExtension)]
[BitAutoData(DeviceType.MacOsDesktop)] [BitAutoData(DeviceType.MacOsDesktop)]
public async void CreateOrUpdateRegistrationAsync_DeviceTypeNotMobile_InstallationCreated(DeviceType deviceType, public async Task CreateOrUpdateRegistrationAsync_DeviceTypeNotMobile_InstallationCreated(DeviceType deviceType,
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier) SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier,
Guid organizationId)
{ {
var notificationHubClient = Substitute.For<INotificationHubClient>(); var notificationHubClient = Substitute.For<INotificationHubClient>();
sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient); sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient);
@ -241,7 +262,7 @@ public class NotificationHubPushRegistrationServiceTests
var pushToken = "test push token"; var pushToken = "test push token";
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifier.ToString(), deviceType); identifier.ToString(), deviceType, [organizationId.ToString()]);
sutProvider.GetDependency<INotificationHubPool>() sutProvider.GetDependency<INotificationHubPool>()
.Received(1) .Received(1)
@ -251,45 +272,13 @@ public class NotificationHubPushRegistrationServiceTests
.CreateOrUpdateInstallationAsync(Arg.Is<Installation>(installation => .CreateOrUpdateInstallationAsync(Arg.Is<Installation>(installation =>
installation.InstallationId == deviceId.ToString() && installation.InstallationId == deviceId.ToString() &&
installation.PushChannel == pushToken && installation.PushChannel == pushToken &&
installation.Tags.Count == 3 &&
installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains($"userId:{userId}") &&
installation.Tags.Contains($"clientType:{DeviceTypes.ToClientType(deviceType)}") && installation.Tags.Contains($"clientType:{DeviceTypes.ToClientType(deviceType)}") &&
installation.Tags.Contains($"deviceIdentifier:{identifier}") && installation.Tags.Contains($"deviceIdentifier:{identifier}") &&
installation.Tags.Contains($"organizationId:{organizationId}") &&
installation.Templates.Count == 0)); installation.Templates.Count == 0));
} }
[Theory]
[BitAutoData]
public async void CreateOrUpdateRegistrationAsync_UserPartOfOrganization_InstallationCreated(
SutProvider<NotificationHubPushRegistrationService> sutProvider, Guid deviceId, Guid userId, Guid identifier,
OrganizationUserOrganizationDetails organizationDetails)
{
var notificationHubClient = Substitute.For<INotificationHubClient>();
sutProvider.GetDependency<INotificationHubPool>().ClientFor(Arg.Any<Guid>()).Returns(notificationHubClient);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByUserAsync(userId, OrganizationUserStatusType.Confirmed)
.Returns([organizationDetails]);
var pushToken = "test push token";
await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(),
identifier.ToString(), DeviceType.ChromeBrowser);
sutProvider.GetDependency<INotificationHubPool>()
.Received(1)
.ClientFor(deviceId);
await notificationHubClient
.Received(1)
.CreateOrUpdateInstallationAsync(Arg.Is<Installation>(installation =>
installation.InstallationId == deviceId.ToString() &&
installation.PushChannel == pushToken &&
installation.Tags.Count == 4 &&
installation.Tags.Contains($"userId:{userId}") &&
installation.Tags.Contains($"clientType:{DeviceTypes.ToClientType(DeviceType.ChromeBrowser)}") &&
installation.Tags.Contains($"deviceIdentifier:{identifier}") &&
installation.Tags.Contains($"organizationId:{organizationDetails.OrganizationId}")));
}
private static bool MatchingInstallationTemplate(IDictionary<string, InstallationTemplate> templates, string key, private static bool MatchingInstallationTemplate(IDictionary<string, InstallationTemplate> templates, string key,
string body, List<string?> tags) string body, List<string?> tags)
{ {

View File

@ -3,6 +3,7 @@ using Bit.Core.Auth.Models.Api.Request;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
@ -15,15 +16,23 @@ namespace Bit.Core.Test.Services;
[SutProviderCustomize] [SutProviderCustomize]
public class DeviceServiceTests public class DeviceServiceTests
{ {
[Fact] [Theory]
public async Task DeviceSaveShouldUpdateRevisionDateAndPushRegistration() [BitAutoData]
public async Task SaveAsync_IdProvided_UpdatedRevisionDateAndPushRegistration(Guid id, Guid userId,
Guid organizationId1, Guid organizationId2,
OrganizationUserOrganizationDetails organizationUserOrganizationDetails1,
OrganizationUserOrganizationDetails organizationUserOrganizationDetails2)
{ {
organizationUserOrganizationDetails1.OrganizationId = organizationId1;
organizationUserOrganizationDetails2.OrganizationId = organizationId2;
var deviceRepo = Substitute.For<IDeviceRepository>(); var deviceRepo = Substitute.For<IDeviceRepository>();
var pushRepo = Substitute.For<IPushRegistrationService>(); var pushRepo = Substitute.For<IPushRegistrationService>();
var deviceService = new DeviceService(deviceRepo, pushRepo); var organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
organizationUserRepository.GetManyDetailsByUserAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType?>())
.Returns([organizationUserOrganizationDetails1, organizationUserOrganizationDetails2]);
var deviceService = new DeviceService(deviceRepo, pushRepo, organizationUserRepository);
var id = Guid.NewGuid();
var userId = Guid.NewGuid();
var device = new Device var device = new Device
{ {
Id = id, Id = id,
@ -36,8 +45,53 @@ public class DeviceServiceTests
await deviceService.SaveAsync(device); await deviceService.SaveAsync(device);
Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1)); Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
await pushRepo.Received().CreateOrUpdateRegistrationAsync("testtoken", id.ToString(), await pushRepo.Received(1).CreateOrUpdateRegistrationAsync("testtoken", id.ToString(),
userId.ToString(), "testid", DeviceType.Android); userId.ToString(), "testid", DeviceType.Android,
Arg.Do<IEnumerable<string>>(organizationIds =>
{
var organizationIdsList = organizationIds.ToList();
Assert.Equal(2, organizationIdsList.Count);
Assert.Contains(organizationId1.ToString(), organizationIdsList);
Assert.Contains(organizationId2.ToString(), organizationIdsList);
}));
}
[Theory]
[BitAutoData]
public async Task SaveAsync_IdNotProvided_CreatedAndPushRegistration(Guid userId, Guid organizationId1,
Guid organizationId2,
OrganizationUserOrganizationDetails organizationUserOrganizationDetails1,
OrganizationUserOrganizationDetails organizationUserOrganizationDetails2)
{
organizationUserOrganizationDetails1.OrganizationId = organizationId1;
organizationUserOrganizationDetails2.OrganizationId = organizationId2;
var deviceRepo = Substitute.For<IDeviceRepository>();
var pushRepo = Substitute.For<IPushRegistrationService>();
var organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
organizationUserRepository.GetManyDetailsByUserAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType?>())
.Returns([organizationUserOrganizationDetails1, organizationUserOrganizationDetails2]);
var deviceService = new DeviceService(deviceRepo, pushRepo, organizationUserRepository);
var device = new Device
{
Name = "test device",
Type = DeviceType.Android,
UserId = userId,
PushToken = "testtoken",
Identifier = "testid"
};
await deviceService.SaveAsync(device);
await pushRepo.Received(1).CreateOrUpdateRegistrationAsync("testtoken",
Arg.Do<string>(id => Guid.TryParse(id, out var _)), userId.ToString(), "testid", DeviceType.Android,
Arg.Do<IEnumerable<string>>(organizationIds =>
{
var organizationIdsList = organizationIds.ToList();
Assert.Equal(2, organizationIdsList.Count);
Assert.Contains(organizationId1.ToString(), organizationIdsList);
Assert.Contains(organizationId2.ToString(), organizationIdsList);
}));
} }
/// <summary> /// <summary>
@ -61,12 +115,7 @@ public class DeviceServiceTests
sutProvider.GetDependency<IDeviceRepository>() sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(currentUserId) .GetManyByUserIdAsync(currentUserId)
.Returns(new List<Device> .Returns(new List<Device> { deviceOne, deviceTwo, deviceThree, });
{
deviceOne,
deviceTwo,
deviceThree,
});
var currentDeviceModel = new DeviceKeysUpdateRequestModel var currentDeviceModel = new DeviceKeysUpdateRequestModel
{ {
@ -84,7 +133,8 @@ public class DeviceServiceTests
}, },
}; };
await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels); await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel,
alteredDeviceModels);
// Updating trust, "current" or "other" only needs to change the EncryptedPublicKey & EncryptedUserKey // Updating trust, "current" or "other" only needs to change the EncryptedPublicKey & EncryptedUserKey
await sutProvider.GetDependency<IDeviceRepository>() await sutProvider.GetDependency<IDeviceRepository>()
@ -148,11 +198,7 @@ public class DeviceServiceTests
sutProvider.GetDependency<IDeviceRepository>() sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(currentUserId) .GetManyByUserIdAsync(currentUserId)
.Returns(new List<Device> .Returns(new List<Device> { deviceOne, deviceTwo, });
{
deviceOne,
deviceTwo,
});
var currentDeviceModel = new DeviceKeysUpdateRequestModel var currentDeviceModel = new DeviceKeysUpdateRequestModel
{ {
@ -170,7 +216,8 @@ public class DeviceServiceTests
}, },
}; };
await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels); await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel,
alteredDeviceModels);
// Check that UpsertAsync was called for the trusted device // Check that UpsertAsync was called for the trusted device
await sutProvider.GetDependency<IDeviceRepository>() await sutProvider.GetDependency<IDeviceRepository>()
@ -202,11 +249,7 @@ public class DeviceServiceTests
sutProvider.GetDependency<IDeviceRepository>() sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(currentUserId) .GetManyByUserIdAsync(currentUserId)
.Returns(new List<Device> .Returns(new List<Device> { deviceOne, deviceTwo, });
{
deviceOne,
deviceTwo,
});
var currentDeviceModel = new DeviceKeysUpdateRequestModel var currentDeviceModel = new DeviceKeysUpdateRequestModel
{ {
@ -236,11 +279,7 @@ public class DeviceServiceTests
sutProvider.GetDependency<IDeviceRepository>() sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(currentUserId) .GetManyByUserIdAsync(currentUserId)
.Returns(new List<Device> .Returns(new List<Device> { deviceOne, deviceTwo, });
{
deviceOne,
deviceTwo,
});
var currentDeviceModel = new DeviceKeysUpdateRequestModel var currentDeviceModel = new DeviceKeysUpdateRequestModel
{ {
@ -259,6 +298,7 @@ public class DeviceServiceTests
}; };
await Assert.ThrowsAsync<BadRequestException>(() => await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels)); sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel,
alteredDeviceModels));
} }
} }