mirror of
https://github.com/bitwarden/server.git
synced 2025-04-04 20:50:21 -05:00
Created new errors and removed references in business code to ErrorMessages property. This aligns Invite User code to use Errors instead of ErrorMessages
This commit is contained in:
parent
8e2ac9a5bb
commit
2656ccf314
@ -4,6 +4,7 @@ using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Errors;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Enums;
|
||||
@ -78,9 +79,9 @@ public class PostUserCommand(
|
||||
var invitedOrganizationUserId = result switch
|
||||
{
|
||||
Success<ScimInviteOrganizationUsersResponse> success => success.Value.InvitedUser.Id,
|
||||
Failure<ScimInviteOrganizationUsersResponse> { ErrorMessage: InviteOrganizationUsersCommand.NoUsersToInvite } => (Guid?)null,
|
||||
Failure<ScimInviteOrganizationUsersResponse> failure when failure.Errors
|
||||
.Any(x => x.Message == NoUsersToInviteError.Code) => (Guid?)null,
|
||||
Failure<ScimInviteOrganizationUsersResponse> failure when failure.Errors.Length != 0 => throw MapToBitException(failure.Errors),
|
||||
Failure<ScimInviteOrganizationUsersResponse> failure when failure.ErrorMessages.Count != 0 => throw new BadRequestException(failure.ErrorMessage),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
|
6
src/Core/AdminConsole/Errors/InvalidResultTypeError.cs
Normal file
6
src/Core/AdminConsole/Errors/InvalidResultTypeError.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.AdminConsole.Errors;
|
||||
|
||||
public record InvalidResultTypeError<T>(T Value) : Error<T>(Code, Value)
|
||||
{
|
||||
public const string Code = "Invalid result type.";
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Errors;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Errors;
|
||||
|
||||
public record FailedToInviteUsersError(InviteOrganizationUsersResponse Response) : Error<InviteOrganizationUsersResponse>(Code, Response)
|
||||
{
|
||||
public const string Code = "Failed to invite users";
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Errors;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Errors;
|
||||
|
||||
public record NoUsersToInviteError(InviteOrganizationUsersResponse Response) : Error<InviteOrganizationUsersResponse>(Code, Response)
|
||||
{
|
||||
public const string Code = "No users to invite";
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Errors;
|
||||
using Bit.Core.AdminConsole.Interfaces;
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Errors;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Shared.Validation;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Commands;
|
||||
@ -40,9 +41,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
{
|
||||
|
||||
public const string IssueNotifyingOwnersOfSeatLimitReached = "Error encountered notifying organization owners of seat limit reached.";
|
||||
public const string FailedToInviteUsers = "Failed to invite user(s).";
|
||||
public const string NoUsersToInvite = "No users to invite.";
|
||||
public const string InvalidResultType = "Invalid result type.";
|
||||
|
||||
public async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(InviteOrganizationUsersRequest request)
|
||||
{
|
||||
@ -50,11 +48,16 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case Failure<IEnumerable<OrganizationUser>> failure:
|
||||
return new Failure<ScimInviteOrganizationUsersResponse>(failure.ErrorMessage);
|
||||
case Failure<InviteOrganizationUsersResponse> failure:
|
||||
return new Failure<ScimInviteOrganizationUsersResponse>(
|
||||
failure.Errors.Select(error => new Error<ScimInviteOrganizationUsersResponse>(error.Message,
|
||||
new ScimInviteOrganizationUsersResponse
|
||||
{
|
||||
InvitedUser = error.ErroredValue.InvitedUsers.FirstOrDefault()
|
||||
})));
|
||||
|
||||
case Success<IEnumerable<OrganizationUser>> success when success.Value.Any():
|
||||
var user = success.Value.First();
|
||||
case Success<InviteOrganizationUsersResponse> success when success.Value.InvitedUsers.Any():
|
||||
var user = success.Value.InvitedUsers.First();
|
||||
|
||||
await eventService.LogOrganizationUserEventAsync<IOrganizationUser>(
|
||||
organizationUser: user,
|
||||
@ -68,17 +71,20 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
});
|
||||
|
||||
default:
|
||||
return new Failure<ScimInviteOrganizationUsersResponse>(InvalidResultType);
|
||||
return new Failure<ScimInviteOrganizationUsersResponse>(
|
||||
new InvalidResultTypeError<ScimInviteOrganizationUsersResponse>(
|
||||
new ScimInviteOrganizationUsersResponse()));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<CommandResult<IEnumerable<OrganizationUser>>> InviteOrganizationUsersAsync(InviteOrganizationUsersRequest request)
|
||||
private async Task<CommandResult<InviteOrganizationUsersResponse>> InviteOrganizationUsersAsync(InviteOrganizationUsersRequest request)
|
||||
{
|
||||
var invitesToSend = (await FilterExistingUsersAsync(request)).ToArray();
|
||||
|
||||
if (invitesToSend.Length == 0)
|
||||
{
|
||||
return new Failure<IEnumerable<OrganizationUser>>(NoUsersToInvite);
|
||||
return new Failure<InviteOrganizationUsersResponse>(new NoUsersToInviteError(
|
||||
new InviteOrganizationUsersResponse(request.InviteOrganization.OrganizationId)));
|
||||
}
|
||||
|
||||
var validationResult = await inviteUsersValidator.ValidateAsync(new InviteUserOrganizationValidationRequest
|
||||
@ -93,7 +99,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
|
||||
if (validationResult is Invalid<InviteUserOrganizationValidationRequest> invalid)
|
||||
{
|
||||
return new Failure<IEnumerable<OrganizationUser>>(invalid.ErrorMessageString);
|
||||
return invalid.MapToFailure(r => new InviteOrganizationUsersResponse(r));
|
||||
}
|
||||
|
||||
var validatedRequest = validationResult as Valid<InviteUserOrganizationValidationRequest>;
|
||||
@ -102,7 +108,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
.Select(x => x.MapToDataModel(request.PerformedAt, validatedRequest!.Value.InviteOrganization))
|
||||
.ToArray();
|
||||
|
||||
var organization = await organizationRepository.GetByIdAsync(validatedRequest.Value.InviteOrganization.OrganizationId);
|
||||
var organization = await organizationRepository.GetByIdAsync(validatedRequest!.Value.InviteOrganization.OrganizationId);
|
||||
|
||||
try
|
||||
{
|
||||
@ -120,7 +126,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, FailedToInviteUsers);
|
||||
logger.LogError(ex, FailedToInviteUsersError.Code);
|
||||
|
||||
await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id));
|
||||
|
||||
@ -129,10 +135,14 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||
|
||||
await RevertPasswordManagerChangesAsync(validatedRequest, organization);
|
||||
|
||||
return new Failure<IEnumerable<OrganizationUser>>(FailedToInviteUsers);
|
||||
return new Failure<InviteOrganizationUsersResponse>(new FailedToInviteUsersError(
|
||||
new InviteOrganizationUsersResponse(validatedRequest.Value)));
|
||||
}
|
||||
|
||||
return new Success<IEnumerable<OrganizationUser>>(organizationUserToInviteEntities.Select(x => x.OrganizationUser));
|
||||
return new Success<InviteOrganizationUsersResponse>(
|
||||
new InviteOrganizationUsersResponse(
|
||||
invitedOrganizationUsers: organizationUserToInviteEntities.Select(x => x.OrganizationUser).ToArray(),
|
||||
organizationId: organization!.Id));
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<OrganizationUserInvite>> FilterExistingUsersAsync(InviteOrganizationUsersRequest request)
|
||||
|
@ -2,9 +2,22 @@
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
|
||||
public class InviteOrganizationUsersResponse
|
||||
public class InviteOrganizationUsersResponse(Guid organizationId)
|
||||
{
|
||||
public IEnumerable<OrganizationUser> InvitedUsers { get; set; } = [];
|
||||
public IEnumerable<OrganizationUser> InvitedUsers { get; } = [];
|
||||
public Guid OrganizationId { get; } = organizationId;
|
||||
|
||||
public InviteOrganizationUsersResponse(InviteUserOrganizationValidationRequest validationRequest)
|
||||
: this(validationRequest.InviteOrganization.OrganizationId)
|
||||
{
|
||||
InvitedUsers = validationRequest.Invites.Select(x => new OrganizationUser { Email = x.Email });
|
||||
}
|
||||
|
||||
public InviteOrganizationUsersResponse(IEnumerable<OrganizationUser> invitedOrganizationUsers, Guid organizationId)
|
||||
: this(organizationId)
|
||||
{
|
||||
InvitedUsers = invitedOrganizationUsers;
|
||||
}
|
||||
}
|
||||
|
||||
public class ScimInviteOrganizationUsersResponse
|
||||
|
@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Errors;
|
||||
using Bit.Core.AdminConsole.Shared.Validation;
|
||||
|
||||
namespace Bit.Core.Models.Commands;
|
||||
|
||||
@ -48,6 +49,11 @@ public class Failure<T>(IEnumerable<string> errorMessages) : CommandResult<T>
|
||||
{
|
||||
}
|
||||
|
||||
public Failure(IEnumerable<Error<T>> errors) : this(errors.Select(e => e.Message))
|
||||
{
|
||||
Errors = errors.ToArray();
|
||||
}
|
||||
|
||||
public Failure(Error<T> error) : this([error.Message])
|
||||
{
|
||||
Errors = [error];
|
||||
@ -65,3 +71,18 @@ public class Partial<T> : CommandResult<T>
|
||||
Failures = failedItems.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This is to help map between the InvalidT ValidationResult and the FailureT CommandResult types.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="invalidResult">This is the invalid type from validating the object.</param>
|
||||
/// <param name="mappingFunction">This function will map between the two types for the inner ErrorT</param>
|
||||
/// <typeparam name="A">Invalid object's type</typeparam>
|
||||
/// <typeparam name="B">Failure object's type</typeparam>
|
||||
/// <returns></returns>
|
||||
public static CommandResult<B> MapToFailure<A, B>(this Invalid<A> invalidResult, Func<A, B> mappingFunction) =>
|
||||
new Failure<B>(invalidResult.Errors.Select(errorA => errorA.ToError(mappingFunction(errorA.ErroredValue))));
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Errors;
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Errors;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
||||
@ -79,7 +80,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
// Assert
|
||||
Assert.IsType<Failure<ScimInviteOrganizationUsersResponse>>(result);
|
||||
Assert.Equal(InviteOrganizationUsersCommand.NoUsersToInvite, (result as Failure<ScimInviteOrganizationUsersResponse>).ErrorMessage);
|
||||
Assert.Equal(NoUsersToInviteError.Code, (result as Failure<ScimInviteOrganizationUsersResponse>).ErrorMessage);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
@ -186,6 +187,8 @@ public class InviteOrganizationUserCommandTests
|
||||
performedBy: Guid.Empty,
|
||||
timeProvider.GetUtcNow());
|
||||
|
||||
var validationRequest = GetInviteValidationRequestMock(request, inviteOrganization, organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
|
||||
.Returns([]);
|
||||
@ -196,7 +199,8 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
sutProvider.GetDependency<IInviteUsersValidator>()
|
||||
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
|
||||
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(new Error<InviteUserOrganizationValidationRequest>(errorMessage, new InviteUserOrganizationValidationRequest())));
|
||||
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(
|
||||
new Error<InviteUserOrganizationValidationRequest>(errorMessage, validationRequest)));
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
|
||||
@ -496,7 +500,7 @@ public class InviteOrganizationUserCommandTests
|
||||
|
||||
// Assert
|
||||
Assert.IsType<Failure<ScimInviteOrganizationUsersResponse>>(result);
|
||||
Assert.Equal(InviteOrganizationUsersCommand.FailedToInviteUsers, (result as Failure<ScimInviteOrganizationUsersResponse>)!.ErrorMessage);
|
||||
Assert.Equal(FailedToInviteUsersError.Code, (result as Failure<ScimInviteOrganizationUsersResponse>)!.ErrorMessage);
|
||||
|
||||
// org user revert
|
||||
await orgUserRepository.Received(1).DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(x => x.Count() == 1));
|
||||
|
Loading…
x
Reference in New Issue
Block a user