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,
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<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)
{
@ -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.
/// </summary>
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<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser,
public async Task<OrganizationUser> 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
/// <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>
/// <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)
{
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)
{
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<OrganizationUser> organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites)
private async
Task<(List<OrganizationUser> 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<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
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<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId,
Guid? invitingUserId,
IEnumerable<Guid> 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<string> 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<ResetPasswordPolicyRequirement>(userId);
var resetPasswordPolicyRequirement =
await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(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<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(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<string>(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.");
}

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,
};
}