diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index f42d81c43..28f319235 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -8,6 +8,8 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.Policies; +using Bit.Core.OrganizationFeatures.OrganizationUsers; +using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; @@ -27,6 +29,7 @@ public class OrganizationUsersController : Controller private readonly IUserService _userService; private readonly IPolicyRepository _policyRepository; private readonly ICurrentContext _currentContext; + private readonly IUpdateUserResetPasswordEnrollmentCommand _updateUserResetPasswordEnrollmentCommand; public OrganizationUsersController( IOrganizationRepository organizationRepository, @@ -36,6 +39,7 @@ public class OrganizationUsersController : Controller IGroupRepository groupRepository, IUserService userService, IPolicyRepository policyRepository, + IUpdateUserResetPasswordEnrollmentCommand updateUserResetPasswordEnrollmentCommand, ICurrentContext currentContext) { _organizationRepository = organizationRepository; @@ -45,6 +49,7 @@ public class OrganizationUsersController : Controller _groupRepository = groupRepository; _userService = userService; _policyRepository = policyRepository; + _updateUserResetPasswordEnrollmentCommand = updateUserResetPasswordEnrollmentCommand; _currentContext = currentContext; } @@ -213,7 +218,7 @@ public class OrganizationUsersController : Controller if (useMasterPasswordPolicy) { - await _organizationService.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, _userService, user.Id); + await _updateUserResetPasswordEnrollmentCommand.UpdateAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); } } @@ -314,8 +319,14 @@ public class OrganizationUsersController : Controller } var callingUserId = user.Id; - await _organizationService.UpdateUserResetPasswordEnrollmentAsync( - new Guid(orgId), new Guid(userId), model.ResetPasswordKey, _userService, callingUserId); + await _updateUserResetPasswordEnrollmentCommand.UpdateAsync( + new Guid(orgId), new Guid(userId), model.ResetPasswordKey, callingUserId); + + //if (orgUser.Status == OrganizationUserStatusType.Invited) + //{ + // var user = await _userRepository.GetByIdAsync(userId); + // await _organizationService.AcceptUserAsync(orgUser, user, _userService); + //} } [HttpPut("{id}/reset-password")] diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 983fa3b35..eb6296419 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -15,6 +15,8 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted; +using Bit.Core.OrganizationFeatures.OrganizationUsers; +using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tokens; @@ -38,6 +40,7 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationGroupCommands(); services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationDomainCommandsQueries(); + services.AddOrganizationUserCommandsQueries(); } private static void AddOrganizationConnectionCommands(this IServiceCollection services) @@ -107,6 +110,11 @@ public static class OrganizationServiceCollectionExtensions services.AddScoped(); } + private static void AddOrganizationUserCommandsQueries(this IServiceCollection services) + { + services.AddScoped(); + } + private static void AddTokenizers(this IServiceCollection services) { services.AddSingleton>(serviceProvider => diff --git a/src/Core/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateUserResetPasswordEnrollmentCommand.cs b/src/Core/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateUserResetPasswordEnrollmentCommand.cs new file mode 100644 index 000000000..7b3d73013 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateUserResetPasswordEnrollmentCommand.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; + +public interface IUpdateUserResetPasswordEnrollmentCommand +{ + Task UpdateAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId); +} + diff --git a/src/Core/OrganizationFeatures/OrganizationUsers/UpdateUserResetPasswordEnrollmentCommand.cs b/src/Core/OrganizationFeatures/OrganizationUsers/UpdateUserResetPasswordEnrollmentCommand.cs new file mode 100644 index 000000000..83321fd95 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationUsers/UpdateUserResetPasswordEnrollmentCommand.cs @@ -0,0 +1,73 @@ +using System.Text.Json; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.Policies; +using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Utilities; + +namespace Bit.Core.OrganizationFeatures.OrganizationUsers; + +public class UpdateUserResetPasswordEnrollmentCommand : IUpdateUserResetPasswordEnrollmentCommand +{ + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IEventService _eventService; + private readonly IPolicyRepository _policyRepository; + + public UpdateUserResetPasswordEnrollmentCommand( + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + IEventService eventService, + IPolicyRepository policyRepository) + { + _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; + _eventService = eventService; + _policyRepository = policyRepository; + } + + public async Task UpdateAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId) + { + // Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID + var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId); + if (!callingUserId.HasValue || orgUser == null || orgUser.UserId != callingUserId.Value || + orgUser.OrganizationId != organizationId) + { + throw new BadRequestException("User not valid."); + } + + // Make sure the organization has the ability to use password reset + var org = await _organizationRepository.GetByIdAsync(organizationId); + if (org == null || !org.UseResetPassword) + { + throw new BadRequestException("Organization does not allow password reset enrollment."); + } + + // Make sure the organization has the policy enabled + var resetPasswordPolicy = + await _policyRepository.GetByOrganizationIdTypeAsync(organizationId, PolicyType.ResetPassword); + if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled) + { + throw new BadRequestException("Organization does not have the password reset policy enabled."); + } + + // Block the user from withdrawal if auto enrollment is enabled + if (resetPasswordKey == null && resetPasswordPolicy.Data != null) + { + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); + + if (data?.AutoEnrollEnabled ?? false) + { + throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from Password Reset."); + } + } + + orgUser.ResetPasswordKey = resetPasswordKey; + await _organizationUserRepository.ReplaceAsync(orgUser); + await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ? + EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); + } +} + diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index e7b2ab284..a1b47594e 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -55,7 +55,6 @@ public interface IOrganizationService Task>> DeleteUsersAsync(Guid organizationId, IEnumerable organizationUserIds, Guid? deletingUserId); Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable groupIds, Guid? loggedInUserId); - Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, IUserService userService, Guid? callingUserId); Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable groups, IEnumerable newUsers, IEnumerable removeUserExternalIds, bool overwriteExisting); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 2efbd2788..0b7681ce9 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -11,7 +11,6 @@ using Bit.Core.Enums.Provider; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data; -using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tools.Enums; @@ -38,7 +37,6 @@ public class OrganizationService : IOrganizationService private readonly IDeviceRepository _deviceRepository; private readonly ILicensingService _licensingService; private readonly IEventService _eventService; - private readonly IInstallationRepository _installationRepository; private readonly IApplicationCacheService _applicationCacheService; private readonly IPaymentService _paymentService; private readonly IPolicyRepository _policyRepository; @@ -67,7 +65,6 @@ public class OrganizationService : IOrganizationService IDeviceRepository deviceRepository, ILicensingService licensingService, IEventService eventService, - IInstallationRepository installationRepository, IApplicationCacheService applicationCacheService, IPaymentService paymentService, IPolicyRepository policyRepository, @@ -95,7 +92,6 @@ public class OrganizationService : IOrganizationService _deviceRepository = deviceRepository; _licensingService = licensingService; _eventService = eventService; - _installationRepository = installationRepository; _applicationCacheService = applicationCacheService; _paymentService = paymentService; _policyRepository = policyRepository; @@ -1734,54 +1730,6 @@ public class OrganizationService : IOrganizationService EventType.OrganizationUser_UpdatedGroups); } - public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, IUserService userService, Guid? callingUserId) - { - // Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID - var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId); - if (!callingUserId.HasValue || orgUser == null || orgUser.UserId != callingUserId.Value || - orgUser.OrganizationId != organizationId) - { - throw new BadRequestException("User not valid."); - } - - // Make sure the organization has the ability to use password reset - var org = await _organizationRepository.GetByIdAsync(organizationId); - if (org == null || !org.UseResetPassword) - { - throw new BadRequestException("Organization does not allow password reset enrollment."); - } - - // Make sure the organization has the policy enabled - var resetPasswordPolicy = - await _policyRepository.GetByOrganizationIdTypeAsync(organizationId, PolicyType.ResetPassword); - if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled) - { - throw new BadRequestException("Organization does not have the password reset policy enabled."); - } - - // Block the user from withdrawal if auto enrollment is enabled - if (resetPasswordKey == null && resetPasswordPolicy.Data != null) - { - var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); - - if (data?.AutoEnrollEnabled ?? false) - { - throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from Password Reset."); - } - } - - orgUser.ResetPasswordKey = resetPasswordKey; - await _organizationUserRepository.ReplaceAsync(orgUser); - await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ? - EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); - - if (orgUser.Status == OrganizationUserStatusType.Invited) - { - var user = await _userRepository.GetByIdAsync(userId); - await AcceptUserAsync(orgUser, user, userService); - } - } - public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email, OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections, IEnumerable groups)