1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

Refactored to use new ValidationResult pattern. added mapping method.

This commit is contained in:
jrmccannon 2025-03-17 13:27:37 -05:00
parent 1620fecc70
commit 59b579f071
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
29 changed files with 226 additions and 108 deletions

View File

@ -1,3 +1,8 @@
namespace Bit.Core.AdminConsole.Errors;
public record Error<T>(string Message, T ErroredValue);
public static class ErrorMappers
{
public static Error<B> ToError<A, B>(this Error<A> errorA, B erroredValue) => new(errorA.Message, erroredValue);
}

View File

@ -2,6 +2,7 @@
using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;

View File

@ -0,0 +1,9 @@
using Bit.Core.AdminConsole.Errors;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
public record CannotAutoScaleOnSelfHostError(IGlobalSettings InvalidSettings) : Error<IGlobalSettings>(Code, InvalidSettings)
{
public const string Code = "CannotAutoScaleOnSelfHost";
}

View File

@ -1,20 +1,21 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.InviteUserValidationErrorMessages;
using SecretsManagerSubscriptionUpdate = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models.SecretsManagerSubscriptionUpdate;
using SecretsManagerSubscriptionUpdate = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager.SecretsManagerSubscriptionUpdate;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
// TODO move into own file ... and change name to validator
public interface IInviteUsersValidation
{
Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateAsync(InviteUserOrganizationValidationRequest request);
}
public interface IInviteUsersValidation : IValidator<InviteUserOrganizationValidationRequest>;
public class InviteUsersValidation(
IGlobalSettings globalSettings,
@ -26,14 +27,14 @@ public class InviteUsersValidation(
{
if (ValidateEnvironment(globalSettings) is Invalid<IGlobalSettings> invalidEnvironment)
{
return new Invalid<InviteUserOrganizationValidationRequest>(invalidEnvironment.ErrorMessageString);
return invalidEnvironment.Map(request);
}
var organizationValidationResult = InvitingUserOrganizationValidation.Validate(request.InviteOrganization);
if (organizationValidationResult is Invalid<InviteOrganization> organizationValidation)
{
return new Invalid<InviteUserOrganizationValidationRequest>(organizationValidation.ErrorMessageString);
return organizationValidation.Map(request);
}
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request);
@ -41,7 +42,7 @@ public class InviteUsersValidation(
if (passwordManagerValidationResult is Invalid<PasswordManagerSubscriptionUpdate> invalidSubscriptionUpdate)
{
return new Invalid<InviteUserOrganizationValidationRequest>(invalidSubscriptionUpdate.ErrorMessageString);
return invalidSubscriptionUpdate.Map(request);
}
var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(request, subscriptionUpdate);
@ -49,7 +50,7 @@ public class InviteUsersValidation(
if (secretsManagerValidationResult is Invalid<SecretsManagerSubscriptionUpdate> invalidSmSubscriptionUpdate)
{
return new Invalid<InviteUserOrganizationValidationRequest>(invalidSmSubscriptionUpdate.ErrorMessageString);
return invalidSmSubscriptionUpdate.Map(request);
}
var provider = await providerRepository.GetByOrganizationIdAsync(request.InviteOrganization.OrganizationId);
@ -59,16 +60,19 @@ public class InviteUsersValidation(
if (providerValidationResult is Invalid<ProviderDto> invalidProviderValidation)
{
return new Invalid<InviteUserOrganizationValidationRequest>(invalidProviderValidation.ErrorMessageString);
return invalidProviderValidation.Map(request);
}
}
var paymentSubscription = await paymentService.GetSubscriptionAsync(await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
var paymentValidationResult = InviteUserPaymentValidation.Validate(new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
var paymentSubscription = await paymentService.GetSubscriptionAsync(
await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
var paymentValidationResult = InviteUserPaymentValidation.Validate(
new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
if (paymentValidationResult is Invalid<PaymentsSubscription> invalidPaymentValidation)
{
return new Invalid<InviteUserOrganizationValidationRequest>(invalidPaymentValidation.ErrorMessageString);
return invalidPaymentValidation.Map(request);
}
return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
@ -79,6 +83,6 @@ public class InviteUsersValidation(
public static ValidationResult<IGlobalSettings> ValidateEnvironment(IGlobalSettings globalSettings) =>
globalSettings.SelfHosted
? new Invalid<IGlobalSettings>(CannotAutoScaleOnSelfHostedError)
? new Invalid<IGlobalSettings>(new CannotAutoScaleOnSelfHostError(globalSettings))
: new Valid<IGlobalSettings>(globalSettings);
}

View File

@ -1,22 +0,0 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
public static class InviteUserValidationErrorMessages
{
public const string CannotAutoScaleOnSelfHostedError = "Cannot autoscale on self-hosted instance.";
public const string ProviderBillableSeatLimitError = "Seat limit has been reached. Please contact your provider to add more seats.";
public const string ProviderResellerSeatLimitError = "Seat limit has been reached. Contact your provider to purchase additional seats.";
public const string CancelledSubscriptionError = "Cannot autoscale with a canceled subscription.";
public const string NoPaymentMethodFoundError = "No payment method found.";
public const string NoSubscriptionFoundError = "No subscription found.";
// Password Manger Invite Users Error Messages
public const string SeatLimitHasBeenReachedError = "Seat limit has been reached.";
public const string PlanDoesNotAllowAdditionalSeats = "Plan does not allow additional seats.";
public const string PlanOnlyAllowsMaxAdditionalSeats = "Organization plan allows a maximum of {0} additional seats.";
// Secrets Manager Invite Users Error Messages
public const string OrganizationNoSecretsManager = "Organization has no access to Secrets Manager";
public const string SecretsManagerSeatLimitReached = "Secrets Manager seat limit has been reached.";
public const string SecretsManagerCannotExceedPasswordManager = "You cannot have more Secrets Manager seats than Password Manager seats.";
public const string SecretsManagerAdditionalSeatLimitReached = "You have reached the maximum number of Secrets Manager seats ({0}) for this plan.";
}

View File

@ -0,0 +1,16 @@
using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.Models.Business;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
public record OrganizationNoPaymentMethodFoundError(InviteOrganization InvalidRequest)
: Error<InviteOrganization>(Code, InvalidRequest)
{
public const string Code = "No payment method found.";
}
public record OrganizationNoSubscriptionFoundError(InviteOrganization InvalidRequest)
: Error<InviteOrganization>(Code, InvalidRequest)
{
public const string Code = "No subscription found.";
}

View File

@ -1,7 +1,7 @@
using Bit.Core.AdminConsole.Models.Business;
using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.InviteUserValidationErrorMessages;
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
public static class InvitingUserOrganizationValidation
{
@ -14,12 +14,12 @@ public static class InvitingUserOrganizationValidation
if (string.IsNullOrWhiteSpace(inviteOrganization.GatewayCustomerId))
{
return new Invalid<InviteOrganization>(NoPaymentMethodFoundError);
return new Invalid<InviteOrganization>(new OrganizationNoPaymentMethodFoundError(inviteOrganization));
}
if (string.IsNullOrWhiteSpace(inviteOrganization.GatewaySubscriptionId))
{
return new Invalid<InviteOrganization>(NoSubscriptionFoundError);
return new Invalid<InviteOrganization>(new OrganizationNoSubscriptionFoundError(inviteOrganization));
}
return new Valid<InviteOrganization>(inviteOrganization);

View File

@ -0,0 +1,24 @@
using Bit.Core.AdminConsole.Errors;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
public record PasswordManagerSeatLimitHasBeenReachedError(PasswordManagerSubscriptionUpdate InvalidRequest)
: Error<PasswordManagerSubscriptionUpdate>(Code, InvalidRequest)
{
public const string Code = "Seat limit has been reached.";
}
public record PasswordManagerPlanDoesNotAllowAdditionalSeatsError(PasswordManagerSubscriptionUpdate InvalidRequest)
: Error<PasswordManagerSubscriptionUpdate>(Code, InvalidRequest)
{
public const string Code = "Plan does not allow additional seats.";
}
public record PasswordManagerPlanOnlyAllowsMaxAdditionalSeatsError(PasswordManagerSubscriptionUpdate InvalidRequest)
: Error<PasswordManagerSubscriptionUpdate>(GetErrorMessage(InvalidRequest), InvalidRequest)
{
private static string GetErrorMessage(PasswordManagerSubscriptionUpdate invalidRequest) =>
string.Format(Code, invalidRequest.PasswordManagerPlan.MaxAdditionalSeats);
public const string Code = "Organization plan allows a maximum of {0} additional seats.";
}

View File

@ -1,12 +1,10 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.InviteUserValidationErrorMessages;
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
public static class PasswordManagerInviteUserValidation
{
// NOTE This is only for validating adding users to an organization, not removing
public static ValidationResult<PasswordManagerSubscriptionUpdate> Validate(PasswordManagerSubscriptionUpdate subscriptionUpdate)
{
if (subscriptionUpdate.Seats is null)
@ -22,19 +20,21 @@ public static class PasswordManagerInviteUserValidation
if (subscriptionUpdate.UpdatedSeatTotal is not null && subscriptionUpdate.MaxAutoScaleSeats is not null &&
subscriptionUpdate.UpdatedSeatTotal > subscriptionUpdate.MaxAutoScaleSeats)
{
return new Invalid<PasswordManagerSubscriptionUpdate>(SeatLimitHasBeenReachedError);
return new Invalid<PasswordManagerSubscriptionUpdate>(
new PasswordManagerSeatLimitHasBeenReachedError(subscriptionUpdate));
}
if (subscriptionUpdate.PasswordManagerPlan.HasAdditionalSeatsOption is false)
{
return new Invalid<PasswordManagerSubscriptionUpdate>(PlanDoesNotAllowAdditionalSeats);
return new Invalid<PasswordManagerSubscriptionUpdate>(
new PasswordManagerPlanDoesNotAllowAdditionalSeatsError(subscriptionUpdate));
}
// Apparently MaxAdditionalSeats is never set. Can probably be removed.
if (subscriptionUpdate.AdditionalSeats > subscriptionUpdate.PasswordManagerPlan.MaxAdditionalSeats)
{
return new Invalid<PasswordManagerSubscriptionUpdate>(string.Format(PlanOnlyAllowsMaxAdditionalSeats,
subscriptionUpdate.PasswordManagerPlan.MaxAdditionalSeats));
return new Invalid<PasswordManagerSubscriptionUpdate>(
new PasswordManagerPlanOnlyAllowsMaxAdditionalSeatsError(subscriptionUpdate));
}
return new Valid<PasswordManagerSubscriptionUpdate>(subscriptionUpdate);

View File

@ -2,7 +2,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
public class PasswordManagerSubscriptionUpdate
{

View File

@ -0,0 +1,10 @@
using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Payments;
public record PaymentCancelledSubscriptionError(PaymentsSubscription InvalidRequest)
: Error<PaymentsSubscription>(Code, InvalidRequest)
{
public const string Code = "Cannot autoscale with a canceled subscription.";
}

View File

@ -1,4 +1,6 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Payments;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
@ -15,7 +17,7 @@ public static class InviteUserPaymentValidation
if (subscription.SubscriptionStatus == StripeConstants.SubscriptionStatus.Canceled)
{
return new Invalid<PaymentsSubscription>(InviteUserValidationErrorMessages.CancelledSubscriptionError);
return new Invalid<PaymentsSubscription>(new PaymentCancelledSubscriptionError(subscription));
}
return new Valid<PaymentsSubscription>(subscription);

View File

@ -0,0 +1,13 @@
using Bit.Core.AdminConsole.Errors;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
public record ProviderBillableSeatLimitError(ProviderDto InvalidRequest) : Error<ProviderDto>(Code, InvalidRequest)
{
public const string Code = "Seat limit has been reached. Please contact your provider to add more seats.";
}
public record ProviderResellerSeatLimitError(ProviderDto InvalidRequest) : Error<ProviderDto>(Code, InvalidRequest)
{
public const string Code = "Seat limit has been reached. Contact your provider to purchase additional seats.";
}

View File

@ -1,8 +1,8 @@
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Extensions;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
public static class InvitingUserOrganizationProviderValidation
{
@ -12,12 +12,12 @@ public static class InvitingUserOrganizationProviderValidation
{
if (provider.IsBillable())
{
return new Invalid<ProviderDto>(InviteUserValidationErrorMessages.ProviderBillableSeatLimitError);
return new Invalid<ProviderDto>(new ProviderBillableSeatLimitError(provider));
}
if (provider.Type == ProviderType.Reseller)
{
return new Invalid<ProviderDto>(InviteUserValidationErrorMessages.ProviderResellerSeatLimitError);
return new Invalid<ProviderDto>(new ProviderResellerSeatLimitError(provider));
}
}

View File

@ -1,7 +1,6 @@
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
public class ProviderDto
{
@ -10,7 +9,7 @@ public class ProviderDto
public ProviderStatusType Status { get; init; }
public bool Enabled { get; init; }
public static ProviderDto FromProviderEntity(Provider provider)
public static ProviderDto FromProviderEntity(Entities.Provider.Provider provider)
{
return new ProviderDto { ProviderId = provider.Id, Type = provider.Type, Status = provider.Status, Enabled = provider.Enabled };
}

View File

@ -0,0 +1,32 @@
using Bit.Core.AdminConsole.Errors;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
public record OrganizationNoSecretsManagerError(SecretsManagerSubscriptionUpdate InvalidRequest)
: Error<SecretsManagerSubscriptionUpdate>(Code, InvalidRequest)
{
public const string Code = "Organization has no access to Secrets Manager";
}
public record SecretsManagerAdditionalSeatLimitReachedError(SecretsManagerSubscriptionUpdate InvalidRequest)
: Error<SecretsManagerSubscriptionUpdate>(GetErrorMessage(InvalidRequest), InvalidRequest)
{
public const string Code = "You have reached the maximum number of Secrets Manager seats ({0}) for this plan.";
public static string GetErrorMessage(SecretsManagerSubscriptionUpdate invalidRequest) =>
string.Format(Code,
invalidRequest.SecretsManagerPlan.BaseSeats +
invalidRequest.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault());
}
public record SecretsManagerSeatLimitReachedError(SecretsManagerSubscriptionUpdate InvalidRequest)
: Error<SecretsManagerSubscriptionUpdate>(Code, InvalidRequest)
{
public const string Code = "Secrets Manager seat limit has been reached.";
}
public record SecretsManagerCannotExceedPasswordManagerError(SecretsManagerSubscriptionUpdate InvalidRequest)
: Error<SecretsManagerSubscriptionUpdate>(Code, InvalidRequest)
{
public const string Code = "You cannot have more Secrets Manager seats than Password Manager seats.";
}

View File

@ -1,7 +1,6 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.InviteUserValidationErrorMessages;
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
public static class SecretsManagerInviteUserValidation
{
@ -10,33 +9,34 @@ public static class SecretsManagerInviteUserValidation
subscriptionUpdate switch
{
{ UseSecretsManger: false, AdditionalSeats: > 0 } =>
new Invalid<SecretsManagerSubscriptionUpdate>(OrganizationNoSecretsManager),
new Invalid<SecretsManagerSubscriptionUpdate>(
new OrganizationNoSecretsManagerError(subscriptionUpdate)),
{ UseSecretsManger: false, AdditionalSeats: 0 } or { UseSecretsManger: true, Seats: null } =>
new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate),
{ UseSecretsManger: true, SecretsManagerPlan.HasAdditionalSeatsOption: false } =>
new Invalid<SecretsManagerSubscriptionUpdate>(
string.Format(SecretsManagerAdditionalSeatLimitReached,
subscriptionUpdate.SecretsManagerPlan.BaseSeats +
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault())),
new SecretsManagerAdditionalSeatLimitReachedError(subscriptionUpdate)),
{ UseSecretsManger: true, SecretsManagerPlan.MaxAdditionalSeats: var planMaxSeats }
when planMaxSeats < subscriptionUpdate.AdditionalSeats =>
new Invalid<SecretsManagerSubscriptionUpdate>(
string.Format(SecretsManagerAdditionalSeatLimitReached,
subscriptionUpdate.SecretsManagerPlan.BaseSeats +
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault())),
new SecretsManagerAdditionalSeatLimitReachedError(subscriptionUpdate)),
{ UseSecretsManger: true, UpdatedSeatTotal: var updateSeatTotal, MaxAutoScaleSeats: var maxAutoScaleSeats }
when updateSeatTotal > maxAutoScaleSeats =>
new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerSeatLimitReached),
new Invalid<SecretsManagerSubscriptionUpdate>(
new SecretsManagerSeatLimitReachedError(subscriptionUpdate)),
{
UseSecretsManger: true,
PasswordManagerUpdatedSeatTotal: var passwordManagerUpdatedSeatTotal,
UpdatedSeatTotal: var secretsManagerUpdatedSeatTotal
} when passwordManagerUpdatedSeatTotal < secretsManagerUpdatedSeatTotal =>
new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerCannotExceedPasswordManager),
}
when passwordManagerUpdatedSeatTotal < secretsManagerUpdatedSeatTotal =>
new Invalid<SecretsManagerSubscriptionUpdate>(
new SecretsManagerCannotExceedPasswordManagerError(subscriptionUpdate)),
_ => new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate)
};

View File

@ -1,8 +1,9 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
public class SecretsManagerSubscriptionUpdate
{

View File

@ -1,15 +0,0 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
public abstract record ValidationResult<T>(T Value, IEnumerable<string> Errors)
{
public bool IsValid => !Errors.Any();
public string ErrorMessageString => string.Join(" ", Errors);
}
public record Valid<T>(T Value) : ValidationResult<T>(Value, []);
public record Invalid<T>(IEnumerable<string> Errors) : ValidationResult<T>(default, Errors)
{
public Invalid(string error) : this([error]) { }
}

View File

@ -6,10 +6,39 @@ public abstract record ValidationResult<T>;
public record Valid<T> : ValidationResult<T>
{
public Valid() { }
public Valid(T Value)
{
this.Value = Value;
}
public T Value { get; init; }
}
public record Invalid<T> : ValidationResult<T>
{
public IEnumerable<Error<T>> Errors { get; init; }
public IEnumerable<Error<T>> Errors { get; init; } = [];
public string ErrorMessageString => string.Join(" ", Errors.Select(e => e.Message));
public Invalid() { }
public Invalid(Error<T> error) : this([error]) { }
public Invalid(IEnumerable<Error<T>> errors)
{
Errors = errors;
}
}
public static class ValidationResultMappers
{
public static ValidationResult<B> Map<A, B>(this ValidationResult<A> validationResult, B invalidValue) =>
validationResult switch
{
Valid<A> => new Valid<B>(invalidValue),
Invalid<A> invalid => new Invalid<B>(invalid.Errors.Select(x => x.ToError(invalidValue))),
_ => throw new ArgumentOutOfRangeException(nameof(validationResult), "Unhandled validation result type")
};
}

View File

@ -1,7 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;

View File

@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers;

View File

@ -1,10 +1,13 @@
using System.Net.Mail;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Commands;
@ -158,7 +161,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(errorMessage));
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(new Error<InviteUserOrganizationValidationRequest>(errorMessage, new InviteUserOrganizationValidationRequest())));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);

View File

@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
@ -27,7 +28,8 @@ public class InviteUserOrganizationValidationTests
var result = InvitingUserOrganizationValidation.Validate(new InviteOrganization(organization));
Assert.IsType<Invalid<InviteOrganization>>(result);
Assert.Equal(InviteUserValidationErrorMessages.NoPaymentMethodFoundError, result.ErrorMessageString);
Assert.Equal(OrganizationNoPaymentMethodFoundError.Code, (result as Invalid<InviteOrganization>).ErrorMessageString);
}
[Theory]
@ -40,6 +42,6 @@ public class InviteUserOrganizationValidationTests
var result = InvitingUserOrganizationValidation.Validate(new InviteOrganization(organization));
Assert.IsType<Invalid<InviteOrganization>>(result);
Assert.Equal(InviteUserValidationErrorMessages.NoSubscriptionFoundError, result.ErrorMessageString);
Assert.Equal(OrganizationNoSubscriptionFoundError.Code, (result as Invalid<InviteOrganization>).ErrorMessageString);
}
}

View File

@ -2,6 +2,8 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Payments;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
using Bit.Test.Common.AutoFixture.Attributes;
@ -36,7 +38,7 @@ public class InviteUserPaymentValidationTests
});
Assert.IsType<Invalid<PaymentsSubscription>>(result);
Assert.Equal(InviteUserValidationErrorMessages.CancelledSubscriptionError, result.ErrorMessageString);
Assert.Equal(PaymentCancelledSubscriptionError.Code, (result as Invalid<PaymentsSubscription>).ErrorMessageString);
}
[Fact]

