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:
@ -56,4 +56,12 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser
|
||||
{
|
||||
return Premium.GetValueOrDefault(false);
|
||||
}
|
||||
|
||||
public bool OccupiesOrganizationSeat
|
||||
{
|
||||
get
|
||||
{
|
||||
return Status != OrganizationUserStatusType.Revoked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user