1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

Made HasSecretsManagerStandalone return if org doesn't have sm. Added overload for lighter weight model and moved common code to private method.

This commit is contained in:
jrmccannon 2025-03-20 09:06:34 -05:00
parent edbf1cea41
commit a2b155e81c
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
8 changed files with 53 additions and 25 deletions

View File

@ -29,12 +29,10 @@ public class ScimUserRequestModel : BaseScimUserModel
public OrganizationUserSingleEmailInvite ToRequest(
ScimProviderType scimProvider,
bool hasSecretsManager,
InviteOrganization inviteOrganization,
DateTimeOffset performedAt) =>
new(
email: EmailForInvite(scimProvider),
hasSecretsManager: hasSecretsManager,
inviteOrganization: inviteOrganization,
performedAt: performedAt,
externalId: ExternalIdForInvite());

View File

@ -65,15 +65,15 @@ public class PostUserCommand(
throw new NotFoundException();
}
var hasStandaloneSecretsManager = await paymentService.HasSecretsManagerStandalone(organization);
invite.AccessSecretsManager = hasStandaloneSecretsManager;
if (featureService.IsEnabled(FeatureFlagKeys.ScimInviteUserOptimization))
{
return await InviteScimOrganizationUserAsync(model, organization, scimProvider,
hasStandaloneSecretsManager);
return await InviteScimOrganizationUserAsync(model, organization, scimProvider);
}
var hasStandaloneSecretsManager = await paymentService.HasSecretsManagerStandalone(organization);
invite.AccessSecretsManager = hasStandaloneSecretsManager;
var invitedOrgUser = await organizationService.InviteUserAsync(organizationId, invitingUserId: null,
EventSystemUser.SCIM,
invite, externalId);
@ -83,7 +83,7 @@ public class PostUserCommand(
}
private async Task<OrganizationUserUserDetails?> InviteScimOrganizationUserAsync(ScimUserRequestModel model,
Organization organization, ScimProviderType scimProvider, bool hasStandaloneSecretsManager)
Organization organization, ScimProviderType scimProvider)
{
var plan = await pricingClient.GetPlan(organization.PlanType);
@ -96,7 +96,6 @@ public class PostUserCommand(
var request = model.ToRequest(
scimProvider: scimProvider,
hasSecretsManager: hasStandaloneSecretsManager,
inviteOrganization: new InviteOrganization(organization, plan),
performedAt: timeProvider.GetUtcNow());

View File

@ -41,7 +41,9 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
public async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(OrganizationUserSingleEmailInvite request)
{
var result = await InviteOrganizationUsersAsync(new InviteOrganizationUsersRequest(request));
var hasSecretsManager = await paymentService.HasSecretsManagerStandalone(request.InviteOrganization);
var result = await InviteOrganizationUsersAsync(new InviteOrganizationUsersRequest(request, hasSecretsManager));
switch (result)
{

View File

@ -20,8 +20,8 @@ public class InviteOrganizationUsersRequest
PerformedAt = performedAt;
}
public InviteOrganizationUsersRequest(OrganizationUserSingleEmailInvite request) :
this([OrganizationUserInvite.Create(request)],
public InviteOrganizationUsersRequest(OrganizationUserSingleEmailInvite request, bool hasStandaloneSecretsManager) :
this([OrganizationUserInvite.Create(request, hasStandaloneSecretsManager)],
request.InviteOrganization,
Guid.Empty,
request.PerformedAt)

View File

@ -42,13 +42,13 @@ public class OrganizationUserInvite
};
}
public static OrganizationUserInvite Create(OrganizationUserSingleEmailInvite invite) =>
public static OrganizationUserInvite Create(OrganizationUserSingleEmailInvite invite, bool hasStandaloneSecretsManager) =>
Create([invite.Email],
invite.AccessibleCollections,
invite.Type,
invite.Permissions,
invite.ExternalId,
invite.AccessSecretsManager);
hasStandaloneSecretsManager);
private static void ValidateEmailAddresses(string[] emails)
{

View File

@ -14,21 +14,18 @@ public record OrganizationUserSingleEmailInvite
public CollectionAccessSelection[] AccessibleCollections { get; init; } = [];
public Permissions Permissions { get; init; } = new();
public OrganizationUserType Type { get; init; } = OrganizationUserType.User;
public bool AccessSecretsManager { get; init; }
public InviteOrganization InviteOrganization { get; private init; }
public DateTimeOffset PerformedAt { get; private init; }
public string ExternalId { get; private init; } = string.Empty;
public OrganizationUserSingleEmailInvite(string email,
bool hasSecretsManager,
InviteOrganization inviteOrganization,
DateTimeOffset performedAt,
string externalId) : this(
email: email,
accessibleCollections: [],
type: OrganizationUserType.User,
permissions: new Permissions(),
accessSecretsManager: hasSecretsManager)
permissions: new Permissions())
{
InviteOrganization = inviteOrganization;
PerformedAt = performedAt;
@ -38,8 +35,7 @@ public record OrganizationUserSingleEmailInvite
public OrganizationUserSingleEmailInvite(string email,
IEnumerable<CollectionAccessSelection> accessibleCollections,
OrganizationUserType type,
Permissions permissions,
bool accessSecretsManager)
Permissions permissions)
{
if (!email.IsValidEmail())
{
@ -55,6 +51,5 @@ public record OrganizationUserSingleEmailInvite
AccessibleCollections = accessibleCollections.ToArray();
Type = type;
Permissions = permissions;
AccessSecretsManager = accessSecretsManager;
}
}

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Models.Api.Requests.Accounts;
using Bit.Core.Billing.Models.Api.Requests.Organizations;
@ -44,9 +45,28 @@ public interface IPaymentService
Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber);
Task<TaxInfo> GetTaxInfoAsync(ISubscriber subscriber);
Task SaveTaxInfoAsync(ISubscriber subscriber, TaxInfo taxInfo);
Task<string> AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats,
int additionalServiceAccount);
Task<string> AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats, int additionalServiceAccount);
/// <summary>
/// Secrets Manager Standalone is a discount in Stripe that is used to give an organization access to Secrets Manager.
/// Usually, this also implies that when they invite a user to their organization, they are doing so for both Password
/// Manager and Secrets Manger.
///
/// This will not call out to Stripe if they don't have a GatewayId or if they don't have Secrets Manager.
/// </summary>
/// <param name="organization">Organization Entity</param>
/// <returns>If the organization has Secrets Manager and has the Standalone Stripe Discount</returns>
Task<bool> HasSecretsManagerStandalone(Organization organization);
/// <summary>
/// Secrets Manager Standalone is a discount in Stripe that is used to give an organization access to Secrets Manager.
/// Usually, this also implies that when they invite a user to their organization, they are doing so for both Password
/// Manager and Secrets Manger.
///
/// This will not call out to Stripe if they don't have a GatewayId or if they don't have Secrets Manager.
/// </summary>
/// <param name="organization">Organization Representation used for Inviting Organization Users</param>
/// <returns>If the organization has Secrets Manager and has the Standalone Stripe Discount</returns>
Task<bool> HasSecretsManagerStandalone(InviteOrganization organization);
Task<PreviewInvoiceResponseModel> PreviewInvoiceAsync(PreviewIndividualInvoiceRequestBody parameters, string gatewayCustomerId, string gatewaySubscriptionId);
Task<PreviewInvoiceResponseModel> PreviewInvoiceAsync(PreviewOrganizationInvoiceRequestBody parameters, string gatewayCustomerId, string gatewaySubscriptionId);

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Models;
@ -1079,14 +1080,27 @@ public class StripePaymentService : IPaymentService
new SecretsManagerSubscribeUpdate(org, plan, additionalSmSeats, additionalServiceAccount),
true);
public async Task<bool> HasSecretsManagerStandalone(Organization organization)
public async Task<bool> HasSecretsManagerStandalone(Organization organization) =>
await HasSecretsManagerAsync(gatewayCustomerId: organization.GatewayCustomerId,
organizationHasSecretsManager: organization.UseSecretsManager);
public async Task<bool> HasSecretsManagerStandalone(InviteOrganization organization) =>
await HasSecretsManagerAsync(gatewayCustomerId: organization.GatewayCustomerId,
organizationHasSecretsManager: organization.UseSecretsManager);
private async Task<bool> HasSecretsManagerAsync(string gatewayCustomerId, bool organizationHasSecretsManager)
{
if (string.IsNullOrEmpty(organization.GatewayCustomerId))
if (string.IsNullOrEmpty(gatewayCustomerId))
{
return false;
}
var customer = await _stripeAdapter.CustomerGetAsync(organization.GatewayCustomerId);
if (organizationHasSecretsManager is false)
{
return false;
}
var customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId);
return customer?.Discount?.Coupon?.Id == SecretsManagerStandaloneDiscountId;
}