1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -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( public OrganizationUserSingleEmailInvite ToRequest(
ScimProviderType scimProvider, ScimProviderType scimProvider,
bool hasSecretsManager,
InviteOrganization inviteOrganization, InviteOrganization inviteOrganization,
DateTimeOffset performedAt) => DateTimeOffset performedAt) =>
new( new(
email: EmailForInvite(scimProvider), email: EmailForInvite(scimProvider),
hasSecretsManager: hasSecretsManager,
inviteOrganization: inviteOrganization, inviteOrganization: inviteOrganization,
performedAt: performedAt, performedAt: performedAt,
externalId: ExternalIdForInvite()); externalId: ExternalIdForInvite());

View File

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

View File

@ -41,7 +41,9 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
public async Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(OrganizationUserSingleEmailInvite request) 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) switch (result)
{ {

View File

@ -20,8 +20,8 @@ public class InviteOrganizationUsersRequest
PerformedAt = performedAt; PerformedAt = performedAt;
} }
public InviteOrganizationUsersRequest(OrganizationUserSingleEmailInvite request) : public InviteOrganizationUsersRequest(OrganizationUserSingleEmailInvite request, bool hasStandaloneSecretsManager) :
this([OrganizationUserInvite.Create(request)], this([OrganizationUserInvite.Create(request, hasStandaloneSecretsManager)],
request.InviteOrganization, request.InviteOrganization,
Guid.Empty, Guid.Empty,
request.PerformedAt) 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], Create([invite.Email],
invite.AccessibleCollections, invite.AccessibleCollections,
invite.Type, invite.Type,
invite.Permissions, invite.Permissions,
invite.ExternalId, invite.ExternalId,
invite.AccessSecretsManager); hasStandaloneSecretsManager);
private static void ValidateEmailAddresses(string[] emails) private static void ValidateEmailAddresses(string[] emails)
{ {

View File

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

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Billing.Models; using Bit.Core.Billing.Models;
using Bit.Core.Billing.Models.Api.Requests.Accounts; using Bit.Core.Billing.Models.Api.Requests.Accounts;
using Bit.Core.Billing.Models.Api.Requests.Organizations; using Bit.Core.Billing.Models.Api.Requests.Organizations;
@ -44,9 +45,28 @@ public interface IPaymentService
Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber); Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber);
Task<TaxInfo> GetTaxInfoAsync(ISubscriber subscriber); Task<TaxInfo> GetTaxInfoAsync(ISubscriber subscriber);
Task SaveTaxInfoAsync(ISubscriber subscriber, TaxInfo taxInfo); Task SaveTaxInfoAsync(ISubscriber subscriber, TaxInfo taxInfo);
Task<string> AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats, Task<string> AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats, int additionalServiceAccount);
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); 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(PreviewIndividualInvoiceRequestBody parameters, string gatewayCustomerId, string gatewaySubscriptionId);
Task<PreviewInvoiceResponseModel> PreviewInvoiceAsync(PreviewOrganizationInvoiceRequestBody 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;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Billing.Constants; using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Models; using Bit.Core.Billing.Models;
@ -1079,14 +1080,27 @@ public class StripePaymentService : IPaymentService
new SecretsManagerSubscribeUpdate(org, plan, additionalSmSeats, additionalServiceAccount), new SecretsManagerSubscribeUpdate(org, plan, additionalSmSeats, additionalServiceAccount),
true); 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; 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; return customer?.Discount?.Coupon?.Id == SecretsManagerStandaloneDiscountId;
} }