1
0
mirror of https://github.com/bitwarden/server synced 2026-01-06 02:23:51 +00:00

Merge branch 'main' into vault/pm-25957/sharing-cipher-to-org

This commit is contained in:
Nick Krantz
2025-10-24 08:55:21 -05:00
committed by GitHub
447 changed files with 49040 additions and 6302 deletions

View File

@@ -34,7 +34,7 @@ public class SlackIntegrationControllerTests
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -60,7 +60,7 @@ public class SlackIntegrationControllerTests
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
@@ -80,7 +80,7 @@ public class SlackIntegrationControllerTests
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
@@ -99,13 +99,13 @@ public class SlackIntegrationControllerTests
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, String.Empty));
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, string.Empty));
}
[Theory, BitAutoData]
@@ -116,7 +116,7 @@ public class SlackIntegrationControllerTests
var timeProvider = new FakeTimeProvider(new DateTime(2024, 4, 3, 2, 1, 0, DateTimeKind.Utc));
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -135,7 +135,7 @@ public class SlackIntegrationControllerTests
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -147,7 +147,7 @@ public class SlackIntegrationControllerTests
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasWrongOgranizationHash_ThrowsNotFound(
public async Task CreateAsync_StateHasWrongOrganizationHash_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration,
OrganizationIntegration wrongOrgIntegration)
@@ -156,7 +156,7 @@ public class SlackIntegrationControllerTests
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -179,7 +179,7 @@ public class SlackIntegrationControllerTests
integration.Configuration = "{}";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -201,7 +201,7 @@ public class SlackIntegrationControllerTests
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
@@ -224,7 +224,7 @@ public class SlackIntegrationControllerTests
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(integration.OrganizationId)
@@ -260,7 +260,7 @@ public class SlackIntegrationControllerTests
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
@@ -291,7 +291,7 @@ public class SlackIntegrationControllerTests
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
@@ -316,7 +316,7 @@ public class SlackIntegrationControllerTests
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == nameof(SlackIntegrationController.CreateAsync)))
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)

View File

