1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[EC-387] Don't count revoked users towards occupied seat count (#2256)

Also autoscale seats when restoring user if required
This commit is contained in:
Thomas Rittson
2022-09-23 14:30:39 +10:00
committed by GitHub
parent c494d344d2
commit 7c3637c8ba
7 changed files with 49 additions and 21 deletions

View File

@ -56,4 +56,12 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser
{
return Premium.GetValueOrDefault(false);
}
public bool OccupiesOrganizationSeat
{
get
{
return Status != OrganizationUserStatusType.Revoked;
}
}
}

View File

@ -64,4 +64,5 @@ public interface IOrganizationService
Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId, IUserService userService);
Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService);
Task<int> GetOccupiedSeatCount(Organization organization);
}

View File

@ -44,7 +44,6 @@ public class OrganizationService : IOrganizationService
private readonly ICurrentContext _currentContext;
private readonly ILogger<OrganizationService> _logger;
public OrganizationService(
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
@ -199,10 +198,10 @@ public class OrganizationService : IOrganizationService
(newPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > newPlanSeats)
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organization.Id);
if (userCount > newPlanSeats)
var occupiedSeats = await GetOccupiedSeatCount(organization);
if (occupiedSeats > newPlanSeats)
{
throw new BadRequestException($"Your organization currently has {userCount} seats filled. " +
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
$"Your new plan only has ({newPlanSeats}) seats. Remove some users.");
}
}
@ -494,10 +493,10 @@ public class OrganizationService : IOrganizationService
if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal)
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organization.Id);
if (userCount > newSeatTotal)
var occupiedSeats = await GetOccupiedSeatCount(organization);
if (occupiedSeats > newSeatTotal)
{
throw new BadRequestException($"Your organization currently has {userCount} seats filled. " +
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
$"Your new plan only has ({newSeatTotal}) seats. Remove some users.");
}
}
@ -861,10 +860,10 @@ public class OrganizationService : IOrganizationService
if (license.Seats.HasValue &&
(!organization.Seats.HasValue || organization.Seats.Value > license.Seats.Value))
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organization.Id);
if (userCount > license.Seats.Value)
var occupiedSeats = await GetOccupiedSeatCount(organization);
if (occupiedSeats > license.Seats.Value)
{
throw new BadRequestException($"Your organization currently has {userCount} seats filled. " +
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
$"Your new license only has ({license.Seats.Value}) seats. Remove some users.");
}
}
@ -1138,8 +1137,8 @@ public class OrganizationService : IOrganizationService
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase);
if (organization.Seats.HasValue)
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organizationId);
var availableSeats = organization.Seats.Value - userCount;
var occupiedSeats = await GetOccupiedSeatCount(organization);
var availableSeats = organization.Seats.Value - occupiedSeats;
newSeatsRequired = invites.Sum(i => i.invite.Emails.Count()) - existingEmails.Count() - availableSeats;
}
@ -1559,7 +1558,7 @@ public class OrganizationService : IOrganizationService
organization.MaxAutoscaleSeats.HasValue &&
organization.MaxAutoscaleSeats.Value < organization.Seats.Value + seatsToAdd)
{
return (false, $"Cannot invite new users. Seat limit has been reached.");
return (false, $"Seat limit has been reached.");
}
return (true, failureReason);
@ -1951,8 +1950,8 @@ public class OrganizationService : IOrganizationService
var enoughSeatsAvailable = true;
if (organization.Seats.HasValue)
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organizationId);
seatsAvailable = organization.Seats.Value - userCount;
var occupiedSeats = await GetOccupiedSeatCount(organization);
seatsAvailable = organization.Seats.Value - occupiedSeats;
enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count;
}
@ -2324,6 +2323,14 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Only owners can restore other owners.");
}
var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId);
var occupiedSeats = await GetOccupiedSeatCount(organization);
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
if (availableSeats < 1)
{
await AutoAddSeatsAsync(organization, 1, DateTime.UtcNow);
}
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
@ -2345,6 +2352,12 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Users invalid.");
}
var organization = await _organizationRepository.GetByIdAsync(organizationId);
var occupiedSeats = await GetOccupiedSeatCount(organization);
var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats;
var newSeatsRequired = organizationUserIds.Count() - availableSeats;
await AutoAddSeatsAsync(organization, newSeatsRequired, DateTime.UtcNow);
var deletingUserIsOwner = false;
if (restoringUserId.HasValue)
{
@ -2455,4 +2468,10 @@ public class OrganizationService : IOrganizationService
return status;
}
public async Task<int> GetOccupiedSeatCount(Organization organization)
{
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organization.Id);
return orgUsers.Count(ou => ou.OccupiesOrganizationSeat);
}
}