1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-27 14:16:19 -05:00

[PM-21734] Read claims from ClaimsPrincipal when creating self-hosted organization (#5953)

* Read claims from claims principal when creating self-hosted organization

* Run dotnet format

* Jared's feedback

* Run dotnet format
This commit is contained in:
Alex Morask 2025-06-26 14:14:05 -05:00 committed by GitHub
parent 1c3bf259e9
commit b951b38c37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 243 additions and 113 deletions

View File

@ -97,7 +97,7 @@ public class OrganizationService : IOrganizationService
IPricingClient pricingClient, IPricingClient pricingClient,
IPolicyRequirementQuery policyRequirementQuery, IPolicyRequirementQuery policyRequirementQuery,
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand ISendOrganizationInvitesCommand sendOrganizationInvitesCommand
) )
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -198,6 +198,7 @@ public class OrganizationService : IOrganizationService
{ {
await AdjustSeatsAsync(organization, seatAdjustment); await AdjustSeatsAsync(organization, seatAdjustment);
} }
if (maxAutoscaleSeats != organization.MaxAutoscaleSeats) if (maxAutoscaleSeats != organization.MaxAutoscaleSeats)
{ {
await UpdateAutoscalingAsync(organization, maxAutoscaleSeats); await UpdateAutoscalingAsync(organization, maxAutoscaleSeats);
@ -206,7 +207,6 @@ public class OrganizationService : IOrganizationService
private async Task UpdateAutoscalingAsync(Organization organization, int? maxAutoscaleSeats) private async Task UpdateAutoscalingAsync(Organization organization, int? maxAutoscaleSeats)
{ {
if (maxAutoscaleSeats.HasValue && if (maxAutoscaleSeats.HasValue &&
organization.Seats.HasValue && organization.Seats.HasValue &&
maxAutoscaleSeats.Value < organization.Seats.Value) maxAutoscaleSeats.Value < organization.Seats.Value)
@ -228,7 +228,8 @@ public class OrganizationService : IOrganizationService
if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue && if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.PasswordManager.MaxSeats) maxAutoscaleSeats > plan.PasswordManager.MaxSeats)
{ {
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ", throw new BadRequestException(string.Concat(
$"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ",
$"but you have specified a max autoscale count of {maxAutoscaleSeats}.", $"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
"Reduce your max autoscale seat count.")); "Reduce your max autoscale seat count."));
} }
@ -249,7 +250,8 @@ public class OrganizationService : IOrganizationService
return await AdjustSeatsAsync(organization, seatAdjustment); return await AdjustSeatsAsync(organization, seatAdjustment);
} }
private async Task<string> AdjustSeatsAsync(Organization organization, int seatAdjustment, IEnumerable<string> ownerEmails = null) private async Task<string> AdjustSeatsAsync(Organization organization, int seatAdjustment,
IEnumerable<string> ownerEmails = null)
{ {
if (organization.Seats == null) if (organization.Seats == null)
{ {
@ -285,10 +287,11 @@ public class OrganizationService : IOrganizationService
} }
var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats; var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats;
if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value) if (plan.PasswordManager.MaxAdditionalSeats.HasValue &&
additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{ {
throw new BadRequestException($"Organization plan allows a maximum of " + throw new BadRequestException($"Organization plan allows a maximum of " +
$"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats.");
} }
if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal)
@ -299,8 +302,9 @@ public class OrganizationService : IOrganizationService
{ {
if (organization.UseAdminSponsoredFamilies || seatCounts.Sponsored > 0) if (organization.UseAdminSponsoredFamilies || seatCounts.Sponsored > 0)
{ {
throw new BadRequestException($"Your organization has {seatCounts.Users} members and {seatCounts.Sponsored} sponsored families. " + throw new BadRequestException(
$"To decrease the seat count below {seatCounts.Total}, you must remove members or sponsorships."); $"Your organization has {seatCounts.Users} members and {seatCounts.Sponsored} sponsored families. " +
$"To decrease the seat count below {seatCounts.Total}, you must remove members or sponsorships.");
} }
else else
{ {
@ -319,7 +323,8 @@ public class OrganizationService : IOrganizationService
organization.Seats = (short?)newSeatTotal; organization.Seats = (short?)newSeatTotal;
await ReplaceAndUpdateCacheAsync(organization); await ReplaceAndUpdateCacheAsync(organization);
if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && organization.Seats == organization.MaxAutoscaleSeats) if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue &&
organization.Seats == organization.MaxAutoscaleSeats)
{ {
try try
{ {
@ -328,7 +333,9 @@ public class OrganizationService : IOrganizationService
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
} }
await _mailService.SendOrganizationMaxSeatLimitReachedEmailAsync(organization, organization.MaxAutoscaleSeats.Value, ownerEmails);
await _mailService.SendOrganizationMaxSeatLimitReachedEmailAsync(organization,
organization.MaxAutoscaleSeats.Value, ownerEmails);
} }
catch (Exception e) catch (Exception e)
{ {
@ -362,7 +369,7 @@ public class OrganizationService : IOrganizationService
} }
var bankAccount = customer.Sources var bankAccount = customer.Sources
.FirstOrDefault(s => s is BankAccount && ((BankAccount)s).Status != "verified") as BankAccount; .FirstOrDefault(s => s is BankAccount && ((BankAccount)s).Status != "verified") as BankAccount;
if (bankAccount == null) if (bankAccount == null)
{ {
throw new GatewayException("Cannot find an unverified bank account."); throw new GatewayException("Cannot find an unverified bank account.");
@ -389,7 +396,7 @@ public class OrganizationService : IOrganizationService
if (anySingleOrgPolicies) if (anySingleOrgPolicies)
{ {
throw new BadRequestException("You may not create an organization. You belong to an organization " + throw new BadRequestException("You may not create an organization. You belong to an organization " +
"which has a policy that prohibits you from being a member of any other organization."); "which has a policy that prohibits you from being a member of any other organization.");
} }
} }
@ -403,7 +410,7 @@ public class OrganizationService : IOrganizationService
if (license.LicenseType != LicenseType.Organization) if (license.LicenseType != LicenseType.Organization)
{ {
throw new BadRequestException("Premium licenses cannot be applied to an organization. " + throw new BadRequestException("Premium licenses cannot be applied to an organization. " +
"Upload this license from your personal account settings page."); "Upload this license from your personal account settings page.");
} }
var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license); var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license);
@ -422,50 +429,11 @@ public class OrganizationService : IOrganizationService
await ValidateSignUpPoliciesAsync(owner.Id); await ValidateSignUpPoliciesAsync(owner.Id);
var organization = new Organization var organization = claimsPrincipal != null
{ // If the ClaimsPrincipal exists (there's a token on the license), use it to build the organization.
Name = license.Name, ? OrganizationFactory.Create(owner, claimsPrincipal, publicKey, privateKey)
BillingEmail = license.BillingEmail, // If there's no ClaimsPrincipal (there's no token on the license), use the license to build the organization.
BusinessName = license.BusinessName, : OrganizationFactory.Create(owner, license, publicKey, privateKey);
PlanType = license.PlanType,
Seats = license.Seats,
MaxCollections = license.MaxCollections,
MaxStorageGb = _globalSettings.SelfHosted ? 10240 : license.MaxStorageGb, // 10 TB
UsePolicies = license.UsePolicies,
UseSso = license.UseSso,
UseKeyConnector = license.UseKeyConnector,
UseScim = license.UseScim,
UseGroups = license.UseGroups,
UseDirectory = license.UseDirectory,
UseEvents = license.UseEvents,
UseTotp = license.UseTotp,
Use2fa = license.Use2fa,
UseApi = license.UseApi,
UseResetPassword = license.UseResetPassword,
Plan = license.Plan,
SelfHost = license.SelfHost,
UsersGetPremium = license.UsersGetPremium,
UseCustomPermissions = license.UseCustomPermissions,
Gateway = null,
GatewayCustomerId = null,
GatewaySubscriptionId = null,
ReferenceData = owner.ReferenceData,
Enabled = license.Enabled,
ExpirationDate = license.Expires,
LicenseKey = license.LicenseKey,
PublicKey = publicKey,
PrivateKey = privateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,
UsePasswordManager = license.UsePasswordManager,
UseSecretsManager = license.UseSecretsManager,
SmSeats = license.SmSeats,
SmServiceAccounts = license.SmServiceAccounts,
UseRiskInsights = license.UseRiskInsights,
UseOrganizationDomains = license.UseOrganizationDomains,
UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies,
};
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false); var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
@ -480,8 +448,9 @@ public class OrganizationService : IOrganizationService
/// Private helper method to create a new organization. /// Private helper method to create a new organization.
/// This is common code used by both the cloud and self-hosted methods. /// This is common code used by both the cloud and self-hosted methods.
/// </summary> /// </summary>
private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignUpAsync(Organization organization, private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)>
Guid ownerId, string ownerKey, string collectionName, bool withPayment) SignUpAsync(Organization organization,
Guid ownerId, string ownerKey, string collectionName, bool withPayment)
{ {
try try
{ {
@ -537,7 +506,15 @@ public class OrganizationService : IOrganizationService
if (orgUser != null) if (orgUser != null)
{ {
defaultOwnerAccess = defaultOwnerAccess =
[new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }]; [
new CollectionAccessSelection
{
Id = orgUser.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
}
];
} }
await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess); await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess);
@ -573,7 +550,8 @@ public class OrganizationService : IOrganizationService
} }
} }
public async Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated) public async Task UpdateAsync(Organization organization, bool updateBilling = false,
EventType eventType = EventType.Organization_Updated)
{ {
if (organization.Id == default(Guid)) if (organization.Id == default(Guid))
{ {
@ -594,11 +572,12 @@ public class OrganizationService : IOrganizationService
if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
{ {
var customerService = new CustomerService(); var customerService = new CustomerService();
await customerService.UpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions await customerService.UpdateAsync(organization.GatewayCustomerId,
{ new CustomerUpdateOptions
Email = organization.BillingEmail, {
Description = organization.DisplayBusinessName() Email = organization.BillingEmail,
}); Description = organization.DisplayBusinessName()
});
} }
if (eventType == EventType.Organization_CollectionManagement_Updated) if (eventType == EventType.Organization_CollectionManagement_Updated)
@ -648,7 +627,8 @@ public class OrganizationService : IOrganizationService
await UpdateAsync(organization); await UpdateAsync(organization);
} }
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId,
EventSystemUser? systemUser,
OrganizationUserInvite invite, string externalId) OrganizationUserInvite invite, string externalId)
{ {
// Ideally OrganizationUserInvite should represent a single user so that this doesn't have to be a runtime check // Ideally OrganizationUserInvite should represent a single user so that this doesn't have to be a runtime check
@ -661,7 +641,8 @@ public class OrganizationService : IOrganizationService
var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations?.Any() ?? false) if (invalidAssociations?.Any() ?? false)
{ {
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); throw new BadRequestException(
"The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
} }
var results = await InviteUsersAsync(organizationId, invitingUserId, systemUser, var results = await InviteUsersAsync(organizationId, invitingUserId, systemUser,
@ -672,6 +653,7 @@ public class OrganizationService : IOrganizationService
{ {
throw new BadRequestException("This user has already been invited."); throw new BadRequestException("This user has already been invited.");
} }
return result; return result;
} }
@ -683,7 +665,8 @@ public class OrganizationService : IOrganizationService
/// <param name="systemUser">The system user which is sending the invite. Only used when inviting via SCIM; null if using a client app or Public API</param> /// <param name="systemUser">The system user which is sending the invite. Only used when inviting via SCIM; null if using a client app or Public API</param>
/// <param name="invites">Details about the users being invited</param> /// <param name="invites">Details about the users being invited</param>
/// <returns></returns> /// <returns></returns>
public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, public async Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId,
EventSystemUser? systemUser,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
{ {
var inviteTypes = new HashSet<OrganizationUserType>(invites.Where(i => i.invite.Type.HasValue) var inviteTypes = new HashSet<OrganizationUserType>(invites.Where(i => i.invite.Type.HasValue)
@ -695,7 +678,8 @@ public class OrganizationService : IOrganizationService
{ {
foreach (var (invite, _) in invites) foreach (var (invite, _) in invites)
{ {
await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null, invite.Permissions); await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null,
invite.Permissions);
await ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, invite.Type.Value); await ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, invite.Type.Value);
} }
} }
@ -705,7 +689,8 @@ public class OrganizationService : IOrganizationService
if (systemUser.HasValue) if (systemUser.HasValue)
{ {
// Log SCIM event // Log SCIM event
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser.Value, e.Item3))); await _eventService.LogOrganizationUserEventsAsync(events.Select(e =>
(e.Item1, e.Item2, systemUser.Value, e.Item3)));
} }
else else
{ {
@ -716,8 +701,10 @@ public class OrganizationService : IOrganizationService
return organizationUsers; return organizationUsers;
} }
private async Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId, private async
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) Task<(List<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)>
SaveUsersSendInvitesAsync(Guid organizationId,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
{ {
var organization = await GetOrgById(organizationId); var organization = await GetOrgById(organizationId);
var initialSeatCount = organization.Seats; var initialSeatCount = organization.Seats;
@ -727,7 +714,8 @@ public class OrganizationService : IOrganizationService
} }
var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync( var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); organizationId, invites.SelectMany(i => i.invite.Emails), false),
StringComparer.InvariantCultureIgnoreCase);
// Seat autoscaling // Seat autoscaling
var initialSmSeatCount = organization.SmSeats; var initialSmSeatCount = organization.SmSeats;
@ -755,7 +743,8 @@ public class OrganizationService : IOrganizationService
.SelectMany(i => i.invite.Emails) .SelectMany(i => i.invite.Emails)
.Count(email => !existingEmails.Contains(email)); .Count(email => !existingEmails.Contains(email));
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount); var additionalSmSeatsRequired =
await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount);
if (additionalSmSeatsRequired > 0) if (additionalSmSeatsRequired > 0)
{ {
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType); var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
@ -764,7 +753,9 @@ public class OrganizationService : IOrganizationService
} }
var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner); var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner);
if (!invitedAreAllOwners && !await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }, includeProvider: true)) if (!invitedAreAllOwners &&
!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { },
includeProvider: true))
{ {
throw new BadRequestException("Organization must have at least one confirmed owner."); throw new BadRequestException("Organization must have at least one confirmed owner.");
} }
@ -887,7 +878,8 @@ public class OrganizationService : IOrganizationService
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdateRevert); await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdateRevert);
} }
if (initialSeatCount.HasValue && currentOrganization.Seats.HasValue && currentOrganization.Seats.Value != initialSeatCount.Value) if (initialSeatCount.HasValue && currentOrganization.Seats.HasValue &&
currentOrganization.Seats.Value != initialSeatCount.Value)
{ {
await AdjustSeatsAsync(organization, initialSeatCount.Value - currentOrganization.Seats.Value); await AdjustSeatsAsync(organization, initialSeatCount.Value - currentOrganization.Seats.Value);
} }
@ -903,7 +895,8 @@ public class OrganizationService : IOrganizationService
return (allOrgUsers, events); return (allOrgUsers, events);
} }
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId,
Guid? invitingUserId,
IEnumerable<Guid> organizationUsersId) IEnumerable<Guid> organizationUsersId)
{ {
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId); var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId);
@ -925,7 +918,8 @@ public class OrganizationService : IOrganizationService
return result; return result;
} }
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false) public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId,
bool initOrganization = false)
{ {
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId); var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (orgUser == null || orgUser.OrganizationId != organizationId || if (orgUser == null || orgUser.OrganizationId != organizationId ||
@ -1012,7 +1006,9 @@ public class OrganizationService : IOrganizationService
IEnumerable<string> ownerEmails; IEnumerable<string> ownerEmails;
if (providerOrg != null) if (providerOrg != null)
{ {
ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed)) ownerEmails =
(await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId,
ProviderUserStatusType.Confirmed))
.Select(u => u.Email).Distinct(); .Select(u => u.Email).Distinct();
} }
else else
@ -1020,6 +1016,7 @@ public class OrganizationService : IOrganizationService
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
} }
var initialSeatCount = organization.Seats.Value; var initialSeatCount = organization.Seats.Value;
await AdjustSeatsAsync(organization, seatsToAdd, ownerEmails); await AdjustSeatsAsync(organization, seatsToAdd, ownerEmails);
@ -1034,8 +1031,8 @@ public class OrganizationService : IOrganizationService
} }
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey,
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId) Guid? callingUserId)
{ {
// Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID // 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, userId); var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
@ -1063,30 +1060,35 @@ public class OrganizationService : IOrganizationService
// Block the user from withdrawal if auto enrollment is enabled // Block the user from withdrawal if auto enrollment is enabled
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{ {
var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(userId); var resetPasswordPolicyRequirement =
await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(userId);
if (resetPasswordKey == null && resetPasswordPolicyRequirement.AutoEnrollEnabled(organizationId)) if (resetPasswordKey == null && resetPasswordPolicyRequirement.AutoEnrollEnabled(organizationId))
{ {
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); throw new BadRequestException(
"Due to an Enterprise Policy, you are not allowed to withdraw from account recovery.");
} }
} }
else else
{ {
if (resetPasswordKey == null && resetPasswordPolicy.Data != null) if (resetPasswordKey == null && resetPasswordPolicy.Data != null)
{ {
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data,
JsonHelpers.IgnoreCase);
if (data?.AutoEnrollEnabled ?? false) if (data?.AutoEnrollEnabled ?? false)
{ {
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); throw new BadRequestException(
"Due to an Enterprise Policy, you are not allowed to withdraw from account recovery.");
} }
} }
} }
orgUser.ResetPasswordKey = resetPasswordKey; orgUser.ResetPasswordKey = resetPasswordKey;
await _organizationUserRepository.ReplaceAsync(orgUser); await _organizationUserRepository.ReplaceAsync(orgUser);
await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ? await _eventService.LogOrganizationUserEventAsync(orgUser,
EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); resetPasswordKey != null
? EventType.OrganizationUser_ResetPassword_Enroll
: EventType.OrganizationUser_ResetPassword_Withdraw);
} }
public async Task ImportAsync(Guid organizationId, public async Task ImportAsync(Guid organizationId,
@ -1123,15 +1125,16 @@ public class OrganizationService : IOrganizationService
var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId); var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId);
var removeUsersSet = new HashSet<string>(removeUserExternalIds) var removeUsersSet = new HashSet<string>(removeUserExternalIds)
.Except(newUsersSet) .Except(newUsersSet)
.Where(u => existingUsersDict.TryGetValue(u, out var existingUser) && existingUser.Type != OrganizationUserType.Owner) .Where(u => existingUsersDict.TryGetValue(u, out var existingUser) &&
existingUser.Type != OrganizationUserType.Owner)
.Select(u => existingUsersDict[u]); .Select(u => existingUsersDict[u]);
await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id)); await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id));
events.AddRange(removeUsersSet.Select(u => ( events.AddRange(removeUsersSet.Select(u => (
u, u,
EventType.OrganizationUser_Removed, EventType.OrganizationUser_Removed,
(DateTime?)DateTime.UtcNow (DateTime?)DateTime.UtcNow
)) ))
); );
} }
@ -1144,10 +1147,10 @@ public class OrganizationService : IOrganizationService
existingExternalUsersIdDict.ContainsKey(u.ExternalId)); existingExternalUsersIdDict.ContainsKey(u.ExternalId));
await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id)); await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id));
events.AddRange(usersToDelete.Select(u => ( events.AddRange(usersToDelete.Select(u => (
u, u,
EventType.OrganizationUser_Removed, EventType.OrganizationUser_Removed,
(DateTime?)DateTime.UtcNow (DateTime?)DateTime.UtcNow
)) ))
); );
foreach (var deletedUser in usersToDelete) foreach (var deletedUser in usersToDelete)
{ {
@ -1175,6 +1178,7 @@ public class OrganizationService : IOrganizationService
existingExternalUsersIdDict.Add(orgUser.ExternalId, orgUser.Id); existingExternalUsersIdDict.Add(orgUser.ExternalId, orgUser.Id);
} }
} }
await _organizationUserRepository.UpsertManyAsync(usersToUpsert); await _organizationUserRepository.UpsertManyAsync(usersToUpsert);
// Add new users // Add new users
@ -1185,7 +1189,8 @@ public class OrganizationService : IOrganizationService
var enoughSeatsAvailable = true; var enoughSeatsAvailable = true;
if (organization.Seats.HasValue) if (organization.Seats.HasValue)
{ {
var seatCounts = await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var seatCounts =
await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
seatsAvailable = organization.Seats.Value - seatCounts.Total; seatsAvailable = organization.Seats.Value - seatCounts.Total;
enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count; enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count;
} }
@ -1218,7 +1223,8 @@ public class OrganizationService : IOrganizationService
} }
} }
var invitedUsers = await InviteUsersAsync(organizationId, invitingUserId: null, systemUser: eventSystemUser, userInvites); var invitedUsers = await InviteUsersAsync(organizationId, invitingUserId: null, systemUser: eventSystemUser,
userInvites);
foreach (var invitedUser in invitedUsers) foreach (var invitedUser in invitedUsers)
{ {
existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id); existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id);
@ -1255,7 +1261,8 @@ public class OrganizationService : IOrganizationService
} }
await _eventService.LogGroupEventsAsync( await _eventService.LogGroupEventsAsync(
savedGroups.Select(g => (g, EventType.Group_Created, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow))); savedGroups.Select(g => (g, EventType.Group_Created, (EventSystemUser?)eventSystemUser,
(DateTime?)DateTime.UtcNow)));
var updateGroups = existingExternalGroups var updateGroups = existingExternalGroups
.Where(g => groupsDict.ContainsKey(g.ExternalId)) .Where(g => groupsDict.ContainsKey(g.ExternalId))
@ -1282,11 +1289,11 @@ public class OrganizationService : IOrganizationService
await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds,
existingExternalUsersIdDict, existingExternalUsersIdDict,
existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null); existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null);
} }
await _eventService.LogGroupEventsAsync( await _eventService.LogGroupEventsAsync(
updateGroups.Select(g => (g, EventType.Group_Updated, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow))); updateGroups.Select(g => (g, EventType.Group_Updated, (EventSystemUser?)eventSystemUser,
(DateTime?)DateTime.UtcNow)));
} }
} }
@ -1298,10 +1305,12 @@ public class OrganizationService : IOrganizationService
await _ssoUserRepository.DeleteAsync(userId, organizationId); await _ssoUserRepository.DeleteAsync(userId, organizationId);
if (organizationId.HasValue) if (organizationId.HasValue)
{ {
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId); var organizationUser =
await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId);
if (organizationUser != null) if (organizationUser != null)
{ {
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UnlinkedSso); await _eventService.LogOrganizationUserEventAsync(organizationUser,
EventType.OrganizationUser_UnlinkedSso);
} }
} }
} }
@ -1423,7 +1432,7 @@ public class OrganizationService : IOrganizationService
} }
if ((plan.ProductTier == ProductTierType.TeamsStarter && if ((plan.ProductTier == ProductTierType.TeamsStarter &&
upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) || upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) ||
(plan.ProductTier != ProductTierType.TeamsStarter && (plan.ProductTier != ProductTierType.TeamsStarter &&
upgrade.AdditionalSmSeats.GetValueOrDefault() > upgrade.AdditionalSeats)) upgrade.AdditionalSmSeats.GetValueOrDefault() > upgrade.AdditionalSeats))
{ {
@ -1446,7 +1455,8 @@ public class OrganizationService : IOrganizationService
} }
} }
public async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType, Permissions permissions) public async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType,
OrganizationUserType? oldType, Permissions permissions)
{ {
if (await _currentContext.OrganizationOwner(organizationId)) if (await _currentContext.OrganizationOwner(organizationId))
{ {
@ -1473,13 +1483,15 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Custom users can not manage Admins or Owners."); throw new BadRequestException("Custom users can not manage Admins or Owners.");
} }
if (newType == OrganizationUserType.Custom && !await ValidateCustomPermissionsGrant(organizationId, permissions)) if (newType == OrganizationUserType.Custom &&
!await ValidateCustomPermissionsGrant(organizationId, permissions))
{ {
throw new BadRequestException("Custom users can only grant the same custom permissions that they have."); throw new BadRequestException("Custom users can only grant the same custom permissions that they have.");
} }
} }
public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType) public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId,
OrganizationUserType newType)
{ {
if (newType != OrganizationUserType.Custom) if (newType != OrganizationUserType.Custom)
{ {
@ -1494,7 +1506,8 @@ public class OrganizationService : IOrganizationService
if (!organization.UseCustomPermissions) if (!organization.UseCustomPermissions)
{ {
throw new BadRequestException("To enable custom permissions the organization must be on an Enterprise plan."); throw new BadRequestException(
"To enable custom permissions the organization must be on an Enterprise plan.");
} }
} }
@ -1600,7 +1613,8 @@ public class OrganizationService : IOrganizationService
EventSystemUser systemUser) EventSystemUser systemUser)
{ {
await RepositoryRevokeUserAsync(organizationUser); await RepositoryRevokeUserAsync(organizationUser);
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked,
systemUser);
if (organizationUser.UserId.HasValue) if (organizationUser.UserId.HasValue)
{ {
@ -1615,7 +1629,8 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Already revoked."); throw new BadRequestException("Already revoked.");
} }
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }, includeProvider: true)) if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId,
new[] { organizationUser.Id }, includeProvider: true))
{ {
throw new BadRequestException("Organization must have at least one confirmed owner."); throw new BadRequestException("Organization must have at least one confirmed owner.");
} }
@ -1663,7 +1678,8 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You cannot revoke yourself."); throw new BadRequestException("You cannot revoke yourself.");
} }
if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue && !deletingUserIsOwner) if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue &&
!deletingUserIsOwner)
{ {
throw new BadRequestException("Only owners can revoke other owners."); throw new BadRequestException("Only owners can revoke other owners.");
} }

