mirror of
https://github.com/bitwarden/server
synced 2026-01-03 17:14:00 +00:00
Merge branch 'refs/heads/main' into km/pm-10600
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Mail;
|
||||
|
||||
public class CannotDeleteManagedAccountViewModel : BaseMailModel
|
||||
{
|
||||
}
|
||||
@@ -11,11 +11,10 @@ namespace Bit.Core.Billing.Extensions;
|
||||
public static class BillingExtensions
|
||||
{
|
||||
public static bool IsBillable(this Provider provider) =>
|
||||
provider is
|
||||
{
|
||||
Type: ProviderType.Msp,
|
||||
Status: ProviderStatusType.Billable
|
||||
};
|
||||
provider.SupportsConsolidatedBilling() && provider.Status == ProviderStatusType.Billable;
|
||||
|
||||
public static bool SupportsConsolidatedBilling(this Provider provider)
|
||||
=> provider.Type is ProviderType.Msp or ProviderType.MultiOrganizationEnterprise;
|
||||
|
||||
public static bool IsValidClient(this Organization organization)
|
||||
=> organization is
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Migration.Models;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -307,7 +308,14 @@ public class ProviderMigrator(
|
||||
.FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly)?
|
||||
.SeatMinimum ?? 0;
|
||||
|
||||
await providerBillingService.UpdateSeatMinimums(provider, enterpriseSeatMinimum, teamsSeatMinimum);
|
||||
var updateSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand(
|
||||
provider.Id,
|
||||
provider.GatewaySubscriptionId,
|
||||
[
|
||||
(Plan: PlanType.EnterpriseMonthly, SeatsMinimum: enterpriseSeatMinimum),
|
||||
(Plan: PlanType.TeamsMonthly, SeatsMinimum: teamsSeatMinimum)
|
||||
]);
|
||||
await providerBillingService.UpdateSeatMinimums(updateSeatMinimumsCommand);
|
||||
|
||||
logger.LogInformation(
|
||||
"CB: Updated Stripe subscription for provider ({ProviderID}) with current seat minimums", provider.Id);
|
||||
@@ -325,13 +333,16 @@ public class ProviderMigrator(
|
||||
|
||||
var organizationCancellationCredit = organizationCustomers.Sum(customer => customer.Balance);
|
||||
|
||||
await stripeAdapter.CustomerBalanceTransactionCreate(provider.GatewayCustomerId,
|
||||
new CustomerBalanceTransactionCreateOptions
|
||||
{
|
||||
Amount = organizationCancellationCredit,
|
||||
Currency = "USD",
|
||||
Description = "Unused, prorated time for client organization subscriptions."
|
||||
});
|
||||
if (organizationCancellationCredit != 0)
|
||||
{
|
||||
await stripeAdapter.CustomerBalanceTransactionCreate(provider.GatewayCustomerId,
|
||||
new CustomerBalanceTransactionCreateOptions
|
||||
{
|
||||
Amount = organizationCancellationCredit,
|
||||
Currency = "USD",
|
||||
Description = "Unused, prorated time for client organization subscriptions."
|
||||
});
|
||||
}
|
||||
|
||||
var migrationRecords = await Task.WhenAll(organizations.Select(organization =>
|
||||
clientOrganizationMigrationRecordRepository.GetByOrganizationId(organization.Id)));
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
public record OrganizationMetadata(
|
||||
bool IsEligibleForSelfHost,
|
||||
bool IsOnSecretsManagerStandalone)
|
||||
{
|
||||
public static OrganizationMetadata Default() => new(
|
||||
IsEligibleForSelfHost: false,
|
||||
IsOnSecretsManagerStandalone: false);
|
||||
}
|
||||
bool IsManaged,
|
||||
bool IsOnSecretsManagerStandalone,
|
||||
bool IsSubscriptionUnpaid);
|
||||
|
||||
@@ -24,6 +24,7 @@ public record TeamsPlan : Plan
|
||||
Has2fa = true;
|
||||
HasApi = true;
|
||||
UsersGetPremium = true;
|
||||
HasScim = true;
|
||||
|
||||
UpgradeSortOrder = 3;
|
||||
DisplaySortOrder = 3;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Contracts;
|
||||
|
||||
public record ChangeProviderPlanCommand(
|
||||
Guid ProviderPlanId,
|
||||
PlanType NewPlan,
|
||||
string GatewaySubscriptionId);
|
||||
@@ -0,0 +1,10 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Contracts;
|
||||
|
||||
/// <param name="Id">The ID of the provider to update the seat minimums for.</param>
|
||||
/// <param name="Configuration">The new seat minimums for the provider.</param>
|
||||
public record UpdateProviderSeatMinimumsCommand(
|
||||
Guid Id,
|
||||
string GatewaySubscriptionId,
|
||||
IReadOnlyCollection<(PlanType Plan, int SeatsMinimum)> Configuration);
|
||||
@@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
using Bit.Core.Models.Business;
|
||||
using Stripe;
|
||||
|
||||
@@ -89,8 +90,12 @@ public interface IProviderBillingService
|
||||
Task<Subscription> SetupSubscription(
|
||||
Provider provider);
|
||||
|
||||
Task UpdateSeatMinimums(
|
||||
Provider provider,
|
||||
int enterpriseSeatMinimum,
|
||||
int teamsSeatMinimum);
|
||||
/// <summary>
|
||||
/// Changes the assigned provider plan for the provider.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to change the provider plan.</param>
|
||||
/// <returns></returns>
|
||||
Task ChangePlan(ChangeProviderPlanCommand command);
|
||||
|
||||
Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Models;
|
||||
@@ -27,7 +26,6 @@ public class OrganizationBillingService(
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<OrganizationBillingService> logger,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IProviderRepository providerRepository,
|
||||
ISetupIntentCache setupIntentCache,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IOrganizationBillingService
|
||||
@@ -64,18 +62,18 @@ public class OrganizationBillingService(
|
||||
return null;
|
||||
}
|
||||
|
||||
var customer = await subscriberService.GetCustomer(organization, new CustomerGetOptions
|
||||
{
|
||||
Expand = ["discount.coupon.applies_to"]
|
||||
});
|
||||
var customer = await subscriberService.GetCustomer(organization,
|
||||
new CustomerGetOptions { Expand = ["discount.coupon.applies_to"] });
|
||||
|
||||
var subscription = await subscriberService.GetSubscription(organization);
|
||||
|
||||
var isEligibleForSelfHost = await IsEligibleForSelfHost(organization, subscription);
|
||||
|
||||
var isEligibleForSelfHost = IsEligibleForSelfHost(organization);
|
||||
var isManaged = organization.Status == OrganizationStatusType.Managed;
|
||||
var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription);
|
||||
var isSubscriptionUnpaid = IsSubscriptionUnpaid(subscription);
|
||||
|
||||
return new OrganizationMetadata(isEligibleForSelfHost, isOnSecretsManagerStandalone);
|
||||
return new OrganizationMetadata(isEligibleForSelfHost, isManaged, isOnSecretsManagerStandalone,
|
||||
isSubscriptionUnpaid);
|
||||
}
|
||||
|
||||
public async Task UpdatePaymentMethod(
|
||||
@@ -339,26 +337,12 @@ public class OrganizationBillingService(
|
||||
return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
}
|
||||
|
||||
private async Task<bool> IsEligibleForSelfHost(
|
||||
Organization organization,
|
||||
Subscription? organizationSubscription)
|
||||
private static bool IsEligibleForSelfHost(
|
||||
Organization organization)
|
||||
{
|
||||
if (organization.Status != OrganizationStatusType.Managed)
|
||||
{
|
||||
return organization.Plan.Contains("Families") ||
|
||||
organization.Plan.Contains("Enterprise") && IsActive(organizationSubscription);
|
||||
}
|
||||
var eligibleSelfHostPlans = StaticStore.Plans.Where(plan => plan.HasSelfHost).Select(plan => plan.Type);
|
||||
|
||||
var provider = await providerRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
|
||||
var providerSubscription = await subscriberService.GetSubscriptionOrThrow(provider);
|
||||
|
||||
return organization.Plan.Contains("Enterprise") && IsActive(providerSubscription);
|
||||
|
||||
bool IsActive(Subscription? subscription) => subscription?.Status is
|
||||
StripeConstants.SubscriptionStatus.Active or
|
||||
StripeConstants.SubscriptionStatus.Trialing or
|
||||
StripeConstants.SubscriptionStatus.PastDue;
|
||||
return eligibleSelfHostPlans.Contains(organization.PlanType);
|
||||
}
|
||||
|
||||
private static bool IsOnSecretsManagerStandalone(
|
||||
@@ -392,5 +376,16 @@ public class OrganizationBillingService(
|
||||
return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any();
|
||||
}
|
||||
|
||||
private static bool IsSubscriptionUnpaid(Subscription subscription)
|
||||
{
|
||||
if (subscription == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return subscription.Status == "unpaid";
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ public static class FeatureFlagKeys
|
||||
public const string UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection";
|
||||
public const string ItemShare = "item-share";
|
||||
public const string DuoRedirect = "duo-redirect";
|
||||
public const string PM5864DollarThreshold = "PM-5864-dollar-threshold";
|
||||
public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email";
|
||||
public const string EnableConsolidatedBilling = "enable-consolidated-billing";
|
||||
public const string AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section";
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.40" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
||||
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
||||
<PackageReference Include="Azure.Storage.Queues" Version="12.19.1" />
|
||||
@@ -35,22 +35,22 @@
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.45.0" />
|
||||
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.6.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" />
|
||||
<PackageReference Include="Quartz" Version="3.9.0" />
|
||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
|
||||
<PackageReference Include="Sentry.Serilog" Version="3.41.4" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="7.0.6" />
|
||||
<PackageReference Include="Duende.IdentityServer" Version="7.0.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Sinks.SyslogMessages" Version="4.0.0" />
|
||||
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
|
||||
@@ -58,7 +58,7 @@
|
||||
<PackageReference Include="Stripe.net" Version="45.14.0" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
|
||||
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ public class Device : ITableObject<Guid>
|
||||
/// </summary>
|
||||
public string? EncryptedPrivateKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the device is active for the user.
|
||||
/// </summary>
|
||||
public bool Active { get; set; } = true;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{{#>FullHtmlLayout}}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: left;" valign="top" align="center">
|
||||
You have requested to delete your account. This action cannot be completed because your account is owned by an organization.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: left;" valign="top" align="center">
|
||||
Please contact your organization administrator for additional details.
|
||||
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{/FullHtmlLayout}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{#>BasicTextLayout}}
|
||||
You have requested to delete your account. This action cannot be completed because your account is owned by an organization.
|
||||
|
||||
Please contact your organization administrator for additional details.
|
||||
|
||||
{{/BasicTextLayout}}
|
||||
@@ -7,7 +7,7 @@ public interface IDeviceService
|
||||
{
|
||||
Task SaveAsync(Device device);
|
||||
Task ClearTokenAsync(Device device);
|
||||
Task DeleteAsync(Device device);
|
||||
Task DeactivateAsync(Device device);
|
||||
Task UpdateDevicesTrustAsync(string currentDeviceIdentifier,
|
||||
Guid currentUserId,
|
||||
DeviceKeysUpdateRequestModel currentDeviceUpdate,
|
||||
|
||||
@@ -18,6 +18,7 @@ public interface IMailService
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products);
|
||||
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
||||
Task SendCannotDeleteManagedAccountEmailAsync(string email);
|
||||
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
||||
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
|
||||
Task SendTwoFactorEmailAsync(string email, string token);
|
||||
|
||||
@@ -14,6 +14,17 @@ public interface IStripeAdapter
|
||||
CustomerBalanceTransactionCreateOptions options);
|
||||
Task<Stripe.Subscription> SubscriptionCreateAsync(Stripe.SubscriptionCreateOptions subscriptionCreateOptions);
|
||||
Task<Stripe.Subscription> SubscriptionGetAsync(string id, Stripe.SubscriptionGetOptions options = null);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a subscription object for a provider.
|
||||
/// </summary>
|
||||
/// <param name="id">The subscription ID.</param>
|
||||
/// <param name="providerId">The provider ID.</param>
|
||||
/// <param name="options">Additional options.</param>
|
||||
/// <returns>The subscription object.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the subscription doesn't belong to the provider.</exception>
|
||||
Task<Stripe.Subscription> ProviderSubscriptionGetAsync(string id, Guid providerId, Stripe.SubscriptionGetOptions options = null);
|
||||
|
||||
Task<List<Stripe.Subscription>> SubscriptionListAsync(StripeSubscriptionListOptions subscriptionSearchOptions);
|
||||
Task<Stripe.Subscription> SubscriptionUpdateAsync(string id, Stripe.SubscriptionUpdateOptions options = null);
|
||||
Task<Stripe.Subscription> SubscriptionCancelAsync(string Id, Stripe.SubscriptionCancelOptions options = null);
|
||||
|
||||
@@ -41,9 +41,18 @@ public class DeviceService : IDeviceService
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Device device)
|
||||
public async Task DeactivateAsync(Device device)
|
||||
{
|
||||
await _deviceRepository.DeleteAsync(device);
|
||||
// already deactivated
|
||||
if (!device.Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
device.Active = false;
|
||||
device.RevisionDate = DateTime.UtcNow;
|
||||
await _deviceRepository.UpsertAsync(device);
|
||||
|
||||
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,19 @@ public class HandlebarsMailService : IMailService
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendCannotDeleteManagedAccountEmailAsync(string email)
|
||||
{
|
||||
var message = CreateDefaultMessage("Delete Your Account", email);
|
||||
var model = new CannotDeleteManagedAccountViewModel
|
||||
{
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName,
|
||||
};
|
||||
await AddMessageContentAsync(message, "AdminConsole.CannotDeleteManagedAccount", model);
|
||||
message.Category = "CannotDeleteManagedAccount";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail)
|
||||
{
|
||||
var message = CreateDefaultMessage("Your Email Change", toEmail);
|
||||
|
||||
@@ -79,6 +79,20 @@ public class StripeAdapter : IStripeAdapter
|
||||
return _subscriptionService.GetAsync(id, options);
|
||||
}
|
||||
|
||||
public async Task<Subscription> ProviderSubscriptionGetAsync(
|
||||
string id,
|
||||
Guid providerId,
|
||||
SubscriptionGetOptions options = null)
|
||||
{
|
||||
var subscription = await _subscriptionService.GetAsync(id, options);
|
||||
if (subscription.Metadata.TryGetValue("providerId", out var value) && value == providerId.ToString())
|
||||
{
|
||||
return subscription;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Subscription does not belong to the provider.");
|
||||
}
|
||||
|
||||
public Task<Stripe.Subscription> SubscriptionUpdateAsync(string id,
|
||||
Stripe.SubscriptionUpdateOptions options = null)
|
||||
{
|
||||
|
||||
@@ -792,19 +792,16 @@ public class StripePaymentService : IPaymentService
|
||||
var daysUntilDue = sub.DaysUntilDue;
|
||||
var chargeNow = collectionMethod == "charge_automatically";
|
||||
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
|
||||
var isPm5864DollarThresholdEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5864DollarThreshold);
|
||||
var isAnnualPlan = sub?.Items?.Data.FirstOrDefault()?.Plan?.Interval == "year";
|
||||
|
||||
var subUpdateOptions = new SubscriptionUpdateOptions
|
||||
{
|
||||
Items = updatedItemOptions,
|
||||
ProrationBehavior = !isPm5864DollarThresholdEnabled || invoiceNow
|
||||
? Constants.AlwaysInvoice
|
||||
: Constants.CreateProrations,
|
||||
ProrationBehavior = invoiceNow ? Constants.AlwaysInvoice : Constants.CreateProrations,
|
||||
DaysUntilDue = daysUntilDue ?? 1,
|
||||
CollectionMethod = "send_invoice"
|
||||
};
|
||||
if (!invoiceNow && isAnnualPlan && isPm5864DollarThresholdEnabled && sub.Status.Trim() != "trialing")
|
||||
if (!invoiceNow && isAnnualPlan && sub.Status.Trim() != "trialing")
|
||||
{
|
||||
subUpdateOptions.PendingInvoiceItemInterval =
|
||||
new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" };
|
||||
@@ -838,7 +835,7 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isPm5864DollarThresholdEnabled && !invoiceNow)
|
||||
if (invoiceNow)
|
||||
{
|
||||
if (chargeNow)
|
||||
{
|
||||
|
||||
@@ -297,6 +297,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (await IsManagedByAnyOrganizationAsync(user.Id))
|
||||
{
|
||||
await _mailService.SendCannotDeleteManagedAccountEmailAsync(user.Email);
|
||||
return;
|
||||
}
|
||||
|
||||
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
||||
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ public class NoopMailService : IMailService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendCannotDeleteManagedAccountEmailAsync(string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
|
||||
16
src/Core/Tools/Entities/PasswordHealthReportApplication.cs
Normal file
16
src/Core/Tools/Entities/PasswordHealthReportApplication.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
public class PasswordHealthReportApplication : ITableObject<Guid>, IRevisable
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user