@@ -0,0 +1,392 @@
#nullable enable
using Bit.Api.AdminConsole.Controllers;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Teams;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Controllers;
[ControllerCustomize(typeof(TeamsIntegrationController))]
[SutProviderCustomize]
public class TeamsIntegrationControllerTests
{
private const string _teamsToken = "test-token";
private const string _validTeamsCode = "A_test_code";
[Theory, BitAutoData]
public async Task CreateAsync_AllParamsProvided_Succeeds(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<ITeamsService>()
.GetJoinedTeamsAsync(_teamsToken)
.Returns([
new TeamInfo() { DisplayName = "Test Team", Id = Guid.NewGuid().ToString(), TenantId = Guid.NewGuid().ToString() }
]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString());
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.UpsertAsync(Arg.Any<OrganizationIntegration>());
Assert.IsType<CreatedResult>(requestAction);
}
[Theory, BitAutoData]
public async Task CreateAsync_CodeIsEmpty_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateAsync(string.Empty, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_NoTeamsFound_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<ITeamsService>()
.GetJoinedTeamsAsync(_teamsToken)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_TeamsServiceReturnsEmptyToken_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(string.Empty);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateEmpty_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, string.Empty));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateExpired_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
var timeProvider = new FakeTimeProvider(new DateTime(2024, 4, 3, 2, 1, 0, DateTimeKind.Utc));
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
var state = IntegrationOAuthState.FromIntegration(integration, timeProvider);
timeProvider.Advance(TimeSpan.FromMinutes(30));
sutProvider.SetDependency<TimeProvider>(timeProvider);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonexistentIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasWrongOrganizationHash_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration,
OrganizationIntegration wrongOrgIntegration)
{
wrongOrgIntegration.Id = integration.Id;
wrongOrgIntegration.Type = IntegrationType.Teams;
wrongOrgIntegration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(wrongOrgIntegration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonEmptyIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = "{}";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonTeamsIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Hec;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task RedirectAsync_Success(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(integration.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(integration.OrganizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.RedirectAsync(integration.OrganizationId);
Assert.IsType<RedirectResult>(requestAction);
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.CreateAsync(Arg.Any<OrganizationIntegration>());
sutProvider.GetDependency<ITeamsService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithNullConfig_Success(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
integration.Type = IntegrationType.Teams;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var requestAction = await sutProvider.Sut.RedirectAsync(organizationId);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
Assert.IsType<RedirectResult>(requestAction);
sutProvider.GetDependency<ITeamsService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithConfig_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = "{}";
integration.Type = IntegrationType.Teams;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_TeamsServiceReturnsEmpty_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(string.Empty);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task IncomingPostAsync_ForwardsToBot(SutProvider<TeamsIntegrationController> sutProvider)
{
var adapter = sutProvider.GetDependency<IBotFrameworkHttpAdapter>();
var bot = sutProvider.GetDependency<IBot>();
await sutProvider.Sut.IncomingPostAsync();
await adapter.Received(1).ProcessAsync(Arg.Any<HttpRequest>(), Arg.Any<HttpResponse>(), bot);
}
}

View File

@@ -39,7 +39,7 @@ public class OrganizationIntegrationConfigurationRequestModelTests
[Theory]
[InlineData(data: "")]
[InlineData(data: " ")]
public void IsValidForType_EmptyNonNullHecConfiguration_ReturnsFalse(string? config)
public void IsValidForType_EmptyNonNullConfiguration_ReturnsFalse(string? config)
{
var model = new OrganizationIntegrationConfigurationRequestModel
{
@@ -48,10 +48,12 @@ public class OrganizationIntegrationConfigurationRequestModelTests
};
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
}
[Fact]
public void IsValidForType_NullHecConfiguration_ReturnsTrue()
public void IsValidForType_NullConfiguration_ReturnsTrue()
{
var model = new OrganizationIntegrationConfigurationRequestModel
{
@@ -60,32 +62,8 @@ public class OrganizationIntegrationConfigurationRequestModelTests
};
Assert.True(condition: model.IsValidForType(IntegrationType.Hec));
}
[Theory]
[InlineData(data: "")]
[InlineData(data: " ")]
public void IsValidForType_EmptyNonNullDatadogConfiguration_ReturnsFalse(string? config)
{
var model = new OrganizationIntegrationConfigurationRequestModel
{
Configuration = config,
Template = "template"
};
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
}
[Fact]
public void IsValidForType_NullDatadogConfiguration_ReturnsTrue()
{
var model = new OrganizationIntegrationConfigurationRequestModel
{
Configuration = null,
Template = "template"
};
Assert.True(condition: model.IsValidForType(IntegrationType.Datadog));
Assert.True(condition: model.IsValidForType(IntegrationType.Teams));
}
[Theory]
@@ -107,6 +85,8 @@ public class OrganizationIntegrationConfigurationRequestModelTests
Assert.False(condition: model.IsValidForType(IntegrationType.Slack));
Assert.False(condition: model.IsValidForType(IntegrationType.Webhook));
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
}
[Fact]
@@ -121,6 +101,8 @@ public class OrganizationIntegrationConfigurationRequestModelTests
Assert.False(condition: model.IsValidForType(IntegrationType.Slack));
Assert.False(condition: model.IsValidForType(IntegrationType.Webhook));
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
}

View File

@@ -57,6 +57,22 @@ public class OrganizationIntegrationRequestModelTests
Assert.Contains("cannot be created directly", results[0].ErrorMessage);
}
[Fact]
public void Validate_Teams_ReturnsCannotBeCreatedDirectlyError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Teams,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("cannot be created directly", results[0].ErrorMessage);
}
[Fact]
public void Validate_Webhook_WithNullConfiguration_ReturnsNoErrors()
{

View File

@@ -1,8 +1,11 @@
#nullable enable
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Models.Teams;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
@@ -58,6 +61,46 @@ public class OrganizationIntegrationResponseModelTests
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_NullConfig_ReturnsInitiated(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Initiated, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_WithTenantAndTeamsConfig_ReturnsInProgress(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = JsonSerializer.Serialize(new TeamsIntegration(
TenantId: "tenant", Teams: [new TeamInfo() { DisplayName = "Team", Id = "TeamId", TenantId = "tenant" }]
));
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.InProgress, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_WithCompletedConfig_ReturnsCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = JsonSerializer.Serialize(new TeamsIntegration(
TenantId: "tenant",
Teams: [new TeamInfo() { DisplayName = "Team", Id = "TeamId", TenantId = "tenant" }],
ServiceUrl: new Uri("https://example.com"),
ChannelId: "channellId"
));
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Webhook_AlwaysCompleted(OrganizationIntegration oi)
{

View File

@@ -11,6 +11,8 @@ using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Kdf;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Queries.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture.Attributes;
@@ -33,10 +35,10 @@ public class AccountsControllerTests : IDisposable
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly IFeatureService _featureService;
private readonly IUserAccountKeysQuery _userAccountKeysQuery;
private readonly ITwoFactorEmailService _twoFactorEmailService;
private readonly IChangeKdfCommand _changeKdfCommand;
public AccountsControllerTests()
{
_userService = Substitute.For<IUserService>();
@@ -48,6 +50,7 @@ public class AccountsControllerTests : IDisposable
_twoFactorIsEnabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
_featureService = Substitute.For<IFeatureService>();
_userAccountKeysQuery = Substitute.For<IUserAccountKeysQuery>();
_twoFactorEmailService = Substitute.For<ITwoFactorEmailService>();
_changeKdfCommand = Substitute.For<IChangeKdfCommand>();
@@ -61,6 +64,7 @@ public class AccountsControllerTests : IDisposable
_tdeOffboardingPasswordCommand,
_twoFactorIsEnabledQuery,
_featureService,
_userAccountKeysQuery,
_twoFactorEmailService,
_changeKdfCommand
);
@@ -614,6 +618,16 @@ public class AccountsControllerTests : IDisposable
await _twoFactorEmailService.Received(1).SendNewDeviceVerificationEmailAsync(user);
}
[Theory]
[BitAutoData]
public async Task PostKdf_UserNotFound_ShouldFail(PasswordRequestModel model)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult<User>(null));
// Act
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => _sut.PostKdf(model));
}
[Theory]
[BitAutoData]
public async Task PostKdf_WithNullAuthenticationData_ShouldFail(
@@ -623,7 +637,9 @@ public class AccountsControllerTests : IDisposable
model.AuthenticationData = null;
// Act
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
Assert.Contains("AuthenticationData and UnlockData must be provided.", exception.Message);
}
[Theory]
@@ -635,7 +651,41 @@ public class AccountsControllerTests : IDisposable
model.UnlockData = null;
// Act
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
Assert.Contains("AuthenticationData and UnlockData must be provided.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task PostKdf_ChangeKdfFailed_ShouldFail(
User user, PasswordRequestModel model)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_changeKdfCommand.ChangeKdfAsync(Arg.Any<User>(), Arg.Any<string>(),
Arg.Any<MasterPasswordAuthenticationData>(), Arg.Any<MasterPasswordUnlockData>())
.Returns(Task.FromResult(IdentityResult.Failed(new IdentityError { Description = "Change KDF failed" })));
// Act
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
Assert.NotNull(exception.ModelState);
Assert.Contains("Change KDF failed",
exception.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage));
}
[Theory]
[BitAutoData]
public async Task PostKdf_ChangeKdfSuccess_NoError(
User user, PasswordRequestModel model)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_changeKdfCommand.ChangeKdfAsync(Arg.Any<User>(), Arg.Any<string>(),
Arg.Any<MasterPasswordAuthenticationData>(), Arg.Any<MasterPasswordUnlockData>())
.Returns(Task.FromResult(IdentityResult.Success));
// Act
await _sut.PostKdf(model);
}
// Below are helper functions that currently belong to this

