@ -16,6 +16,7 @@ using System.IO;
@@ -16,6 +16,7 @@ using System.IO;
using Newtonsoft.Json ;
using System.Text.Json ;
using Bit.Core.Context ;
using Microsoft.Extensions.Logging ;
namespace Bit.Core.Services
{
@ -40,9 +41,11 @@ namespace Bit.Core.Services
@@ -40,9 +41,11 @@ namespace Bit.Core.Services
private readonly ISsoConfigRepository _ ssoConfigRepository ;
private readonly ISsoUserRepository _ ssoUserRepository ;
private readonly IReferenceEventService _ referenceEventService ;
private readonly GlobalSettings _ globalSettings ;
private readonly I GlobalSettings _ globalSettings ;
private readonly ITaxRateRepository _ taxRateRepository ;
private readonly ICurrentContext _ currentContext ;
private readonly ILogger < OrganizationService > _l ogger ;
public OrganizationService (
IOrganizationRepository organizationRepository ,
@ -64,9 +67,10 @@ namespace Bit.Core.Services
@@ -64,9 +67,10 @@ namespace Bit.Core.Services
ISsoConfigRepository ssoConfigRepository ,
ISsoUserRepository ssoUserRepository ,
IReferenceEventService referenceEventService ,
GlobalSettings globalSettings ,
I GlobalSettings globalSettings ,
ITaxRateRepository taxRateRepository ,
ICurrentContext currentContext )
ICurrentContext currentContext ,
ILogger < OrganizationService > logger )
{
_ organizationRepository = organizationRepository ;
_ organizationUserRepository = organizationUserRepository ;
@ -90,6 +94,7 @@ namespace Bit.Core.Services
@@ -90,6 +94,7 @@ namespace Bit.Core.Services
_ globalSettings = globalSettings ;
_ taxRateRepository = taxRateRepository ;
_ currentContext = currentContext ;
_l ogger = logger ;
}
public async Task ReplacePaymentMethodAsync ( Guid organizationId , string paymentToken ,
@ -241,14 +246,14 @@ namespace Bit.Core.Services
@@ -241,14 +246,14 @@ namespace Bit.Core.Services
$"Disable your SSO configuration." ) ;
}
}
if ( ! newPlan . HasResetPassword & & organization . UseResetPassword )
{
var resetPasswordPolicy =
await _ policyRepository . GetByOrganizationIdTypeAsync ( organization . Id , PolicyType . ResetPassword ) ;
if ( resetPasswordPolicy ! = null & & resetPasswordPolicy . Enabled )
{
throw new BadRequestException ( "Your new plan does not allow the Password Reset feature. " +
throw new BadRequestException ( "Your new plan does not allow the Password Reset feature. " +
"Disable your Password Reset policy." ) ;
}
}
@ -347,7 +352,65 @@ namespace Bit.Core.Services
@@ -347,7 +352,65 @@ namespace Bit.Core.Services
return secret ;
}
public async Task < string > AdjustSeatsAsync ( Guid organizationId , int seatAdjustment )
public async Task UpdateSubscription ( Guid organizationId , int seatAdjustment , int? maxAutoscaleSeats )
{
var organization = await GetOrgById ( organizationId ) ;
if ( organization = = null )
{
throw new NotFoundException ( ) ;
}
var newSeatCount = organization . Seats + seatAdjustment ;
if ( maxAutoscaleSeats . HasValue & & newSeatCount > maxAutoscaleSeats . Value )
{
throw new BadRequestException ( "Cannot set max seat autoscaling below seat count." ) ;
}
if ( seatAdjustment ! = 0 )
{
await AdjustSeatsAsync ( organization , seatAdjustment ) ;
}
if ( maxAutoscaleSeats ! = organization . MaxAutoscaleSeats )
{
await UpdateAutoscalingAsync ( organization , maxAutoscaleSeats ) ;
}
}
private async Task UpdateAutoscalingAsync ( Organization organization , int? maxAutoscaleSeats )
{
if ( maxAutoscaleSeats . HasValue & &
organization . Seats . HasValue & &
maxAutoscaleSeats . Value < organization . Seats . Value )
{
throw new BadRequestException ( $"Cannot set max seat autoscaling below current seat count." ) ;
}
var plan = StaticStore . Plans . FirstOrDefault ( p = > p . Type = = organization . PlanType ) ;
if ( plan = = null )
{
throw new BadRequestException ( "Existing plan not found." ) ;
}
if ( ! plan . AllowSeatAutoscale )
{
throw new BadRequestException ( "Your plan does not allow seat autoscaling." ) ;
}
if ( plan . MaxUsers . HasValue & & maxAutoscaleSeats . HasValue & &
maxAutoscaleSeats > plan . MaxUsers )
{
throw new BadRequestException ( string . Concat ( $"Your plan has a seat limit of {plan.MaxUsers}, " ,
$"but you have specified a max autoscale count of {maxAutoscaleSeats}." ,
"Reduce your max autoscale seat count." ) ) ;
}
organization . MaxAutoscaleSeats = maxAutoscaleSeats ;
await ReplaceAndUpdateCache ( organization ) ;
}
public async Task < string > AdjustSeatsAsync ( Guid organizationId , int seatAdjustment , DateTime ? prorationDate = null )
{
var organization = await GetOrgById ( organizationId ) ;
if ( organization = = null )
@ -355,6 +418,11 @@ namespace Bit.Core.Services
@@ -355,6 +418,11 @@ namespace Bit.Core.Services
throw new NotFoundException ( ) ;
}
return await AdjustSeatsAsync ( organization , seatAdjustment , prorationDate ) ;
}
private async Task < string > AdjustSeatsAsync ( Organization organization , int seatAdjustment , DateTime ? prorationDate = null , IEnumerable < string > ownerEmails = null )
{
if ( organization . Seats = = null )
{
throw new BadRequestException ( "Organization has no seat limit, no need to adjust seats" ) ;
@ -420,6 +488,24 @@ namespace Bit.Core.Services
@@ -420,6 +488,24 @@ namespace Bit.Core.Services
} ) ;
organization . Seats = ( short? ) newSeatTotal ;
await ReplaceAndUpdateCache ( organization ) ;
if ( organization . Seats . HasValue & & organization . MaxAutoscaleSeats . HasValue & & organization . Seats = = organization . MaxAutoscaleSeats )
{
try
{
if ( ownerEmails = = null )
{
ownerEmails = ( await _ organizationUserRepository . GetManyByMinimumRoleAsync ( organization . Id ,
OrganizationUserType . Owner ) ) . Select ( u = > u . Email ) . Distinct ( ) ;
}
await _ mailService . SendOrganizationMaxSeatLimitReachedEmailAsync ( organization , organization . MaxAutoscaleSeats . Value , ownerEmails ) ;
}
catch ( Exception e )
{
_l ogger . LogError ( e , "Error encountered notifying organization owners of seat limit reached." ) ;
}
}
return paymentIntentClientSecret ;
}
@ -470,7 +556,7 @@ namespace Bit.Core.Services
@@ -470,7 +556,7 @@ namespace Bit.Core.Services
bool provider = false )
{
var plan = StaticStore . Plans . FirstOrDefault ( p = > p . Type = = signup . Plan ) ;
if ( ! ( plan is { LegacyYear : null } ) )
if ( ! ( plan is { LegacyYear : null } ) )
{
throw new BadRequestException ( "Invalid plan selected." ) ;
}
@ -558,7 +644,7 @@ namespace Bit.Core.Services
@@ -558,7 +644,7 @@ namespace Bit.Core.Services
var orgsWithSingleOrgPolicy = policies . Where ( p = > p . Enabled & & p . Type = = PolicyType . SingleOrg )
. Select ( p = > p . OrganizationId ) ;
var blockedBySingleOrgPolicy = orgUsers . Any ( ou = > ou is { Type : OrganizationUserType . Owner } & &
var blockedBySingleOrgPolicy = orgUsers . Any ( ou = > ou is { Type : OrganizationUserType . Owner } & &
ou . Type ! = OrganizationUserType . Admin & &
ou . Status ! = OrganizationUserStatusType . Invited & &
orgsWithSingleOrgPolicy . Contains ( ou . OrganizationId ) ) ;
@ -786,14 +872,14 @@ namespace Bit.Core.Services
@@ -786,14 +872,14 @@ namespace Bit.Core.Services
$"Your new license does not allow for the use of SSO. Disable your SSO configuration." ) ;
}
}
if ( ! license . UseResetPassword & & organization . UseResetPassword )
{
var resetPasswordPolicy =
await _ policyRepository . GetByOrganizationIdTypeAsync ( organization . Id , PolicyType . ResetPassword ) ;
if ( resetPasswordPolicy ! = null & & resetPasswordPolicy . Enabled )
{
throw new BadRequestException ( "Your new license does not allow the Password Reset feature. "
throw new BadRequestException ( "Your new license does not allow the Password Reset feature. "
+ "Disable your Password Reset policy." ) ;
}
}
@ -964,11 +1050,12 @@ namespace Bit.Core.Services
@@ -964,11 +1050,12 @@ namespace Bit.Core.Services
await UpdateAsync ( organization ) ;
}
private async Task < List < OrganizationUser > > InviteUsersAsync ( Guid organizationId , Guid ? invitingUserId ,
public async Task < List < OrganizationUser > > InviteUsersAsync ( Guid organizationId , Guid ? invitingUserId ,
IEnumerable < ( OrganizationUserInvite invite , string externalId ) > invites )
{
var organization = await GetOrgById ( organizationId ) ;
if ( organization = = null | | invites . Any ( i = > i . invite . Emails = = null | | i . externalId = = null ) )
var initialSeatCount = organization . Seats ;
if ( organization = = null | | invites . Any ( i = > i . invite . Emails = = null ) )
{
throw new NotFoundException ( ) ;
}
@ -983,23 +1070,34 @@ namespace Bit.Core.Services
@@ -983,23 +1070,34 @@ namespace Bit.Core.Services
}
}
var newSeatsRequired = 0 ;
var existingEmails = new HashSet < string > ( await _ organizationUserRepository . SelectKnownEmailsAsync (
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 ;
if ( availableSeats < invites . Select ( i = > i . invite . Emails . Count ( ) ) . Sum ( ) )
{
throw new BadRequestException ( "You have reached the maximum number of users " +
$"({organization.Seats.Value}) for this organization." ) ;
}
newSeatsRequired = invites . Sum ( i = > i . invite . Emails . Count ( ) ) - existingEmails . Count ( ) - availableSeats ;
}
var ( canScale , failureReason ) = await CanScaleAsync ( organization , newSeatsRequired ) ;
if ( ! canScale )
{
throw new BadRequestException ( failureReason ) ;
}
var invitedAreAllOwners = invites . All ( i = > i . invite . Type = = OrganizationUserType . Owner ) ;
if ( ! invitedAreAllOwners & & ! await HasConfirmedOwnersExceptAsync ( organizationId , new Guid [ ] { } ) )
{
throw new BadRequestException ( "Organization must have at least one confirmed owner." ) ;
}
var orgUsers = new List < OrganizationUser > ( ) ;
var limitedCollectionOrgUsers = new List < ( OrganizationUser , IEnumerable < SelectionReadOnly > ) > ( ) ;
var orgUserInvitedCount = 0 ;
var exceptions = new List < Exception > ( ) ;
var events = new List < ( OrganizationUser , EventType , DateTime ? ) > ( ) ;
var existingEmails = new HashSet < string > ( await _ organizationUserRepository . SelectKnownEmailsAsync (
organizationId , invites . SelectMany ( i = > i . invite . Emails ) , false ) , StringComparer . InvariantCultureIgnoreCase ) ;
foreach ( var ( invite , externalId ) in invites )
{
foreach ( var email in invite . Emails )
@ -1036,11 +1134,14 @@ namespace Bit.Core.Services
@@ -1036,11 +1134,14 @@ namespace Bit.Core.Services
if ( ! orgUser . AccessAll & & invite . Collections . Any ( ) )
{
throw new Exception ( "Bulk invite does not support limited collection invites" ) ;
limitedCollectionOrgUsers . Add ( ( orgUser , invite . Collections ) ) ;
}
else
{
orgUsers . Add ( orgUser ) ;
}
events . Add ( ( orgUser , EventType . OrganizationUser_Invited , DateTime . UtcNow ) ) ;
orgUsers . Add ( orgUser ) ;
orgUserInvitedCount + + ;
}
catch ( Exception e )
@ -1050,9 +1151,21 @@ namespace Bit.Core.Services
@@ -1050,9 +1151,21 @@ namespace Bit.Core.Services
}
}
if ( exceptions . Any ( ) )
{
throw new AggregateException ( "One or more errors occurred while inviting users." , exceptions ) ;
}
var prorationDate = DateTime . UtcNow ;
try
{
await _ organizationUserRepository . CreateManyAsync ( orgUsers ) ;
foreach ( var ( orgUser , collections ) in limitedCollectionOrgUsers )
{
await _ organizationUserRepository . CreateAsync ( orgUser , collections ) ;
}
await AutoAddSeatsAsync ( organization , newSeatsRequired , prorationDate ) ;
await SendInvitesAsync ( orgUsers , organization ) ;
await _ eventService . LogOrganizationUserEventsAsync ( events ) ;
@ -1064,101 +1177,23 @@ namespace Bit.Core.Services
@@ -1064,101 +1177,23 @@ namespace Bit.Core.Services
}
catch ( Exception e )
{
exceptions . Add ( e ) ;
}
if ( exceptions . Any ( ) )
{
throw new AggregateException ( "One or more errors occurred while inviting users." , exceptions ) ;
}
return orgUsers ;
}
public async Task < List < OrganizationUser > > InviteUserAsync ( Guid organizationId , Guid ? invitingUserId ,
string externalId , OrganizationUserInvite invite )
{
var organization = await GetOrgById ( organizationId ) ;
if ( organization = = null | | invite ? . Emails = = null )
{
throw new NotFoundException ( ) ;
}
// Revert any added users.
var invitedOrgUserIds = orgUsers . Select ( u = > u . Id ) . Concat ( limitedCollectionOrgUsers . Select ( u = > u . Item1 . Id ) ) ;
await _ organizationUserRepository . DeleteManyAsync ( invitedOrgUserIds ) ;
var currentSeatCount = ( await _ organizationRepository . GetByIdAsync ( organization . Id ) ) . Seats ;
if ( invitingUserId . HasValue & & invite . Type . HasValue )
{
await ValidateOrganizationUserUpdatePermissions ( organizationId , invite . Type . Value , null ) ;
}
if ( organization . Seats . HasValue )
{
var userCount = await _ organizationUserRepository . GetCountByOrganizationIdAsync ( organizationId ) ;
var availableSeats = organization . Seats . Value - userCount ;
if ( availableSeats < invite . Emails . Count ( ) )
if ( initialSeatCount . HasValue & & currentSeatCount . HasValue & & currentSeatCount . Value ! = initialSeatCount . Value )
{
throw new BadRequestException ( "You have reached the maximum number of users " +
$"({organization.Seats.Value}) for this organization." ) ;
await AdjustSeatsAsync ( organization , initialSeatCount . Value - currentSeatCount . Value , prorationDate ) ;
}
}
var invitedIsOwner = invite . Type is OrganizationUserType . Owner ;
if ( ! invitedIsOwner & & ! await HasConfirmedOwnersExceptAsync ( organizationId , new Guid [ ] { } ) )
{
throw new BadRequestException ( "Organization must have at least one confirmed owner." ) ;
exceptions . Add ( e ) ;
}
var orgUsers = new List < OrganizationUser > ( ) ;
var orgUserInvitedCount = 0 ;
foreach ( var email in invite . Emails )
if ( exceptions . Any ( ) )
{
// Make sure user is not already invited
var existingOrgUserCount = await _ organizationUserRepository . GetCountByOrganizationAsync (
organizationId , email , false ) ;
if ( existingOrgUserCount > 0 )
{
continue ;
}
var orgUser = new OrganizationUser
{
OrganizationId = organizationId ,
UserId = null ,
Email = email . ToLowerInvariant ( ) ,
Key = null ,
Type = invite . Type . Value ,
Status = OrganizationUserStatusType . Invited ,
AccessAll = invite . AccessAll ,
ExternalId = externalId ,
CreationDate = DateTime . UtcNow ,
RevisionDate = DateTime . UtcNow ,
} ;
if ( invite . Permissions ! = null )
{
orgUser . Permissions = System . Text . Json . JsonSerializer . Serialize ( invite . Permissions , new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy . CamelCase ,
} ) ;
}
if ( ! orgUser . AccessAll & & invite . Collections . Any ( ) )
{
await _ organizationUserRepository . CreateAsync ( orgUser , invite . Collections ) ;
}
else
{
await _ organizationUserRepository . CreateAsync ( orgUser ) ;
}
await SendInviteAsync ( orgUser , organization ) ;
await _ eventService . LogOrganizationUserEventAsync ( orgUser , EventType . OrganizationUser_Invited ) ;
orgUsers . Add ( orgUser ) ;
orgUserInvitedCount + + ;
throw new AggregateException ( "One or more errors occurred while inviting users." , exceptions ) ;
}
await _ referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . InvitedUsers , organization )
{
Users = orgUserInvitedCount
} ) ;
return orgUsers ;
}
@ -1349,7 +1384,7 @@ namespace Bit.Core.Services
@@ -1349,7 +1384,7 @@ namespace Bit.Core.Services
public async Task < OrganizationUser > ConfirmUserAsync ( Guid organizationId , Guid organizationUserId , string key ,
Guid confirmingUserId , IUserService userService )
{
var result = await ConfirmUsersAsync ( organizationId , new Dictionary < Guid , string > ( ) { { organizationUserId , key } } ,
var result = await ConfirmUsersAsync ( organizationId , new Dictionary < Guid , string > ( ) { { organizationUserId , key } } ,
confirmingUserId , userService ) ;
if ( ! result . Any ( ) )
@ -1364,7 +1399,7 @@ namespace Bit.Core.Services
@@ -1364,7 +1399,7 @@ namespace Bit.Core.Services
}
return orgUser ;
}
public async Task < List < Tuple < OrganizationUser , string > > > ConfirmUsersAsync ( Guid organizationId , Dictionary < Guid , string > keys ,
Guid confirmingUserId , IUserService userService )
{
@ -1379,7 +1414,7 @@ namespace Bit.Core.Services
@@ -1379,7 +1414,7 @@ namespace Bit.Core.Services
}
var validOrganizationUserIds = validOrganizationUsers . Select ( u = > u . UserId . Value ) . ToList ( ) ;
var organization = await GetOrgById ( organizationId ) ;
var policies = await _ policyRepository . GetManyByOrganizationIdAsync ( organizationId ) ;
var usersOrgs = await _ organizationUserRepository . GetManyByManyUsersAsync ( validOrganizationUserIds ) ;
@ -1417,7 +1452,7 @@ namespace Bit.Core.Services
@@ -1417,7 +1452,7 @@ namespace Bit.Core.Services
orgUser . Status = OrganizationUserStatusType . Confirmed ;
orgUser . Key = keys [ orgUser . Id ] ;
orgUser . Email = null ;
await _ eventService . LogOrganizationUserEventAsync ( orgUser , EventType . OrganizationUser_Confirmed ) ;
await _ mailService . SendOrganizationConfirmedEmailAsync ( organization . Name , user . Email ) ;
await DeleteAndPushUserRegistrationAsync ( organizationId , user . Id ) ;
@ -1435,6 +1470,68 @@ namespace Bit.Core.Services
@@ -1435,6 +1470,68 @@ namespace Bit.Core.Services
return result ;
}
internal async Task < ( bool canScale , string failureReason ) > CanScaleAsync ( Organization organization , int seatsToAdd )
{
var failureReason = "" ;
if ( _ globalSettings . SelfHosted )
{
failureReason = "Cannot autoscale on self-hosted instance." ;
return ( false , failureReason ) ;
}
if ( ! await _ currentContext . ManageUsers ( organization . Id ) )
{
failureReason = "Cannot manage organization users." ;
return ( false , failureReason ) ;
}
// if (!await _currentContext.OrganizationOwner(organization.Id))
// {
// failureReason = "Only organization owners can autoscale seats.";
// return (false, failureReason);
// }
if ( seatsToAdd < 1 )
{
return ( true , failureReason ) ;
}
if ( organization . Seats . HasValue & &
organization . MaxAutoscaleSeats . HasValue & &
organization . MaxAutoscaleSeats . Value < organization . Seats . Value + seatsToAdd )
{
return ( false , $"Cannot invite new users. Seat limit has been reached." ) ;
}
return ( true , failureReason ) ;
}
private async Task AutoAddSeatsAsync ( Organization organization , int seatsToAdd , DateTime ? prorationDate = null )
{
if ( seatsToAdd < 1 | | ! organization . Seats . HasValue )
{
return ;
}
var ( canScale , failureMessage ) = await CanScaleAsync ( organization , seatsToAdd ) ;
if ( ! canScale )
{
throw new BadRequestException ( failureMessage ) ;
}
var ownerEmails = ( await _ organizationUserRepository . GetManyByMinimumRoleAsync ( organization . Id ,
OrganizationUserType . Owner ) ) . Select ( u = > u . Email ) . Distinct ( ) ;
await AdjustSeatsAsync ( organization , seatsToAdd , prorationDate , ownerEmails ) ;
if ( ! organization . OwnersNotifiedOfAutoscaling . HasValue )
{
await _ mailService . SendOrganizationAutoscaledEmailAsync ( organization , organization . Seats . Value + seatsToAdd ,
ownerEmails ) ;
organization . OwnersNotifiedOfAutoscaling = DateTime . UtcNow ;
await _ organizationRepository . UpsertAsync ( organization ) ;
}
}
private async Task CheckPolicies ( ICollection < Policy > policies , Guid organizationId , User user ,
ICollection < OrganizationUser > userOrgs , IUserService userService )
{
@ -1474,7 +1571,7 @@ namespace Bit.Core.Services
@@ -1474,7 +1571,7 @@ namespace Bit.Core.Services
}
if ( user . Type ! = OrganizationUserType . Owner & &
! await HasConfirmedOwnersExceptAsync ( user . OrganizationId , new [ ] { user . Id } ) )
! await HasConfirmedOwnersExceptAsync ( user . OrganizationId , new [ ] { user . Id } ) )
{
throw new BadRequestException ( "Organization must have at least one confirmed owner." ) ;
}
@ -1507,7 +1604,7 @@ namespace Bit.Core.Services
@@ -1507,7 +1604,7 @@ namespace Bit.Core.Services
throw new BadRequestException ( "Only owners can delete other owners." ) ;
}
if ( ! await HasConfirmedOwnersExceptAsync ( organizationId , new [ ] { organizationUserId } ) )
if ( ! await HasConfirmedOwnersExceptAsync ( organizationId , new [ ] { organizationUserId } ) )
{
throw new BadRequestException ( "Organization must have at least one confirmed owner." ) ;
}
@ -1529,7 +1626,7 @@ namespace Bit.Core.Services
@@ -1529,7 +1626,7 @@ namespace Bit.Core.Services
throw new NotFoundException ( ) ;
}
if ( ! await HasConfirmedOwnersExceptAsync ( organizationId , new [ ] { orgUser . Id } ) )
if ( ! await HasConfirmedOwnersExceptAsync ( organizationId , new [ ] { orgUser . Id } ) )
{
throw new BadRequestException ( "Organization must have at least one confirmed owner." ) ;
}
@ -1630,12 +1727,12 @@ namespace Bit.Core.Services
@@ -1630,12 +1727,12 @@ namespace Bit.Core.Services
{
// 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 , organizationUserId ) ;
if ( ! callingUserId . HasValue | | orgUser = = null | | orgUser . UserId ! = callingUserId . Value | |
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 )
@ -1650,7 +1747,7 @@ namespace Bit.Core.Services
@@ -1650,7 +1747,7 @@ namespace Bit.Core.Services
{
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 )
{
@ -1661,7 +1758,7 @@ namespace Bit.Core.Services
@@ -1661,7 +1758,7 @@ namespace Bit.Core.Services
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 ?
@ -1702,7 +1799,8 @@ namespace Bit.Core.Services
@@ -1702,7 +1799,8 @@ namespace Bit.Core.Services
AccessAll = accessAll ,
Collections = collections ,
} ;
var results = await InviteUserAsync ( organizationId , invitingUserId , externalId , invite ) ;
var results = await InviteUsersAsync ( organizationId , invitingUserId ,
new ( OrganizationUserInvite , string ) [ ] { ( invite , externalId ) } ) ;
var result = results . FirstOrDefault ( ) ;
if ( result = = null )
{
@ -1797,11 +1895,6 @@ namespace Bit.Core.Services
@@ -1797,11 +1895,6 @@ namespace Bit.Core.Services
enoughSeatsAvailable = seatsAvailable > = usersToAdd . Count ;
}
if ( ! enoughSeatsAvailable )
{
throw new BadRequestException ( $"Organization does not have enough seats available. Need {usersToAdd.Count} but {seatsAvailable} available." ) ;
}
var userInvites = new List < ( OrganizationUserInvite , string ) > ( ) ;
foreach ( var user in newUsers )
{
@ -1891,7 +1984,7 @@ namespace Bit.Core.Services
@@ -1891,7 +1984,7 @@ namespace Bit.Core.Services
}
}
}
await _ referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . DirectorySynced , organization ) ) ;
}
@ -1934,7 +2027,7 @@ namespace Bit.Core.Services
@@ -1934,7 +2027,7 @@ namespace Bit.Core.Services
org . PublicKey = publicKey ;
org . PrivateKey = privateKey ;
await UpdateAsync ( org ) ;
return org ;
}