mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 21:18:13 -05:00
created response model and split interface out.
This commit is contained in:
parent
5d4f58aacb
commit
4ff27fd668
@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Commands;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -65,9 +66,9 @@ public class PostUserCommand(
|
|||||||
|
|
||||||
var result = await inviteOrganizationUsersCommand.InviteScimOrganizationUserAsync(request);
|
var result = await inviteOrganizationUsersCommand.InviteScimOrganizationUserAsync(request);
|
||||||
|
|
||||||
if (result.Success)
|
if (result is Success<ScimInviteOrganizationUsersResponse> successfulResponse)
|
||||||
{
|
{
|
||||||
var invitedUser = await organizationUserRepository.GetDetailsByIdAsync(result.Value.Id);
|
var invitedUser = await organizationUserRepository.GetDetailsByIdAsync(successfulResponse.Value.InvitedUser.Id);
|
||||||
|
|
||||||
return invitedUser;
|
return invitedUser;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||||
|
using Bit.Core.Models.Commands;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||||
|
|
||||||
|
public interface IInviteOrganizationUsersCommand
|
||||||
|
{
|
||||||
|
Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(InviteScimOrganizationUserRequest request);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Interfaces;
|
||||||
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.Context;
|
using Bit.Core.Context;
|
||||||
@ -17,17 +18,6 @@ using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Invite
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||||
|
|
||||||
public static class InviteOrganizationUsersErrorMessages
|
|
||||||
{
|
|
||||||
public const string IssueNotifyingOwnersOfSeatLimitReached = "Error encountered notifying organization owners of seat limit reached.";
|
|
||||||
public const string FailedToInviteUsers = "Failed to invite user(s).";
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IInviteOrganizationUsersCommand
|
|
||||||
{
|
|
||||||
Task<CommandResult<OrganizationUser>> InviteScimOrganizationUserAsync(InviteScimOrganizationUserRequest request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InviteOrganizationUsersCommand(IEventService eventService,
|
public class InviteOrganizationUsersCommand(IEventService eventService,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IInviteUsersValidation inviteUsersValidation,
|
IInviteUsersValidation inviteUsersValidation,
|
||||||
@ -42,23 +32,28 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand
|
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand
|
||||||
) : IInviteOrganizationUsersCommand
|
) : IInviteOrganizationUsersCommand
|
||||||
{
|
{
|
||||||
public async Task<CommandResult<OrganizationUser>> InviteScimOrganizationUserAsync(InviteScimOrganizationUserRequest request)
|
|
||||||
|
public const string IssueNotifyingOwnersOfSeatLimitReached = "Error encountered notifying organization owners of seat limit reached.";
|
||||||
|
public const string FailedToInviteUsers = "Failed to invite user(s).";
|
||||||
|
|
||||||
|
public async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(InviteScimOrganizationUserRequest request)
|
||||||
{
|
{
|
||||||
var result = await InviteOrganizationUsersAsync(InviteOrganizationUsersRequest.Create(request));
|
var result = await InviteOrganizationUsersAsync(InviteOrganizationUsersRequest.Create(request));
|
||||||
|
|
||||||
if (result is Failure<IEnumerable<OrganizationUser>> failure)
|
if (result is Failure<IEnumerable<OrganizationUser>> failure)
|
||||||
{
|
{
|
||||||
return new Failure<OrganizationUser>(failure.ErrorMessage);
|
return new Failure<ScimInviteOrganizationUsersResponse>(failure.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.Value.Any())
|
if (result.Value.Any())
|
||||||
{
|
{
|
||||||
(OrganizationUser User, EventType type, EventSystemUser system, DateTime performedAt) log = (result.Value.First(), EventType.OrganizationUser_Invited, EventSystemUser.SCIM, request.PerformedAt.UtcDateTime);
|
await eventService.LogOrganizationUserEventAsync((IOrganizationUser)result.Value.First(), EventType.OrganizationUser_Invited, EventSystemUser.SCIM, request.PerformedAt.UtcDateTime);
|
||||||
|
|
||||||
await eventService.LogOrganizationUserEventsAsync([log]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Success<OrganizationUser>(result.Value.FirstOrDefault());
|
return new Success<ScimInviteOrganizationUsersResponse>(new ScimInviteOrganizationUsersResponse
|
||||||
|
{
|
||||||
|
InvitedUser = result.Value.FirstOrDefault()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CommandResult<IEnumerable<OrganizationUser>>> InviteOrganizationUsersAsync(InviteOrganizationUsersRequest request)
|
private async Task<CommandResult<IEnumerable<OrganizationUser>>> InviteOrganizationUsersAsync(InviteOrganizationUsersRequest request)
|
||||||
@ -71,9 +66,9 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
.SelectMany(invite => invite.Emails
|
.SelectMany(invite => invite.Emails
|
||||||
.Where(email => !existingEmails.Contains(email))
|
.Where(email => !existingEmails.Contains(email))
|
||||||
.Select(email => OrganizationUserInviteDto.Create(email, invite, request.Organization.OrganizationId))
|
.Select(email => OrganizationUserInviteDto.Create(email, invite, request.Organization.OrganizationId))
|
||||||
);
|
).ToArray();
|
||||||
|
|
||||||
if (invitesToSend.Any() is false)
|
if (invitesToSend.Length == 0)
|
||||||
{
|
{
|
||||||
return new Success<IEnumerable<OrganizationUser>>([]);
|
return new Success<IEnumerable<OrganizationUser>>([]);
|
||||||
}
|
}
|
||||||
@ -93,38 +88,38 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
return new Failure<IEnumerable<OrganizationUser>>(invalid.ErrorMessageString);
|
return new Failure<IEnumerable<OrganizationUser>>(invalid.ErrorMessageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
var valid = validationResult as Valid<InviteUserOrganizationValidationRequest>;
|
var validatedRequest = validationResult as Valid<InviteUserOrganizationValidationRequest>;
|
||||||
|
|
||||||
var organizationUserCollection = invitesToSend
|
var organizationUserCollection = invitesToSend
|
||||||
.Select(MapToDataModel(request.PerformedAt))
|
.Select(MapToDataModel(request.PerformedAt))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var organization = await organizationRepository.GetByIdAsync(valid.Value.Organization.OrganizationId);
|
var organization = await organizationRepository.GetByIdAsync(validatedRequest.Value.Organization.OrganizationId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await organizationUserRepository.CreateManyAsync(organizationUserCollection);
|
await organizationUserRepository.CreateManyAsync(organizationUserCollection);
|
||||||
|
|
||||||
await AdjustPasswordManagerSeatsAsync(valid, organization);
|
await AdjustPasswordManagerSeatsAsync(validatedRequest, organization);
|
||||||
|
|
||||||
await AdjustSecretsManagerSeatsAsync(valid, organization);
|
await AdjustSecretsManagerSeatsAsync(validatedRequest, organization);
|
||||||
|
|
||||||
await SendNotificationsAsync(valid, organization);
|
await SendAdditionalEmailsAsync(validatedRequest, organization);
|
||||||
|
|
||||||
await SendInvitesAsync(organizationUserCollection, organization);
|
await SendInvitesAsync(organizationUserCollection, organization);
|
||||||
|
|
||||||
await PublishEventAsync(valid, organization);
|
await PublishReferenceEventAsync(validatedRequest, organization);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, InviteOrganizationUsersErrorMessages.FailedToInviteUsers);
|
logger.LogError(ex, FailedToInviteUsers);
|
||||||
|
|
||||||
await organizationUserRepository.DeleteManyAsync(organizationUserCollection.Select(x => x.User.Id));
|
await organizationUserRepository.DeleteManyAsync(organizationUserCollection.Select(x => x.User.Id));
|
||||||
|
|
||||||
await RevertSecretsManagerChangesAsync(valid, organization);
|
await RevertSecretsManagerChangesAsync(validatedRequest, organization);
|
||||||
|
|
||||||
await RevertPasswordManagerChangesAsync(valid, organization);
|
await RevertPasswordManagerChangesAsync(validatedRequest, organization);
|
||||||
|
|
||||||
return new Failure<IEnumerable<OrganizationUser>>(InviteOrganizationUsersErrorMessages.FailedToInviteUsers);
|
return new Failure<IEnumerable<OrganizationUser>>(FailedToInviteUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Success<IEnumerable<OrganizationUser>>(organizationUserCollection.Select(x => x.User));
|
return new Success<IEnumerable<OrganizationUser>>(organizationUserCollection.Select(x => x.User));
|
||||||
@ -132,7 +127,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
|
|
||||||
private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
|
private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
|
||||||
{
|
{
|
||||||
if (valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd < 0)
|
if (valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd > 0)
|
||||||
{
|
{
|
||||||
await paymentService.AdjustSeatsAsync(organization, valid.Value.Organization.Plan, -valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd);
|
await paymentService.AdjustSeatsAsync(organization, valid.Value.Organization.Plan, -valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd);
|
||||||
|
|
||||||
@ -156,7 +151,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PublishEventAsync(Valid<InviteUserOrganizationValidationRequest> valid,
|
private async Task PublishReferenceEventAsync(Valid<InviteUserOrganizationValidationRequest> valid,
|
||||||
Organization organization) =>
|
Organization organization) =>
|
||||||
await referenceEventService.RaiseEventAsync(
|
await referenceEventService.RaiseEventAsync(
|
||||||
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, currentContext)
|
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, currentContext)
|
||||||
@ -170,7 +165,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
users.Select(x => x.User),
|
users.Select(x => x.User),
|
||||||
organization));
|
organization));
|
||||||
|
|
||||||
private async Task SendNotificationsAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
|
private async Task SendAdditionalEmailsAsync(Valid<InviteUserOrganizationValidationRequest> valid, Organization organization)
|
||||||
{
|
{
|
||||||
await SendPasswordManagerMaxSeatLimitEmailsAsync(valid, organization);
|
await SendPasswordManagerMaxSeatLimitEmailsAsync(valid, organization);
|
||||||
}
|
}
|
||||||
@ -194,7 +189,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, InviteOrganizationUsersErrorMessages.IssueNotifyingOwnersOfSeatLimitReached);
|
logger.LogError(ex, IssueNotifyingOwnersOfSeatLimitReached);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +213,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the important steps
|
|
||||||
await paymentService.AdjustSeatsAsync(organization, valid.Value.Organization.Plan, valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd);
|
await paymentService.AdjustSeatsAsync(organization, valid.Value.Organization.Plan, valid.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd);
|
||||||
|
|
||||||
organization.Seats = (short?)valid.Value.PasswordManagerSubscriptionUpdate.UpdatedSeatTotal;
|
organization.Seats = (short?)valid.Value.PasswordManagerSubscriptionUpdate.UpdatedSeatTotal;
|
||||||
@ -226,7 +220,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
await organizationRepository.ReplaceAsync(organization); // could optimize this with only a property update
|
await organizationRepository.ReplaceAsync(organization); // could optimize this with only a property update
|
||||||
await applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
await applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||||
|
|
||||||
// Do we want to fail if this fails?
|
|
||||||
await referenceEventService.RaiseEventAsync(
|
await referenceEventService.RaiseEventAsync(
|
||||||
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization, currentContext)
|
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization, currentContext)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
using Bit.Core.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||||
|
|
||||||
|
public class InviteOrganizationUsersResponse
|
||||||
|
{
|
||||||
|
public IEnumerable<OrganizationUser> InvitedUsers { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScimInviteOrganizationUsersResponse
|
||||||
|
{
|
||||||
|
public OrganizationUser InvitedUser { get; init; }
|
||||||
|
|
||||||
|
public static ScimInviteOrganizationUsersResponse Create(OrganizationUser invitedUser)
|
||||||
|
{
|
||||||
|
return new ScimInviteOrganizationUsersResponse
|
||||||
|
{
|
||||||
|
InvitedUser = invitedUser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -161,8 +161,8 @@ public class InviteOrganizationUserCommandTests
|
|||||||
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
|
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<Failure<OrganizationUser>>(result);
|
Assert.IsType<Failure<ScimInviteOrganizationUsersResponse>>(result);
|
||||||
var failure = result as Failure<OrganizationUser>;
|
var failure = result as Failure<ScimInviteOrganizationUsersResponse>;
|
||||||
|
|
||||||
Assert.Equal(errorMessage, failure.ErrorMessage);
|
Assert.Equal(errorMessage, failure.ErrorMessage);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user