1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

[PM-1033] refactor: UpdateUserResetPasswordEnrollmentCommand

This commit is contained in:
Andreas Coroiu 2023-06-29 11:32:50 +02:00
parent dc503b3035
commit 43df689c7f
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
6 changed files with 102 additions and 56 deletions

View File

@ -8,6 +8,8 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies; 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.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -27,6 +29,7 @@ public class OrganizationUsersController : Controller
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IUpdateUserResetPasswordEnrollmentCommand _updateUserResetPasswordEnrollmentCommand;
public OrganizationUsersController( public OrganizationUsersController(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -36,6 +39,7 @@ public class OrganizationUsersController : Controller
IGroupRepository groupRepository, IGroupRepository groupRepository,
IUserService userService, IUserService userService,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
IUpdateUserResetPasswordEnrollmentCommand updateUserResetPasswordEnrollmentCommand,
ICurrentContext currentContext) ICurrentContext currentContext)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -45,6 +49,7 @@ public class OrganizationUsersController : Controller
_groupRepository = groupRepository; _groupRepository = groupRepository;
_userService = userService; _userService = userService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_updateUserResetPasswordEnrollmentCommand = updateUserResetPasswordEnrollmentCommand;
_currentContext = currentContext; _currentContext = currentContext;
} }
@ -213,7 +218,7 @@ public class OrganizationUsersController : Controller
if (useMasterPasswordPolicy) 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; var callingUserId = user.Id;
await _organizationService.UpdateUserResetPasswordEnrollmentAsync( await _updateUserResetPasswordEnrollmentCommand.UpdateAsync(
new Guid(orgId), new Guid(userId), model.ResetPasswordKey, _userService, callingUserId); 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")] [HttpPut("{id}/reset-password")]

View File

@ -15,6 +15,8 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted; 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.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tokens; using Bit.Core.Tokens;
@ -38,6 +40,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddOrganizationGroupCommands(); services.AddOrganizationGroupCommands();
services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationLicenseCommandsQueries();
services.AddOrganizationDomainCommandsQueries(); services.AddOrganizationDomainCommandsQueries();
services.AddOrganizationUserCommandsQueries();
} }
private static void AddOrganizationConnectionCommands(this IServiceCollection services) private static void AddOrganizationConnectionCommands(this IServiceCollection services)
@ -107,6 +110,11 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IDeleteOrganizationDomainCommand, DeleteOrganizationDomainCommand>(); services.AddScoped<IDeleteOrganizationDomainCommand, DeleteOrganizationDomainCommand>();
} }
private static void AddOrganizationUserCommandsQueries(this IServiceCollection services)
{
services.AddScoped<IUpdateUserResetPasswordEnrollmentCommand, UpdateUserResetPasswordEnrollmentCommand>();
}
private static void AddTokenizers(this IServiceCollection services) private static void AddTokenizers(this IServiceCollection services)
{ {
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider => services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>

View File

@ -0,0 +1,7 @@
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IUpdateUserResetPasswordEnrollmentCommand
{
Task UpdateAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
}

View File

@ -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<ResetPasswordDataModel>(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);
}
}

View File

@ -55,7 +55,6 @@ public interface IOrganizationService
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId, Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId); IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId); Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, IUserService userService, Guid? callingUserId);
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups, Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds, IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting); bool overwriteExisting);

View File

@ -11,7 +11,6 @@ using Bit.Core.Enums.Provider;
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.Data;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Enums; using Bit.Core.Tools.Enums;
@ -38,7 +37,6 @@ public class OrganizationService : IOrganizationService
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly ILicensingService _licensingService; private readonly ILicensingService _licensingService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IInstallationRepository _installationRepository;
private readonly IApplicationCacheService _applicationCacheService; private readonly IApplicationCacheService _applicationCacheService;
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
@ -67,7 +65,6 @@ public class OrganizationService : IOrganizationService
IDeviceRepository deviceRepository, IDeviceRepository deviceRepository,
ILicensingService licensingService, ILicensingService licensingService,
IEventService eventService, IEventService eventService,
IInstallationRepository installationRepository,
IApplicationCacheService applicationCacheService, IApplicationCacheService applicationCacheService,
IPaymentService paymentService, IPaymentService paymentService,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
@ -95,7 +92,6 @@ public class OrganizationService : IOrganizationService
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_licensingService = licensingService; _licensingService = licensingService;
_eventService = eventService; _eventService = eventService;
_installationRepository = installationRepository;
_applicationCacheService = applicationCacheService; _applicationCacheService = applicationCacheService;
_paymentService = paymentService; _paymentService = paymentService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
@ -1734,54 +1730,6 @@ public class OrganizationService : IOrganizationService
EventType.OrganizationUser_UpdatedGroups); 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<ResetPasswordDataModel>(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<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email, public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups) IEnumerable<Guid> groups)