1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-10 06:02:24 -05:00

rename OrganizatinUserInvite for command, implement command

This commit is contained in:
Brandon 2025-05-08 13:26:52 -04:00
parent 1f5ea44a07
commit f47a65cfad
No known key found for this signature in database
GPG Key ID: A0E0EF0B207BA40D
10 changed files with 48 additions and 63 deletions

View File

@ -4,7 +4,7 @@ using Bit.Api.Models.Public.Response;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Services; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Settings; using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -15,18 +15,18 @@ namespace Bit.Api.AdminConsole.Public.Controllers;
[Authorize("Organization")] [Authorize("Organization")]
public class OrganizationController : Controller public class OrganizationController : Controller
{ {
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IImportOrganizationUserCommand _importOrganizationUserCommand;
public OrganizationController( public OrganizationController(
IOrganizationService organizationService,
ICurrentContext currentContext, ICurrentContext currentContext,
GlobalSettings globalSettings) GlobalSettings globalSettings,
IImportOrganizationUserCommand importOrganizationUserCommand)
{ {
_organizationService = organizationService;
_currentContext = currentContext; _currentContext = currentContext;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_importOrganizationUserCommand = importOrganizationUserCommand;
} }
/// <summary> /// <summary>
@ -47,7 +47,7 @@ public class OrganizationController : Controller
throw new BadRequestException("You cannot import this much data at once."); throw new BadRequestException("You cannot import this much data at once.");
} }
await _organizationService.ImportAsync( await _importOrganizationUserCommand.ImportAsync(
_currentContext.OrganizationId.Value, _currentContext.OrganizationId.Value,
model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)), model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)),
model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()),

View File