View File

@ -1,7 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Enums;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
@ -61,7 +61,7 @@ public class PasswordManagerInviteUserValidationTests
var result = PasswordManagerInviteUserValidation.Validate(subscriptionUpdate);
Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.SeatLimitHasBeenReachedError, result.ErrorMessageString);
Assert.Equal(PasswordManagerSeatLimitHasBeenReachedError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>).ErrorMessageString);
}
[Theory]
@ -80,6 +80,6 @@ public class PasswordManagerInviteUserValidationTests
var result = PasswordManagerInviteUserValidation.Validate(subscriptionUpdate);
Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.PlanDoesNotAllowAdditionalSeats, result.ErrorMessageString);
Assert.Equal(PasswordManagerPlanDoesNotAllowAdditionalSeatsError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>).ErrorMessageString);
}
}

View File

@ -1,8 +1,9 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@ -68,7 +69,7 @@ public class SecretsManagerInviteUserValidationTests
var result = SecretsManagerInviteUserValidation.Validate(update);
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.OrganizationNoSecretsManager, result.ErrorMessageString);
Assert.Equal(OrganizationNoSecretsManagerError.Code, (result as Invalid<SecretsManagerSubscriptionUpdate>).ErrorMessageString);
}
[Theory]
@ -127,7 +128,7 @@ public class SecretsManagerInviteUserValidationTests
var result = SecretsManagerInviteUserValidation.Validate(update);
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.SecretsManagerSeatLimitReached, result.ErrorMessageString);
Assert.Equal(SecretsManagerSeatLimitReachedError.Code, (result as Invalid<SecretsManagerSubscriptionUpdate>).ErrorMessageString);
}
[Theory]
@ -158,6 +159,6 @@ public class SecretsManagerInviteUserValidationTests
var result = SecretsManagerInviteUserValidation.Validate(update);
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
Assert.Equal(InviteUserValidationErrorMessages.SecretsManagerCannotExceedPasswordManager, result.ErrorMessageString);
Assert.Equal(SecretsManagerCannotExceedPasswordManagerError.Code, (result as Invalid<SecretsManagerSubscriptionUpdate>).ErrorMessageString);
}
}