1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-03 10:42:21 -05:00

Added integration test around enabling feature and sending invite via scim. Did a bit of refactoring on the SM validation. Fixed couple bugs found.

This commit is contained in:
jrmccannon 2025-02-24 11:10:48 -06:00
parent bd5189491e
commit 001a5dea86
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
7 changed files with 96 additions and 36 deletions

View File

@ -1,9 +1,12 @@
using System.Text.Json; using System.Text.Json;
using Bit.Core;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Scim.IntegrationTest.Factories; using Bit.Scim.IntegrationTest.Factories;
using Bit.Scim.Models; using Bit.Scim.Models;
using Bit.Scim.Utilities; using Bit.Scim.Utilities;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit; using Xunit;
namespace Bit.Scim.IntegrationTest.Controllers.v2; namespace Bit.Scim.IntegrationTest.Controllers.v2;
@ -276,9 +279,15 @@ public class UsersControllerTests : IClassFixture<ScimApplicationFactory>, IAsyn
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel); AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
} }
[Fact] [Theory]
public async Task Post_Success() [InlineData(true)]
[InlineData(false)]
public async Task Post_Success(bool isScimInviteUserOptimizationEnabled)
{ {
_factory.SubstituteService((IFeatureService featureService)
=> featureService.IsEnabled(FeatureFlagKeys.ScimInviteUserOptimization)
.Returns(isScimInviteUserOptimizationEnabled));
var email = "user5@example.com"; var email = "user5@example.com";
var displayName = "Test User 5"; var displayName = "Test User 5";
var externalId = "UE"; var externalId = "UE";

View File

@ -96,7 +96,8 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
var valid = validationResult as Valid<InviteUserOrganizationValidationRequest>; var valid = validationResult as Valid<InviteUserOrganizationValidationRequest>;
var organizationUserCollection = invitesToSend var organizationUserCollection = invitesToSend
.Select(MapToDataModel(request.PerformedAt)); .Select(MapToDataModel(request.PerformedAt))
.ToArray();
var organization = await organizationRepository.GetByIdAsync(valid.Value.Organization.OrganizationId); var organization = await organizationRepository.GetByIdAsync(valid.Value.Organization.OrganizationId);
try try

View File

@ -5,6 +5,20 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse
public class InviteUserOrganizationValidationRequest public class InviteUserOrganizationValidationRequest
{ {
public InviteUserOrganizationValidationRequest() { }
public InviteUserOrganizationValidationRequest(InviteUserOrganizationValidationRequest request, PasswordManagerSubscriptionUpdate subscriptionUpdate, SecretsManagerSubscriptionUpdate smSubscriptionUpdate)
{
Invites = request.Invites;
Organization = request.Organization;
PerformedBy = request.PerformedBy;
PerformedAt = request.PerformedAt;
OccupiedPmSeats = request.OccupiedPmSeats;
OccupiedSmSeats = request.OccupiedSmSeats;
PasswordManagerSubscriptionUpdate = subscriptionUpdate;
SecretsManagerSubscriptionUpdate = smSubscriptionUpdate;
}
public OrganizationUserInviteDto[] Invites { get; init; } = []; public OrganizationUserInviteDto[] Invites { get; init; } = [];
public OrganizationDto Organization { get; init; } public OrganizationDto Organization { get; init; }
public Guid PerformedBy { get; init; } public Guid PerformedBy { get; init; }

View File

@ -51,7 +51,8 @@ public class InviteUsersValidation(
var provider = await providerRepository.GetByOrganizationIdAsync(request.Organization.OrganizationId); var provider = await providerRepository.GetByOrganizationIdAsync(request.Organization.OrganizationId);
if (InvitingUserOrganizationProviderValidation.Validate(ProviderDto.FromProviderEntity(provider)) is if (provider is not null &&
InvitingUserOrganizationProviderValidation.Validate(ProviderDto.FromProviderEntity(provider)) is
Invalid<ProviderDto> invalidProviderValidation) Invalid<ProviderDto> invalidProviderValidation)
{ {
return new Invalid<InviteUserOrganizationValidationRequest>(invalidProviderValidation.ErrorMessageString); return new Invalid<InviteUserOrganizationValidationRequest>(invalidProviderValidation.ErrorMessageString);
@ -65,7 +66,10 @@ public class InviteUsersValidation(
return new Invalid<InviteUserOrganizationValidationRequest>(invalidPaymentValidation.ErrorMessageString); return new Invalid<InviteUserOrganizationValidationRequest>(invalidPaymentValidation.ErrorMessageString);
} }
return new Valid<InviteUserOrganizationValidationRequest>(null); return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
request,
subscriptionUpdate,
smSubscriptionUpdate));
} }
public static ValidationResult<IGlobalSettings> ValidateEnvironment(IGlobalSettings globalSettings) => public static ValidationResult<IGlobalSettings> ValidateEnvironment(IGlobalSettings globalSettings) =>

View File

@ -7,6 +7,11 @@ public static class InvitingUserOrganizationValidation
{ {
public static ValidationResult<OrganizationDto> Validate(OrganizationDto organization) public static ValidationResult<OrganizationDto> Validate(OrganizationDto organization)
{ {
if (organization.Seats is null)
{
return new Valid<OrganizationDto>(organization);
}
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{ {
return new Invalid<OrganizationDto>(NoPaymentMethodFoundError); return new Invalid<OrganizationDto>(NoPaymentMethodFoundError);

View File

@ -5,41 +5,39 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse
public static class SecretsManagerInviteUserValidation public static class SecretsManagerInviteUserValidation
{ {
// NOTE This is only validating adding new users public static ValidationResult<SecretsManagerSubscriptionUpdate> Validate(
public static ValidationResult<SecretsManagerSubscriptionUpdate> Validate(SecretsManagerSubscriptionUpdate subscriptionUpdate) SecretsManagerSubscriptionUpdate subscriptionUpdate) =>
{ subscriptionUpdate switch
if (subscriptionUpdate.UseSecretsManger is false)
{ {
return new Invalid<SecretsManagerSubscriptionUpdate>(OrganizationNoSecretsManager); { UseSecretsManger: false, AdditionalSeats: > 0 } =>
} new Invalid<SecretsManagerSubscriptionUpdate>(OrganizationNoSecretsManager),
if (subscriptionUpdate.Seats == null) { UseSecretsManger: false, AdditionalSeats: 0 } or { UseSecretsManger: true, Seats: null } =>
{ new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate),
return new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate); // no need to adjust seats...continue on
}
// max additional seats is never set...maybe remove this { UseSecretsManger: true, SecretsManagerPlan.HasAdditionalSeatsOption: false } =>
if (subscriptionUpdate.SecretsManagerPlan is { HasAdditionalSeatsOption: false } || new Invalid<SecretsManagerSubscriptionUpdate>(
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats is not null && string.Format(SecretsManagerAdditionalSeatLimitReached,
subscriptionUpdate.AdditionalSeats > subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats)
{
return new Invalid<SecretsManagerSubscriptionUpdate>(
string.Format(SecretsManagerAdditionalSeatLimitReached,
subscriptionUpdate.SecretsManagerPlan.BaseSeats + subscriptionUpdate.SecretsManagerPlan.BaseSeats +
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault())); subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault())),
}
if (subscriptionUpdate.UpdatedSeatTotal is not null && subscriptionUpdate.MaxAutoScaleSeats is not null && { UseSecretsManger: true, SecretsManagerPlan.MaxAdditionalSeats: var planMaxSeats }
subscriptionUpdate.UpdatedSeatTotal > subscriptionUpdate.MaxAutoScaleSeats) when planMaxSeats < subscriptionUpdate.AdditionalSeats =>
{ new Invalid<SecretsManagerSubscriptionUpdate>(
return new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerSeatLimitReached); string.Format(SecretsManagerAdditionalSeatLimitReached,
} subscriptionUpdate.SecretsManagerPlan.BaseSeats +
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault())),
if (subscriptionUpdate.PasswordManagerUpdatedSeatTotal < subscriptionUpdate.UpdatedSeatTotal) { UseSecretsManger: true, UpdatedSeatTotal: var updateSeatTotal, MaxAutoScaleSeats: var maxAutoScaleSeats }
{ when updateSeatTotal > maxAutoScaleSeats =>
return new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerCannotExceedPasswordManager); new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerSeatLimitReached),
}
return new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate); {
} PasswordManagerUpdatedSeatTotal: var passwordManagerUpdatedSeatTotal,
UpdatedSeatTotal: var secretsManagerUpdatedSeatTotal
} when passwordManagerUpdatedSeatTotal < secretsManagerUpdatedSeatTotal =>
new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerCannotExceedPasswordManager),
_ => new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate)
};
} }

