mirror of
https://github.com/bitwarden/server.git
synced 2025-04-06 05:28:15 -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.Enums;
|
||||||
using Bit.Core.AdminConsole.Models.Business;
|
using Bit.Core.AdminConsole.Models.Business;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
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.Models;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -78,9 +79,9 @@ public class PostUserCommand(
|
|||||||
var invitedOrganizationUserId = result switch
|
var invitedOrganizationUserId = result switch
|
||||||
{
|
{
|
||||||
Success<ScimInviteOrganizationUsersResponse> success => success.Value.InvitedUser.Id,
|
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.Errors.Length != 0 => throw MapToBitException(failure.Errors),
|
||||||
Failure<ScimInviteOrganizationUsersResponse> failure when failure.ErrorMessages.Count != 0 => throw new BadRequestException(failure.ErrorMessage),
|
|
||||||
_ => throw new InvalidOperationException()
|
_ => 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.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
|
using Bit.Core.AdminConsole.Errors;
|
||||||
using Bit.Core.AdminConsole.Interfaces;
|
using Bit.Core.AdminConsole.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Models.Business;
|
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.Models;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Shared.Validation;
|
using Bit.Core.AdminConsole.Shared.Validation;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Commands;
|
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 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)
|
public async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(InviteOrganizationUsersRequest request)
|
||||||
{
|
{
|
||||||
@ -50,11 +48,16 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case Failure<IEnumerable<OrganizationUser>> failure:
|
case Failure<InviteOrganizationUsersResponse> failure:
|
||||||
return new Failure<ScimInviteOrganizationUsersResponse>(failure.ErrorMessage);
|
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():
|
case Success<InviteOrganizationUsersResponse> success when success.Value.InvitedUsers.Any():
|
||||||
var user = success.Value.First();
|
var user = success.Value.InvitedUsers.First();
|
||||||
|
|
||||||
await eventService.LogOrganizationUserEventAsync<IOrganizationUser>(
|
await eventService.LogOrganizationUserEventAsync<IOrganizationUser>(
|
||||||
organizationUser: user,
|
organizationUser: user,
|
||||||
@ -68,17 +71,20 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
});
|
});
|
||||||
|
|
||||||
default:
|
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();
|
var invitesToSend = (await FilterExistingUsersAsync(request)).ToArray();
|
||||||
|
|
||||||
if (invitesToSend.Length == 0)
|
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
|
var validationResult = await inviteUsersValidator.ValidateAsync(new InviteUserOrganizationValidationRequest
|
||||||
@ -93,7 +99,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
|
|
||||||
if (validationResult is Invalid<InviteUserOrganizationValidationRequest> invalid)
|
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>;
|
var validatedRequest = validationResult as Valid<InviteUserOrganizationValidationRequest>;
|
||||||
@ -102,7 +108,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
.Select(x => x.MapToDataModel(request.PerformedAt, validatedRequest!.Value.InviteOrganization))
|
.Select(x => x.MapToDataModel(request.PerformedAt, validatedRequest!.Value.InviteOrganization))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var organization = await organizationRepository.GetByIdAsync(validatedRequest.Value.InviteOrganization.OrganizationId);
|
var organization = await organizationRepository.GetByIdAsync(validatedRequest!.Value.InviteOrganization.OrganizationId);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -120,7 +126,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, FailedToInviteUsers);
|
logger.LogError(ex, FailedToInviteUsersError.Code);
|
||||||
|
|
||||||
await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id));
|
await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id));
|
||||||
|
|
||||||
@ -129,10 +135,14 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
|
|
||||||
await RevertPasswordManagerChangesAsync(validatedRequest, organization);
|
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)
|
private async Task<IEnumerable<OrganizationUserInvite>> FilterExistingUsersAsync(InviteOrganizationUsersRequest request)
|
||||||
|
@ -2,9 +2,22 @@
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
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
|
public class ScimInviteOrganizationUsersResponse
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using Bit.Core.AdminConsole.Errors;
|
using Bit.Core.AdminConsole.Errors;
|
||||||
|
using Bit.Core.AdminConsole.Shared.Validation;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Commands;
|
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])
|
public Failure(Error<T> error) : this([error.Message])
|
||||||
{
|
{
|
||||||
Errors = [error];
|
Errors = [error];
|
||||||
@ -65,3 +71,18 @@ public class Partial<T> : CommandResult<T>
|
|||||||
Failures = failedItems.ToArray();
|
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.Business;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
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.Models;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
||||||
@ -79,7 +80,7 @@ public class InviteOrganizationUserCommandTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<Failure<ScimInviteOrganizationUsersResponse>>(result);
|
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>()
|
await sutProvider.GetDependency<IPaymentService>()
|
||||||
.DidNotReceiveWithAnyArgs()
|
.DidNotReceiveWithAnyArgs()
|
||||||
@ -186,6 +187,8 @@ public class InviteOrganizationUserCommandTests
|
|||||||
performedBy: Guid.Empty,
|
performedBy: Guid.Empty,
|
||||||
timeProvider.GetUtcNow());
|
timeProvider.GetUtcNow());
|
||||||
|
|
||||||
|
var validationRequest = GetInviteValidationRequestMock(request, inviteOrganization, organization);
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
|
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
|
||||||
.Returns([]);
|
.Returns([]);
|
||||||
@ -196,7 +199,8 @@ public class InviteOrganizationUserCommandTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IInviteUsersValidator>()
|
sutProvider.GetDependency<IInviteUsersValidator>()
|
||||||
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
|
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
|
||||||
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(new Error<InviteUserOrganizationValidationRequest>(errorMessage, new InviteUserOrganizationValidationRequest())));
|
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(
|
||||||
|
new Error<InviteUserOrganizationValidationRequest>(errorMessage, validationRequest)));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
|
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
|
||||||
@ -496,7 +500,7 @@ public class InviteOrganizationUserCommandTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<Failure<ScimInviteOrganizationUsersResponse>>(result);
|
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
|
// org user revert
|
||||||
await orgUserRepository.Received(1).DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(x => x.Count() == 1));
|
await orgUserRepository.Received(1).DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(x => x.Count() == 1));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user