mirror of
https://github.com/bitwarden/server.git
synced 2024-12-01 13:43:23 +01:00
ae1fdb0992
* Upgrade to .NET 8 * Linting * Clean up old JSON deserialization code * More .NET 8-oriented linting * Light feedback * Get rid of old test we don't know the root issue for * Fix a new test * Remove now-unnecessary Renovate constraint * Use Any() * Somehow a 6.0 tooling config we don't need snuck back in * Space out properties that always change per release * Bump a few core packages since the last update
265 lines
10 KiB
C#
265 lines
10 KiB
C#
using System.Runtime.CompilerServices;
|
|
using Bit.Core.Auth.Models.Api.Request;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
|
|
namespace Bit.Core.Test.Services;
|
|
|
|
[SutProviderCustomize]
|
|
public class DeviceServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task DeviceSaveShouldUpdateRevisionDateAndPushRegistration()
|
|
{
|
|
var deviceRepo = Substitute.For<IDeviceRepository>();
|
|
var pushRepo = Substitute.For<IPushRegistrationService>();
|
|
var deviceService = new DeviceService(deviceRepo, pushRepo);
|
|
|
|
var id = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var device = new Device
|
|
{
|
|
Id = id,
|
|
Name = "test device",
|
|
Type = DeviceType.Android,
|
|
UserId = userId,
|
|
PushToken = "testtoken",
|
|
Identifier = "testid"
|
|
};
|
|
await deviceService.SaveAsync(device);
|
|
|
|
Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1));
|
|
await pushRepo.Received().CreateOrUpdateRegistrationAsync("testtoken", id.ToString(),
|
|
userId.ToString(), "testid", DeviceType.Android);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Story: A user chose to keep trust in one of their current trusted devices, but not in another one of their
|
|
/// devices. We will rotate the trust of the currently signed in device as well as the device they chose but will
|
|
/// remove the trust of the device they didn't give new keys for.
|
|
/// </summary>
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateDevicesTrustAsync_Works(
|
|
SutProvider<DeviceService> sutProvider,
|
|
Guid currentUserId,
|
|
Device deviceOne,
|
|
Device deviceTwo,
|
|
Device deviceThree)
|
|
{
|
|
SetupOldTrust(deviceOne);
|
|
SetupOldTrust(deviceTwo);
|
|
SetupOldTrust(deviceThree);
|
|
|
|
deviceOne.Identifier = "current_device";
|
|
|
|
sutProvider.GetDependency<IDeviceRepository>()
|
|
.GetManyByUserIdAsync(currentUserId)
|
|
.Returns(new List<Device>
|
|
{
|
|
deviceOne,
|
|
deviceTwo,
|
|
deviceThree,
|
|
});
|
|
|
|
var currentDeviceModel = new DeviceKeysUpdateRequestModel
|
|
{
|
|
EncryptedPublicKey = "current_encrypted_public_key",
|
|
EncryptedUserKey = "current_encrypted_user_key",
|
|
};
|
|
|
|
var alteredDeviceModels = new List<OtherDeviceKeysUpdateRequestModel>
|
|
{
|
|
new OtherDeviceKeysUpdateRequestModel
|
|
{
|
|
DeviceId = deviceTwo.Id,
|
|
EncryptedPublicKey = "encrypted_public_key_two",
|
|
EncryptedUserKey = "encrypted_user_key_two",
|
|
},
|
|
};
|
|
|
|
await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels);
|
|
|
|
// Updating trust, "current" or "other" only needs to change the EncryptedPublicKey & EncryptedUserKey
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.Received(1)
|
|
.UpsertAsync(Arg.Is<Device>(d =>
|
|
d.Id == deviceOne.Id &&
|
|
d.EncryptedPublicKey == "current_encrypted_public_key" &&
|
|
d.EncryptedUserKey == "current_encrypted_user_key" &&
|
|
d.EncryptedPrivateKey == "old_private_deviceOne"));
|
|
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.Received(1)
|
|
.UpsertAsync(Arg.Is<Device>(d =>
|
|
d.Id == deviceTwo.Id &&
|
|
d.EncryptedPublicKey == "encrypted_public_key_two" &&
|
|
d.EncryptedUserKey == "encrypted_user_key_two" &&
|
|
d.EncryptedPrivateKey == "old_private_deviceTwo"));
|
|
|
|
// Clearing trust should remove all key values
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.Received(1)
|
|
.UpsertAsync(Arg.Is<Device>(d =>
|
|
d.Id == deviceThree.Id &&
|
|
d.EncryptedPublicKey == null &&
|
|
d.EncryptedUserKey == null &&
|
|
d.EncryptedPrivateKey == null));
|
|
|
|
// Should have recieved a total of 3 calls, the ones asserted above
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.Received(3)
|
|
.UpsertAsync(Arg.Any<Device>());
|
|
|
|
static void SetupOldTrust(Device device, [CallerArgumentExpression(nameof(device))] string expression = null)
|
|
{
|
|
device.EncryptedPublicKey = $"old_public_{expression}";
|
|
device.EncryptedPrivateKey = $"old_private_{expression}";
|
|
device.EncryptedUserKey = $"old_user_{expression}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Story: This could result from a poor implementation of this method, if they attempt add trust to a device
|
|
/// that doesn't already have trust. They would have to create brand new values and for that values to be accurate
|
|
/// they would technically have all the values needed to trust a device, that is why we don't consider this bad
|
|
/// enough to throw but do skip it because we'd rather keep number of ways for trust to be added to the endpoint we
|
|
/// already have.
|
|
/// </summary>
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateDevicesTrustAsync_DoesNotUpdateUntrustedDevices(
|
|
SutProvider<DeviceService> sutProvider,
|
|
Guid currentUserId,
|
|
Device deviceOne,
|
|
Device deviceTwo)
|
|
{
|
|
deviceOne.Identifier = "current_device";
|
|
|
|
// Make deviceTwo untrusted
|
|
deviceTwo.EncryptedUserKey = string.Empty;
|
|
deviceTwo.EncryptedPublicKey = string.Empty;
|
|
deviceTwo.EncryptedPrivateKey = string.Empty;
|
|
|
|
sutProvider.GetDependency<IDeviceRepository>()
|
|
.GetManyByUserIdAsync(currentUserId)
|
|
.Returns(new List<Device>
|
|
{
|
|
deviceOne,
|
|
deviceTwo,
|
|
});
|
|
|
|
var currentDeviceModel = new DeviceKeysUpdateRequestModel
|
|
{
|
|
EncryptedPublicKey = "current_encrypted_public_key",
|
|
EncryptedUserKey = "current_encrypted_user_key",
|
|
};
|
|
|
|
var alteredDeviceModels = new List<OtherDeviceKeysUpdateRequestModel>
|
|
{
|
|
new OtherDeviceKeysUpdateRequestModel
|
|
{
|
|
DeviceId = deviceTwo.Id,
|
|
EncryptedPublicKey = "encrypted_public_key_two",
|
|
EncryptedUserKey = "encrypted_user_key_two",
|
|
},
|
|
};
|
|
|
|
await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels);
|
|
|
|
// Check that UpsertAsync was called for the trusted device
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.Received(1)
|
|
.UpsertAsync(Arg.Is<Device>(d =>
|
|
d.Id == deviceOne.Id &&
|
|
d.EncryptedPublicKey == "current_encrypted_public_key" &&
|
|
d.EncryptedUserKey == "current_encrypted_user_key"));
|
|
|
|
// Check that UpsertAsync was not called for the untrusted device
|
|
await sutProvider.GetDependency<IDeviceRepository>()
|
|
.DidNotReceive()
|
|
.UpsertAsync(Arg.Is<Device>(d => d.Id == deviceTwo.Id));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Story: This should only happen if someone were to take the access token from a different device and try to rotate
|
|
/// a device that they don't actually have.
|
|
/// </summary>
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateDevicesTrustAsync_ThrowsNotFoundException_WhenCurrentDeviceIdentifierDoesNotExist(
|
|
SutProvider<DeviceService> sutProvider,
|
|
Guid currentUserId,
|
|
Device deviceOne,
|
|
Device deviceTwo)
|
|
{
|
|
deviceOne.Identifier = "some_other_device";
|
|
deviceTwo.Identifier = "another_device";
|
|
|
|
sutProvider.GetDependency<IDeviceRepository>()
|
|
.GetManyByUserIdAsync(currentUserId)
|
|
.Returns(new List<Device>
|
|
{
|
|
deviceOne,
|
|
deviceTwo,
|
|
});
|
|
|
|
var currentDeviceModel = new DeviceKeysUpdateRequestModel
|
|
{
|
|
EncryptedPublicKey = "current_encrypted_public_key",
|
|
EncryptedUserKey = "current_encrypted_user_key",
|
|
};
|
|
|
|
await Assert.ThrowsAsync<NotFoundException>(() =>
|
|
sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel,
|
|
Enumerable.Empty<OtherDeviceKeysUpdateRequestModel>()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Story: This should only happen from a poorly implemented user of this method but important to enforce someone
|
|
/// using the method correctly, a device should only be rotated intentionally and including it as both the current
|
|
/// device and one of the users other device would mean they could rotate it twice and we aren't sure
|
|
/// which one they would want to win out.
|
|
/// </summary>
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateDevicesTrustAsync_ThrowsBadRequestException_WhenCurrentDeviceIsIncludedInAlteredDevices(
|
|
SutProvider<DeviceService> sutProvider,
|
|
Guid currentUserId,
|
|
Device deviceOne,
|
|
Device deviceTwo)
|
|
{
|
|
deviceOne.Identifier = "current_device";
|
|
|
|
sutProvider.GetDependency<IDeviceRepository>()
|
|
.GetManyByUserIdAsync(currentUserId)
|
|
.Returns(new List<Device>
|
|
{
|
|
deviceOne,
|
|
deviceTwo,
|
|
});
|
|
|
|
var currentDeviceModel = new DeviceKeysUpdateRequestModel
|
|
{
|
|
EncryptedPublicKey = "current_encrypted_public_key",
|
|
EncryptedUserKey = "current_encrypted_user_key",
|
|
};
|
|
|
|
var alteredDeviceModels = new List<OtherDeviceKeysUpdateRequestModel>
|
|
{
|
|
new OtherDeviceKeysUpdateRequestModel
|
|
{
|
|
DeviceId = deviceOne.Id, // current device is included in alteredDevices
|
|
EncryptedPublicKey = "encrypted_public_key_one",
|
|
EncryptedUserKey = "encrypted_user_key_one",
|
|
},
|
|
};
|
|
|
|
await Assert.ThrowsAsync<BadRequestException>(() =>
|
|
sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels));
|
|
}
|
|
}
|