View File

@ -0,0 +1,114 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Licenses;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Business;
namespace Bit.Core.AdminConsole.Services;
public static class OrganizationFactory
{
public static Organization Create(
User owner,
ClaimsPrincipal claimsPrincipal,
string publicKey,
string privateKey) => new()
{
Name = claimsPrincipal.GetValue<string>(OrganizationLicenseConstants.Name),
BillingEmail = claimsPrincipal.GetValue<string>(OrganizationLicenseConstants.BillingEmail),
BusinessName = claimsPrincipal.GetValue<string>(OrganizationLicenseConstants.BusinessName),
PlanType = claimsPrincipal.GetValue<PlanType>(OrganizationLicenseConstants.PlanType),
Seats = claimsPrincipal.GetValue<int?>(OrganizationLicenseConstants.Seats),
MaxCollections = claimsPrincipal.GetValue<short?>(OrganizationLicenseConstants.MaxCollections),
MaxStorageGb = 10240,
UsePolicies = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UsePolicies),
UseSso = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseSso),
UseKeyConnector = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseKeyConnector),
UseScim = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseScim),
UseGroups = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseGroups),
UseDirectory = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseDirectory),
UseEvents = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseEvents),
UseTotp = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseTotp),
Use2fa = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.Use2fa),
UseApi = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseApi),
UseResetPassword = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseResetPassword),
Plan = claimsPrincipal.GetValue<string>(OrganizationLicenseConstants.Plan),
SelfHost = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.SelfHost),
UsersGetPremium = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UsersGetPremium),
UseCustomPermissions =
claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseCustomPermissions),
Gateway = null,
GatewayCustomerId = null,
GatewaySubscriptionId = null,
ReferenceData = owner.ReferenceData,
Enabled = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.Enabled),
ExpirationDate = claimsPrincipal.GetValue<DateTime?>(OrganizationLicenseConstants.Expires),
LicenseKey = claimsPrincipal.GetValue<string>(OrganizationLicenseConstants.LicenseKey),
PublicKey = publicKey,
PrivateKey = privateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,
UsePasswordManager = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UsePasswordManager),
UseSecretsManager = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseSecretsManager),
SmSeats = claimsPrincipal.GetValue<int?>(OrganizationLicenseConstants.SmSeats),
SmServiceAccounts = claimsPrincipal.GetValue<int?>(OrganizationLicenseConstants.SmServiceAccounts),
UseRiskInsights = claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseRiskInsights),
UseOrganizationDomains =
claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseOrganizationDomains),
UseAdminSponsoredFamilies =
claimsPrincipal.GetValue<bool>(OrganizationLicenseConstants.UseAdminSponsoredFamilies),
};
public static Organization Create(
User owner,
OrganizationLicense license,
string publicKey,
string privateKey) => new()
{
Name = license.Name,
BillingEmail = license.BillingEmail,
BusinessName = license.BusinessName,
PlanType = license.PlanType,
Seats = license.Seats,
MaxCollections = license.MaxCollections,
MaxStorageGb = 10240,
UsePolicies = license.UsePolicies,
UseSso = license.UseSso,
UseKeyConnector = license.UseKeyConnector,
UseScim = license.UseScim,
UseGroups = license.UseGroups,
UseDirectory = license.UseDirectory,
UseEvents = license.UseEvents,
UseTotp = license.UseTotp,
Use2fa = license.Use2fa,
UseApi = license.UseApi,
UseResetPassword = license.UseResetPassword,
Plan = license.Plan,
SelfHost = license.SelfHost,
UsersGetPremium = license.UsersGetPremium,
UseCustomPermissions = license.UseCustomPermissions,
Gateway = null,
GatewayCustomerId = null,
GatewaySubscriptionId = null,
ReferenceData = owner.ReferenceData,
Enabled = license.Enabled,
ExpirationDate = license.Expires,
LicenseKey = license.LicenseKey,
PublicKey = publicKey,
PrivateKey = privateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,
UsePasswordManager = license.UsePasswordManager,
UseSecretsManager = license.UseSecretsManager,
SmSeats = license.SmSeats,
SmServiceAccounts = license.SmServiceAccounts,
UseRiskInsights = license.UseRiskInsights,
UseOrganizationDomains = license.UseOrganizationDomains,
UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies,
};
}