View File

@@ -1,5 +1,4 @@
using Bit.Api.Billing.Controllers;
using Bit.Api.Billing.Models.Responses;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Organizations.Models;
@@ -53,19 +52,16 @@ public class OrganizationBillingControllerTests
{
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
.Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null, 0));
.Returns(new OrganizationMetadata(true, 10));
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
Assert.IsType<Ok<OrganizationMetadataResponse>>(result);
Assert.IsType<Ok<OrganizationMetadata>>(result);
var response = ((Ok<OrganizationMetadataResponse>)result).Value;
var response = ((Ok<OrganizationMetadata>)result).Value;
Assert.True(response.IsEligibleForSelfHost);
Assert.True(response.IsManaged);
Assert.True(response.IsOnSecretsManagerStandalone);
Assert.True(response.IsSubscriptionUnpaid);
Assert.True(response.HasSubscription);
Assert.Equal(10, response.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]

View File

@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Providers.Entities;
using Bit.Core.Billing.Providers.Repositories;
@@ -270,7 +271,6 @@ public class ProviderBillingControllerTests
var subscription = new Subscription
{
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth),
Customer = new Customer
{
Address = new Address
@@ -291,20 +291,23 @@ public class ProviderBillingControllerTests
Data = [
new SubscriptionItem
{
CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth),
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
},
new SubscriptionItem
{
CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth),
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams }
}
]
},
Status = "unpaid",
Status = "unpaid"
};
stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, Arg.Is<SubscriptionGetOptions>(
options =>
options.Expand.Contains("customer.tax_ids") &&
options.Expand.Contains("discounts") &&
options.Expand.Contains("test_clock"))).Returns(subscription);
var daysInLastMonth = DateTime.DaysInMonth(oneMonthAgo.Year, oneMonthAgo.Month);
@@ -365,7 +368,7 @@ public class ProviderBillingControllerTests
var response = ((Ok<ProviderSubscriptionResponse>)result).Value;
Assert.Equal(subscription.Status, response.Status);
Assert.Equal(subscription.CurrentPeriodEnd, response.CurrentPeriodEndDate);
Assert.Equal(subscription.GetCurrentPeriodEnd(), response.CurrentPeriodEndDate);
Assert.Equal(subscription.Customer!.Discount!.Coupon!.PercentOff, response.DiscountPercentage);
Assert.Equal(subscription.CollectionMethod, response.CollectionMethod);
@@ -405,6 +408,118 @@ public class ProviderBillingControllerTests
Assert.Equal(14, response.Suspension.GracePeriod);
}
[Theory, BitAutoData]
public async Task GetSubscriptionAsync_SubscriptionLevelDiscount_Ok(
Provider provider,
SutProvider<ProviderBillingController> sutProvider)
{
ConfigureStableProviderServiceUserInputs(provider, sutProvider);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
var now = DateTime.UtcNow;
var oneMonthAgo = now.AddMonths(-1);
var daysInThisMonth = DateTime.DaysInMonth(now.Year, now.Month);
var subscription = new Subscription
{
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically,
Customer = new Customer
{
Address = new Address
{
Country = "US",
PostalCode = "12345",
Line1 = "123 Example St.",
Line2 = "Unit 1",
City = "Example Town",
State = "NY"
},
Balance = -100000,
Discount = null, // No customer-level discount
TaxIds = new StripeList<TaxId> { Data = [new TaxId { Value = "123456789" }] }
},
Discounts =
[
new Discount { Coupon = new Coupon { PercentOff = 15 } } // Subscription-level discount
],
Items = new StripeList<SubscriptionItem>
{
Data = [
new SubscriptionItem
{
CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth),
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise }
},
new SubscriptionItem
{
CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth),
Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams }
}
]
},
Status = "active"
};
stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, Arg.Is<SubscriptionGetOptions>(
options =>
options.Expand.Contains("customer.tax_ids") &&
options.Expand.Contains("discounts") &&
options.Expand.Contains("test_clock"))).Returns(subscription);
stripeAdapter.InvoiceSearchAsync(Arg.Is<InvoiceSearchOptions>(
options => options.Query == $"subscription:'{subscription.Id}' status:'open'"))
.Returns([]);
var providerPlans = new List<ProviderPlan>
{
new ()
{
Id = Guid.NewGuid(),
ProviderId = provider.Id,
PlanType = PlanType.TeamsMonthly,
SeatMinimum = 50,
PurchasedSeats = 10,
AllocatedSeats = 60
},
new ()
{
Id = Guid.NewGuid(),
ProviderId = provider.Id,
PlanType = PlanType.EnterpriseMonthly,
SeatMinimum = 100,
PurchasedSeats = 0,
AllocatedSeats = 90
}
};
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id).Returns(providerPlans);
foreach (var providerPlan in providerPlans)
{
var plan = StaticStore.GetPlan(providerPlan.PlanType);
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(providerPlan.PlanType).Returns(plan);
var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType);
sutProvider.GetDependency<IStripeAdapter>().PriceGetAsync(priceId)
.Returns(new Price
{
UnitAmountDecimal = plan.PasswordManager.ProviderPortalSeatPrice * 100
});
}
var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id);
Assert.IsType<Ok<ProviderSubscriptionResponse>>(result);
var response = ((Ok<ProviderSubscriptionResponse>)result).Value;
Assert.Equal(subscription.Status, response.Status);
Assert.Equal(subscription.GetCurrentPeriodEnd(), response.CurrentPeriodEndDate);
Assert.Equal(15, response.DiscountPercentage); // Verify subscription-level discount is used
Assert.Equal(subscription.CollectionMethod, response.CollectionMethod);
}
#endregion
#region UpdateTaxInformationAsync

