mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 08:32:50 -05:00
[PM-3176] Extract IOrganizationService.SaveUserAsync to a command (#3894)
* [PM-3176] Extract IOrganizationService.SaveUserAsync to a command * [PM-3176] Enabled nullable on command * [PM-3176] Removed check that was not working
This commit is contained in:
@ -38,6 +38,7 @@ public class OrganizationUsersController : Controller
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||
private readonly IUpdateOrganizationUserCommand _updateOrganizationUserCommand;
|
||||
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
|
||||
private readonly IAcceptOrgUserCommand _acceptOrgUserCommand;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
@ -55,6 +56,7 @@ public class OrganizationUsersController : Controller
|
||||
ICurrentContext currentContext,
|
||||
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||
IUpdateOrganizationUserCommand updateOrganizationUserCommand,
|
||||
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
|
||||
IAcceptOrgUserCommand acceptOrgUserCommand,
|
||||
IAuthorizationService authorizationService,
|
||||
@ -71,6 +73,7 @@ public class OrganizationUsersController : Controller
|
||||
_currentContext = currentContext;
|
||||
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
_updateOrganizationUserCommand = updateOrganizationUserCommand;
|
||||
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
|
||||
_acceptOrgUserCommand = acceptOrgUserCommand;
|
||||
_authorizationService = authorizationService;
|
||||
@ -338,7 +341,7 @@ public class OrganizationUsersController : Controller
|
||||
? null
|
||||
: model.Groups;
|
||||
|
||||
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId,
|
||||
await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), userId,
|
||||
model.Collections?.Select(c => c.ToSelectionReadOnly()).ToList(), groups);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ public class MembersController : Controller
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUpdateOrganizationUserCommand _updateOrganizationUserCommand;
|
||||
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
|
||||
@ -30,6 +31,7 @@ public class MembersController : Controller
|
||||
IOrganizationService organizationService,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext,
|
||||
IUpdateOrganizationUserCommand updateOrganizationUserCommand,
|
||||
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
|
||||
IApplicationCacheService applicationCacheService)
|
||||
{
|
||||
@ -38,6 +40,7 @@ public class MembersController : Controller
|
||||
_organizationService = organizationService;
|
||||
_userService = userService;
|
||||
_currentContext = currentContext;
|
||||
_updateOrganizationUserCommand = updateOrganizationUserCommand;
|
||||
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
}
|
||||
@ -154,7 +157,7 @@ public class MembersController : Controller
|
||||
var updatedUser = model.ToOrganizationUser(existingUser);
|
||||
var flexibleCollectionsIsEnabled = await FlexibleCollectionsIsEnabledAsync(_currentContext.OrganizationId.Value);
|
||||
var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection(flexibleCollectionsIsEnabled)).ToList();
|
||||
await _organizationService.SaveUserAsync(updatedUser, null, associations, model.Groups);
|
||||
await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, null, associations, model.Groups);
|
||||
MemberResponseModel response = null;
|
||||
if (existingUser.UserId.HasValue)
|
||||
{
|
||||
|
@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationUserCommand
|
||||
{
|
||||
Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid>? groups);
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
#nullable enable
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
|
||||
{
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||
|
||||
public UpdateOrganizationUserCommand(
|
||||
IEventService eventService,
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand)
|
||||
{
|
||||
_eventService = eventService;
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
}
|
||||
|
||||
public async Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId,
|
||||
IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid>? groups)
|
||||
{
|
||||
if (user.Id.Equals(default(Guid)))
|
||||
{
|
||||
throw new BadRequestException("Invite the user first.");
|
||||
}
|
||||
|
||||
var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id);
|
||||
|
||||
if (savingUserId.HasValue)
|
||||
{
|
||||
await _organizationService.ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type, user.GetPermissions());
|
||||
}
|
||||
|
||||
await _organizationService.ValidateOrganizationCustomPermissionsEnabledAsync(user.OrganizationId, user.Type);
|
||||
|
||||
if (user.Type != OrganizationUserType.Owner &&
|
||||
!await _organizationService.HasConfirmedOwnersExceptAsync(user.OrganizationId, new[] { user.Id }))
|
||||
{
|
||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||
}
|
||||
|
||||
// If the organization is using Flexible Collections, prevent use of any deprecated permissions
|
||||
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
|
||||
if (organization.FlexibleCollections && user.Type == OrganizationUserType.Manager)
|
||||
{
|
||||
throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead.");
|
||||
}
|
||||
|
||||
if (organization.FlexibleCollections && user.AccessAll)
|
||||
{
|
||||
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead.");
|
||||
}
|
||||
|
||||
if (organization.FlexibleCollections && collections?.Any() == true)
|
||||
{
|
||||
var invalidAssociations = collections.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
|
||||
if (invalidAssociations.Any())
|
||||
{
|
||||
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
|
||||
}
|
||||
}
|
||||
// End Flexible Collections
|
||||
|
||||
// Only autoscale (if required) after all validation has passed so that we know it's a valid request before
|
||||
// updating Stripe
|
||||
if (!originalUser.AccessSecretsManager && user.AccessSecretsManager)
|
||||
{
|
||||
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1);
|
||||
if (additionalSmSeatsRequired > 0)
|
||||
{
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, true)
|
||||
.AdjustSeats(additionalSmSeatsRequired);
|
||||
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
|
||||
}
|
||||
}
|
||||
|
||||
if (user.AccessAll)
|
||||
{
|
||||
// We don't need any collections if we're flagged to have all access.
|
||||
collections = new List<CollectionAccessSelection>();
|
||||
}
|
||||
await _organizationUserRepository.ReplaceAsync(user, collections);
|
||||
|
||||
if (groups != null)
|
||||
{
|
||||
await _organizationUserRepository.UpdateGroupsAsync(user.Id, groups);
|
||||
}
|
||||
|
||||
await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated);
|
||||
}
|
||||
}
|
@ -56,7 +56,6 @@ public interface IOrganizationService
|
||||
Guid confirmingUserId, IUserService userService);
|
||||
Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId, IUserService userService);
|
||||
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, ICollection<CollectionAccessSelection> collections, IEnumerable<Guid> groups);
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
@ -93,4 +92,5 @@ public interface IOrganizationService
|
||||
void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade);
|
||||
Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
|
||||
OrganizationUserType? oldType, Permissions permissions);
|
||||
Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType);
|
||||
}
|
||||
|
@ -1467,84 +1467,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveUserAsync(OrganizationUser user, Guid? savingUserId,
|
||||
ICollection<CollectionAccessSelection> collections,
|
||||
IEnumerable<Guid> groups)
|
||||
{
|
||||
if (user.Id.Equals(default(Guid)))
|
||||
{
|
||||
throw new BadRequestException("Invite the user first.");
|
||||
}
|
||||
|
||||
var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id);
|
||||
if (user.Equals(originalUser))
|
||||
{
|
||||
throw new BadRequestException("Please make changes before saving.");
|
||||
}
|
||||
|
||||
if (savingUserId.HasValue)
|
||||
{
|
||||
await ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type, user.GetPermissions());
|
||||
}
|
||||
|
||||
await ValidateOrganizationCustomPermissionsEnabledAsync(user.OrganizationId, user.Type);
|
||||
|
||||
if (user.Type != OrganizationUserType.Owner &&
|
||||
!await HasConfirmedOwnersExceptAsync(user.OrganizationId, new[] { user.Id }))
|
||||
{
|
||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||
}
|
||||
|
||||
// If the organization is using Flexible Collections, prevent use of any deprecated permissions
|
||||
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
|
||||
if (organization.FlexibleCollections && user.Type == OrganizationUserType.Manager)
|
||||
{
|
||||
throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead.");
|
||||
}
|
||||
|
||||
if (organization.FlexibleCollections && user.AccessAll)
|
||||
{
|
||||
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead.");
|
||||
}
|
||||
|
||||
if (organization.FlexibleCollections && collections?.Any() == true)
|
||||
{
|
||||
var invalidAssociations = collections.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
|
||||
if (invalidAssociations.Any())
|
||||
{
|
||||
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
|
||||
}
|
||||
}
|
||||
// End Flexible Collections
|
||||
|
||||
// Only autoscale (if required) after all validation has passed so that we know it's a valid request before
|
||||
// updating Stripe
|
||||
if (!originalUser.AccessSecretsManager && user.AccessSecretsManager)
|
||||
{
|
||||
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1);
|
||||
if (additionalSmSeatsRequired > 0)
|
||||
{
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, true)
|
||||
.AdjustSeats(additionalSmSeatsRequired);
|
||||
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
|
||||
}
|
||||
}
|
||||
|
||||
if (user.AccessAll)
|
||||
{
|
||||
// We don't need any collections if we're flagged to have all access.
|
||||
collections = new List<CollectionAccessSelection>();
|
||||
}
|
||||
await _organizationUserRepository.ReplaceAsync(user, collections);
|
||||
|
||||
if (groups != null)
|
||||
{
|
||||
await _organizationUserRepository.UpdateGroupsAsync(user.Id, groups);
|
||||
}
|
||||
|
||||
await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated);
|
||||
}
|
||||
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||
{
|
||||
@ -2182,7 +2104,7 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType)
|
||||
public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType)
|
||||
{
|
||||
if (newType != OrganizationUserType.Custom)
|
||||
{
|
||||
|
@ -90,6 +90,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
private static void AddOrganizationUserCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IDeleteOrganizationUserCommand, DeleteOrganizationUserCommand>();
|
||||
services.AddScoped<IUpdateOrganizationUserCommand, UpdateOrganizationUserCommand>();
|
||||
services.AddScoped<IUpdateOrganizationUserGroupsCommand, UpdateOrganizationUserGroupsCommand>();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user