mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
Merge branch 'main' into km/pm-10600
# Conflicts: # src/Core/NotificationHub/NotificationHubPushRegistrationService.cs
This commit is contained in:
@ -0,0 +1,336 @@
|
||||
using Bit.Admin.AdminConsole.Controllers;
|
||||
using Bit.Admin.AdminConsole.Models;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Admin.Test.AdminConsole.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(OrganizationsController))]
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationsControllerTests
|
||||
{
|
||||
#region Edit (POST)
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_RequiredFFDisabled_NoOp(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel { UseSecretsManager = false };
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>());
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_NonBillableProvider_NoOp(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel { UseSecretsManager = false };
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Created };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>());
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_UnmanagedOrganization_NoOp(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel { UseSecretsManager = false };
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Created
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>());
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_NonCBPlanType_NoOp(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
|
||||
var update = new OrganizationEditModel
|
||||
{
|
||||
UseSecretsManager = false,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.FamiliesAnnually
|
||||
};
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Managed,
|
||||
Seats = 10
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>());
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_NoUpdateRequired_NoOp(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel
|
||||
{
|
||||
UseSecretsManager = false,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Managed,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>());
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_PlanTypesUpdate_ScalesSeatsCorrectly(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel
|
||||
{
|
||||
UseSecretsManager = false,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Managed,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.TeamsMonthly
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
var providerBillingService = sutProvider.GetDependency<IProviderBillingService>();
|
||||
|
||||
await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, -organization.Seats.Value);
|
||||
await providerBillingService.Received(1).ScaleSeats(provider, update.PlanType!.Value, organization.Seats.Value);
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_SeatsUpdate_ScalesSeatsCorrectly(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel
|
||||
{
|
||||
UseSecretsManager = false,
|
||||
Seats = 15,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Managed,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
var providerBillingService = sutProvider.GetDependency<IProviderBillingService>();
|
||||
|
||||
await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, update.Seats!.Value - organization.Seats.Value);
|
||||
}
|
||||
|
||||
[BitAutoData]
|
||||
[SutProviderCustomize]
|
||||
[Theory]
|
||||
public async Task Edit_ProviderSeatScaling_FullUpdate_ScalesSeatsCorrectly(
|
||||
SutProvider<OrganizationsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var organizationId = new Guid();
|
||||
var update = new OrganizationEditModel
|
||||
{
|
||||
UseSecretsManager = false,
|
||||
Seats = 15,
|
||||
PlanType = PlanType.EnterpriseMonthly
|
||||
};
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
Status = OrganizationStatusType.Managed,
|
||||
Seats = 10,
|
||||
PlanType = PlanType.TeamsMonthly
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||
|
||||
featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true);
|
||||
|
||||
var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable };
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider);
|
||||
|
||||
// Act
|
||||
_ = await sutProvider.Sut.Edit(organizationId, update);
|
||||
|
||||
// Assert
|
||||
var providerBillingService = sutProvider.GetDependency<IProviderBillingService>();
|
||||
|
||||
await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, -organization.Seats.Value);
|
||||
await providerBillingService.Received(1).ScaleSeats(provider, update.PlanType!.Value, update.Seats!.Value - organization.Seats.Value + organization.Seats.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.AuthorizationHandlers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GroupAuthorizationHandlerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanReadAllAsync_WhenMemberOfOrg_Success(
|
||||
OrganizationUserType userType,
|
||||
OrganizationScope scope,
|
||||
Guid userId, SutProvider<GroupAuthorizationHandler> sutProvider,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll },
|
||||
new ClaimsPrincipal(),
|
||||
scope);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CanReadAllAsync_WithProviderUser_Success(
|
||||
Guid userId,
|
||||
OrganizationScope scope,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider, CurrentContextOrganization organization)
|
||||
{
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll },
|
||||
new ClaimsPrincipal(),
|
||||
scope);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId
|
||||
.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ProviderUserForOrgAsync(scope)
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CanReadAllAsync_WhenMissingOrgAccess_NoSuccess(
|
||||
Guid userId,
|
||||
OrganizationScope scope,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll },
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenUserIsAdminOwner_ThenShouldSucceed(OrganizationUserType userType,
|
||||
OrganizationScope scope,
|
||||
CurrentContextOrganization organization, SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
[GroupOperations.ReadAllDetails],
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenUserIsNotOwnerOrAdmin_ThenShouldFail(OrganizationUserType userType,
|
||||
OrganizationScope scope,
|
||||
CurrentContextOrganization organization, SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
[GroupOperations.ReadAllDetails],
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenUserHasManageGroupPermission_ThenShouldSucceed(
|
||||
OrganizationScope scope,
|
||||
CurrentContextOrganization organization, SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
organization.Type = OrganizationUserType.Custom;
|
||||
organization.Permissions = new Permissions
|
||||
{
|
||||
ManageGroups = true
|
||||
};
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
[GroupOperations.ReadAllDetails],
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenUserHasManageUserPermission_ThenShouldSucceed(
|
||||
OrganizationScope scope,
|
||||
CurrentContextOrganization organization, SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
organization.Type = OrganizationUserType.Custom;
|
||||
organization.Permissions = new Permissions
|
||||
{
|
||||
ManageUsers = true
|
||||
};
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
[GroupOperations.ReadAllDetails],
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenUserIsStandardUserTypeWithoutElevatedPermissions_ThenShouldFail(
|
||||
OrganizationScope scope,
|
||||
CurrentContextOrganization organization, SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
[GroupOperations.ReadAllDetails],
|
||||
new ClaimsPrincipal(),
|
||||
scope
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(scope).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementsAsync_GivenViewDetailsOperation_WhenIsProviderUser_ThenShouldSucceed(
|
||||
OrganizationScope scope,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider, CurrentContextOrganization organization)
|
||||
{
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll },
|
||||
new ClaimsPrincipal(),
|
||||
scope);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(scope).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(scope).Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
}
|
@ -51,7 +51,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
public OrganizationsControllerTests()
|
||||
@ -123,7 +122,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
|
||||
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization> { null });
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
|
||||
|
||||
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
|
||||
@ -132,6 +132,36 @@ public class OrganizationsControllerTests : IDisposable
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task OrganizationsController_UserCannotLeaveOrganizationThatManagesUser(
|
||||
Guid orgId, User user)
|
||||
{
|
||||
var ssoConfig = new SsoConfig
|
||||
{
|
||||
Id = default,
|
||||
Data = new SsoConfigurationData
|
||||
{
|
||||
MemberDecryptionType = MemberDecryptionType.KeyConnector
|
||||
}.Serialize(),
|
||||
Enabled = true,
|
||||
OrganizationId = orgId,
|
||||
};
|
||||
var foundOrg = new Organization();
|
||||
foundOrg.Id = orgId;
|
||||
|
||||
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization> { { foundOrg } });
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
|
||||
|
||||
Assert.Contains("Managed user account cannot leave managing organization. Contact your organization administrator for additional details.",
|
||||
exception.Message);
|
||||
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineAutoData(true, false)]
|
||||
[InlineAutoData(false, true)]
|
||||
@ -157,6 +187,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization>());
|
||||
|
||||
await _sut.Leave(orgId);
|
||||
|
||||
@ -186,8 +218,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
|
||||
_userService.VerifySecretAsync(user, requestModel.Secret).Returns(true);
|
||||
|
||||
_featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling).Returns(true);
|
||||
|
||||
_providerRepository.GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
||||
|
||||
await _sut.Delete(organizationId.ToString(), requestModel);
|
||||
|
@ -0,0 +1,69 @@
|
||||
using AutoFixture;
|
||||
using Bit.Api.AdminConsole.Models.Response.Helpers;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Models.Response.Helpers;
|
||||
|
||||
public class PolicyDetailResponsesTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsSingleOrgTypeAndHasVerifiedDomains_ThenShouldNotBeAbleToToggle()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
|
||||
var policy = fixture.Build<Policy>()
|
||||
.Without(p => p.Data)
|
||||
.With(p => p.Type, PolicyType.SingleOrg)
|
||||
.Create();
|
||||
|
||||
var querySub = Substitute.For<IOrganizationHasVerifiedDomainsQuery>();
|
||||
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
|
||||
|
||||
Assert.False(result.CanToggleState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsNotSingleOrgType_ThenShouldThrowArgumentException()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
|
||||
var policy = fixture.Build<Policy>()
|
||||
.Without(p => p.Data)
|
||||
.With(p => p.Type, PolicyType.TwoFactorAuthentication)
|
||||
.Create();
|
||||
|
||||
var querySub = Substitute.For<IOrganizationHasVerifiedDomainsQuery>();
|
||||
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
var action = async () => await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>("policy", action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSingleOrgPolicyDetailResponseAsync_GivenPolicyEntity_WhenIsSingleOrgTypeAndDoesNotHaveVerifiedDomains_ThenShouldBeAbleToToggle()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
|
||||
var policy = fixture.Build<Policy>()
|
||||
.Without(p => p.Data)
|
||||
.With(p => p.Type, PolicyType.SingleOrg)
|
||||
.Create();
|
||||
|
||||
var querySub = Substitute.For<IOrganizationHasVerifiedDomainsQuery>();
|
||||
querySub.HasVerifiedDomainsAsync(policy.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
var result = await policy.GetSingleOrgPolicyDetailResponseAsync(querySub);
|
||||
|
||||
Assert.True(result.CanToggleState);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using Bit.Api.AdminConsole.Public.Models.Response;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Public.Models.Response;
|
||||
|
||||
|
||||
public class MemberResponseModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResetPasswordEnrolled_ShouldBeTrue_WhenUserHasResetPasswordKey()
|
||||
{
|
||||
// Arrange
|
||||
var user = Substitute.For<OrganizationUser>();
|
||||
var collections = Substitute.For<IEnumerable<CollectionAccessSelection>>();
|
||||
user.ResetPasswordKey = "none-empty";
|
||||
|
||||
|
||||
// Act
|
||||
var sut = new MemberResponseModel(user, collections);
|
||||
|
||||
// Assert
|
||||
Assert.True(sut.ResetPasswordEnrolled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetPasswordEnrolled_ShouldBeFalse_WhenUserDoesNotHaveResetPasswordKey()
|
||||
{
|
||||
// Arrange
|
||||
var user = Substitute.For<OrganizationUser>();
|
||||
var collections = Substitute.For<IEnumerable<CollectionAccessSelection>>();
|
||||
|
||||
// Act
|
||||
var sut = new MemberResponseModel(user, collections);
|
||||
|
||||
// Assert
|
||||
Assert.False(sut.ResetPasswordEnrolled);
|
||||
}
|
||||
}
|
295
test/Api.Test/Auth/Controllers/TwoFactorControllerTests.cs
Normal file
295
test/Api.Test/Auth/Controllers/TwoFactorControllerTests.cs
Normal file
@ -0,0 +1,295 @@
|
||||
using Bit.Api.Auth.Controllers;
|
||||
using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Auth.Models.Response.TwoFactor;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
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.Api.Test.Auth.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(TwoFactorController))]
|
||||
[SutProviderCustomize]
|
||||
public class TwoFactorControllerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CheckAsync_UserNull_ThrowsUnauthorizedException(SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(null as User);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.GetDuo(request);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CheckAsync_BadSecret_ThrowsBadRequestException(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.VerifySecretAsync(default, default)
|
||||
.ReturnsForAnyArgs(false);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
await sutProvider.Sut.GetDuo(request);
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("The model state is invalid.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CheckAsync_CannotAccessPremium_ThrowsBadRequestException(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.VerifySecretAsync(default, default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CanAccessPremium(default)
|
||||
.ReturnsForAnyArgs(false);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
await sutProvider.Sut.GetDuo(request);
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("Premium status is required.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetDuo_Success(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetDuo(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PutDuo_InvalidConfiguration_ThrowsBadRequestException(User user, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.ValidateDuoConfiguration(default, default, default)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
await sutProvider.Sut.PutDuo(request);
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("Duo configuration settings are not valid. Please re-check the Duo Admin panel.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PutDuo_Success(User user, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.ValidateDuoConfiguration(default, default, default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.PutDuo(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
||||
Assert.Equal(user.TwoFactorProviders, request.ToUser(user).TwoFactorProviders);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CheckOrganizationAsync_ManagePolicies_ThrowsNotFoundException(
|
||||
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(default)
|
||||
.ReturnsForAnyArgs(false);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CheckOrganizationAsync_GetByIdAsync_ThrowsNotFoundException(
|
||||
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(default)
|
||||
.ReturnsForAnyArgs(null as Organization);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationDuo_Success(
|
||||
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PutOrganizationDuo_InvalidConfiguration_ThrowsBadRequestException(
|
||||
User user, Organization organization, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.ValidateDuoConfiguration(default, default, default)
|
||||
.ReturnsForAnyArgs(false);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
await sutProvider.Sut.PutOrganizationDuo(organization.Id.ToString(), request);
|
||||
}
|
||||
catch (BadRequestException e)
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("Duo configuration settings are not valid. Please re-check the Duo Admin panel.", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PutOrganizationDuo_Success(
|
||||
User user, Organization organization, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetupCheckAsyncToPass(sutProvider, user);
|
||||
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
||||
organization.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.ValidateDuoConfiguration(default, default, default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
// Act
|
||||
var result =
|
||||
await sutProvider.Sut.PutOrganizationDuo(organization.Id.ToString(), request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
||||
Assert.Equal(organization.TwoFactorProviders, request.ToOrganization(organization).TwoFactorProviders);
|
||||
}
|
||||
|
||||
|
||||
private string GetUserTwoFactorDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetOrganizationTwoFactorDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the CheckAsync method to pass.
|
||||
/// </summary>
|
||||
/// <param name="sutProvider">uses bit auto data</param>
|
||||
/// <param name="user">uses bit auto data</param>
|
||||
private void SetupCheckAsyncToPass(SutProvider<TwoFactorController> sutProvider, User user)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.VerifySecretAsync(default, default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CanAccessPremium(default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
}
|
||||
|
||||
private void SetupCheckOrganizationAsyncToPass(SutProvider<TwoFactorController> sutProvider, Organization organization)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(default)
|
||||
.ReturnsForAnyArgs(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(default)
|
||||
.ReturnsForAnyArgs(organization);
|
||||
}
|
||||
}
|
@ -18,8 +18,6 @@ public class OrganizationTwoFactorDuoRequestModelTests
|
||||
{
|
||||
ClientId = "clientId",
|
||||
ClientSecret = "clientSecret",
|
||||
IntegrationKey = "integrationKey",
|
||||
SecretKey = "secretKey",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
@ -30,8 +28,6 @@ public class OrganizationTwoFactorDuoRequestModelTests
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
|
||||
}
|
||||
@ -49,8 +45,6 @@ public class OrganizationTwoFactorDuoRequestModelTests
|
||||
{
|
||||
ClientId = "newClientId",
|
||||
ClientSecret = "newClientSecret",
|
||||
IntegrationKey = "newIntegrationKey",
|
||||
SecretKey = "newSecretKey",
|
||||
Host = "newExample.com"
|
||||
};
|
||||
|
||||
@ -61,61 +55,7 @@ public class OrganizationTwoFactorDuoRequestModelTests
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
|
||||
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
|
||||
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
|
||||
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
|
||||
Assert.Equal("newExample.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuoV2ParamsSync_WhenExistingProviderDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var existingOrg = new Organization();
|
||||
var model = new UpdateTwoFactorDuoRequestModel
|
||||
{
|
||||
IntegrationKey = "integrationKey",
|
||||
SecretKey = "secretKey",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = model.ToOrganization(existingOrg);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
|
||||
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
|
||||
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
|
||||
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuoV4ParamsSync_WhenExistingProviderDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var existingOrg = new Organization();
|
||||
var model = new UpdateTwoFactorDuoRequestModel
|
||||
{
|
||||
ClientId = "clientId",
|
||||
ClientSecret = "clientSecret",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = model.ToOrganization(existingOrg);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +39,9 @@ public class TwoFactorDuoRequestModelValidationTests
|
||||
var result = model.Validate(new ValidationContext(model));
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal("Neither v2 or v4 values are valid.", result.First().ErrorMessage);
|
||||
Assert.Contains("ClientId", result.First().MemberNames);
|
||||
Assert.Contains("ClientSecret", result.First().MemberNames);
|
||||
Assert.Contains("IntegrationKey", result.First().MemberNames);
|
||||
Assert.Contains("SecretKey", result.First().MemberNames);
|
||||
Assert.NotEmpty(result);
|
||||
Assert.True(result.Select(x => x.MemberNames.Contains("ClientId")).Any());
|
||||
Assert.True(result.Select(x => x.MemberNames.Contains("ClientSecret")).Any());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -17,8 +17,6 @@ public class UserTwoFactorDuoRequestModelTests
|
||||
{
|
||||
ClientId = "clientId",
|
||||
ClientSecret = "clientSecret",
|
||||
IntegrationKey = "integrationKey",
|
||||
SecretKey = "secretKey",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
@ -26,12 +24,9 @@ public class UserTwoFactorDuoRequestModelTests
|
||||
var result = model.ToUser(existingUser);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
|
||||
}
|
||||
@ -49,8 +44,6 @@ public class UserTwoFactorDuoRequestModelTests
|
||||
{
|
||||
ClientId = "newClientId",
|
||||
ClientSecret = "newClientSecret",
|
||||
IntegrationKey = "newIntegrationKey",
|
||||
SecretKey = "newSecretKey",
|
||||
Host = "newExample.com"
|
||||
};
|
||||
|
||||
@ -58,65 +51,10 @@ public class UserTwoFactorDuoRequestModelTests
|
||||
var result = model.ToUser(existingUser);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
|
||||
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
|
||||
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
|
||||
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
|
||||
Assert.Equal("newExample.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuoV2ParamsSync_WhenExistingProviderDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var existingUser = new User();
|
||||
var model = new UpdateTwoFactorDuoRequestModel
|
||||
{
|
||||
IntegrationKey = "integrationKey",
|
||||
SecretKey = "secretKey",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = model.ToUser(existingUser);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
|
||||
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
|
||||
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
|
||||
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuoV4ParamsSync_WhenExistingProviderDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var existingUser = new User();
|
||||
var model = new UpdateTwoFactorDuoRequestModel
|
||||
{
|
||||
ClientId = "clientId",
|
||||
ClientSecret = "clientSecret",
|
||||
Host = "example.com"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = model.ToUser(existingUser);
|
||||
|
||||
// Assert
|
||||
// IKey and SKey should be the same as ClientId and ClientSecret
|
||||
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
|
||||
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
|
||||
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
|
||||
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
|
||||
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
|
||||
}
|
||||
}
|
||||
|
@ -8,42 +8,6 @@ namespace Bit.Api.Test.Auth.Models.Response;
|
||||
|
||||
public class OrganizationTwoFactorDuoResponseModelTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Organization_WithDuoV4_ShouldBuildModel(Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoV4ProvidersJson();
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
|
||||
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("clientId", model.ClientId);
|
||||
Assert.Equal("secret************", model.ClientSecret);
|
||||
Assert.Equal("clientId", model.IntegrationKey);
|
||||
Assert.Equal("secret************", model.SecretKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Organization_WithDuoV2_ShouldBuildModel(Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoV2ProvidersJson();
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
|
||||
// Assert if only v2 data clientId and clientSecret are set to Ikey and Sk
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("IKey", model.ClientId);
|
||||
Assert.Equal("SKey", model.ClientSecret);
|
||||
Assert.Equal("IKey", model.IntegrationKey);
|
||||
Assert.Equal("SKey", model.SecretKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Organization_WithDuo_ShouldBuildModel(Organization organization)
|
||||
@ -54,12 +18,10 @@ public class OrganizationTwoFactorDuoResponseModelTests
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
|
||||
/// Assert Even if both versions are present priority is given to v4 data
|
||||
// Assert
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("clientId", model.ClientId);
|
||||
Assert.Equal("secret************", model.ClientSecret);
|
||||
Assert.Equal("clientId", model.IntegrationKey);
|
||||
Assert.Equal("secret************", model.SecretKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -72,38 +34,33 @@ public class OrganizationTwoFactorDuoResponseModelTests
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
|
||||
/// Assert
|
||||
// Assert
|
||||
Assert.False(model.Enabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Organization_WithTwoFactorProvidersNull_ShouldFail(Organization organization)
|
||||
public void Organization_WithTwoFactorProvidersNull_ShouldThrow(Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = "{\"6\" : {}}";
|
||||
organization.TwoFactorProviders = null;
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
try
|
||||
{
|
||||
var model = new TwoFactorDuoResponseModel(organization);
|
||||
|
||||
/// Assert
|
||||
Assert.False(model.Enabled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsType<ArgumentNullException>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoV4ProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoV2ProvidersJson()
|
||||
{
|
||||
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
||||
|
@ -10,38 +10,21 @@ public class UserTwoFactorDuoResponseModelTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void User_WithDuoV4_ShouldBuildModel(User user)
|
||||
public void User_WithDuo_UserNull_ThrowsArgumentException(User user)
|
||||
{
|
||||
// Arrange
|
||||
user.TwoFactorProviders = GetTwoFactorDuoV4ProvidersJson();
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(user);
|
||||
|
||||
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("clientId", model.ClientId);
|
||||
Assert.Equal("secret************", model.ClientSecret);
|
||||
Assert.Equal("clientId", model.IntegrationKey);
|
||||
Assert.Equal("secret************", model.SecretKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void User_WithDuov2_ShouldBuildModel(User user)
|
||||
{
|
||||
// Arrange
|
||||
user.TwoFactorProviders = GetTwoFactorDuoV2ProvidersJson();
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(user);
|
||||
|
||||
// Assert if only v2 data clientId and clientSecret are set to Ikey and Skey
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("IKey", model.ClientId);
|
||||
Assert.Equal("SKey", model.ClientSecret);
|
||||
Assert.Equal("IKey", model.IntegrationKey);
|
||||
Assert.Equal("SKey", model.SecretKey);
|
||||
try
|
||||
{
|
||||
var model = new TwoFactorDuoResponseModel(null as User);
|
||||
}
|
||||
catch (ArgumentNullException e)
|
||||
{
|
||||
// Assert
|
||||
Assert.Equal("Value cannot be null. (Parameter 'user')", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -54,12 +37,10 @@ public class UserTwoFactorDuoResponseModelTests
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(user);
|
||||
|
||||
// Assert Even if both versions are present priority is given to v4 data
|
||||
// Assert
|
||||
Assert.NotNull(model);
|
||||
Assert.Equal("clientId", model.ClientId);
|
||||
Assert.Equal("secret************", model.ClientSecret);
|
||||
Assert.Equal("clientId", model.IntegrationKey);
|
||||
Assert.Equal("secret************", model.SecretKey);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -84,26 +65,23 @@ public class UserTwoFactorDuoResponseModelTests
|
||||
user.TwoFactorProviders = null;
|
||||
|
||||
// Act
|
||||
var model = new TwoFactorDuoResponseModel(user);
|
||||
try
|
||||
{
|
||||
var model = new TwoFactorDuoResponseModel(user);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Assert
|
||||
Assert.IsType<ArgumentNullException>(ex);
|
||||
|
||||
}
|
||||
|
||||
/// Assert
|
||||
Assert.False(model.Enabled);
|
||||
}
|
||||
|
||||
private string GetTwoFactorDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorDuoV4ProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorDuoV2ProvidersJson()
|
||||
{
|
||||
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class OrganizationBillingControllerTests
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
|
||||
.Returns(new OrganizationMetadata(true, true, true, true));
|
||||
.Returns(new OrganizationMetadata(true, true, true, true, true));
|
||||
|
||||
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
||||
|
||||
@ -64,6 +64,7 @@ public class OrganizationBillingControllerTests
|
||||
Assert.True(response.IsManaged);
|
||||
Assert.True(response.IsOnSecretsManagerStandalone);
|
||||
Assert.True(response.IsSubscriptionUnpaid);
|
||||
Assert.True(response.HasSubscription);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Bit.Api.Billing.Controllers;
|
||||
using Bit.Api.Billing.Models.Requests;
|
||||
using Bit.Api.Billing.Models.Responses;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
@ -35,27 +34,11 @@ public class ProviderBillingControllerTests
|
||||
{
|
||||
#region GetInvoicesAsync & TryGetBillableProviderForAdminOperations
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetInvoicesAsync_FFDisabled_NotFound(
|
||||
Guid providerId,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.GetInvoicesAsync(providerId);
|
||||
|
||||
AssertNotFound(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetInvoicesAsync_NullProvider_NotFound(
|
||||
Guid providerId,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(providerId).ReturnsNull();
|
||||
|
||||
var result = await sutProvider.Sut.GetInvoicesAsync(providerId);
|
||||
@ -68,9 +51,6 @@ public class ProviderBillingControllerTests
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id)
|
||||
@ -86,9 +66,6 @@ public class ProviderBillingControllerTests
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
provider.Type = ProviderType.Reseller;
|
||||
provider.Status = ProviderStatusType.Created;
|
||||
|
||||
@ -229,27 +206,11 @@ public class ProviderBillingControllerTests
|
||||
|
||||
#region GetSubscriptionAsync & TryGetBillableProviderForServiceUserOperation
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSubscriptionAsync_FFDisabled_NotFound(
|
||||
Guid providerId,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.GetSubscriptionAsync(providerId);
|
||||
|
||||
AssertNotFound(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetSubscriptionAsync_NullProvider_NotFound(
|
||||
Guid providerId,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(providerId).ReturnsNull();
|
||||
|
||||
var result = await sutProvider.Sut.GetSubscriptionAsync(providerId);
|
||||
@ -262,9 +223,6 @@ public class ProviderBillingControllerTests
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUser(provider.Id)
|
||||
@ -280,9 +238,6 @@ public class ProviderBillingControllerTests
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
provider.Type = ProviderType.Reseller;
|
||||
provider.Status = ProviderStatusType.Created;
|
||||
|
||||
|
@ -5,8 +5,11 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -93,24 +96,7 @@ public class ProviderClientsControllerTests
|
||||
#region UpdateAsync
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_NoProviderOrganization_NotFound(
|
||||
Provider provider,
|
||||
Guid providerOrganizationId,
|
||||
UpdateClientOrganizationRequestBody requestBody,
|
||||
SutProvider<ProviderClientsController> sutProvider)
|
||||
{
|
||||
ConfigureStableProviderServiceUserInputs(provider, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganizationId)
|
||||
.ReturnsNull();
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody);
|
||||
|
||||
AssertNotFound(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_AssignedSeats_Ok(
|
||||
public async Task UpdateAsync_ServiceUserMakingPurchase_Unauthorized(
|
||||
Provider provider,
|
||||
Guid providerOrganizationId,
|
||||
UpdateClientOrganizationRequestBody requestBody,
|
||||
@ -118,6 +104,11 @@ public class ProviderClientsControllerTests
|
||||
Organization organization,
|
||||
SutProvider<ProviderClientsController> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsMonthly;
|
||||
organization.Seats = 10;
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
requestBody.AssignedSeats = 20;
|
||||
|
||||
ConfigureStableProviderServiceUserInputs(provider, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganizationId)
|
||||
@ -126,49 +117,57 @@ public class ProviderClientsControllerTests
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(providerOrganization.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IProviderBillingService>().SeatAdjustmentResultsInPurchase(
|
||||
provider,
|
||||
PlanType.TeamsMonthly,
|
||||
10).Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody);
|
||||
|
||||
AssertUnauthorized(result, message: "Service users cannot purchase additional seats.");
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Ok(
|
||||
Provider provider,
|
||||
Guid providerOrganizationId,
|
||||
UpdateClientOrganizationRequestBody requestBody,
|
||||
ProviderOrganization providerOrganization,
|
||||
Organization organization,
|
||||
SutProvider<ProviderClientsController> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.TeamsMonthly;
|
||||
organization.Seats = 10;
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
requestBody.AssignedSeats = 20;
|
||||
|
||||
ConfigureStableProviderServiceUserInputs(provider, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganizationId)
|
||||
.Returns(providerOrganization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(providerOrganization.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IProviderBillingService>().SeatAdjustmentResultsInPurchase(
|
||||
provider,
|
||||
PlanType.TeamsMonthly,
|
||||
10).Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody);
|
||||
|
||||
await sutProvider.GetDependency<IProviderBillingService>().Received(1)
|
||||
.AssignSeatsToClientOrganization(
|
||||
.ScaleSeats(
|
||||
provider,
|
||||
organization,
|
||||
requestBody.AssignedSeats);
|
||||
PlanType.TeamsMonthly,
|
||||
10);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Is<Organization>(org => org.Name == requestBody.Name));
|
||||
|
||||
Assert.IsType<Ok>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Name_Ok(
|
||||
Provider provider,
|
||||
Guid providerOrganizationId,
|
||||
UpdateClientOrganizationRequestBody requestBody,
|
||||
ProviderOrganization providerOrganization,
|
||||
Organization organization,
|
||||
SutProvider<ProviderClientsController> sutProvider)
|
||||
{
|
||||
ConfigureStableProviderServiceUserInputs(provider, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IProviderOrganizationRepository>().GetByIdAsync(providerOrganizationId)
|
||||
.Returns(providerOrganization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(providerOrganization.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
requestBody.AssignedSeats = organization.Seats!.Value;
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody);
|
||||
|
||||
await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs()
|
||||
.AssignSeatsToClientOrganization(
|
||||
Arg.Any<Provider>(),
|
||||
Arg.Any<Organization>(),
|
||||
Arg.Any<int>());
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Is<Organization>(org => org.Name == requestBody.Name));
|
||||
.ReplaceAsync(Arg.Is<Organization>(org => org.Seats == requestBody.AssignedSeats && org.Name == requestBody.Name));
|
||||
|
||||
Assert.IsType<Ok>(result);
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
using Bit.Api.Billing.Controllers;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
@ -25,14 +23,14 @@ public static class Utilities
|
||||
Assert.Equal("Resource not found.", response.Message);
|
||||
}
|
||||
|
||||
public static void AssertUnauthorized(IResult result)
|
||||
public static void AssertUnauthorized(IResult result, string message = "Unauthorized.")
|
||||
{
|
||||
Assert.IsType<JsonHttpResult<ErrorResponseModel>>(result);
|
||||
|
||||
var response = (JsonHttpResult<ErrorResponseModel>)result;
|
||||
|
||||
Assert.Equal(StatusCodes.Status401Unauthorized, response.StatusCode);
|
||||
Assert.Equal("Unauthorized.", response.Value.Message);
|
||||
Assert.Equal(message, response.Value.Message);
|
||||
}
|
||||
|
||||
public static void ConfigureStableProviderAdminInputs<T>(
|
||||
@ -59,9 +57,6 @@ public static class Utilities
|
||||
Provider provider,
|
||||
SutProvider<T> sutProvider) where T : BaseProviderController
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)
|
||||
.Returns(true);
|
||||
|
||||
provider.Type = ProviderType.Msp;
|
||||
provider.Status = ProviderStatusType.Billable;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Controllers;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Api.Response;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
@ -157,7 +157,7 @@ public class PoliciesControllerTests
|
||||
var result = await sutProvider.Sut.Get(orgId, type);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PolicyResponseModel>(result);
|
||||
Assert.IsType<PolicyDetailResponseModel>(result);
|
||||
Assert.Equal(policy.Id, result.Id);
|
||||
Assert.Equal(policy.Type, result.Type);
|
||||
Assert.Equal(policy.Enabled, result.Enabled);
|
||||
@ -182,7 +182,7 @@ public class PoliciesControllerTests
|
||||
var result = await sutProvider.Sut.Get(orgId, type);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PolicyResponseModel>(result);
|
||||
Assert.IsType<PolicyDetailResponseModel>(result);
|
||||
Assert.Equal(result.Type, (PolicyType)type);
|
||||
Assert.False(result.Enabled);
|
||||
}
|
||||
|
49
test/Api.Test/Tools/Controllers/ReportsControllerTests.cs
Normal file
49
test/Api.Test/Tools/Controllers/ReportsControllerTests.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Bit.Api.Tools.Controllers;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.ReportFeatures.Interfaces;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Tools.Controllers;
|
||||
|
||||
|
||||
[ControllerCustomize(typeof(ReportsController))]
|
||||
[SutProviderCustomize]
|
||||
public class ReportsControllerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_Success(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var orgId = Guid.NewGuid();
|
||||
var result = await sutProvider.Sut.GetPasswordHealthReportApplications(orgId);
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IGetPasswordHealthReportApplicationQuery>()
|
||||
.Received(1)
|
||||
.GetPasswordHealthReportApplicationAsync(Arg.Is<Guid>(_ => _ == orgId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_withoutAccess(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
var orgId = Guid.NewGuid();
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetPasswordHealthReportApplications(orgId));
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IGetPasswordHealthReportApplicationQuery>()
|
||||
.Received(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.Vault.AuthorizationHandlers.Groups;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Vault.AuthorizationHandlers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GroupAuthorizationHandlerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanReadAllAsync_WhenMemberOfOrg_Success(
|
||||
OrganizationUserType userType,
|
||||
Guid userId, SutProvider<GroupAuthorizationHandler> sutProvider,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll(organization.Id) },
|
||||
new ClaimsPrincipal(),
|
||||
null);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CanReadAllAsync_WithProviderUser_Success(
|
||||
Guid userId,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider, CurrentContextOrganization organization)
|
||||
{
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll(organization.Id) },
|
||||
new ClaimsPrincipal(),
|
||||
null);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId
|
||||
.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ProviderUserForOrgAsync(organization.Id)
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CanReadAllAsync_WhenMissingOrgAccess_NoSuccess(
|
||||
Guid userId,
|
||||
CurrentContextOrganization organization,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll(organization.Id) },
|
||||
new ClaimsPrincipal(),
|
||||
null
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_MissingUserId_Failure(
|
||||
Guid organizationId,
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll(organizationId) },
|
||||
new ClaimsPrincipal(),
|
||||
null
|
||||
);
|
||||
|
||||
// Simulate missing user id
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns((Guid?)null);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.False(context.HasSucceeded);
|
||||
Assert.True(context.HasFailed);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_NoSpecifiedOrgId_Failure(
|
||||
SutProvider<GroupAuthorizationHandler> sutProvider)
|
||||
{
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { GroupOperations.ReadAll(default) },
|
||||
new ClaimsPrincipal(),
|
||||
null
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(new Guid());
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.False(context.HasSucceeded);
|
||||
Assert.True(context.HasFailed);
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Stripe;
|
||||
@ -89,54 +88,6 @@ public class ProviderEventServiceTests
|
||||
await _providerOrganizationRepository.DidNotReceiveWithAnyArgs().GetManyDetailsByProviderAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryRecordInvoiceLineItems_InvoiceCreated_MisconfiguredProviderPlans_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.InvoiceCreated);
|
||||
|
||||
const string subscriptionId = "sub_1";
|
||||
var providerId = Guid.NewGuid();
|
||||
|
||||
var invoice = new Invoice
|
||||
{
|
||||
SubscriptionId = subscriptionId
|
||||
};
|
||||
|
||||
_stripeEventService.GetInvoice(stripeEvent).Returns(invoice);
|
||||
|
||||
var subscription = new Subscription
|
||||
{
|
||||
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } }
|
||||
};
|
||||
|
||||
_stripeFacade.GetSubscription(subscriptionId).Returns(subscription);
|
||||
|
||||
var providerPlans = new List<ProviderPlan>
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProviderId = providerId,
|
||||
PlanType = PlanType.TeamsMonthly,
|
||||
AllocatedSeats = 0,
|
||||
PurchasedSeats = 0,
|
||||
SeatMinimum = 100
|
||||
}
|
||||
};
|
||||
|
||||
_providerPlanRepository.GetByProviderId(providerId).Returns(providerPlans);
|
||||
|
||||
// Act
|
||||
var function = async () => await _providerEventService.TryRecordInvoiceLineItems(stripeEvent);
|
||||
|
||||
// Assert
|
||||
await function
|
||||
.Should()
|
||||
.ThrowAsync<Exception>()
|
||||
.WithMessage("Cannot record invoice line items for Provider with missing or misconfigured provider plans");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryRecordInvoiceLineItems_InvoiceCreated_Succeeds()
|
||||
{
|
||||
|
@ -13,8 +13,8 @@ namespace Bit.Test.Common.AutoFixture.Attributes;
|
||||
public abstract class BitCustomizeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// /// Gets a customization for the method's parameters.
|
||||
/// Gets a customization for the method's parameters.
|
||||
/// </summary>
|
||||
/// <returns>A customization for the method's paramters.</returns>
|
||||
/// <returns>A customization for the method's parameters.</returns>
|
||||
public abstract ICustomization GetCustomization();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
|
@ -0,0 +1,75 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class FreeFamiliesForEnterprisePolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_DoesNotNotifyUserWhenPolicyDisabled(
|
||||
Organization organization,
|
||||
List<OrganizationSponsorship> organizationSponsorships,
|
||||
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy,
|
||||
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||
{
|
||||
|
||||
policy.Enabled = true;
|
||||
policyUpdate.Enabled = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organizationSponsorships);
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(organizationSponsorships[0].FriendlyName, organizationSponsorships[0].ValidUntil.ToString(),
|
||||
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.DisplayName());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_DoesNotifyUserWhenPolicyDisabled(
|
||||
Organization organization,
|
||||
List<OrganizationSponsorship> organizationSponsorships,
|
||||
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy,
|
||||
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||
{
|
||||
|
||||
policy.Enabled = false;
|
||||
policyUpdate.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organizationSponsorships);
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
// Assert
|
||||
var offerAcceptanceDate = organizationSponsorships[0].ValidUntil!.Value.AddDays(-7).ToString("MM/dd/yyyy");
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(organizationSponsorships[0].FriendlyName, offerAcceptanceDate,
|
||||
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.Name);
|
||||
|
||||
}
|
||||
}
|
@ -420,8 +420,6 @@ public class OrganizationServiceTests
|
||||
OrganizationSignup signup,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling).Returns(true);
|
||||
|
||||
signup.Plan = PlanType.TeamsMonthly;
|
||||
|
||||
var (organization, _, _) = await sutProvider.Sut.SignupClientAsync(signup);
|
||||
|
@ -842,6 +842,6 @@ public class PolicyServiceTests
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, null));
|
||||
|
||||
Assert.Equal("Organization has verified domains.", badRequestException.Message);
|
||||
Assert.Equal("The Single organization policy is required for organizations that have enabled domain verification.", badRequestException.Message);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
@ -19,7 +19,6 @@ public abstract class BaseTokenProviderTests<T>
|
||||
{
|
||||
public abstract TwoFactorProviderType TwoFactorProviderType { get; }
|
||||
|
||||
#region Helpers
|
||||
protected static IEnumerable<object[]> SetupCanGenerateData(params (Dictionary<string, object> MetaData, bool ExpectedResponse)[] data)
|
||||
{
|
||||
return data.Select(d =>
|
||||
@ -48,6 +47,9 @@ public abstract class BaseTokenProviderTests<T>
|
||||
userService
|
||||
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
|
||||
.Returns(true);
|
||||
userService
|
||||
.CanAccessPremium(user)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
protected static UserManager<User> SubstituteUserManager()
|
||||
@ -76,7 +78,6 @@ public abstract class BaseTokenProviderTests<T>
|
||||
|
||||
user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<T> sutProvider)
|
||||
|
@ -0,0 +1,262 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Duo = DuoUniversal;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
public class DuoUniversalTwoFactorTokenProviderTests : BaseTokenProviderTests<DuoUniversalTokenProvider>
|
||||
{
|
||||
private readonly IDuoUniversalTokenService _duoUniversalTokenService = Substitute.For<IDuoUniversalTokenService>();
|
||||
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Duo;
|
||||
|
||||
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
( // correct data
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duosecurity.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // correct data duo federal
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // correct data duo federal
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // invalid host
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "",
|
||||
},
|
||||
false
|
||||
),
|
||||
( // clientId missing
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
public static IEnumerable<object[]> NonPremiumCanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
( // correct data
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duosecurity.com",
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
|
||||
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = true;
|
||||
user.PremiumExpirationDate = DateTime.UtcNow.AddDays(1);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(expectedResponse);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(NonPremiumCanGenerateTwoFactorTokenAsyncData))]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_UserCanNotAccessPremium_ReturnsNull(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(expectedResponse);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_Success_ReturnsAuthUrl(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string authUrl)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperDuoUniversalTokenService(user, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.GenerateAuthUrl(
|
||||
Arg.Any<Duo.Client>(),
|
||||
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
|
||||
user)
|
||||
.Returns(authUrl);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(token);
|
||||
Assert.Equal(token, authUrl);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_DuoClientNull_ReturnsNull(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_UserCanNotAccessPremium_ReturnsNull(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateToken_ValidToken_ReturnsTrue(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperDuoUniversalTokenService(user, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(
|
||||
Arg.Any<Duo.Client>(),
|
||||
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
|
||||
user,
|
||||
token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var response = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.True(response);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateToken_DuoClientNull_ReturnsFalse(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
|
||||
{
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the IDuoUniversalTokenService is properly setup for the test.
|
||||
/// This ensures that the private GetDuoClientAsync, and GetDuoTwoFactorProvider
|
||||
/// methods will return true enabling the test to execute on the correct path.
|
||||
/// </summary>
|
||||
/// <param name="user">user from calling test</param>
|
||||
/// <param name="sutProvider">self</param>
|
||||
private void SetUpProperDuoUniversalTokenService(User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
var client = BuildDuoClient();
|
||||
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(client);
|
||||
}
|
||||
|
||||
private Duo.Client BuildDuoClient()
|
||||
{
|
||||
var clientId = new string('c', 20);
|
||||
var clientSecret = new string('s', 40);
|
||||
return new Duo.ClientBuilder(clientId, clientSecret, "api-abcd1234.duosecurity.com", "redirectUrl").Build();
|
||||
}
|
||||
|
||||
private string GetTwoFactorDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
@ -0,0 +1,289 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Duo = DuoUniversal;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationDuoUniversalTwoFactorTokenProviderTests
|
||||
{
|
||||
private readonly IDuoUniversalTokenService _duoUniversalTokenService = Substitute.For<IDuoUniversalTokenService>();
|
||||
private readonly IDataProtectorTokenFactory<DuoUserStateTokenable> _tokenDataFactory = Substitute.For<IDataProtectorTokenFactory<DuoUserStateTokenable>>();
|
||||
|
||||
// Happy path
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_ReturnsTrue(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Enabled = true;
|
||||
organization.Use2fa = true;
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_DuoTwoFactorNotEnabled_ReturnsFalse(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderNotEnabledJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_BadMetaData_ProviderNull_ReturnsFalse(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(false);
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationNotEnabled_ReturnsNull(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
organization.Enabled = false;
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationUse2FAFalse_ReturnsNull(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
organization.Use2fa = false;
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoClient_ProviderNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(null, default);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoClient_DuoClientNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, default);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateAsync_ReturnsAuthUrl(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string AuthUrl)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(BuildDuoClient(), organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.GenerateAuthUrl(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user)
|
||||
.Returns(AuthUrl);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(AuthUrl, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateAsync_ClientNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_TokenValid_ReturnsTrue(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(BuildDuoClient(), organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user, token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(token, organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_ClientNull_ReturnsFalse(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user, token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(token, organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the IDuoUniversalTokenService is properly setup for the test.
|
||||
/// This ensures that the private GetDuoClientAsync, and GetDuoTwoFactorProvider
|
||||
/// methods will return true enabling the test to execute on the correct path.
|
||||
///
|
||||
/// BitAutoData cannot create the Duo.Client since it does not have a public constructor
|
||||
/// so we have to use the ClientBUilder(), the client is not used meaningfully in the tests.
|
||||
/// </summary>
|
||||
/// <param name="user">user from calling test</param>
|
||||
/// <param name="sutProvider">self</param>
|
||||
private void SetUpProperOrganizationDuoUniversalTokenService(
|
||||
Duo.Client client, Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Enabled = true;
|
||||
organization.Use2fa = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(client);
|
||||
}
|
||||
|
||||
private Duo.Client BuildDuoClient()
|
||||
{
|
||||
var clientId = new string('c', 20);
|
||||
var clientSecret = new string('s', 40);
|
||||
return new Duo.ClientBuilder(clientId, clientSecret, "api-abcd1234.duosecurity.com", "redirectUrl").Build();
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoProviderJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoProviderNotEnabledJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":false,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DuoUniversalTokenServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData("", "ClientId", "ClientSecret")]
|
||||
[BitAutoData("api-valid.duosecurity.com", "", "ClientSecret")]
|
||||
[BitAutoData("api-valid.duosecurity.com", "ClientId", "")]
|
||||
public async void ValidateDuoConfiguration_InvalidConfig_ReturnsFalse(
|
||||
string host, string clientId, string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
/* AutoData handles arrangement */
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateDuoConfiguration(clientSecret, clientId, host);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, "api-valid.duosecurity.com")]
|
||||
[BitAutoData(false, "invalid")]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", null, "clientSecret")]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", "ClientId", null)]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", null, null)]
|
||||
public void HasProperDuoMetadata_ReturnMatchesExpected(
|
||||
bool expectedResponse, string host, string clientId,
|
||||
string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var metaData = new Dictionary<string, object> { ["Host"] = host };
|
||||
|
||||
if (clientId != null)
|
||||
{
|
||||
metaData.Add("ClientId", clientId);
|
||||
}
|
||||
|
||||
if (clientSecret != null)
|
||||
{
|
||||
metaData.Add("ClientSecret", clientSecret);
|
||||
}
|
||||
|
||||
var provider = new TwoFactorProvider
|
||||
{
|
||||
MetaData = metaData
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = sutProvider.Sut.HasProperDuoMetadata(provider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, expectedResponse);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void HasProperDuoMetadata_ProviderIsNull_ReturnsFalse(
|
||||
SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = sutProvider.Sut.HasProperDuoMetadata(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("api-valid.duosecurity.com", true)]
|
||||
[BitAutoData("api-valid.duofederal.com", true)]
|
||||
[BitAutoData("invalid", false)]
|
||||
public void ValidDuoHost_HostIsValid_ReturnTrue(
|
||||
string host, bool expectedResponse)
|
||||
{
|
||||
// Act
|
||||
var result = DuoUniversalTokenService.ValidDuoHost(host);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, expectedResponse);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ public class LaunchDarklyFeatureServiceTests
|
||||
var currentContext = Substitute.For<ICurrentContext>();
|
||||
currentContext.UserId.Returns(Guid.NewGuid());
|
||||
currentContext.ClientVersion.Returns(new Version(AssemblyHelpers.GetVersion()));
|
||||
currentContext.ClientVersionIsPrerelease.Returns(true);
|
||||
currentContext.DeviceType.Returns(Enums.DeviceType.ChromeBrowser);
|
||||
|
||||
var client = Substitute.For<ILdClient>();
|
||||
|
@ -0,0 +1,149 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.Requests;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.ReportFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class AddPasswordHealthReportApplicationCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithValidRequest_ShouldReturnPasswordHealthReportApplication(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<AddPasswordHealthReportApplicationRequest>();
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.CreateAsync(Arg.Any<PasswordHealthReportApplication>())
|
||||
.Returns(c => c.Arg<PasswordHealthReportApplication>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithInvalidOrganizationId_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<AddPasswordHealthReportApplicationRequest>();
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(null as Organization);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("Invalid Organization", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithInvalidUrl_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.Without(_ => _.Url)
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("URL is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithInvalidOrganizationId_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.Without(_ => _.OrganizationId)
|
||||
.CreateMany(2).ToList();
|
||||
|
||||
request[0].OrganizationId = Guid.NewGuid();
|
||||
request[1].OrganizationId = Guid.Empty;
|
||||
|
||||
// only return an organization for the first request and null for the second
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Is<Guid>(x => x == request[0].OrganizationId))
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("Invalid Organization", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithInvalidUrl_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.CreateMany(2).ToList();
|
||||
|
||||
request[1].Url = string.Empty;
|
||||
|
||||
// return an organization for both requests
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("URL is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithValidRequest_ShouldBeSuccessful(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.CreateMany<AddPasswordHealthReportApplicationRequest>(2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.CreateAsync(Arg.Any<PasswordHealthReportApplication>())
|
||||
.Returns(c => c.Arg<PasswordHealthReportApplication>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Count() == 2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().Received(2);
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>().Received(2);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.ReportFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetPasswordHealthReportApplicationQueryTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_WithValidOrganizationId_ShouldReturnPasswordHealthReportApplication(
|
||||
SutProvider<GetPasswordHealthReportApplicationQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var organizationId = fixture.Create<Guid>();
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.CreateMany<PasswordHealthReportApplication>(2).ToList());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPasswordHealthReportApplicationAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_WithInvalidOrganizationId_ShouldFail(
|
||||
SutProvider<GetPasswordHealthReportApplicationQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Is<Guid>(x => x == Guid.Empty))
|
||||
.Returns(new List<PasswordHealthReportApplication>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetPasswordHealthReportApplicationAsync(Guid.Empty));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("OrganizationId is required.", exception.Message);
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace Bit.Identity.IntegrationTest.Endpoints;
|
||||
|
||||
public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFactory>
|
||||
{
|
||||
const string _organizationTwoFactor = """{"6":{"Enabled":true,"MetaData":{"IKey":"DIEFB13LB49IEB3459N2","SKey":"0ZnsZHav0KcNPBZTS6EOUwqLPoB0sfMd5aJeWExQ","Host":"api-example.duosecurity.com"}}}""";
|
||||
const string _organizationTwoFactor = """{"6":{"Enabled":true,"MetaData":{"ClientId":"DIEFB13LB49IEB3459N2","ClientSecret":"0ZnsZHav0KcNPBZTS6EOUwqLPoB0sfMd5aJeWExQ","Host":"api-example.duosecurity.com"}}}""";
|
||||
const string _testEmail = "test+2farequired@email.com";
|
||||
const string _testPassword = "master_password_hash";
|
||||
const string _userEmailTwoFactor = """{"1": { "Enabled": true, "MetaData": { "Email": "test+2farequired@email.com"}}}""";
|
||||
@ -140,7 +140,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
{ "password", _testPassword },
|
||||
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||
var root = responseBody.RootElement;
|
||||
var error = AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String).GetString();
|
||||
@ -168,7 +168,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypeClientCredential_OrgTwoFactorRequired_Success(Organization organization, OrganizationApiKey organizationApiKey)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
organization.Enabled = true;
|
||||
organization.UseApi = true;
|
||||
organization.Use2fa = true;
|
||||
@ -258,7 +258,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
|
||||
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||
var root = responseBody.RootElement;
|
||||
var error = AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String).GetString();
|
||||
@ -320,7 +320,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
|
||||
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(twoFactorProvidedContext);
|
||||
var root = body.RootElement;
|
||||
|
||||
@ -338,6 +338,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
{
|
||||
MemberDecryptionType = MemberDecryptionType.MasterPassword,
|
||||
};
|
||||
|
||||
await CreateSsoOrganizationAndUserAsync(
|
||||
localFactory, ssoConfigData, challenge, _testEmail, orgTwoFactor: _organizationTwoFactor);
|
||||
|
||||
@ -355,7 +356,7 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
{ "redirect_uri", "https://localhost:8080/sso-connector.html" }
|
||||
}), context => context.Request.Headers.Append("Auth-Email", CoreHelpers.Base64UrlEncodeString(_testEmail)));
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
using var responseBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||
var root = responseBody.RootElement;
|
||||
var error = AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String).GetString();
|
||||
|
@ -1,8 +1,6 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -28,8 +26,7 @@ public class TwoFactorAuthenticationValidatorTests
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly UserManagerTestWrapper<User> _userManager;
|
||||
private readonly IOrganizationDuoWebTokenProvider _organizationDuoWebTokenProvider;
|
||||
private readonly ITemporaryDuoWebV4SDKService _temporaryDuoWebV4SDKService;
|
||||
private readonly IOrganizationDuoUniversalTokenProvider _organizationDuoUniversalTokenProvider;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
@ -42,8 +39,7 @@ public class TwoFactorAuthenticationValidatorTests
|
||||
{
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_userManager = SubstituteUserManager();
|
||||
_organizationDuoWebTokenProvider = Substitute.For<IOrganizationDuoWebTokenProvider>();
|
||||
_temporaryDuoWebV4SDKService = Substitute.For<ITemporaryDuoWebV4SDKService>();
|
||||
_organizationDuoUniversalTokenProvider = Substitute.For<IOrganizationDuoUniversalTokenProvider>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_applicationCacheService = Substitute.For<IApplicationCacheService>();
|
||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||
@ -54,8 +50,7 @@ public class TwoFactorAuthenticationValidatorTests
|
||||
_sut = new TwoFactorAuthenticationValidator(
|
||||
_userService,
|
||||
_userManager,
|
||||
_organizationDuoWebTokenProvider,
|
||||
_temporaryDuoWebV4SDKService,
|
||||
_organizationDuoUniversalTokenProvider,
|
||||
_featureService,
|
||||
_applicationCacheService,
|
||||
_organizationUserRepository,
|
||||
@ -439,7 +434,7 @@ public class TwoFactorAuthenticationValidatorTests
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
_organizationDuoWebTokenProvider.ValidateAsync(
|
||||
_organizationDuoUniversalTokenProvider.ValidateAsync(
|
||||
token, organization, user).Returns(true);
|
||||
|
||||
_userManager.TWO_FACTOR_ENABLED = true;
|
||||
@ -457,70 +452,6 @@ public class TwoFactorAuthenticationValidatorTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(TwoFactorProviderType.Duo)]
|
||||
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
|
||||
public async void VerifyTwoFactorAsync_TemporaryDuoService_ValidToken_ReturnsTrue(
|
||||
TwoFactorProviderType providerType,
|
||||
User user,
|
||||
Organization organization,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
_featureService.IsEnabled(FeatureFlagKeys.DuoRedirect).Returns(true);
|
||||
_userService.TwoFactorProviderIsEnabledAsync(providerType, user).Returns(true);
|
||||
_temporaryDuoWebV4SDKService.ValidateAsync(
|
||||
token, Arg.Any<TwoFactorProvider>(), user).Returns(true);
|
||||
|
||||
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
|
||||
organization.Use2fa = true;
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Enabled = true;
|
||||
|
||||
_userManager.TWO_FACTOR_ENABLED = true;
|
||||
_userManager.TWO_FACTOR_TOKEN = token;
|
||||
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
|
||||
|
||||
// Act
|
||||
var result = await _sut.VerifyTwoFactor(
|
||||
user, organization, providerType, token);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(TwoFactorProviderType.Duo)]
|
||||
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
|
||||
public async void VerifyTwoFactorAsync_TemporaryDuoService_InvalidToken_ReturnsFalse(
|
||||
TwoFactorProviderType providerType,
|
||||
User user,
|
||||
Organization organization,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
_featureService.IsEnabled(FeatureFlagKeys.DuoRedirect).Returns(true);
|
||||
_userService.TwoFactorProviderIsEnabledAsync(providerType, user).Returns(true);
|
||||
_temporaryDuoWebV4SDKService.ValidateAsync(
|
||||
token, Arg.Any<TwoFactorProvider>(), user).Returns(true);
|
||||
|
||||
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
|
||||
organization.Use2fa = true;
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Enabled = true;
|
||||
|
||||
_userManager.TWO_FACTOR_ENABLED = true;
|
||||
_userManager.TWO_FACTOR_TOKEN = token;
|
||||
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
|
||||
|
||||
// Act
|
||||
var result = await _sut.VerifyTwoFactor(
|
||||
user, organization, providerType, token);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
private static UserManagerTestWrapper<User> SubstituteUserManager()
|
||||
{
|
||||
return new UserManagerTestWrapper<User>(
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -9,6 +9,7 @@ using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider;
|
||||
using Bit.Infrastructure.EntityFramework.Auth.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -89,6 +90,7 @@ public class EfRepositoryListBuilder<T> : ISpecimenBuilder where T : BaseEntityF
|
||||
cfg.AddProfile<TaxRateMapperProfile>();
|
||||
cfg.AddProfile<TransactionMapperProfile>();
|
||||
cfg.AddProfile<UserMapperProfile>();
|
||||
cfg.AddProfile<PasswordHealthReportApplicationProfile>();
|
||||
})
|
||||
.CreateMapper()));
|
||||
|
||||
|
@ -0,0 +1,82 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||
|
||||
internal class PasswordHealthReportApplicationBuilder : ISpecimenBuilder
|
||||
{
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var type = request as Type;
|
||||
if (type == null || type != typeof(PasswordHealthReportApplication))
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
|
||||
var fixture = new Fixture();
|
||||
var obj = fixture.WithAutoNSubstitutions().Create<PasswordHealthReportApplication>();
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfPasswordHealthReportApplication : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||
fixture.Customizations.Add(new PasswordHealthReportApplicationBuilder());
|
||||
fixture.Customizations.Add(new OrganizationBuilder());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<PasswordHealthReportApplicationRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfPasswordHealthReportApplicationApplicableToUser : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||
fixture.Customizations.Add(new PasswordHealthReportApplicationBuilder());
|
||||
fixture.Customizations.Add(new OrganizationBuilder());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<PasswordHealthReportApplicationRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderOrganizationRepository>());
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfPasswordHealthReportApplicationAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public EfPasswordHealthReportApplicationAutoDataAttribute() : base(new SutProviderCustomization(), new EfPasswordHealthReportApplication())
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class EfPasswordHealthReportApplicationApplicableToUserInlineAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public EfPasswordHealthReportApplicationApplicableToUserInlineAutoDataAttribute(params object[] values) :
|
||||
base(new[] { typeof(SutProviderCustomization), typeof(EfPasswordHealthReportApplicationApplicableToUser) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineEfPasswordHealthReportApplicationAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineEfPasswordHealthReportApplicationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(EfPolicy) }, values)
|
||||
{ }
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||
using Xunit;
|
||||
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using EfToolsRepo = Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||
using SqlAdminConsoleRepo = Bit.Infrastructure.Dapper.Tools.Repositories;
|
||||
using SqlRepo = Bit.Infrastructure.Dapper.Repositories;
|
||||
|
||||
namespace Bit.Infrastructure.EFIntegration.Test.Tools.Repositories;
|
||||
|
||||
public class PasswordHealthReportApplicationRepositoryTests
|
||||
{
|
||||
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||
public async Task CreateAsync_Works_DataMatches(
|
||||
PasswordHealthReportApplication passwordHealthReportApplication,
|
||||
Organization organization,
|
||||
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo
|
||||
)
|
||||
{
|
||||
var passwordHealthReportApplicationRecords = new List<PasswordHealthReportApplication>();
|
||||
foreach (var sut in suts)
|
||||
{
|
||||
var i = suts.IndexOf(sut);
|
||||
|
||||
var efOrganization = await efOrganizationRepos[i].CreateAsync(organization);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
passwordHealthReportApplication.OrganizationId = efOrganization.Id;
|
||||
var postEfPasswordHeathReportApp = await sut.CreateAsync(passwordHealthReportApplication);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
var savedPasswordHealthReportApplication = await sut.GetByIdAsync(postEfPasswordHeathReportApp.Id);
|
||||
passwordHealthReportApplicationRecords.Add(savedPasswordHealthReportApplication);
|
||||
}
|
||||
|
||||
var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization);
|
||||
|
||||
passwordHealthReportApplication.OrganizationId = sqlOrganization.Id;
|
||||
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.CreateAsync(passwordHealthReportApplication);
|
||||
var savedSqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||
passwordHealthReportApplicationRecords.Add(savedSqlPasswordHealthReportApplicationRecord);
|
||||
|
||||
Assert.True(passwordHealthReportApplicationRecords.Count == 4);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||
public async Task RetrieveByOrganisation_Works(
|
||||
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
var (firstOrg, firstRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
var (secondOrg, secondRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
|
||||
var firstSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(firstOrg.Id);
|
||||
var nextSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(secondOrg.Id);
|
||||
|
||||
Assert.True(firstSetOfRecords.Count == 1 && firstSetOfRecords.First().OrganizationId == firstOrg.Id);
|
||||
Assert.True(nextSetOfRecords.Count == 1 && nextSetOfRecords.First().OrganizationId == secondOrg.Id);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||
public async Task ReplaceQuery_Works(
|
||||
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
var (org, pwdRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
var exampleUri = "http://www.example.com";
|
||||
var exampleRevisionDate = new DateTime(2021, 1, 1);
|
||||
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||
|
||||
foreach (var sut in suts)
|
||||
{
|
||||
var i = suts.IndexOf(sut);
|
||||
|
||||
// create a new organization for each repository
|
||||
var organization = await efOrganizationRepos[i].CreateAsync(org);
|
||||
|
||||
// map the organization Id and create the PasswordHealthReportApp record
|
||||
pwdRecord.OrganizationId = organization.Id;
|
||||
var passwordHealthReportApplication = await sut.CreateAsync(pwdRecord);
|
||||
|
||||
// update the record with new values
|
||||
passwordHealthReportApplication.Uri = exampleUri;
|
||||
passwordHealthReportApplication.RevisionDate = exampleRevisionDate;
|
||||
|
||||
// apply update to the database
|
||||
await sut.ReplaceAsync(passwordHealthReportApplication);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
// retrieve the data and add to the list for assertions
|
||||
var recordFromDb = await sut.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
dbRecords.Add(recordFromDb);
|
||||
}
|
||||
|
||||
// sql - create a new organization and PasswordHealthReportApplication record
|
||||
var (sqlOrg, sqlPwdRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPwdRecord.Id);
|
||||
|
||||
// sql - update the record with new values
|
||||
sqlPasswordHealthReportApplicationRecord.Uri = exampleUri;
|
||||
sqlPasswordHealthReportApplicationRecord.RevisionDate = exampleRevisionDate;
|
||||
await sqlPasswordHealthReportApplicationRepo.ReplaceAsync(sqlPasswordHealthReportApplicationRecord);
|
||||
|
||||
// sql - retrieve the data and add to the list for assertions
|
||||
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||
dbRecords.Add(sqlDbRecord);
|
||||
|
||||
// assertions
|
||||
// the Guids must be distinct across all records
|
||||
Assert.True(dbRecords.Select(_ => _.Id).Distinct().Count() == dbRecords.Count);
|
||||
|
||||
// the Uri and RevisionDate must match the updated values
|
||||
Assert.True(dbRecords.All(_ => _.Uri == exampleUri && _.RevisionDate == exampleRevisionDate));
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||
public async Task Upsert_Works(
|
||||
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var rawOrg = fixture.Build<Organization>().Create();
|
||||
var rawPwdRecord = fixture.Build<PasswordHealthReportApplication>()
|
||||
.With(_ => _.OrganizationId, rawOrg.Id)
|
||||
.Without(_ => _.Id)
|
||||
.Create();
|
||||
var exampleUri = "http://www.example.com";
|
||||
var exampleRevisionDate = new DateTime(2021, 1, 1);
|
||||
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||
|
||||
foreach (var sut in suts)
|
||||
{
|
||||
var i = suts.IndexOf(sut);
|
||||
|
||||
// create a new organization for each repository
|
||||
var organization = await efOrganizationRepos[i].CreateAsync(rawOrg);
|
||||
|
||||
// map the organization Id and use Upsert to save new record
|
||||
rawPwdRecord.OrganizationId = organization.Id;
|
||||
rawPwdRecord.Id = default(Guid);
|
||||
await sut.UpsertAsync(rawPwdRecord);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
// retrieve the data and add to the list for assertions
|
||||
var passwordHealthReportApplication = await sut.GetByIdAsync(rawPwdRecord.Id);
|
||||
|
||||
// update the record with new values
|
||||
passwordHealthReportApplication.Uri = exampleUri;
|
||||
passwordHealthReportApplication.RevisionDate = exampleRevisionDate;
|
||||
|
||||
// apply update using Upsert to make changes to db
|
||||
await sut.UpsertAsync(passwordHealthReportApplication);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
// retrieve the data and add to the list for assertions
|
||||
var recordFromDb = await sut.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||
dbRecords.Add(recordFromDb);
|
||||
|
||||
sut.ClearChangeTracking();
|
||||
}
|
||||
|
||||
// sql - create new records
|
||||
var organizationForSql = fixture.Create<Organization>();
|
||||
var passwordHealthReportApplicationForSql = fixture.Build<PasswordHealthReportApplication>()
|
||||
.With(_ => _.OrganizationId, organizationForSql.Id)
|
||||
.Without(_ => _.Id)
|
||||
.Create();
|
||||
|
||||
// sql - use Upsert to insert this data
|
||||
var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organizationForSql);
|
||||
await sqlPasswordHealthReportApplicationRepo.UpsertAsync(passwordHealthReportApplicationForSql);
|
||||
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(passwordHealthReportApplicationForSql.Id);
|
||||
|
||||
// sql - update the record with new values
|
||||
sqlPasswordHealthReportApplicationRecord.Uri = exampleUri;
|
||||
sqlPasswordHealthReportApplicationRecord.RevisionDate = exampleRevisionDate;
|
||||
await sqlPasswordHealthReportApplicationRepo.UpsertAsync(sqlPasswordHealthReportApplicationRecord);
|
||||
|
||||
// sql - retrieve the data and add to the list for assertions
|
||||
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||
dbRecords.Add(sqlDbRecord);
|
||||
|
||||
// assertions
|
||||
// the Guids must be distinct across all records
|
||||
Assert.True(dbRecords.Select(_ => _.Id).Distinct().Count() == dbRecords.Count);
|
||||
|
||||
// the Uri and RevisionDate must match the updated values
|
||||
Assert.True(dbRecords.All(_ => _.Uri == exampleUri && _.RevisionDate == exampleRevisionDate));
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||
public async Task Delete_Works(
|
||||
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var rawOrg = fixture.Build<Organization>().Create();
|
||||
var rawPwdRecord = fixture.Build<PasswordHealthReportApplication>()
|
||||
.With(_ => _.OrganizationId, rawOrg.Id)
|
||||
.Create();
|
||||
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||
|
||||
foreach (var sut in suts)
|
||||
{
|
||||
var i = suts.IndexOf(sut);
|
||||
|
||||
// create a new organization for each repository
|
||||
var organization = await efOrganizationRepos[i].CreateAsync(rawOrg);
|
||||
|
||||
// map the organization Id and use Upsert to save new record
|
||||
rawPwdRecord.OrganizationId = organization.Id;
|
||||
rawPwdRecord = await sut.CreateAsync(rawPwdRecord);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
// apply update using Upsert to make changes to db
|
||||
await sut.DeleteAsync(rawPwdRecord);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
// retrieve the data and add to the list for assertions
|
||||
var recordFromDb = await sut.GetByIdAsync(rawPwdRecord.Id);
|
||||
dbRecords.Add(recordFromDb);
|
||||
|
||||
sut.ClearChangeTracking();
|
||||
}
|
||||
|
||||
// sql - create new records
|
||||
var (org, passwordHealthReportApplication) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||
await sqlPasswordHealthReportApplicationRepo.DeleteAsync(passwordHealthReportApplication);
|
||||
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||
dbRecords.Add(sqlDbRecord);
|
||||
|
||||
// assertions
|
||||
// all records should be null - as they were deleted before querying
|
||||
Assert.True(dbRecords.Where(_ => _ == null).Count() == 4);
|
||||
}
|
||||
|
||||
private async Task<(Organization, PasswordHealthReportApplication)> CreateSampleRecord(
|
||||
IOrganizationRepository organizationRepo,
|
||||
IPasswordHealthReportApplicationRepository passwordHealthReportApplicationRepo
|
||||
)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var organization = fixture.Create<Organization>();
|
||||
var passwordHealthReportApplication = fixture.Build<PasswordHealthReportApplication>()
|
||||
.With(_ => _.OrganizationId, organization.Id)
|
||||
.Create();
|
||||
|
||||
organization = await organizationRepo.CreateAsync(organization);
|
||||
passwordHealthReportApplication = await passwordHealthReportApplicationRepo.CreateAsync(passwordHealthReportApplication);
|
||||
|
||||
return (organization, passwordHealthReportApplication);
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Vault.Repositories;
|
||||
|
||||
public class SecurityTaskRepositoryTests
|
||||
{
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task CreateAsync(
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ISecurityTaskRepository securityTaskRepository)
|
||||
{
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Test Plan",
|
||||
BillingEmail = "billing@email.com"
|
||||
});
|
||||
|
||||
var cipher = await cipherRepository.CreateAsync(new Cipher
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = "",
|
||||
});
|
||||
|
||||
var task = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
Assert.NotNull(task);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task ReadByIdAsync(
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ISecurityTaskRepository securityTaskRepository)
|
||||
{
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Test Plan",
|
||||
BillingEmail = "billing@email.com"
|
||||
});
|
||||
|
||||
var cipher = await cipherRepository.CreateAsync(new Cipher
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = "",
|
||||
});
|
||||
|
||||
var task = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
Assert.NotNull(task);
|
||||
|
||||
var readTask = await securityTaskRepository.GetByIdAsync(task.Id);
|
||||
|
||||
Assert.NotNull(readTask);
|
||||
Assert.Equal(task.Id, readTask.Id);
|
||||
Assert.Equal(task.Status, readTask.Status);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task UpdateAsync(
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ISecurityTaskRepository securityTaskRepository)
|
||||
{
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Test Plan",
|
||||
BillingEmail = "billing@email.com"
|
||||
});
|
||||
|
||||
var cipher = await cipherRepository.CreateAsync(new Cipher
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = "",
|
||||
});
|
||||
|
||||
var task = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
Assert.NotNull(task);
|
||||
|
||||
task.Status = SecurityTaskStatus.Completed;
|
||||
await securityTaskRepository.ReplaceAsync(task);
|
||||
|
||||
var updatedTask = await securityTaskRepository.GetByIdAsync(task.Id);
|
||||
|
||||
Assert.NotNull(updatedTask);
|
||||
Assert.Equal(task.Id, updatedTask.Id);
|
||||
Assert.Equal(SecurityTaskStatus.Completed, updatedTask.Status);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user