View File

@@ -110,6 +110,7 @@ public class AccountsKeyManagementControllerTests
public async Task RotateUserAccountKeysSuccess(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user)
{
data.AccountKeys.SignatureKeyPair = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().RotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<RotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
@@ -142,8 +143,60 @@ public class AccountsKeyManagementControllerTests
&& d.MasterPasswordUnlockData.MasterKeyAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash
&& d.MasterPasswordUnlockData.MasterKeyEncryptedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey
&& d.AccountPublicKey == data.AccountKeys.AccountPublicKey
&& d.UserKeyEncryptedAccountPrivateKey == data.AccountKeys.UserKeyEncryptedAccountPrivateKey
&& d.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey
&& d.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey
));
}
[Theory]
[BitAutoData]
public async Task RotateUserAccountKeys_UserCryptoV2_Success_Async(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user)
{
data.AccountKeys.SignatureKeyPair = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = "ed25519",
WrappedSigningKey = "wrappedSigningKey",
VerifyingKey = "verifyingKey"
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().RotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<RotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.RotateUserAccountKeysAsync(data);
await sutProvider.GetDependency<IRotationValidator<IEnumerable<EmergencyAccessWithIdRequestModel>, IEnumerable<EmergencyAccess>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.EmergencyAccessUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.OrganizationAccountRecoveryUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.PasskeyUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Ciphers));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Folders));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Sends));
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.RotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<RotateUserAccountKeysData>(d =>
d.OldMasterKeyAuthenticationHash == data.OldMasterKeyAuthenticationHash
&& d.MasterPasswordUnlockData.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType
&& d.MasterPasswordUnlockData.KdfIterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations
&& d.MasterPasswordUnlockData.KdfMemory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory
&& d.MasterPasswordUnlockData.KdfParallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism
&& d.MasterPasswordUnlockData.Email == data.AccountUnlockData.MasterPasswordUnlockData.Email
&& d.MasterPasswordUnlockData.MasterKeyAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash
&& d.MasterPasswordUnlockData.MasterKeyEncryptedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey
&& d.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey
&& d.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey
&& d.AccountKeys!.PublicKeyEncryptionKeyPairData.SignedPublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.SignedPublicKey
&& d.AccountKeys!.SignatureKeyPairData!.SignatureAlgorithm == Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519
&& d.AccountKeys!.SignatureKeyPairData.WrappedSigningKey == data.AccountKeys.SignatureKeyPair!.WrappedSigningKey
&& d.AccountKeys!.SignatureKeyPairData.VerifyingKey == data.AccountKeys.SignatureKeyPair!.VerifyingKey
));
}
@@ -153,6 +206,7 @@ public class AccountsKeyManagementControllerTests
public async Task RotateUserKeyNoUser_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data)
{
data.AccountKeys.SignatureKeyPair = null;
User? user = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().RotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<RotateUserAccountKeysData>())
@@ -165,6 +219,7 @@ public class AccountsKeyManagementControllerTests
public async Task RotateUserKeyWrongData_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user, IdentityErrorDescriber _identityErrorDescriber)
{
data.AccountKeys.SignatureKeyPair = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().RotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<RotateUserAccountKeysData>())
.Returns(IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()));

