1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-18 16:11:28 -05:00

[PM-19703] Fix admin count logic to exclude current organization (#5918)

This commit is contained in:
Jimmy Vo
2025-06-13 16:27:48 -04:00
committed by GitHub
parent db77201ca4
commit 4a12120950
6 changed files with 126 additions and 40 deletions

View File

@ -1,11 +1,12 @@
#nullable enable
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IUpdateOrganizationUserCommand
{
Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId,
Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType, Guid? savingUserId,
List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess);
}

View File

@ -55,11 +55,13 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
/// Update an organization user.
/// </summary>
/// <param name="organizationUser">The modified organization user to save.</param>
/// <param name="existingUserType">The current type (member role) of the user.</param>
/// <param name="savingUserId">The userId of the currently logged in user who is making the change.</param>
/// <param name="collectionAccess">The user's updated collection access. If set to null, this removes all collection access.</param>
/// <param name="groupAccess">The user's updated group access. If set to null, groups are not updated.</param>
/// <exception cref="BadRequestException"></exception>
public async Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId,
public async Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType,
Guid? savingUserId,
List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess)
{
// Avoid multiple enumeration
@ -83,15 +85,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
throw new NotFoundException();
}
if (organizationUser.UserId.HasValue && organization.PlanType == PlanType.Free && organizationUser.Type is OrganizationUserType.Admin or OrganizationUserType.Owner)
{
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this.
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(organizationUser.UserId.Value);
if (adminCount > 0)
{
throw new BadRequestException("User can only be an admin of one free organization.");
}
}
await EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(organizationUser, existingUserType, organization);
if (collectionAccessList.Count != 0)
{
@ -151,6 +145,40 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated);
}
private async Task EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(OrganizationUser updatedOrgUser, OrganizationUserType existingUserType, Entities.Organization organization)
{
if (organization.PlanType != PlanType.Free)
{
return;
}
if (!updatedOrgUser.UserId.HasValue)
{
return;
}
if (updatedOrgUser.Type is not (OrganizationUserType.Admin or OrganizationUserType.Owner))
{
return;
}
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this.
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(updatedOrgUser.UserId!.Value);
var isCurrentAdminOrOwner = existingUserType is OrganizationUserType.Admin or OrganizationUserType.Owner;
if (isCurrentAdminOrOwner && adminCount <= 1)
{
return;
}
if (!isCurrentAdminOrOwner && adminCount == 0)
{
return;
}
throw new BadRequestException("User can only be an admin of one free organization.");
}
private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser,
ICollection<CollectionAccessSelection> collectionAccess)
{