View File

@ -16,7 +16,7 @@ public class SecretsManagerInviteUserValidationTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_GivenOrganizationDoesNotHaveSecretsManager_ThenShouldNotBeAllowedToAddSecretsManagerUsers( public void Validate_GivenOrganizationDoesNotHaveSecretsManagerAndNotTryingToAddSecretsManagerUser_ThenTheRequestIsValid(
Organization organization) Organization organization)
{ {
organization.UseSecretsManager = false; organization.UseSecretsManager = false;
@ -38,6 +38,35 @@ public class SecretsManagerInviteUserValidationTests
var result = SecretsManagerInviteUserValidation.Validate(update); var result = SecretsManagerInviteUserValidation.Validate(update);
Assert.IsType<Valid<SecretsManagerSubscriptionUpdate>>(result);
}
[Theory]
[BitAutoData]
public void Validate_GivenOrganizationDoesNotHaveSecretsManagerAndTryingToAddSecretsManagerUser_ThenShouldReturnInvalidMessage(
Organization organization)
{
organization.UseSecretsManager = false;
var organizationDto = OrganizationDto.FromOrganization(organization);
var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0);
var invite = OrganizationUserInvite.Create(["email@test.com"], [], OrganizationUserType.User, new Permissions(), string.Empty, true);
var request = new InviteUserOrganizationValidationRequest
{
Invites = [OrganizationUserInviteDto.Create(invite.Emails.First(), invite, organizationDto.OrganizationId)],
Organization = organizationDto,
PerformedBy = Guid.Empty,
PerformedAt = default,
OccupiedPmSeats = 0,
OccupiedSmSeats = 0
};
var update = SecretsManagerSubscriptionUpdate.Create(request, subscriptionUpdate);
var result = SecretsManagerInviteUserValidation.Validate(update);
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result); Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.OrganizationNoSecretsManager, result.ErrorMessageString); Assert.Equal(InviteUserValidationErrorMessages.OrganizationNoSecretsManager, result.ErrorMessageString);
} }