View File

@@ -0,0 +1,112 @@
#nullable enable
using Bit.Api.KeyManagement.Controllers;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Enums;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Queries.Interfaces;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Api.Test.KeyManagement.Controllers;
[ControllerCustomize(typeof(UsersController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class UsersControllerTests
{
[Theory]
[BitAutoData]
public async Task GetPublicKey_NotFound_ThrowsNotFoundException(
SutProvider<UsersController> sutProvider)
{
sutProvider.GetDependency<IUserRepository>().GetPublicKeyAsync(Arg.Any<Guid>()).ReturnsNull();
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetPublicKeyAsync(new Guid()));
}
[Theory]
[BitAutoData]
public async Task GetPublicKey_ReturnsUserKeyResponseModel(
SutProvider<UsersController> sutProvider,
Guid userId)
{
var publicKey = "publicKey";
sutProvider.GetDependency<IUserRepository>().GetPublicKeyAsync(userId).Returns(publicKey);
var result = await sutProvider.Sut.GetPublicKeyAsync(userId);
Assert.NotNull(result);
Assert.Equal(userId, result.UserId);
Assert.Equal(publicKey, result.PublicKey);
}
[Theory]
[BitAutoData]
public async Task GetAccountKeys_UserNotFound_ThrowsNotFoundException(
SutProvider<UsersController> sutProvider)
{
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(Arg.Any<Guid>()).ReturnsNull();
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetAccountKeysAsync(new Guid()));
}
[Theory]
[BitAutoData]
public async Task GetAccountKeys_ReturnsPublicUserKeysResponseModel(
SutProvider<UsersController> sutProvider,
Guid userId)
{
var user = new User
{
Id = userId,
PublicKey = "publicKey",
SignedPublicKey = "signedPublicKey",
};
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(userId).Returns(user);
sutProvider.GetDependency<IUserAccountKeysQuery>()
.Run(user)
.Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("wrappedPrivateKey", "publicKey", "signedPublicKey"),
SignatureKeyPairData = new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "wrappedSigningKey", "verifyingKey"),
});
var result = await sutProvider.Sut.GetAccountKeysAsync(userId);
Assert.NotNull(result);
Assert.Equal("publicKey", result.PublicKey);
Assert.Equal("signedPublicKey", result.SignedPublicKey);
Assert.Equal("verifyingKey", result.VerifyingKey);
}
[Theory]
[BitAutoData]
public async Task GetAccountKeys_ReturnsPublicUserKeysResponseModel_WithNullVerifyingKey(
SutProvider<UsersController> sutProvider,
Guid userId)
{
var user = new User
{
Id = userId,
PublicKey = "publicKey",
SignedPublicKey = null,
};
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(userId).Returns(user);
sutProvider.GetDependency<IUserAccountKeysQuery>()
.Run(user)
.Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("wrappedPrivateKey", "publicKey", null),
SignatureKeyPairData = null,
});
var result = await sutProvider.Sut.GetAccountKeysAsync(userId);
Assert.NotNull(result);
Assert.Equal("publicKey", result.PublicKey);
Assert.Null(result.SignedPublicKey);
Assert.Null(result.VerifyingKey);
}
}

