From b951b38c376e5c4c1a537c81be6c935b06040fb2 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:14:05 -0500 Subject: [PATCH] [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 --- .../Implementations/OrganizationService.cs | 242 ++++++++++-------- .../Services/OrganizationFactory.cs | 114 +++++++++ 2 files changed, 243 insertions(+), 113 deletions(-) create mode 100644 src/Core/AdminConsole/Services/OrganizationFactory.cs diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index d5320b5110..7947cbefff 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -97,7 +97,7 @@ public class OrganizationService : IOrganizationService IPricingClient pricingClient, IPolicyRequirementQuery policyRequirementQuery, ISendOrganizationInvitesCommand sendOrganizationInvitesCommand - ) + ) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -198,6 +198,7 @@ public class OrganizationService : IOrganizationService { await AdjustSeatsAsync(organization, seatAdjustment); } + if (maxAutoscaleSeats != organization.MaxAutoscaleSeats) { await UpdateAutoscalingAsync(organization, maxAutoscaleSeats); @@ -206,7 +207,6 @@ public class OrganizationService : IOrganizationService private async Task UpdateAutoscalingAsync(Organization organization, int? maxAutoscaleSeats) { - if (maxAutoscaleSeats.HasValue && organization.Seats.HasValue && maxAutoscaleSeats.Value < organization.Seats.Value) @@ -228,7 +228,8 @@ public class OrganizationService : IOrganizationService if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue && 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}.", "Reduce your max autoscale seat count.")); } @@ -249,7 +250,8 @@ public class OrganizationService : IOrganizationService return await AdjustSeatsAsync(organization, seatAdjustment); } - private async Task AdjustSeatsAsync(Organization organization, int seatAdjustment, IEnumerable ownerEmails = null) + private async Task AdjustSeatsAsync(Organization organization, int seatAdjustment, + IEnumerable ownerEmails = null) { if (organization.Seats == null) { @@ -285,10 +287,11 @@ public class OrganizationService : IOrganizationService } 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 " + - $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); + $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); } if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) @@ -299,8 +302,9 @@ public class OrganizationService : IOrganizationService { if (organization.UseAdminSponsoredFamilies || seatCounts.Sponsored > 0) { - throw new BadRequestException($"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."); + throw new BadRequestException( + $"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 { @@ -319,7 +323,8 @@ public class OrganizationService : IOrganizationService organization.Seats = (short?)newSeatTotal; 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 { @@ -328,7 +333,9 @@ public class OrganizationService : IOrganizationService ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, 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) { @@ -362,7 +369,7 @@ public class OrganizationService : IOrganizationService } 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) { throw new GatewayException("Cannot find an unverified bank account."); @@ -389,7 +396,7 @@ public class OrganizationService : IOrganizationService if (anySingleOrgPolicies) { 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) { 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); @@ -422,50 +429,11 @@ public class OrganizationService : IOrganizationService await ValidateSignUpPoliciesAsync(owner.Id); - var organization = new Organization - { - Name = license.Name, - BillingEmail = license.BillingEmail, - BusinessName = license.BusinessName, - 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 organization = claimsPrincipal != null + // If the ClaimsPrincipal exists (there's a token on the license), use it to build the organization. + ? OrganizationFactory.Create(owner, claimsPrincipal, publicKey, privateKey) + // If there's no ClaimsPrincipal (there's no token on the license), use the license to build the organization. + : OrganizationFactory.Create(owner, license, publicKey, privateKey); 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. /// This is common code used by both the cloud and self-hosted methods. /// - private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignUpAsync(Organization organization, - Guid ownerId, string ownerKey, string collectionName, bool withPayment) + private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> + SignUpAsync(Organization organization, + Guid ownerId, string ownerKey, string collectionName, bool withPayment) { try { @@ -537,7 +506,15 @@ public class OrganizationService : IOrganizationService if (orgUser != null) { 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); @@ -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)) { @@ -594,11 +572,12 @@ public class OrganizationService : IOrganizationService if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) { var customerService = new CustomerService(); - await customerService.UpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions - { - Email = organization.BillingEmail, - Description = organization.DisplayBusinessName() - }); + await customerService.UpdateAsync(organization.GatewayCustomerId, + new CustomerUpdateOptions + { + Email = organization.BillingEmail, + Description = organization.DisplayBusinessName() + }); } if (eventType == EventType.Organization_CollectionManagement_Updated) @@ -648,7 +627,8 @@ public class OrganizationService : IOrganizationService await UpdateAsync(organization); } - public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, + public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, + EventSystemUser? systemUser, OrganizationUserInvite invite, string externalId) { // 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)); 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, @@ -672,6 +653,7 @@ public class OrganizationService : IOrganizationService { throw new BadRequestException("This user has already been invited."); } + return result; } @@ -683,7 +665,8 @@ public class OrganizationService : IOrganizationService /// The system user which is sending the invite. Only used when inviting via SCIM; null if using a client app or Public API /// Details about the users being invited /// - public async Task> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, + public async Task> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, + EventSystemUser? systemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) { var inviteTypes = new HashSet(invites.Where(i => i.invite.Type.HasValue) @@ -695,7 +678,8 @@ public class OrganizationService : IOrganizationService { 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); } } @@ -705,7 +689,8 @@ public class OrganizationService : IOrganizationService if (systemUser.HasValue) { // 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 { @@ -716,8 +701,10 @@ public class OrganizationService : IOrganizationService return organizationUsers; } - private async Task<(List organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId, - IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) + private async + Task<(List organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> + SaveUsersSendInvitesAsync(Guid organizationId, + IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) { var organization = await GetOrgById(organizationId); var initialSeatCount = organization.Seats; @@ -727,7 +714,8 @@ public class OrganizationService : IOrganizationService } var existingEmails = new HashSet(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 var initialSmSeatCount = organization.SmSeats; @@ -755,7 +743,8 @@ public class OrganizationService : IOrganizationService .SelectMany(i => i.invite.Emails) .Count(email => !existingEmails.Contains(email)); - var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount); + var additionalSmSeatsRequired = + await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount); if (additionalSmSeatsRequired > 0) { 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); - 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."); } @@ -887,7 +878,8 @@ public class OrganizationService : IOrganizationService 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); } @@ -903,7 +895,8 @@ public class OrganizationService : IOrganizationService return (allOrgUsers, events); } - public async Task>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, + public async Task>> ResendInvitesAsync(Guid organizationId, + Guid? invitingUserId, IEnumerable organizationUsersId) { var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId); @@ -925,7 +918,8 @@ public class OrganizationService : IOrganizationService 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); if (orgUser == null || orgUser.OrganizationId != organizationId || @@ -1012,7 +1006,9 @@ public class OrganizationService : IOrganizationService IEnumerable ownerEmails; if (providerOrg != null) { - ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed)) + ownerEmails = + (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, + ProviderUserStatusType.Confirmed)) .Select(u => u.Email).Distinct(); } else @@ -1020,6 +1016,7 @@ public class OrganizationService : IOrganizationService ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); } + var initialSeatCount = organization.Seats.Value; await AdjustSeatsAsync(organization, seatsToAdd, ownerEmails); @@ -1034,8 +1031,8 @@ public class OrganizationService : IOrganizationService } - - public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId) + public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, + 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 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 if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) { - var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync(userId); + var resetPasswordPolicyRequirement = + await _policyRequirementQuery.GetAsync(userId); 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 { if (resetPasswordKey == null && resetPasswordPolicy.Data != null) { - var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, + JsonHelpers.IgnoreCase); 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; await _organizationUserRepository.ReplaceAsync(orgUser); - await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ? - EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); + await _eventService.LogOrganizationUserEventAsync(orgUser, + resetPasswordKey != null + ? EventType.OrganizationUser_ResetPassword_Enroll + : EventType.OrganizationUser_ResetPassword_Withdraw); } public async Task ImportAsync(Guid organizationId, @@ -1123,15 +1125,16 @@ public class OrganizationService : IOrganizationService var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId); var removeUsersSet = new HashSet(removeUserExternalIds) .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]); await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id)); events.AddRange(removeUsersSet.Select(u => ( - u, - EventType.OrganizationUser_Removed, - (DateTime?)DateTime.UtcNow - )) + u, + EventType.OrganizationUser_Removed, + (DateTime?)DateTime.UtcNow + )) ); } @@ -1144,10 +1147,10 @@ public class OrganizationService : IOrganizationService existingExternalUsersIdDict.ContainsKey(u.ExternalId)); await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id)); events.AddRange(usersToDelete.Select(u => ( - u, - EventType.OrganizationUser_Removed, - (DateTime?)DateTime.UtcNow - )) + u, + EventType.OrganizationUser_Removed, + (DateTime?)DateTime.UtcNow + )) ); foreach (var deletedUser in usersToDelete) { @@ -1175,6 +1178,7 @@ public class OrganizationService : IOrganizationService existingExternalUsersIdDict.Add(orgUser.ExternalId, orgUser.Id); } } + await _organizationUserRepository.UpsertManyAsync(usersToUpsert); // Add new users @@ -1185,7 +1189,8 @@ public class OrganizationService : IOrganizationService var enoughSeatsAvailable = true; if (organization.Seats.HasValue) { - var seatCounts = await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); + var seatCounts = + await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); seatsAvailable = organization.Seats.Value - seatCounts.Total; 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) { existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id); @@ -1255,7 +1261,8 @@ public class OrganizationService : IOrganizationService } 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 .Where(g => groupsDict.ContainsKey(g.ExternalId)) @@ -1282,11 +1289,11 @@ public class OrganizationService : IOrganizationService await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, existingExternalUsersIdDict, existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null); - } 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); if (organizationId.HasValue) { - var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId); + var organizationUser = + await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId); 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 && - upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) || + upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) || (plan.ProductTier != ProductTierType.TeamsStarter && 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)) { @@ -1473,13 +1483,15 @@ public class OrganizationService : IOrganizationService 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."); } } - public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType) + public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, + OrganizationUserType newType) { if (newType != OrganizationUserType.Custom) { @@ -1494,7 +1506,8 @@ public class OrganizationService : IOrganizationService 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) { await RepositoryRevokeUserAsync(organizationUser); - await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser); + await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, + systemUser); if (organizationUser.UserId.HasValue) { @@ -1615,7 +1629,8 @@ public class OrganizationService : IOrganizationService 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."); } @@ -1663,7 +1678,8 @@ public class OrganizationService : IOrganizationService 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."); } diff --git a/src/Core/AdminConsole/Services/OrganizationFactory.cs b/src/Core/AdminConsole/Services/OrganizationFactory.cs new file mode 100644 index 0000000000..3261d89253 --- /dev/null +++ b/src/Core/AdminConsole/Services/OrganizationFactory.cs @@ -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(OrganizationLicenseConstants.Name), + BillingEmail = claimsPrincipal.GetValue(OrganizationLicenseConstants.BillingEmail), + BusinessName = claimsPrincipal.GetValue(OrganizationLicenseConstants.BusinessName), + PlanType = claimsPrincipal.GetValue(OrganizationLicenseConstants.PlanType), + Seats = claimsPrincipal.GetValue(OrganizationLicenseConstants.Seats), + MaxCollections = claimsPrincipal.GetValue(OrganizationLicenseConstants.MaxCollections), + MaxStorageGb = 10240, + UsePolicies = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePolicies), + UseSso = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSso), + UseKeyConnector = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseKeyConnector), + UseScim = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseScim), + UseGroups = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseGroups), + UseDirectory = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseDirectory), + UseEvents = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseEvents), + UseTotp = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseTotp), + Use2fa = claimsPrincipal.GetValue(OrganizationLicenseConstants.Use2fa), + UseApi = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseApi), + UseResetPassword = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseResetPassword), + Plan = claimsPrincipal.GetValue(OrganizationLicenseConstants.Plan), + SelfHost = claimsPrincipal.GetValue(OrganizationLicenseConstants.SelfHost), + UsersGetPremium = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsersGetPremium), + UseCustomPermissions = + claimsPrincipal.GetValue(OrganizationLicenseConstants.UseCustomPermissions), + Gateway = null, + GatewayCustomerId = null, + GatewaySubscriptionId = null, + ReferenceData = owner.ReferenceData, + Enabled = claimsPrincipal.GetValue(OrganizationLicenseConstants.Enabled), + ExpirationDate = claimsPrincipal.GetValue(OrganizationLicenseConstants.Expires), + LicenseKey = claimsPrincipal.GetValue(OrganizationLicenseConstants.LicenseKey), + PublicKey = publicKey, + PrivateKey = privateKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + Status = OrganizationStatusType.Created, + UsePasswordManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePasswordManager), + UseSecretsManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSecretsManager), + SmSeats = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmSeats), + SmServiceAccounts = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmServiceAccounts), + UseRiskInsights = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseRiskInsights), + UseOrganizationDomains = + claimsPrincipal.GetValue(OrganizationLicenseConstants.UseOrganizationDomains), + UseAdminSponsoredFamilies = + claimsPrincipal.GetValue(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, + }; +}