@ -1,12 +1,15 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
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.Models;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Pricing;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data; using Bit.Core.Models.Commands;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -14,7 +17,6 @@ using Bit.Core.Services;
using Bit.Core.Tools.Enums; using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business; using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services; using Bit.Core.Tools.Services;
using OrganizationUserInvite = Bit.Core.Models.Business.OrganizationUserInvite;
public class ImportOrganizationUserCommand : IImportOrganizationUserCommand public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
{ {
@ -26,6 +28,8 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IInviteOrganizationUsersCommand _inviteOrganizationUsersCommand;
private readonly IPricingClient _pricingClient;
public ImportOrganizationUserCommand(IOrganizationRepository organizationRepository, public ImportOrganizationUserCommand(IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
@ -34,7 +38,9 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
IEventService eventService, IEventService eventService,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
ICurrentContext currentContext, ICurrentContext currentContext,
IOrganizationService organizationService IOrganizationService organizationService,
IInviteOrganizationUsersCommand inviteOrganizationUsersCommand,
IPricingClient pricingClient
) )
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -45,6 +51,8 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_currentContext = currentContext; _currentContext = currentContext;
_organizationService = organizationService; _organizationService = organizationService;
_inviteOrganizationUsersCommand = inviteOrganizationUsersCommand;
_pricingClient = pricingClient;
} }
public async Task ImportAsync(Guid organizationId, public async Task ImportAsync(Guid organizationId,
@ -187,7 +195,7 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization); var hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);
var userInvites = new List<(OrganizationUserInvite, string)>(); var userInvites = new List<OrganizationUserInviteCommandModel>();
foreach (var user in newUsers) foreach (var user in newUsers)
{ {
if (!usersToAdd.Contains(user.ExternalId) || string.IsNullOrWhiteSpace(user.Email)) if (!usersToAdd.Contains(user.ExternalId) || string.IsNullOrWhiteSpace(user.Email))
@ -197,14 +205,8 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
try try
{ {
var invite = new OrganizationUserInvite var invite = new OrganizationUserInviteCommandModel(user.Email, user.ExternalId);
{ userInvites.Add(new OrganizationUserInviteCommandModel(invite, hasStandaloneSecretsManager));
Emails = new List<string> { user.Email },
Type = OrganizationUserType.User,
Collections = new List<CollectionAccessSelection>(),
AccessSecretsManager = hasStandaloneSecretsManager
};
userInvites.Add((invite, user.ExternalId));
} }
catch (BadRequestException) catch (BadRequestException)
{ {
@ -213,12 +215,23 @@ public class ImportOrganizationUserCommand : IImportOrganizationUserCommand
} }
} }
//@TODO: replace with command var commandResult = await InviteUsersAsync(userInvites, organization);
var invitedUsers = await _organizationService.InviteUsersAsync(organization.Id, invitingUserId: null, systemUser: eventSystemUser, userInvites);
foreach (var invitedUser in invitedUsers) //@TODO: replace with command, add invited users from commandResult to importData
{ //var invitedUsers = await _organizationService.InviteUsersAsync(organization.Id, invitingUserId: null, systemUser: eventSystemUser, userInvites);
importData.ExistingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id); //foreach (var invitedUser in invitedUsers)
//{
// importData.ExistingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id);
//}
} }
private async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteUsersAsync(List<OrganizationUserInviteCommandModel> invites, Organization organization)
{
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
var inviteOrganization = new InviteOrganization(organization, plan);
var request = new InviteOrganizationUsersRequest(invites.ToArray(), inviteOrganization, Guid.Empty, DateTimeOffset.UtcNow);
return await _inviteOrganizationUsersCommand.InviteScimOrganizationUserAsync(request);
} }
private async Task OverwriteExisting( private async Task OverwriteExisting(

View File

@ -19,7 +19,6 @@ using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business; using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services; using Bit.Core.Tools.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@ -146,7 +145,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
organizationId: organization!.Id)); organizationId: organization!.Id));
} }
private async Task<IEnumerable<OrganizationUserInvite>> FilterExistingUsersAsync(InviteOrganizationUsersRequest request) private async Task<IEnumerable<OrganizationUserInviteCommandModel>> FilterExistingUsersAsync(InviteOrganizationUsersRequest request)
{ {
var existingEmails = new HashSet<string>(await organizationUserRepository.SelectKnownEmailsAsync( var existingEmails = new HashSet<string>(await organizationUserRepository.SelectKnownEmailsAsync(
request.InviteOrganization.OrganizationId, request.Invites.Select(i => i.Email), false), request.InviteOrganization.OrganizationId, request.Invites.Select(i => i.Email), false),

View File

@ -7,7 +7,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse
public static class CreateOrganizationUserExtensions public static class CreateOrganizationUserExtensions
{ {
public static CreateOrganizationUser MapToDataModel(this OrganizationUserInvite organizationUserInvite, public static CreateOrganizationUser MapToDataModel(this OrganizationUserInviteCommandModel organizationUserInvite,
DateTimeOffset performedAt, DateTimeOffset performedAt,
InviteOrganization organization) => InviteOrganization organization) =>
new() new()

View File

@ -4,12 +4,12 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse
public class InviteOrganizationUsersRequest public class InviteOrganizationUsersRequest
{ {
public OrganizationUserInvite[] Invites { get; } = []; public OrganizationUserInviteCommandModel[] Invites { get; } = [];
public InviteOrganization InviteOrganization { get; } public InviteOrganization InviteOrganization { get; }
public Guid PerformedBy { get; } public Guid PerformedBy { get; }
public DateTimeOffset PerformedAt { get; } public DateTimeOffset PerformedAt { get; }
public InviteOrganizationUsersRequest(OrganizationUserInvite[] invites, public InviteOrganizationUsersRequest(OrganizationUserInviteCommandModel[] invites,
InviteOrganization inviteOrganization, InviteOrganization inviteOrganization,
Guid performedBy, Guid performedBy,
DateTimeOffset performedAt) DateTimeOffset performedAt)

View File

@ -29,7 +29,7 @@ public class InviteOrganizationUsersValidationRequest
SecretsManagerSubscriptionUpdate = smSubscriptionUpdate; SecretsManagerSubscriptionUpdate = smSubscriptionUpdate;
} }
public OrganizationUserInvite[] Invites { get; init; } = []; public OrganizationUserInviteCommandModel[] Invites { get; init; } = [];
public InviteOrganization InviteOrganization { get; init; } public InviteOrganization InviteOrganization { get; init; }
public Guid PerformedBy { get; init; } public Guid PerformedBy { get; init; }
public DateTimeOffset PerformedAt { get; init; } public DateTimeOffset PerformedAt { get; init; }

View File

@ -7,7 +7,7 @@ using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Invite
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
public class OrganizationUserInvite public class OrganizationUserInviteCommandModel
{ {
public string Email { get; private init; } public string Email { get; private init; }
public CollectionAccessSelection[] AssignedCollections { get; private init; } public CollectionAccessSelection[] AssignedCollections { get; private init; }
@ -17,7 +17,7 @@ public class OrganizationUserInvite
public bool AccessSecretsManager { get; private init; } public bool AccessSecretsManager { get; private init; }
public Guid[] Groups { get; private init; } public Guid[] Groups { get; private init; }
public OrganizationUserInvite(string email, string externalId) : public OrganizationUserInviteCommandModel(string email, string externalId) :
this( this(
email: email, email: email,
assignedCollections: [], assignedCollections: [],
@ -29,7 +29,7 @@ public class OrganizationUserInvite
{ {
} }
public OrganizationUserInvite(OrganizationUserInvite invite, bool accessSecretsManager) : public OrganizationUserInviteCommandModel(OrganizationUserInviteCommandModel invite, bool accessSecretsManager) :
this(invite.Email, this(invite.Email,
invite.AssignedCollections, invite.AssignedCollections,
invite.Groups, invite.Groups,
@ -41,7 +41,7 @@ public class OrganizationUserInvite
} }
public OrganizationUserInvite(string email, public OrganizationUserInviteCommandModel(string email,
IEnumerable<CollectionAccessSelection> assignedCollections, IEnumerable<CollectionAccessSelection> assignedCollections,
IEnumerable<Guid> groups, IEnumerable<Guid> groups,
OrganizationUserType type, OrganizationUserType type,

View File

@ -6,7 +6,6 @@ using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
@ -38,7 +37,7 @@ public class InviteOrganizationUsersValidator(
request = new InviteOrganizationUsersValidationRequest(request) request = new InviteOrganizationUsersValidationRequest(request)
{ {
Invites = request.Invites Invites = request.Invites
.Select(x => new OrganizationUserInvite(x, accessSecretsManager: true)) .Select(x => new OrganizationUserInviteCommandModel(x, accessSecretsManager: true))
.ToArray() .ToArray()
}; };
} }

View File

@ -1,6 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -39,9 +38,6 @@ public interface IOrganizationService
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId); Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false); Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId); Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting, EventSystemUser eventSystemUser);
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId); Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId); Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId);
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser); Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);

View File

@ -3,7 +3,6 @@ using System.Text.Json;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@ -26,7 +25,6 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Platform.Push; using Bit.Core.Platform.Push;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -74,7 +72,6 @@ public class OrganizationService : IOrganizationService
private readonly IPricingClient _pricingClient; private readonly IPricingClient _pricingClient;
private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand; private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand;
private readonly IImportOrganizationUserCommand _importOrganizationUserCommand;
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -108,8 +105,7 @@ public class OrganizationService : IOrganizationService
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery, IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
IPricingClient pricingClient, IPricingClient pricingClient,
IPolicyRequirementQuery policyRequirementQuery, IPolicyRequirementQuery policyRequirementQuery,
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand, ISendOrganizationInvitesCommand sendOrganizationInvitesCommand
IImportOrganizationUserCommand importOrganizationUserCommand
) )
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -144,7 +140,6 @@ public class OrganizationService : IOrganizationService
_pricingClient = pricingClient; _pricingClient = pricingClient;
_policyRequirementQuery = policyRequirementQuery; _policyRequirementQuery = policyRequirementQuery;
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand; _sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
_importOrganizationUserCommand = importOrganizationUserCommand;
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
@ -1203,23 +1198,6 @@ public class OrganizationService : IOrganizationService
EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw);
} }
public async Task ImportAsync(Guid organizationId,
IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers,
IEnumerable<string> removeUserExternalIds,
bool overwriteExisting,
EventSystemUser eventSystemUser
)
{
// @TODO DEVELOPMENT FLAG FOR TESTING ---- REVERT THIS LATER
await _importOrganizationUserCommand.ImportAsync(organizationId,
groups,
newUsers,
removeUserExternalIds,
overwriteExisting,
eventSystemUser);
}
public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId) public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
{ {
await _ssoUserRepository.DeleteAsync(userId, organizationId); await _ssoUserRepository.DeleteAsync(userId, organizationId);