View File

@@ -0,0 +1,22 @@
#nullable enable
using Bit.Api.KeyManagement.Models.Requests;
using Xunit;
namespace Bit.Api.Test.KeyManagement.Models.Request;
public class SignatureKeyPairRequestModelTests
{
[Fact]
public void ToSignatureKeyPairData_WrongAlgorithm_Rejects()
{
var model = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = "abc",
WrappedSigningKey = "wrappedKey",
VerifyingKey = "verifyingKey"
};
Assert.Throws<ArgumentException>(() => model.ToSignatureKeyPairData());
}
}

View File

@@ -75,6 +75,7 @@ public class ImportCiphersControllerTests
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
.With(c => c.FolderId, Guid.NewGuid().ToString())
.With(c => c.ArchivedDate, (DateTime?)null)
.CreateMany(1).ToArray())
.Create();
@@ -92,6 +93,37 @@ public class ImportCiphersControllerTests
);
}
[Theory, BitAutoData]
public async Task PostImportIndividual_WithArchivedDate_SavesArchivedDate(User user,
IFixture fixture, SutProvider<ImportCiphersController> sutProvider)
{
var archivedDate = DateTime.UtcNow;
sutProvider.GetDependency<GlobalSettings>()
.SelfHosted = false;
sutProvider.GetDependency<Core.Services.IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
var request = fixture.Build<ImportCiphersRequestModel>()
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.ArchivedDate, archivedDate)
.With(c => c.FolderId, (string)null)
.CreateMany(1).ToArray())
.Create();
await sutProvider.Sut.PostImport(request);
await sutProvider.GetDependency<IImportCiphersCommand>()
.Received()
.ImportIntoIndividualVaultAsync(
Arg.Any<List<Folder>>(),
Arg.Is<List<CipherDetails>>(ciphers => ciphers.First().ArchivedDate == archivedDate),
Arg.Any<IEnumerable<KeyValuePair<int, int>>>(),
user.Id
);
}
/****************************
* PostImport - Organization
****************************/
@@ -156,6 +188,7 @@ public class ImportCiphersControllerTests
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
.With(c => c.FolderId, Guid.NewGuid().ToString())
.With(c => c.ArchivedDate, (DateTime?)null)
.CreateMany(1).ToArray())
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
.With(c => c.Id, orgIdGuid)
@@ -227,6 +260,7 @@ public class ImportCiphersControllerTests
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
.With(c => c.FolderId, Guid.NewGuid().ToString())
.With(c => c.ArchivedDate, (DateTime?)null)
.CreateMany(1).ToArray())
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
.With(c => c.Id, orgIdGuid)
@@ -291,6 +325,7 @@ public class ImportCiphersControllerTests
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
.With(c => c.FolderId, Guid.NewGuid().ToString())
.With(c => c.ArchivedDate, (DateTime?)null)
.CreateMany(1).ToArray())
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
.With(c => c.Id, orgIdGuid)
@@ -354,6 +389,7 @@ public class ImportCiphersControllerTests
.With(x => x.Ciphers, fixture.Build<CipherRequestModel>()
.With(c => c.OrganizationId, Guid.NewGuid().ToString())
.With(c => c.FolderId, Guid.NewGuid().ToString())
.With(c => c.ArchivedDate, (DateTime?)null)
.CreateMany(1).ToArray())
.With(y => y.Collections, fixture.Build<CollectionWithIdRequestModel>()
.With(c => c.Id, orgIdGuid)
@@ -423,6 +459,7 @@ public class ImportCiphersControllerTests
Ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.OrganizationId, orgId.ToString())
.With(_ => _.FolderId, Guid.NewGuid().ToString())
.With(_ => _.ArchivedDate, (DateTime?)null)
.CreateMany(2).ToArray(),
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
@@ -499,6 +536,7 @@ public class ImportCiphersControllerTests
Ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.OrganizationId, orgId.ToString())
.With(_ => _.FolderId, Guid.NewGuid().ToString())
.With(_ => _.ArchivedDate, (DateTime?)null)
.CreateMany(2).ToArray(),
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
@@ -578,6 +616,7 @@ public class ImportCiphersControllerTests
Ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.OrganizationId, orgId.ToString())
.With(_ => _.FolderId, Guid.NewGuid().ToString())
.With(_ => _.ArchivedDate, (DateTime?)null)
.CreateMany(2).ToArray(),
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
@@ -651,6 +690,7 @@ public class ImportCiphersControllerTests
Ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.OrganizationId, orgId.ToString())
.With(_ => _.FolderId, Guid.NewGuid().ToString())
.With(_ => _.ArchivedDate, (DateTime?)null)
.CreateMany(2).ToArray(),
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
@@ -720,6 +760,7 @@ public class ImportCiphersControllerTests
Ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.OrganizationId, orgId.ToString())
.With(_ => _.FolderId, Guid.NewGuid().ToString())
.With(_ => _.ArchivedDate, (DateTime?)null)
.CreateMany(2).ToArray(),
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
@@ -765,6 +806,63 @@ public class ImportCiphersControllerTests
Arg.Any<Guid>());
}
[Theory, BitAutoData]
public async Task PostImportOrganization_ThrowsException_WhenAnyCipherIsArchived(
SutProvider<ImportCiphersController> sutProvider,
IFixture fixture,
User user
)
{
var orgId = Guid.NewGuid();
sutProvider.GetDependency<GlobalSettings>()
.SelfHosted = false;
sutProvider.GetDependency<GlobalSettings>()
.ImportCiphersLimitation = _organizationCiphersLimitations;
SetupUserService(sutProvider, user);
var ciphers = fixture.Build<CipherRequestModel>()
.With(_ => _.ArchivedDate, DateTime.UtcNow)
.CreateMany(2).ToArray();
var request = new ImportOrganizationCiphersRequestModel
{
Collections = new List<CollectionWithIdRequestModel>().ToArray(),
Ciphers = ciphers,
CollectionRelationships = new List<KeyValuePair<int, int>>().ToArray(),
};
sutProvider.GetDependency<ICurrentContext>()
.AccessImportExport(Arg.Any<Guid>())
.Returns(false);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
Arg.Any<IEnumerable<Collection>>(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
reqs.Contains(BulkCollectionOperations.ImportCiphers)))
.Returns(AuthorizationResult.Failed());
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(),
Arg.Any<IEnumerable<Collection>>(),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
reqs.Contains(BulkCollectionOperations.Create)))
.Returns(AuthorizationResult.Success());
sutProvider.GetDependency<ICollectionRepository>()
.GetManyByOrganizationIdAsync(orgId)
.Returns(new List<Collection>());
var exception = await Assert.ThrowsAsync<BadRequestException>(async () =>
{
await sutProvider.Sut.PostImportOrganization(orgId.ToString(), request);
});
Assert.Equal("You cannot import archived items into an organization.", exception.Message);
}
private static void SetupUserService(SutProvider<ImportCiphersController> sutProvider, User user)
{
// This is a workaround for the NSubstitute issue with ambiguous arguments

View File

@@ -1790,6 +1790,118 @@ public class CiphersControllerTests
);
}
[Theory, BitAutoData]
public async Task PutShareMany_ArchivedCipher_ThrowsBadRequestException(
Guid organizationId,
Guid userId,
CipherWithIdRequestModel request,
SutProvider<CiphersController> sutProvider)
{
request.EncryptedFor = userId;
request.OrganizationId = organizationId.ToString();
request.ArchivedDate = DateTime.UtcNow;
var model = new CipherBulkShareRequestModel
{
Ciphers = [request],
CollectionIds = [Guid.NewGuid().ToString()]
};
sutProvider.GetDependency<ICurrentContext>()
.OrganizationUser(organizationId)
.Returns(Task.FromResult(true));
sutProvider.GetDependency<IUserService>()
.GetProperUserId(default)
.ReturnsForAnyArgs(userId);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.PutShareMany(model)
);
Assert.Equal("Cannot move archived items to an organization.", exception.Message);
}
[Theory, BitAutoData]
public async Task PutShareMany_ExistingCipherArchived_ThrowsBadRequestException(
Guid organizationId,
Guid userId,
CipherWithIdRequestModel request,
SutProvider<CiphersController> sutProvider)
{
// Request model does not have ArchivedDate (only the existing cipher does)
request.EncryptedFor = userId;
request.OrganizationId = organizationId.ToString();
request.ArchivedDate = null;
var model = new CipherBulkShareRequestModel
{
Ciphers = [request],
CollectionIds = [Guid.NewGuid().ToString()]
};
// The existing cipher from the repository IS archived
var existingCipher = new CipherDetails
{
Id = request.Id!.Value,
UserId = userId,
Type = CipherType.Login,
Data = JsonSerializer.Serialize(new CipherLoginData()),
ArchivedDate = DateTime.UtcNow
};
sutProvider.GetDependency<ICurrentContext>()
.OrganizationUser(organizationId)
.Returns(Task.FromResult(true));
sutProvider.GetDependency<IUserService>()
.GetProperUserId(default)
.ReturnsForAnyArgs(userId);
sutProvider.GetDependency<ICipherRepository>()
.GetManyByUserIdAsync(userId, withOrganizations: false)
.Returns(Task.FromResult((ICollection<CipherDetails>)[existingCipher]));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.PutShareMany(model)
);
Assert.Equal("Cannot move archived items to an organization.", exception.Message);
}
[Theory, BitAutoData]
public async Task PutShare_ArchivedCipher_ThrowsBadRequestException(
Guid cipherId,
Guid organizationId,
User user,
CipherShareRequestModel model,
SutProvider<CiphersController> sutProvider)
{
model.Cipher.OrganizationId = organizationId.ToString();
model.Cipher.EncryptedFor = user.Id;
var cipher = new Cipher
{
Id = cipherId,
UserId = user.Id,
ArchivedDate = DateTime.UtcNow.AddDays(-1),
Type = CipherType.Login,
Data = JsonSerializer.Serialize(new CipherLoginData())
};
sutProvider.GetDependency<IUserService>()
.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(user);
sutProvider.GetDependency<ICipherRepository>()
.GetByIdAsync(cipherId)
.Returns(cipher);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationUser(organizationId)
.Returns(Task.FromResult(true));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.PutShare(cipherId, model)
);
Assert.Equal("Cannot move an archived item to an organization.", exception.Message);
}
[Theory, BitAutoData]
public async Task PostPurge_WhenUserNotFound_ThrowsUnauthorizedAccessException(
SecretVerificationRequestModel model,

View File

@@ -12,6 +12,8 @@ using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Queries.Interfaces;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
@@ -74,6 +76,7 @@ public class SyncControllerTests
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
var userAccountKeysQuery = sutProvider.GetDependency<IUserAccountKeysQuery>();
// Adjust random data to match required formats / test intentions
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
@@ -98,6 +101,11 @@ public class SyncControllerTests
// Setup returns
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(),
SignatureKeyPairData = null,
});
organizationUserRepository
.GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails);
@@ -127,7 +135,6 @@ public class SyncControllerTests
// Execute GET
var result = await sutProvider.Sut.Get();
// Asserts
// Assert that methods are called
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
@@ -166,6 +173,7 @@ public class SyncControllerTests
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
var userAccountKeysQuery = sutProvider.GetDependency<IUserAccountKeysQuery>();
// Adjust random data to match required formats / test intentions
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
@@ -189,6 +197,11 @@ public class SyncControllerTests
// Setup returns
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(),
SignatureKeyPairData = null,
});
organizationUserRepository
.GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails);
@@ -256,6 +269,7 @@ public class SyncControllerTests
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
var collectionRepository = sutProvider.GetDependency<ICollectionRepository>();
var collectionCipherRepository = sutProvider.GetDependency<ICollectionCipherRepository>();
var userAccountKeysQuery = sutProvider.GetDependency<IUserAccountKeysQuery>();
// Adjust random data to match required formats / test intentions
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
@@ -290,6 +304,12 @@ public class SyncControllerTests
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
userService.HasPremiumFromOrganization(user).Returns(false);
userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(),
SignatureKeyPairData = null,
});
// Execute GET
var result = await sutProvider.Sut.Get();
@@ -327,6 +347,13 @@ public class SyncControllerTests
user.MasterPassword = null;
var userAccountKeysQuery = sutProvider.GetDependency<IUserAccountKeysQuery>();
userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(),
SignatureKeyPairData = null,
});
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);
@@ -352,6 +379,13 @@ public class SyncControllerTests
user.KdfMemory = kdfMemory;
user.KdfParallelism = kdfParallelism;
var userAccountKeysQuery = sutProvider.GetDependency<IUserAccountKeysQuery>();
userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData
{
PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(),
SignatureKeyPairData = null,
});
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsForAnyArgs(user);