mirror of
https://github.com/bitwarden/server.git
synced 2025-06-07 11:40:31 -05:00
Merge remote-tracking branch 'origin/main' into user-service-test-tweaks
This commit is contained in:
commit
dadc227ee3
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@ -90,6 +90,9 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev
|
|||||||
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
||||||
.github/workflows/test.yml @bitwarden/team-platform-dev
|
.github/workflows/test.yml @bitwarden/team-platform-dev
|
||||||
**/*Platform* @bitwarden/team-platform-dev
|
**/*Platform* @bitwarden/team-platform-dev
|
||||||
|
**/.dockerignore @bitwarden/team-platform-dev
|
||||||
|
**/Dockerfile @bitwarden/team-platform-dev
|
||||||
|
**/entrypoint.sh @bitwarden/team-platform-dev
|
||||||
|
|
||||||
# Multiple owners - DO NOT REMOVE (BRE)
|
# Multiple owners - DO NOT REMOVE (BRE)
|
||||||
**/packages.lock.json
|
**/packages.lock.json
|
||||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -636,7 +636,9 @@ jobs:
|
|||||||
|
|
||||||
setup-ephemeral-environment:
|
setup-ephemeral-environment:
|
||||||
name: Setup Ephemeral Environment
|
name: Setup Ephemeral Environment
|
||||||
needs: build-docker
|
needs:
|
||||||
|
- build-artifacts
|
||||||
|
- build-docker
|
||||||
if: |
|
if: |
|
||||||
needs.build-artifacts.outputs.has_secrets == 'true'
|
needs.build-artifacts.outputs.has_secrets == 'true'
|
||||||
&& github.event_name == 'pull_request'
|
&& github.event_name == 'pull_request'
|
||||||
|
4
.github/workflows/build_target.yml
vendored
4
.github/workflows/build_target.yml
vendored
@ -2,7 +2,9 @@ name: Build on PR Target
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize, reopened]
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
8
.github/workflows/scan.yml
vendored
8
.github/workflows/scan.yml
vendored
@ -7,8 +7,14 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
- "rc"
|
- "rc"
|
||||||
- "hotfix-rc"
|
- "hotfix-rc"
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
branches-ignore:
|
||||||
|
- main
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize, reopened]
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-run:
|
check-run:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<Version>2025.5.0</Version>
|
<Version>2025.5.1</Version>
|
||||||
|
|
||||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
@ -3,9 +3,9 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
|||||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
@ -7,13 +7,12 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.AdminConsole.Providers;
|
namespace Bit.Commercial.Core.AdminConsole.Providers;
|
||||||
@ -23,7 +22,6 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationService _organizationService;
|
|
||||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||||
private readonly IStripeAdapter _stripeAdapter;
|
private readonly IStripeAdapter _stripeAdapter;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
@ -31,26 +29,22 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
private readonly ISubscriberService _subscriberService;
|
private readonly ISubscriberService _subscriberService;
|
||||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
private readonly IAutomaticTaxStrategy _automaticTaxStrategy;
|
|
||||||
|
|
||||||
public RemoveOrganizationFromProviderCommand(
|
public RemoveOrganizationFromProviderCommand(
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationService organizationService,
|
|
||||||
IProviderOrganizationRepository providerOrganizationRepository,
|
IProviderOrganizationRepository providerOrganizationRepository,
|
||||||
IStripeAdapter stripeAdapter,
|
IStripeAdapter stripeAdapter,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IProviderBillingService providerBillingService,
|
IProviderBillingService providerBillingService,
|
||||||
ISubscriberService subscriberService,
|
ISubscriberService subscriberService,
|
||||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||||
IPricingClient pricingClient,
|
IPricingClient pricingClient)
|
||||||
[FromKeyedServices(AutomaticTaxFactory.BusinessUse)] IAutomaticTaxStrategy automaticTaxStrategy)
|
|
||||||
{
|
{
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationService = organizationService;
|
|
||||||
_providerOrganizationRepository = providerOrganizationRepository;
|
_providerOrganizationRepository = providerOrganizationRepository;
|
||||||
_stripeAdapter = stripeAdapter;
|
_stripeAdapter = stripeAdapter;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
@ -58,7 +52,6 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
_subscriberService = subscriberService;
|
_subscriberService = subscriberService;
|
||||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
_automaticTaxStrategy = automaticTaxStrategy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveOrganizationFromProvider(
|
public async Task RemoveOrganizationFromProvider(
|
||||||
@ -76,7 +69,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
|
|
||||||
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(
|
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(
|
||||||
providerOrganization.OrganizationId,
|
providerOrganization.OrganizationId,
|
||||||
Array.Empty<Guid>(),
|
[],
|
||||||
includeProvider: false))
|
includeProvider: false))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
||||||
@ -101,7 +94,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// When a client organization is unlinked from a provider, we have to check if they're Stripe-enabled
|
/// When a client organization is unlinked from a provider, we have to check if they're Stripe-enabled
|
||||||
/// and, if they are, we remove their MSP discount and set their Subscription to `send_invoice`. This is because
|
/// and, if they are, we remove their MSP discount and set their Subscription to `send_invoice`. This is because
|
||||||
/// the provider's payment method will be removed from their Stripe customer causing ensuing charges to fail. Lastly,
|
/// the provider's payment method will be removed from their Stripe customer, causing ensuing charges to fail. Lastly,
|
||||||
/// we email the organization owners letting them know they need to add a new payment method.
|
/// we email the organization owners letting them know they need to add a new payment method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ResetOrganizationBillingAsync(
|
private async Task ResetOrganizationBillingAsync(
|
||||||
@ -141,15 +134,18 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
Items = [new SubscriptionItemOptions { Price = plan.PasswordManager.StripeSeatPlanId, Quantity = organization.Seats }]
|
Items = [new SubscriptionItemOptions { Price = plan.PasswordManager.StripeSeatPlanId, Quantity = organization.Seats }]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
var setNonUSBusinessUseToReverseCharge = _featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
|
if (setNonUSBusinessUseToReverseCharge)
|
||||||
{
|
{
|
||||||
_automaticTaxStrategy.SetCreateOptions(subscriptionCreateOptions, customer);
|
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||||
}
|
}
|
||||||
else
|
else if (customer.HasRecognizedTaxLocation())
|
||||||
{
|
{
|
||||||
subscriptionCreateOptions.AutomaticTax ??= new SubscriptionAutomaticTaxOptions
|
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||||
{
|
{
|
||||||
Enabled = true
|
Enabled = customer.Address.Country == "US" ||
|
||||||
|
customer.TaxIds.Any()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +182,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
|||||||
await _mailService.SendProviderUpdatePaymentMethod(
|
await _mailService.SendProviderUpdatePaymentMethod(
|
||||||
organization.Id,
|
organization.Id,
|
||||||
organization.Name,
|
organization.Name,
|
||||||
provider.Name,
|
provider.Name!,
|
||||||
organizationOwnerEmails);
|
organizationOwnerEmails);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,13 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
|||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -53,6 +54,7 @@ public class ProviderService : IProviderService
|
|||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
|
private readonly IProviderClientOrganizationSignUpCommand _providerClientOrganizationSignUpCommand;
|
||||||
|
|
||||||
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||||
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
|
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
|
||||||
@ -61,7 +63,8 @@ public class ProviderService : IProviderService
|
|||||||
IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
|
IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
|
||||||
ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService,
|
ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService,
|
||||||
IDataProtectorTokenFactory<ProviderDeleteTokenable> providerDeleteTokenDataFactory,
|
IDataProtectorTokenFactory<ProviderDeleteTokenable> providerDeleteTokenDataFactory,
|
||||||
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient)
|
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient,
|
||||||
|
IProviderClientOrganizationSignUpCommand providerClientOrganizationSignUpCommand)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_providerUserRepository = providerUserRepository;
|
_providerUserRepository = providerUserRepository;
|
||||||
@ -81,6 +84,7 @@ public class ProviderService : IProviderService
|
|||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_providerBillingService = providerBillingService;
|
_providerBillingService = providerBillingService;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
|
_providerClientOrganizationSignUpCommand = providerClientOrganizationSignUpCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo, TokenizedPaymentSource tokenizedPaymentSource = null)
|
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo, TokenizedPaymentSource tokenizedPaymentSource = null)
|
||||||
@ -560,12 +564,12 @@ public class ProviderService : IProviderService
|
|||||||
|
|
||||||
ThrowOnInvalidPlanType(provider.Type, organizationSignup.Plan);
|
ThrowOnInvalidPlanType(provider.Type, organizationSignup.Plan);
|
||||||
|
|
||||||
var (organization, _, defaultCollection) = await _organizationService.SignupClientAsync(organizationSignup);
|
var signUpResponse = await _providerClientOrganizationSignUpCommand.SignUpClientOrganizationAsync(organizationSignup);
|
||||||
|
|
||||||
var providerOrganization = new ProviderOrganization
|
var providerOrganization = new ProviderOrganization
|
||||||
{
|
{
|
||||||
ProviderId = providerId,
|
ProviderId = providerId,
|
||||||
OrganizationId = organization.Id,
|
OrganizationId = signUpResponse.Organization.Id,
|
||||||
Key = organizationSignup.OwnerKey,
|
Key = organizationSignup.OwnerKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -574,12 +578,12 @@ public class ProviderService : IProviderService
|
|||||||
|
|
||||||
// Give the owner Can Manage access over the default collection
|
// Give the owner Can Manage access over the default collection
|
||||||
// The orgUser is not available when the org is created so we have to do it here as part of the invite
|
// The orgUser is not available when the org is created so we have to do it here as part of the invite
|
||||||
var defaultOwnerAccess = defaultCollection != null
|
var defaultOwnerAccess = signUpResponse.DefaultCollection != null
|
||||||
?
|
?
|
||||||
[
|
[
|
||||||
new CollectionAccessSelection
|
new CollectionAccessSelection
|
||||||
{
|
{
|
||||||
Id = defaultCollection.Id,
|
Id = signUpResponse.DefaultCollection.Id,
|
||||||
HidePasswords = false,
|
HidePasswords = false,
|
||||||
ReadOnly = false,
|
ReadOnly = false,
|
||||||
Manage = true
|
Manage = true
|
||||||
@ -587,7 +591,7 @@ public class ProviderService : IProviderService
|
|||||||
]
|
]
|
||||||
: Array.Empty<CollectionAccessSelection>();
|
: Array.Empty<CollectionAccessSelection>();
|
||||||
|
|
||||||
await _organizationService.InviteUsersAsync(organization.Id, user.Id, systemUser: null,
|
await _organizationService.InviteUsersAsync(signUpResponse.Organization.Id, user.Id, systemUser: null,
|
||||||
new (OrganizationUserInvite, string)[]
|
new (OrganizationUserInvite, string)[]
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Bit.Core.Billing.Entities;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
using CsvHelper.Configuration.Attributes;
|
using CsvHelper.Configuration.Attributes;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Billing.Models;
|
namespace Bit.Commercial.Core.Billing.Providers.Models;
|
||||||
|
|
||||||
public class ProviderClientInvoiceReportRow
|
public class ProviderClientInvoiceReportRow
|
||||||
{
|
{
|
@ -7,11 +7,12 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing;
|
using Bit.Core.Billing;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -24,7 +25,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using OneOf;
|
using OneOf;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Billing;
|
namespace Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
|
|
||||||
[RequireFeature(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion)]
|
[RequireFeature(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion)]
|
||||||
public class BusinessUnitConverter(
|
public class BusinessUnitConverter(
|
||||||
@ -67,6 +68,7 @@ public class BusinessUnitConverter(
|
|||||||
organization.MaxStorageGb = updatedPlan.PasswordManager.BaseStorageGb;
|
organization.MaxStorageGb = updatedPlan.PasswordManager.BaseStorageGb;
|
||||||
organization.UsePolicies = updatedPlan.HasPolicies;
|
organization.UsePolicies = updatedPlan.HasPolicies;
|
||||||
organization.UseSso = updatedPlan.HasSso;
|
organization.UseSso = updatedPlan.HasSso;
|
||||||
|
organization.UseOrganizationDomains = updatedPlan.HasOrganizationDomains;
|
||||||
organization.UseGroups = updatedPlan.HasGroups;
|
organization.UseGroups = updatedPlan.HasGroups;
|
||||||
organization.UseEvents = updatedPlan.HasEvents;
|
organization.UseEvents = updatedPlan.HasEvents;
|
||||||
organization.UseDirectory = updatedPlan.HasDirectory;
|
organization.UseDirectory = updatedPlan.HasDirectory;
|
@ -1,5 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Bit.Commercial.Core.Billing.Models;
|
using Bit.Commercial.Core.Billing.Providers.Models;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
@ -8,15 +8,17 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Billing;
|
using Bit.Core.Billing;
|
||||||
using Bit.Core.Billing.Caches;
|
using Bit.Core.Billing.Caches;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Models;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Billing.Services.Contracts;
|
using Bit.Core.Billing.Tax.Models;
|
||||||
using Bit.Core.Billing.Services.Implementations.AutomaticTax;
|
using Bit.Core.Billing.Tax.Services;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
@ -25,15 +27,13 @@ using Bit.Core.Services;
|
|||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Braintree;
|
using Braintree;
|
||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
using static Bit.Core.Billing.Utilities;
|
using static Bit.Core.Billing.Utilities;
|
||||||
using Customer = Stripe.Customer;
|
using Customer = Stripe.Customer;
|
||||||
using Subscription = Stripe.Subscription;
|
using Subscription = Stripe.Subscription;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Billing;
|
namespace Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
|
|
||||||
public class ProviderBillingService(
|
public class ProviderBillingService(
|
||||||
IBraintreeGateway braintreeGateway,
|
IBraintreeGateway braintreeGateway,
|
||||||
@ -50,8 +50,7 @@ public class ProviderBillingService(
|
|||||||
ISetupIntentCache setupIntentCache,
|
ISetupIntentCache setupIntentCache,
|
||||||
IStripeAdapter stripeAdapter,
|
IStripeAdapter stripeAdapter,
|
||||||
ISubscriberService subscriberService,
|
ISubscriberService subscriberService,
|
||||||
ITaxService taxService,
|
ITaxService taxService)
|
||||||
[FromKeyedServices(AutomaticTaxFactory.BusinessUse)] IAutomaticTaxStrategy automaticTaxStrategy)
|
|
||||||
: IProviderBillingService
|
: IProviderBillingService
|
||||||
{
|
{
|
||||||
public async Task AddExistingOrganization(
|
public async Task AddExistingOrganization(
|
||||||
@ -97,6 +96,7 @@ public class ProviderBillingService(
|
|||||||
organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb;
|
organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb;
|
||||||
organization.UsePolicies = plan.HasPolicies;
|
organization.UsePolicies = plan.HasPolicies;
|
||||||
organization.UseSso = plan.HasSso;
|
organization.UseSso = plan.HasSso;
|
||||||
|
organization.UseOrganizationDomains = plan.HasOrganizationDomains;
|
||||||
organization.UseGroups = plan.HasGroups;
|
organization.UseGroups = plan.HasGroups;
|
||||||
organization.UseEvents = plan.HasEvents;
|
organization.UseEvents = plan.HasEvents;
|
||||||
organization.UseDirectory = plan.HasDirectory;
|
organization.UseDirectory = plan.HasDirectory;
|
||||||
@ -125,7 +125,7 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* We have to scale the provider's seats before the ProviderOrganization
|
* We have to scale the provider's seats before the ProviderOrganization
|
||||||
* row is inserted so the added organization's seats don't get double counted.
|
* row is inserted so the added organization's seats don't get double-counted.
|
||||||
*/
|
*/
|
||||||
await ScaleSeats(provider, organization.PlanType, organization.Seats!.Value);
|
await ScaleSeats(provider, organization.PlanType, organization.Seats!.Value);
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ public class ProviderBillingService(
|
|||||||
|
|
||||||
var providerCustomer = await subscriberService.GetCustomerOrThrow(provider, new CustomerGetOptions
|
var providerCustomer = await subscriberService.GetCustomerOrThrow(provider, new CustomerGetOptions
|
||||||
{
|
{
|
||||||
Expand = ["tax_ids"]
|
Expand = ["tax", "tax_ids"]
|
||||||
});
|
});
|
||||||
|
|
||||||
var providerTaxId = providerCustomer.TaxIds.FirstOrDefault();
|
var providerTaxId = providerCustomer.TaxIds.FirstOrDefault();
|
||||||
@ -281,6 +281,13 @@ public class ProviderBillingService(
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var setNonUSBusinessUseToReverseCharge = featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
|
if (setNonUSBusinessUseToReverseCharge && providerCustomer.Address is not { Country: "US" })
|
||||||
|
{
|
||||||
|
customerCreateOptions.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||||
|
}
|
||||||
|
|
||||||
var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||||
|
|
||||||
organization.GatewayCustomerId = customer.Id;
|
organization.GatewayCustomerId = customer.Id;
|
||||||
@ -517,6 +524,13 @@ public class ProviderBillingService(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var setNonUSBusinessUseToReverseCharge = featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
|
if (setNonUSBusinessUseToReverseCharge && taxInfo.BillingAddressCountry != "US")
|
||||||
|
{
|
||||||
|
options.TaxExempt = StripeConstants.TaxExempt.Reverse;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(taxInfo.TaxIdNumber))
|
if (!string.IsNullOrEmpty(taxInfo.TaxIdNumber))
|
||||||
{
|
{
|
||||||
var taxIdType = taxService.GetStripeTaxCode(
|
var taxIdType = taxService.GetStripeTaxCode(
|
||||||
@ -528,6 +542,7 @@ public class ProviderBillingService(
|
|||||||
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||||
taxInfo.BillingAddressCountry,
|
taxInfo.BillingAddressCountry,
|
||||||
taxInfo.TaxIdNumber);
|
taxInfo.TaxIdNumber);
|
||||||
|
|
||||||
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,6 +707,13 @@ public class ProviderBillingService(
|
|||||||
customer.Metadata.ContainsKey(BraintreeCustomerIdKey) ||
|
customer.Metadata.ContainsKey(BraintreeCustomerIdKey) ||
|
||||||
setupIntent.IsUnverifiedBankAccount());
|
setupIntent.IsUnverifiedBankAccount());
|
||||||
|
|
||||||
|
int? trialPeriodDays = provider.Type switch
|
||||||
|
{
|
||||||
|
ProviderType.Msp when usePaymentMethod => 14,
|
||||||
|
ProviderType.BusinessUnit when usePaymentMethod => 4,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
var subscriptionCreateOptions = new SubscriptionCreateOptions
|
var subscriptionCreateOptions = new SubscriptionCreateOptions
|
||||||
{
|
{
|
||||||
CollectionMethod = usePaymentMethod ?
|
CollectionMethod = usePaymentMethod ?
|
||||||
@ -705,17 +727,24 @@ public class ProviderBillingService(
|
|||||||
},
|
},
|
||||||
OffSession = true,
|
OffSession = true,
|
||||||
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations,
|
ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations,
|
||||||
TrialPeriodDays = usePaymentMethod ? 14 : null
|
TrialPeriodDays = trialPeriodDays
|
||||||
};
|
};
|
||||||
|
|
||||||
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
var setNonUSBusinessUseToReverseCharge =
|
||||||
{
|
featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
automaticTaxStrategy.SetCreateOptions(subscriptionCreateOptions, customer);
|
|
||||||
}
|
if (setNonUSBusinessUseToReverseCharge)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||||
}
|
}
|
||||||
|
else if (customer.HasRecognizedTaxLocation())
|
||||||
|
{
|
||||||
|
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions
|
||||||
|
{
|
||||||
|
Enabled = customer.Address.Country == "US" ||
|
||||||
|
customer.TaxIds.Any()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
@ -6,7 +6,7 @@ using Bit.Core.Billing;
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Billing;
|
namespace Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
|
|
||||||
public static class ProviderPriceAdapter
|
public static class ProviderPriceAdapter
|
||||||
{
|
{
|
@ -1,13 +1,9 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Enums;
|
|
||||||
using Bit.Core.Billing.Licenses;
|
|
||||||
using Bit.Core.Billing.Licenses.Extensions;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.SecretsManager.Queries.Projects.Interfaces;
|
using Bit.Core.SecretsManager.Queries.Projects.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.SecretsManager.Queries.Projects;
|
namespace Bit.Commercial.Core.SecretsManager.Queries.Projects;
|
||||||
@ -17,72 +13,42 @@ public class MaxProjectsQuery : IMaxProjectsQuery
|
|||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
private readonly IGlobalSettings _globalSettings;
|
private readonly IGlobalSettings _globalSettings;
|
||||||
private readonly ILicensingService _licensingService;
|
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
|
|
||||||
public MaxProjectsQuery(
|
public MaxProjectsQuery(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
ILicensingService licensingService,
|
|
||||||
IPricingClient pricingClient)
|
IPricingClient pricingClient)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_licensingService = licensingService;
|
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(short? max, bool? overMax)> GetByOrgIdAsync(Guid organizationId, int projectsToAdd)
|
public async Task<(short? max, bool? overMax)> GetByOrgIdAsync(Guid organizationId, int projectsToAdd)
|
||||||
{
|
{
|
||||||
|
// "MaxProjects" only applies to free 2-person organizations, which can't be self-hosted.
|
||||||
|
if (_globalSettings.SelfHosted)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
||||||
if (org == null)
|
if (org == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var (planType, maxProjects) = await GetPlanTypeAndMaxProjectsAsync(org);
|
var plan = await _pricingClient.GetPlan(org.PlanType);
|
||||||
|
|
||||||
if (planType != PlanType.Free)
|
if (plan is not { SecretsManager: not null, Type: PlanType.Free })
|
||||||
{
|
{
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId);
|
var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId);
|
||||||
return ((short? max, bool? overMax))(projects + projectsToAdd > maxProjects ? (maxProjects, true) : (maxProjects, false));
|
return ((short? max, bool? overMax))(projects + projectsToAdd > plan.SecretsManager.MaxProjects ? (plan.SecretsManager.MaxProjects, true) : (plan.SecretsManager.MaxProjects, false));
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<(PlanType planType, int maxProjects)> GetPlanTypeAndMaxProjectsAsync(Organization organization)
|
|
||||||
{
|
|
||||||
if (_globalSettings.SelfHosted)
|
|
||||||
{
|
|
||||||
var license = await _licensingService.ReadOrganizationLicenseAsync(organization);
|
|
||||||
|
|
||||||
if (license == null)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("License not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license);
|
|
||||||
var maxProjects = claimsPrincipal.GetValue<int?>(OrganizationLicenseConstants.SmMaxProjects);
|
|
||||||
|
|
||||||
if (!maxProjects.HasValue)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("License does not contain a value for max Secrets Manager projects");
|
|
||||||
}
|
|
||||||
|
|
||||||
var planType = claimsPrincipal.GetValue<PlanType>(OrganizationLicenseConstants.PlanType);
|
|
||||||
return (planType, maxProjects.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var plan = await _pricingClient.GetPlan(organization.PlanType);
|
|
||||||
|
|
||||||
if (plan is { SupportsSecretsManager: true })
|
|
||||||
{
|
|
||||||
return (plan.Type, plan.SecretsManager.MaxProjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestException("Existing plan not found.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using Bit.Commercial.Core.AdminConsole.Providers;
|
using Bit.Commercial.Core.AdminConsole.Providers;
|
||||||
using Bit.Commercial.Core.AdminConsole.Services;
|
using Bit.Commercial.Core.AdminConsole.Services;
|
||||||
using Bit.Commercial.Core.Billing;
|
using Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Utilities;
|
namespace Bit.Commercial.Core.Utilities;
|
||||||
|
207
bitwarden_license/src/Sso/package-lock.json
generated
207
bitwarden_license/src/Sso/package-lock.json
generated
@ -9,17 +9,17 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "-",
|
"license": "-",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.6",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"jquery": "3.7.1"
|
"jquery": "3.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"expose-loader": "5.0.0",
|
"expose-loader": "5.0.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"sass": "1.85.0",
|
"sass": "1.88.0",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.99.8",
|
||||||
"webpack-cli": "5.1.4"
|
"webpack-cli": "5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -455,13 +455,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.14",
|
"version": "22.15.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
|
||||||
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
|
"integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
@ -748,9 +748,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bootstrap": {
|
"node_modules/bootstrap": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
|
||||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -781,9 +781,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.4",
|
"version": "4.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
|
||||||
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
|
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -801,10 +801,10 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001716",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"electron-to-chromium": "^1.5.149",
|
||||||
"node-releases": "^2.0.19",
|
"node-releases": "^2.0.19",
|
||||||
"update-browserslist-db": "^1.1.1"
|
"update-browserslist-db": "^1.1.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@ -821,9 +821,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001707",
|
"version": "1.0.30001718",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
|
||||||
"integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==",
|
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -975,9 +975,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.128",
|
"version": "1.5.155",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
||||||
"integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==",
|
"integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@ -1009,9 +1009,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||||
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
|
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -1083,9 +1083,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expose-loader": {
|
"node_modules/expose-loader": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.1.tgz",
|
||||||
"integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==",
|
"integrity": "sha512-5YPZuszN/eWND/B+xuq5nIpb/l5TV1HYmdO6SubYtHv+HenVw9/6bn33Mm5reY8DNid7AVtbARvyUD34edfCtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1106,13 +1106,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||||
@ -1248,9 +1241,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
|
||||||
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
|
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -1754,16 +1747,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
@ -1877,9 +1860,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.85.0",
|
"version": "1.88.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz",
|
||||||
"integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==",
|
"integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1898,9 +1881,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass-loader": {
|
"node_modules/sass-loader": {
|
||||||
"version": "16.0.4",
|
"version": "16.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz",
|
||||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
"integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1939,9 +1922,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1959,9 +1942,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.1",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -2078,9 +2061,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
|
||||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2088,14 +2071,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.39.0",
|
"version": "5.39.2",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz",
|
||||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
"integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.8.2",
|
"acorn": "^8.14.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
@ -2156,9 +2139,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -2193,16 +2176,6 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uri-js": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@ -2211,9 +2184,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2225,14 +2198,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.97.1",
|
"version": "5.99.8",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz",
|
||||||
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
|
"integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.7",
|
"@types/eslint-scope": "^3.7.7",
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"@webassemblyjs/ast": "^1.14.1",
|
"@webassemblyjs/ast": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||||
@ -2249,9 +2223,9 @@
|
|||||||
"loader-runner": "^4.2.0",
|
"loader-runner": "^4.2.0",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"neo-async": "^2.6.2",
|
"neo-async": "^2.6.2",
|
||||||
"schema-utils": "^3.2.0",
|
"schema-utils": "^4.3.2",
|
||||||
"tapable": "^2.1.1",
|
"tapable": "^2.1.1",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.11",
|
||||||
"watchpack": "^2.4.1",
|
"watchpack": "^2.4.1",
|
||||||
"webpack-sources": "^3.2.3"
|
"webpack-sources": "^3.2.3"
|
||||||
},
|
},
|
||||||
@ -2352,59 +2326,6 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/ajv": {
|
|
||||||
"version": "6.12.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.1",
|
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
|
||||||
"json-schema-traverse": "^0.4.1",
|
|
||||||
"uri-js": "^4.2.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/ajv-keywords": {
|
|
||||||
"version": "3.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^6.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/json-schema-traverse": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/schema-utils": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/json-schema": "^7.0.8",
|
|
||||||
"ajv": "^6.12.5",
|
|
||||||
"ajv-keywords": "^3.5.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/webpack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@ -8,17 +8,17 @@
|
|||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.6",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"jquery": "3.7.1"
|
"jquery": "3.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"expose-loader": "5.0.0",
|
"expose-loader": "5.0.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"sass": "1.85.0",
|
"sass": "1.88.0",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.99.8",
|
||||||
"webpack-cli": "5.1.4"
|
"webpack-cli": "5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Commercial.Core.AdminConsole.Providers;
|
using Bit.Commercial.Core.AdminConsole.Providers;
|
||||||
|
using Bit.Core;
|
||||||
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.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
@ -7,6 +8,7 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -223,31 +225,115 @@ public class RemoveOrganizationFromProviderCommandTests
|
|||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
|
|
||||||
|
stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||||
|
options.Description == string.Empty &&
|
||||||
|
options.Email == organization.BillingEmail &&
|
||||||
|
options.Expand[0] == "tax" &&
|
||||||
|
options.Expand[1] == "tax_ids")).Returns(new Customer
|
||||||
|
{
|
||||||
|
Id = "customer_id",
|
||||||
|
Address = new Address
|
||||||
|
{
|
||||||
|
Country = "US"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||||
{
|
{
|
||||||
Id = "subscription_id"
|
Id = "subscription_id"
|
||||||
});
|
});
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
|
||||||
.When(x => x.SetCreateOptions(
|
|
||||||
Arg.Is<SubscriptionCreateOptions>(options =>
|
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||||
options.Customer == organization.GatewayCustomerId &&
|
options.Customer == organization.GatewayCustomerId &&
|
||||||
options.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
options.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
||||||
options.DaysUntilDue == 30 &&
|
options.DaysUntilDue == 30 &&
|
||||||
options.Metadata["organizationId"] == organization.Id.ToString() &&
|
options.AutomaticTax.Enabled == true &&
|
||||||
options.OffSession == true &&
|
options.Metadata["organizationId"] == organization.Id.ToString() &&
|
||||||
options.ProrationBehavior == StripeConstants.ProrationBehavior.CreateProrations &&
|
options.OffSession == true &&
|
||||||
options.Items.First().Price == teamsMonthlyPlan.PasswordManager.StripeSeatPlanId &&
|
options.ProrationBehavior == StripeConstants.ProrationBehavior.CreateProrations &&
|
||||||
options.Items.First().Quantity == organization.Seats)
|
options.Items.First().Price == teamsMonthlyPlan.PasswordManager.StripeSeatPlanId &&
|
||||||
, Arg.Any<Customer>()))
|
options.Items.First().Quantity == organization.Seats));
|
||||||
.Do(x =>
|
|
||||||
|
await sutProvider.GetDependency<IProviderBillingService>().Received(1)
|
||||||
|
.ScaleSeats(provider, organization.PlanType, -organization.Seats ?? 0);
|
||||||
|
|
||||||
|
await organizationRepository.Received(1).ReplaceAsync(Arg.Is<Organization>(
|
||||||
|
org =>
|
||||||
|
org.BillingEmail == "a@example.com" &&
|
||||||
|
org.GatewaySubscriptionId == "subscription_id" &&
|
||||||
|
org.Status == OrganizationStatusType.Created));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IProviderOrganizationRepository>().Received(1)
|
||||||
|
.DeleteAsync(providerOrganization);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||||
|
.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||||
|
.SendProviderUpdatePaymentMethod(
|
||||||
|
organization.Id,
|
||||||
|
organization.Name,
|
||||||
|
provider.Name,
|
||||||
|
Arg.Is<IEnumerable<string>>(emails => emails.FirstOrDefault() == "a@example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveOrganizationFromProvider_OrganizationStripeEnabled_ConsolidatedBilling_ReverseCharge_MakesCorrectInvocations(
|
||||||
|
Provider provider,
|
||||||
|
ProviderOrganization providerOrganization,
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<RemoveOrganizationFromProviderCommand> sutProvider)
|
||||||
|
{
|
||||||
|
provider.Status = ProviderStatusType.Billable;
|
||||||
|
|
||||||
|
providerOrganization.ProviderId = provider.Id;
|
||||||
|
|
||||||
|
organization.Status = OrganizationStatusType.Managed;
|
||||||
|
|
||||||
|
organization.PlanType = PlanType.TeamsMonthly;
|
||||||
|
|
||||||
|
var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(PlanType.TeamsMonthly).Returns(teamsMonthlyPlan);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>().HasConfirmedOwnersExceptAsync(
|
||||||
|
providerOrganization.OrganizationId,
|
||||||
|
[],
|
||||||
|
includeProvider: false)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||||
|
|
||||||
|
organizationRepository.GetOwnerEmailAddressesById(organization.Id).Returns([
|
||||||
|
"a@example.com",
|
||||||
|
"b@example.com"
|
||||||
|
]);
|
||||||
|
|
||||||
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
|
|
||||||
|
stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||||
|
options.Description == string.Empty &&
|
||||||
|
options.Email == organization.BillingEmail &&
|
||||||
|
options.Expand[0] == "tax" &&
|
||||||
|
options.Expand[1] == "tax_ids")).Returns(new Customer
|
||||||
{
|
{
|
||||||
x.Arg<SubscriptionCreateOptions>().AutomaticTax = new SubscriptionAutomaticTaxOptions
|
Id = "customer_id",
|
||||||
|
Address = new Address
|
||||||
{
|
{
|
||||||
Enabled = true
|
Country = "US"
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||||
|
{
|
||||||
|
Id = "subscription_id"
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge).Returns(true);
|
||||||
|
|
||||||
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
|
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
|
||||||
|
|
||||||
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||||
|
@ -6,11 +6,12 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
|||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -717,8 +718,8 @@ public class ProviderServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||||
|
|
||||||
var providerOrganization =
|
var providerOrganization =
|
||||||
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||||
@ -755,8 +756,8 @@ public class ProviderServiceTests
|
|||||||
|
|
||||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||||
|
|
||||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user));
|
sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user));
|
||||||
@ -782,8 +783,8 @@ public class ProviderServiceTests
|
|||||||
|
|
||||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||||
|
|
||||||
var providerOrganization = await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
var providerOrganization = await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||||
|
|
||||||
@ -821,8 +822,8 @@ public class ProviderServiceTests
|
|||||||
|
|
||||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||||
.Returns((organization, null as OrganizationUser, defaultCollection));
|
.Returns(new ProviderClientOrganizationSignUpResponse(organization, defaultCollection));
|
||||||
|
|
||||||
var providerOrganization =
|
var providerOrganization =
|
||||||
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Bit.Commercial.Core.Billing;
|
using Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
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.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing;
|
using Bit.Core.Billing;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -25,7 +25,7 @@ using NSubstitute;
|
|||||||
using Stripe;
|
using Stripe;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.Billing;
|
namespace Bit.Commercial.Core.Test.Billing.Providers;
|
||||||
|
|
||||||
public class BusinessUnitConverterTests
|
public class BusinessUnitConverterTests
|
||||||
{
|
{
|
@ -1,7 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Bit.Commercial.Core.Billing;
|
using Bit.Commercial.Core.Billing.Providers.Models;
|
||||||
using Bit.Commercial.Core.Billing.Models;
|
using Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
@ -10,13 +10,14 @@ using Bit.Core.AdminConsole.Models.Data.Provider;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Caches;
|
using Bit.Core.Billing.Caches;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Models;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Billing.Services.Contracts;
|
using Bit.Core.Billing.Tax.Services;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -39,7 +40,7 @@ using Customer = Stripe.Customer;
|
|||||||
using PaymentMethod = Stripe.PaymentMethod;
|
using PaymentMethod = Stripe.PaymentMethod;
|
||||||
using Subscription = Stripe.Subscription;
|
using Subscription = Stripe.Subscription;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.Billing;
|
namespace Bit.Commercial.Core.Test.Billing.Providers;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class ProviderBillingServiceTests
|
public class ProviderBillingServiceTests
|
||||||
@ -261,7 +262,7 @@ public class ProviderBillingServiceTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>().GetCustomerOrThrow(provider, Arg.Is<CustomerGetOptions>(
|
sutProvider.GetDependency<ISubscriberService>().GetCustomerOrThrow(provider, Arg.Is<CustomerGetOptions>(
|
||||||
options => options.Expand.FirstOrDefault() == "tax_ids"))
|
options => options.Expand.Contains("tax") && options.Expand.Contains("tax_ids")))
|
||||||
.Returns(providerCustomer);
|
.Returns(providerCustomer);
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().BaseServiceUri
|
sutProvider.GetDependency<IGlobalSettings>().BaseServiceUri
|
||||||
@ -311,6 +312,91 @@ public class ProviderBillingServiceTests
|
|||||||
org => org.GatewayCustomerId == "customer_id"));
|
org => org.GatewayCustomerId == "customer_id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task CreateCustomer_ForClientOrg_ReverseCharge_Succeeds(
|
||||||
|
Provider provider,
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<ProviderBillingService> sutProvider)
|
||||||
|
{
|
||||||
|
organization.GatewayCustomerId = null;
|
||||||
|
organization.Name = "Name";
|
||||||
|
organization.BusinessName = "BusinessName";
|
||||||
|
|
||||||
|
var providerCustomer = new Customer
|
||||||
|
{
|
||||||
|
Address = new Address
|
||||||
|
{
|
||||||
|
Country = "CA",
|
||||||
|
PostalCode = "12345",
|
||||||
|
Line1 = "123 Main St.",
|
||||||
|
Line2 = "Unit 4",
|
||||||
|
City = "Fake Town",
|
||||||
|
State = "Fake State"
|
||||||
|
},
|
||||||
|
TaxIds = new StripeList<TaxId>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new TaxId { Type = "TYPE", Value = "VALUE" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISubscriberService>().GetCustomerOrThrow(provider, Arg.Is<CustomerGetOptions>(
|
||||||
|
options => options.Expand.Contains("tax") && options.Expand.Contains("tax_ids")))
|
||||||
|
.Returns(providerCustomer);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IGlobalSettings>().BaseServiceUri
|
||||||
|
.Returns(new Bit.Core.Settings.GlobalSettings.BaseServiceUriSettings(new Bit.Core.Settings.GlobalSettings())
|
||||||
|
{
|
||||||
|
CloudRegion = "US"
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge).Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IStripeAdapter>().CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||||
|
options =>
|
||||||
|
options.Address.Country == providerCustomer.Address.Country &&
|
||||||
|
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||||
|
options.Address.Line1 == providerCustomer.Address.Line1 &&
|
||||||
|
options.Address.Line2 == providerCustomer.Address.Line2 &&
|
||||||
|
options.Address.City == providerCustomer.Address.City &&
|
||||||
|
options.Address.State == providerCustomer.Address.State &&
|
||||||
|
options.Name == organization.DisplayName() &&
|
||||||
|
options.Description == $"{provider.Name} Client Organization" &&
|
||||||
|
options.Email == provider.BillingEmail &&
|
||||||
|
options.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Organization" &&
|
||||||
|
options.InvoiceSettings.CustomFields.FirstOrDefault().Value == "Name" &&
|
||||||
|
options.Metadata["region"] == "US" &&
|
||||||
|
options.TaxIdData.FirstOrDefault().Type == providerCustomer.TaxIds.FirstOrDefault().Type &&
|
||||||
|
options.TaxIdData.FirstOrDefault().Value == providerCustomer.TaxIds.FirstOrDefault().Value &&
|
||||||
|
options.TaxExempt == StripeConstants.TaxExempt.Reverse))
|
||||||
|
.Returns(new Customer { Id = "customer_id" });
|
||||||
|
|
||||||
|
await sutProvider.Sut.CreateCustomerForClientOrganization(provider, organization);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||||
|
options =>
|
||||||
|
options.Address.Country == providerCustomer.Address.Country &&
|
||||||
|
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||||
|
options.Address.Line1 == providerCustomer.Address.Line1 &&
|
||||||
|
options.Address.Line2 == providerCustomer.Address.Line2 &&
|
||||||
|
options.Address.City == providerCustomer.Address.City &&
|
||||||
|
options.Address.State == providerCustomer.Address.State &&
|
||||||
|
options.Name == organization.DisplayName() &&
|
||||||
|
options.Description == $"{provider.Name} Client Organization" &&
|
||||||
|
options.Email == provider.BillingEmail &&
|
||||||
|
options.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Organization" &&
|
||||||
|
options.InvoiceSettings.CustomFields.FirstOrDefault().Value == "Name" &&
|
||||||
|
options.Metadata["region"] == "US" &&
|
||||||
|
options.TaxIdData.FirstOrDefault().Type == providerCustomer.TaxIds.FirstOrDefault().Type &&
|
||||||
|
options.TaxIdData.FirstOrDefault().Value == providerCustomer.TaxIds.FirstOrDefault().Value));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).ReplaceAsync(Arg.Is<Organization>(
|
||||||
|
org => org.GatewayCustomerId == "customer_id"));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GenerateClientInvoiceReport
|
#region GenerateClientInvoiceReport
|
||||||
@ -1181,6 +1267,62 @@ public class ProviderBillingServiceTests
|
|||||||
Assert.Equivalent(expected, actual);
|
Assert.Equivalent(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SetupCustomer_WithCard_ReverseCharge_Success(
|
||||||
|
SutProvider<ProviderBillingService> sutProvider,
|
||||||
|
Provider provider,
|
||||||
|
TaxInfo taxInfo)
|
||||||
|
{
|
||||||
|
provider.Name = "MSP";
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITaxService>()
|
||||||
|
.GetStripeTaxCode(Arg.Is<string>(
|
||||||
|
p => p == taxInfo.BillingAddressCountry),
|
||||||
|
Arg.Is<string>(p => p == taxInfo.TaxIdNumber))
|
||||||
|
.Returns(taxInfo.TaxIdType);
|
||||||
|
|
||||||
|
taxInfo.BillingAddressCountry = "AD";
|
||||||
|
|
||||||
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
|
|
||||||
|
var expected = new Customer
|
||||||
|
{
|
||||||
|
Id = "customer_id",
|
||||||
|
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenizedPaymentSource = new TokenizedPaymentSource(PaymentMethodType.Card, "token");
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge).Returns(true);
|
||||||
|
|
||||||
|
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||||
|
o.Address.Country == taxInfo.BillingAddressCountry &&
|
||||||
|
o.Address.PostalCode == taxInfo.BillingAddressPostalCode &&
|
||||||
|
o.Address.Line1 == taxInfo.BillingAddressLine1 &&
|
||||||
|
o.Address.Line2 == taxInfo.BillingAddressLine2 &&
|
||||||
|
o.Address.City == taxInfo.BillingAddressCity &&
|
||||||
|
o.Address.State == taxInfo.BillingAddressState &&
|
||||||
|
o.Description == WebUtility.HtmlDecode(provider.BusinessName) &&
|
||||||
|
o.Email == provider.BillingEmail &&
|
||||||
|
o.PaymentMethod == tokenizedPaymentSource.Token &&
|
||||||
|
o.InvoiceSettings.DefaultPaymentMethod == tokenizedPaymentSource.Token &&
|
||||||
|
o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" &&
|
||||||
|
o.InvoiceSettings.CustomFields.FirstOrDefault().Value == "MSP" &&
|
||||||
|
o.Metadata["region"] == "" &&
|
||||||
|
o.TaxIdData.FirstOrDefault().Type == taxInfo.TaxIdType &&
|
||||||
|
o.TaxIdData.FirstOrDefault().Value == taxInfo.TaxIdNumber &&
|
||||||
|
o.TaxExempt == StripeConstants.TaxExempt.Reverse))
|
||||||
|
.Returns(expected);
|
||||||
|
|
||||||
|
var actual = await sutProvider.Sut.SetupCustomer(provider, taxInfo, tokenizedPaymentSource);
|
||||||
|
|
||||||
|
Assert.Equivalent(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task SetupCustomer_Throws_BadRequestException_WhenTaxIdIsInvalid(
|
public async Task SetupCustomer_Throws_BadRequestException_WhenTaxIdIsInvalid(
|
||||||
SutProvider<ProviderBillingService> sutProvider,
|
SutProvider<ProviderBillingService> sutProvider,
|
||||||
@ -1306,7 +1448,7 @@ public class ProviderBillingServiceTests
|
|||||||
.Returns(new Customer
|
.Returns(new Customer
|
||||||
{
|
{
|
||||||
Id = "customer_id",
|
Id = "customer_id",
|
||||||
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
Address = new Address { Country = "US" }
|
||||||
});
|
});
|
||||||
|
|
||||||
var providerPlans = new List<ProviderPlan>
|
var providerPlans = new List<ProviderPlan>
|
||||||
@ -1358,7 +1500,7 @@ public class ProviderBillingServiceTests
|
|||||||
var customer = new Customer
|
var customer = new Customer
|
||||||
{
|
{
|
||||||
Id = "customer_id",
|
Id = "customer_id",
|
||||||
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
Address = new Address { Country = "US" }
|
||||||
};
|
};
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
sutProvider.GetDependency<ISubscriberService>()
|
||||||
.GetCustomerOrThrow(
|
.GetCustomerOrThrow(
|
||||||
@ -1398,19 +1540,6 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
|
||||||
.When(x => x.SetCreateOptions(
|
|
||||||
Arg.Is<SubscriptionCreateOptions>(options =>
|
|
||||||
options.Customer == "customer_id")
|
|
||||||
, Arg.Is<Customer>(p => p == customer)))
|
|
||||||
.Do(x =>
|
|
||||||
{
|
|
||||||
x.Arg<SubscriptionCreateOptions>().AutomaticTax = new SubscriptionAutomaticTaxOptions
|
|
||||||
{
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||||
sub =>
|
sub =>
|
||||||
sub.AutomaticTax.Enabled == true &&
|
sub.AutomaticTax.Enabled == true &&
|
||||||
@ -1442,11 +1571,11 @@ public class ProviderBillingServiceTests
|
|||||||
var customer = new Customer
|
var customer = new Customer
|
||||||
{
|
{
|
||||||
Id = "customer_id",
|
Id = "customer_id",
|
||||||
|
Address = new Address { Country = "US" },
|
||||||
InvoiceSettings = new CustomerInvoiceSettings
|
InvoiceSettings = new CustomerInvoiceSettings
|
||||||
{
|
{
|
||||||
DefaultPaymentMethodId = "pm_123"
|
DefaultPaymentMethodId = "pm_123"
|
||||||
},
|
}
|
||||||
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
sutProvider.GetDependency<ISubscriberService>()
|
||||||
@ -1487,19 +1616,6 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
|
||||||
.When(x => x.SetCreateOptions(
|
|
||||||
Arg.Is<SubscriptionCreateOptions>(options =>
|
|
||||||
options.Customer == "customer_id")
|
|
||||||
, Arg.Is<Customer>(p => p == customer)))
|
|
||||||
.Do(x =>
|
|
||||||
{
|
|
||||||
x.Arg<SubscriptionCreateOptions>().AutomaticTax = new SubscriptionAutomaticTaxOptions
|
|
||||||
{
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IFeatureService>()
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
||||||
|
|
||||||
@ -1535,9 +1651,9 @@ public class ProviderBillingServiceTests
|
|||||||
var customer = new Customer
|
var customer = new Customer
|
||||||
{
|
{
|
||||||
Id = "customer_id",
|
Id = "customer_id",
|
||||||
|
Address = new Address { Country = "US" },
|
||||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||||
Metadata = new Dictionary<string, string>(),
|
Metadata = new Dictionary<string, string>()
|
||||||
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
sutProvider.GetDependency<ISubscriberService>()
|
||||||
@ -1578,19 +1694,6 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
|
||||||
.When(x => x.SetCreateOptions(
|
|
||||||
Arg.Is<SubscriptionCreateOptions>(options =>
|
|
||||||
options.Customer == "customer_id")
|
|
||||||
, Arg.Is<Customer>(p => p == customer)))
|
|
||||||
.Do(x =>
|
|
||||||
{
|
|
||||||
x.Arg<SubscriptionCreateOptions>().AutomaticTax = new SubscriptionAutomaticTaxOptions
|
|
||||||
{
|
|
||||||
Enabled = true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IFeatureService>()
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
||||||
|
|
||||||
@ -1645,12 +1748,15 @@ public class ProviderBillingServiceTests
|
|||||||
var customer = new Customer
|
var customer = new Customer
|
||||||
{
|
{
|
||||||
Id = "customer_id",
|
Id = "customer_id",
|
||||||
|
Address = new Address
|
||||||
|
{
|
||||||
|
Country = "US"
|
||||||
|
},
|
||||||
InvoiceSettings = new CustomerInvoiceSettings(),
|
InvoiceSettings = new CustomerInvoiceSettings(),
|
||||||
Metadata = new Dictionary<string, string>
|
Metadata = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["btCustomerId"] = "braintree_customer_id"
|
["btCustomerId"] = "braintree_customer_id"
|
||||||
},
|
}
|
||||||
Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sutProvider.GetDependency<ISubscriberService>()
|
sutProvider.GetDependency<ISubscriberService>()
|
||||||
@ -1691,22 +1797,92 @@ public class ProviderBillingServiceTests
|
|||||||
|
|
||||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IAutomaticTaxStrategy>()
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
.When(x => x.SetCreateOptions(
|
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
||||||
Arg.Is<SubscriptionCreateOptions>(options =>
|
|
||||||
options.Customer == "customer_id")
|
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||||
, Arg.Is<Customer>(p => p == customer)))
|
sub =>
|
||||||
.Do(x =>
|
sub.AutomaticTax.Enabled == true &&
|
||||||
|
sub.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically &&
|
||||||
|
sub.Customer == "customer_id" &&
|
||||||
|
sub.DaysUntilDue == null &&
|
||||||
|
sub.Items.Count == 2 &&
|
||||||
|
sub.Items.ElementAt(0).Price == ProviderPriceAdapter.MSP.Active.Teams &&
|
||||||
|
sub.Items.ElementAt(0).Quantity == 100 &&
|
||||||
|
sub.Items.ElementAt(1).Price == ProviderPriceAdapter.MSP.Active.Enterprise &&
|
||||||
|
sub.Items.ElementAt(1).Quantity == 100 &&
|
||||||
|
sub.Metadata["providerId"] == provider.Id.ToString() &&
|
||||||
|
sub.OffSession == true &&
|
||||||
|
sub.ProrationBehavior == StripeConstants.ProrationBehavior.CreateProrations &&
|
||||||
|
sub.TrialPeriodDays == 14)).Returns(expected);
|
||||||
|
|
||||||
|
var actual = await sutProvider.Sut.SetupSubscription(provider);
|
||||||
|
|
||||||
|
Assert.Equivalent(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SetupSubscription_ReverseCharge_Succeeds(
|
||||||
|
SutProvider<ProviderBillingService> sutProvider,
|
||||||
|
Provider provider)
|
||||||
|
{
|
||||||
|
provider.Type = ProviderType.Msp;
|
||||||
|
provider.GatewaySubscriptionId = null;
|
||||||
|
|
||||||
|
var customer = new Customer
|
||||||
|
{
|
||||||
|
Id = "customer_id",
|
||||||
|
Address = new Address { Country = "CA" },
|
||||||
|
InvoiceSettings = new CustomerInvoiceSettings
|
||||||
{
|
{
|
||||||
x.Arg<SubscriptionCreateOptions>().AutomaticTax = new SubscriptionAutomaticTaxOptions
|
DefaultPaymentMethodId = "pm_123"
|
||||||
{
|
}
|
||||||
Enabled = true
|
};
|
||||||
};
|
|
||||||
});
|
sutProvider.GetDependency<ISubscriberService>()
|
||||||
|
.GetCustomerOrThrow(
|
||||||
|
provider,
|
||||||
|
Arg.Is<CustomerGetOptions>(p => p.Expand.Contains("tax") || p.Expand.Contains("tax_ids"))).Returns(customer);
|
||||||
|
|
||||||
|
var providerPlans = new List<ProviderPlan>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ProviderId = provider.Id,
|
||||||
|
PlanType = PlanType.TeamsMonthly,
|
||||||
|
SeatMinimum = 100,
|
||||||
|
PurchasedSeats = 0,
|
||||||
|
AllocatedSeats = 0
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
ProviderId = provider.Id,
|
||||||
|
PlanType = PlanType.EnterpriseMonthly,
|
||||||
|
SeatMinimum = 100,
|
||||||
|
PurchasedSeats = 0,
|
||||||
|
AllocatedSeats = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var plan in providerPlans)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(plan.PlanType)
|
||||||
|
.Returns(StaticStore.GetPlan(plan.PlanType));
|
||||||
|
}
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id)
|
||||||
|
.Returns(providerPlans);
|
||||||
|
|
||||||
|
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||||
|
|
||||||
sutProvider.GetDependency<IFeatureService>()
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
.IsEnabled(FeatureFlagKeys.PM19956_RequireProviderPaymentMethodDuringSetup).Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge).Returns(true);
|
||||||
|
|
||||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||||
sub =>
|
sub =>
|
||||||
sub.AutomaticTax.Enabled == true &&
|
sub.AutomaticTax.Enabled == true &&
|
@ -1,11 +1,11 @@
|
|||||||
using Bit.Commercial.Core.Billing;
|
using Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.Billing;
|
namespace Bit.Commercial.Core.Test.Billing.Providers;
|
||||||
|
|
||||||
public class ProviderPriceAdapterTests
|
public class ProviderPriceAdapterTests
|
||||||
{
|
{
|
@ -1,9 +1,9 @@
|
|||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Tax.Services.Implementations;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Commercial.Core.Test.Billing;
|
namespace Bit.Commercial.Core.Test.Billing.Tax;
|
||||||
|
|
||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class TaxServiceTests
|
public class TaxServiceTests
|
@ -1,14 +1,10 @@
|
|||||||
using System.Security.Claims;
|
using Bit.Commercial.Core.SecretsManager.Queries.Projects;
|
||||||
using Bit.Commercial.Core.SecretsManager.Queries.Projects;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Licenses;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@ -22,11 +18,26 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Queries.Projects;
|
|||||||
[SutProviderCustomize]
|
[SutProviderCustomize]
|
||||||
public class MaxProjectsQueryTests
|
public class MaxProjectsQueryTests
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task GetByOrgIdAsync_SelfHosted_ReturnsNulls(SutProvider<MaxProjectsQuery> sutProvider,
|
||||||
|
Guid organizationId)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
||||||
|
|
||||||
|
var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organizationId, 1);
|
||||||
|
|
||||||
|
Assert.Null(max);
|
||||||
|
Assert.Null(overMax);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task GetByOrgIdAsync_OrganizationIsNull_ThrowsNotFound(SutProvider<MaxProjectsQuery> sutProvider,
|
public async Task GetByOrgIdAsync_OrganizationIsNull_ThrowsNotFound(SutProvider<MaxProjectsQuery> sutProvider,
|
||||||
Guid organizationId)
|
Guid organizationId)
|
||||||
{
|
{
|
||||||
|
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
||||||
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(default).ReturnsNull();
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(default).ReturnsNull();
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetByOrgIdAsync(organizationId, 1));
|
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetByOrgIdAsync(organizationId, 1));
|
||||||
@ -35,54 +46,6 @@ public class MaxProjectsQueryTests
|
|||||||
.GetProjectCountByOrganizationIdAsync(organizationId);
|
.GetProjectCountByOrganizationIdAsync(organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.Custom)]
|
|
||||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
|
||||||
public async Task GetByOrgIdAsync_Cloud_SmPlanIsNull_ThrowsBadRequest(PlanType planType,
|
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
|
||||||
{
|
|
||||||
organization.PlanType = planType;
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetByIdAsync(organization.Id)
|
|
||||||
.Returns(organization);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
|
||||||
var plan = StaticStore.GetPlan(planType);
|
|
||||||
sutProvider.GetDependency<IPricingClient>().GetPlan(organization.PlanType).Returns(plan);
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1));
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task GetByOrgIdAsync_SelfHosted_NoMaxProjectsClaim_ThrowsBadRequest(
|
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
|
||||||
{
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
|
||||||
.GetByIdAsync(organization.Id)
|
|
||||||
.Returns(organization);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
|
||||||
|
|
||||||
var license = new OrganizationLicense();
|
|
||||||
var claimsPrincipal = new ClaimsPrincipal();
|
|
||||||
sutProvider.GetDependency<ILicensingService>().ReadOrganizationLicenseAsync(organization).Returns(license);
|
|
||||||
sutProvider.GetDependency<ILicensingService>().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal);
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1));
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(PlanType.TeamsMonthly2019)]
|
[BitAutoData(PlanType.TeamsMonthly2019)]
|
||||||
[BitAutoData(PlanType.TeamsMonthly2020)]
|
[BitAutoData(PlanType.TeamsMonthly2020)]
|
||||||
@ -97,57 +60,16 @@ public class MaxProjectsQueryTests
|
|||||||
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually2020)]
|
[BitAutoData(PlanType.EnterpriseAnnually2020)]
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||||
public async Task GetByOrgIdAsync_Cloud_SmNoneFreePlans_ReturnsNull(PlanType planType,
|
public async Task GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull(PlanType planType,
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
||||||
{
|
{
|
||||||
organization.PlanType = planType;
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
||||||
var plan = StaticStore.GetPlan(planType);
|
|
||||||
sutProvider.GetDependency<IPricingClient>().GetPlan(organization.PlanType).Returns(plan);
|
|
||||||
|
|
||||||
var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1);
|
|
||||||
|
|
||||||
Assert.Null(limit);
|
|
||||||
Assert.Null(overLimit);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs()
|
|
||||||
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.TeamsMonthly2019)]
|
|
||||||
[BitAutoData(PlanType.TeamsMonthly2020)]
|
|
||||||
[BitAutoData(PlanType.TeamsMonthly)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually2020)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually)]
|
|
||||||
[BitAutoData(PlanType.TeamsStarter)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly2019)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly2020)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually2020)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
||||||
public async Task GetByOrgIdAsync_SelfHosted_SmNoneFreePlans_ReturnsNull(PlanType planType,
|
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
|
||||||
{
|
|
||||||
organization.PlanType = planType;
|
organization.PlanType = planType;
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
|
||||||
|
|
||||||
var license = new OrganizationLicense();
|
sutProvider.GetDependency<IPricingClient>().GetPlan(organization.PlanType)
|
||||||
var plan = StaticStore.GetPlan(planType);
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new (nameof(OrganizationLicenseConstants.PlanType), organization.PlanType.ToString()),
|
|
||||||
new (nameof(OrganizationLicenseConstants.SmMaxProjects), plan.SecretsManager.MaxProjects.ToString())
|
|
||||||
};
|
|
||||||
var identity = new ClaimsIdentity(claims, "TestAuthenticationType");
|
|
||||||
var claimsPrincipal = new ClaimsPrincipal(identity);
|
|
||||||
sutProvider.GetDependency<ILicensingService>().ReadOrganizationLicenseAsync(organization).Returns(license);
|
|
||||||
sutProvider.GetDependency<ILicensingService>().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal);
|
|
||||||
|
|
||||||
var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1);
|
var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1);
|
||||||
|
|
||||||
@ -183,7 +105,7 @@ public class MaxProjectsQueryTests
|
|||||||
[BitAutoData(PlanType.Free, 3, 4, true)]
|
[BitAutoData(PlanType.Free, 3, 4, true)]
|
||||||
[BitAutoData(PlanType.Free, 4, 4, true)]
|
[BitAutoData(PlanType.Free, 4, 4, true)]
|
||||||
[BitAutoData(PlanType.Free, 40, 4, true)]
|
[BitAutoData(PlanType.Free, 40, 4, true)]
|
||||||
public async Task GetByOrgIdAsync_Cloud_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax,
|
public async Task GetByOrgIdAsync_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax,
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
||||||
{
|
{
|
||||||
organization.PlanType = planType;
|
organization.PlanType = planType;
|
||||||
@ -191,66 +113,8 @@ public class MaxProjectsQueryTests
|
|||||||
sutProvider.GetDependency<IProjectRepository>().GetProjectCountByOrganizationIdAsync(organization.Id)
|
sutProvider.GetDependency<IProjectRepository>().GetProjectCountByOrganizationIdAsync(organization.Id)
|
||||||
.Returns(projects);
|
.Returns(projects);
|
||||||
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
|
sutProvider.GetDependency<IPricingClient>().GetPlan(organization.PlanType)
|
||||||
var plan = StaticStore.GetPlan(planType);
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
||||||
sutProvider.GetDependency<IPricingClient>().GetPlan(organization.PlanType).Returns(plan);
|
|
||||||
|
|
||||||
var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd);
|
|
||||||
|
|
||||||
Assert.NotNull(max);
|
|
||||||
Assert.NotNull(overMax);
|
|
||||||
Assert.Equal(3, max.Value);
|
|
||||||
Assert.Equal(expectedOverMax, overMax);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().Received(1)
|
|
||||||
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.Free, 0, 1, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 1, 1, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 2, 1, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 3, 1, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 4, 1, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 40, 1, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 0, 2, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 1, 2, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 2, 2, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 3, 2, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 4, 2, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 40, 2, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 0, 3, false)]
|
|
||||||
[BitAutoData(PlanType.Free, 1, 3, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 2, 3, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 3, 3, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 4, 3, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 40, 3, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 0, 4, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 1, 4, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 2, 4, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 3, 4, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 4, 4, true)]
|
|
||||||
[BitAutoData(PlanType.Free, 40, 4, true)]
|
|
||||||
public async Task GetByOrgIdAsync_SelfHosted_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax,
|
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
|
||||||
{
|
|
||||||
organization.PlanType = planType;
|
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
||||||
sutProvider.GetDependency<IProjectRepository>().GetProjectCountByOrganizationIdAsync(organization.Id)
|
|
||||||
.Returns(projects);
|
|
||||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
|
||||||
|
|
||||||
var license = new OrganizationLicense();
|
|
||||||
var plan = StaticStore.GetPlan(planType);
|
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new (nameof(OrganizationLicenseConstants.PlanType), organization.PlanType.ToString()),
|
|
||||||
new (nameof(OrganizationLicenseConstants.SmMaxProjects), plan.SecretsManager.MaxProjects.ToString())
|
|
||||||
};
|
|
||||||
var identity = new ClaimsIdentity(claims, "TestAuthenticationType");
|
|
||||||
var claimsPrincipal = new ClaimsPrincipal(identity);
|
|
||||||
sutProvider.GetDependency<ILicensingService>().ReadOrganizationLicenseAsync(organization).Returns(license);
|
|
||||||
sutProvider.GetDependency<ILicensingService>().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal);
|
|
||||||
|
|
||||||
var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd);
|
var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd);
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ MAILCATCHER_PORT=1080
|
|||||||
# Alternative databases
|
# Alternative databases
|
||||||
POSTGRES_PASSWORD=SET_A_PASSWORD_HERE_123
|
POSTGRES_PASSWORD=SET_A_PASSWORD_HERE_123
|
||||||
MYSQL_ROOT_PASSWORD=SET_A_PASSWORD_HERE_123
|
MYSQL_ROOT_PASSWORD=SET_A_PASSWORD_HERE_123
|
||||||
|
MARIADB_ROOT_PASSWORD=SET_A_PASSWORD_HERE_123
|
||||||
|
|
||||||
# IdP configuration
|
# IdP configuration
|
||||||
# Complete using the values from the Manage SSO page in the web vault
|
# Complete using the values from the Manage SSO page in the web vault
|
||||||
|
@ -70,6 +70,20 @@ services:
|
|||||||
profiles:
|
profiles:
|
||||||
- mysql
|
- mysql
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:10
|
||||||
|
ports:
|
||||||
|
- 4306:3306
|
||||||
|
environment:
|
||||||
|
MARIADB_USER: maria
|
||||||
|
MARIADB_PASSWORD: ${MARIADB_ROOT_PASSWORD}
|
||||||
|
MARIADB_DATABASE: vault_dev
|
||||||
|
MARIADB_RANDOM_ROOT_PASSWORD: "true"
|
||||||
|
volumes:
|
||||||
|
- mariadb_dev_data:/var/lib/mysql
|
||||||
|
profiles:
|
||||||
|
- mariadb
|
||||||
|
|
||||||
idp:
|
idp:
|
||||||
image: kenchan0130/simplesamlphp:1.19.8
|
image: kenchan0130/simplesamlphp:1.19.8
|
||||||
container_name: idp
|
container_name: idp
|
||||||
|
@ -5,6 +5,7 @@ param(
|
|||||||
[switch]$all,
|
[switch]$all,
|
||||||
[switch]$postgres,
|
[switch]$postgres,
|
||||||
[switch]$mysql,
|
[switch]$mysql,
|
||||||
|
[switch]$mariadb,
|
||||||
[switch]$mssql,
|
[switch]$mssql,
|
||||||
[switch]$sqlite,
|
[switch]$sqlite,
|
||||||
[switch]$selfhost,
|
[switch]$selfhost,
|
||||||
@ -15,11 +16,15 @@ param(
|
|||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$currentDir = Get-Location
|
$currentDir = Get-Location
|
||||||
|
|
||||||
if (!$all -and !$postgres -and !$mysql -and !$sqlite) {
|
function Get-IsEFDatabase {
|
||||||
|
return $postgres -or $mysql -or $mariadb -or $sqlite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$all -and !$(Get-IsEFDatabase)) {
|
||||||
$mssql = $true;
|
$mssql = $true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($all -or $postgres -or $mysql -or $sqlite) {
|
if ($all -or $(Get-IsEFDatabase)) {
|
||||||
dotnet ef *> $null
|
dotnet ef *> $null
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Host "Entity Framework Core tools were not found in the dotnet global tools. Attempting to install"
|
Write-Host "Entity Framework Core tools were not found in the dotnet global tools. Attempting to install"
|
||||||
@ -60,9 +65,12 @@ if ($all -or $mssql) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Foreach ($item in @(
|
Foreach ($item in @(
|
||||||
@($mysql, "MySQL", "MySqlMigrations", "mySql", 2),
|
|
||||||
@($postgres, "PostgreSQL", "PostgresMigrations", "postgreSql", 0),
|
@($postgres, "PostgreSQL", "PostgresMigrations", "postgreSql", 0),
|
||||||
@($sqlite, "SQLite", "SqliteMigrations", "sqlite", 1)
|
@($sqlite, "SQLite", "SqliteMigrations", "sqlite", 1),
|
||||||
|
@($mysql, "MySQL", "MySqlMigrations", "mySql", 2),
|
||||||
|
# MariaDB shares the MySQL connection string in the server config so they are mutually exclusive in that context.
|
||||||
|
# However they can still be run independently for integration tests.
|
||||||
|
@($mariadb, "MariaDB", "MySqlMigrations", "mySql", 3)
|
||||||
)) {
|
)) {
|
||||||
if (!$item[0] -and !$all) {
|
if (!$item[0] -and !$all) {
|
||||||
continue
|
continue
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "4.1.0"
|
"Microsoft.Build.Traversal": "4.1.0",
|
||||||
|
"Microsoft.Build.Sql": "0.1.9-preview"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,6 @@ export function authenticate(
|
|||||||
payload["deviceName"] = "chrome";
|
payload["deviceName"] = "chrome";
|
||||||
payload["username"] = username;
|
payload["username"] = username;
|
||||||
payload["password"] = password;
|
payload["password"] = password;
|
||||||
|
|
||||||
params.headers["Auth-Email"] = encoding.b64encode(username);
|
|
||||||
} else {
|
} else {
|
||||||
payload["scope"] = "api.organization";
|
payload["scope"] = "api.organization";
|
||||||
payload["grant_type"] = "client_credentials";
|
payload["grant_type"] = "client_credentials";
|
||||||
|
@ -11,7 +11,7 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||||
@ -462,6 +462,7 @@ public class OrganizationsController : Controller
|
|||||||
organization.UsersGetPremium = model.UsersGetPremium;
|
organization.UsersGetPremium = model.UsersGetPremium;
|
||||||
organization.UseSecretsManager = model.UseSecretsManager;
|
organization.UseSecretsManager = model.UseSecretsManager;
|
||||||
organization.UseRiskInsights = model.UseRiskInsights;
|
organization.UseRiskInsights = model.UseRiskInsights;
|
||||||
|
organization.UseOrganizationDomains = model.UseOrganizationDomains;
|
||||||
organization.UseAdminSponsoredFamilies = model.UseAdminSponsoredFamilies;
|
organization.UseAdminSponsoredFamilies = model.UseAdminSponsoredFamilies;
|
||||||
|
|
||||||
//secrets
|
//secrets
|
||||||
|
@ -10,13 +10,13 @@ using Bit.Core.AdminConsole.Providers.Interfaces;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Models;
|
||||||
using Bit.Core.Billing.Services.Contracts;
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
@ -102,7 +102,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
MaxAutoscaleSmSeats = org.MaxAutoscaleSmSeats;
|
MaxAutoscaleSmSeats = org.MaxAutoscaleSmSeats;
|
||||||
SmServiceAccounts = org.SmServiceAccounts;
|
SmServiceAccounts = org.SmServiceAccounts;
|
||||||
MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts;
|
MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts;
|
||||||
|
UseOrganizationDomains = org.UseOrganizationDomains;
|
||||||
_plans = plans;
|
_plans = plans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +186,8 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
public int? SmServiceAccounts { get; set; }
|
public int? SmServiceAccounts { get; set; }
|
||||||
[Display(Name = "Max Autoscale Machine Accounts")]
|
[Display(Name = "Max Autoscale Machine Accounts")]
|
||||||
public int? MaxAutoscaleSmServiceAccounts { get; set; }
|
public int? MaxAutoscaleSmServiceAccounts { get; set; }
|
||||||
|
[Display(Name = "Use Organization Domains")]
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Plan[] object for use in Javascript
|
* Creates a Plan[] object for use in Javascript
|
||||||
@ -215,6 +217,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
Has2fa = p.Has2fa,
|
Has2fa = p.Has2fa,
|
||||||
HasApi = p.HasApi,
|
HasApi = p.HasApi,
|
||||||
HasSso = p.HasSso,
|
HasSso = p.HasSso,
|
||||||
|
HasOrganizationDomains = p.HasOrganizationDomains,
|
||||||
HasKeyConnector = p.HasKeyConnector,
|
HasKeyConnector = p.HasKeyConnector,
|
||||||
HasScim = p.HasScim,
|
HasScim = p.HasScim,
|
||||||
HasResetPassword = p.HasResetPassword,
|
HasResetPassword = p.HasResetPassword,
|
||||||
@ -315,6 +318,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
existingOrganization.MaxAutoscaleSmSeats = MaxAutoscaleSmSeats;
|
existingOrganization.MaxAutoscaleSmSeats = MaxAutoscaleSmSeats;
|
||||||
existingOrganization.SmServiceAccounts = SmServiceAccounts;
|
existingOrganization.SmServiceAccounts = SmServiceAccounts;
|
||||||
existingOrganization.MaxAutoscaleSmServiceAccounts = MaxAutoscaleSmServiceAccounts;
|
existingOrganization.MaxAutoscaleSmServiceAccounts = MaxAutoscaleSmServiceAccounts;
|
||||||
|
existingOrganization.UseOrganizationDomains = UseOrganizationDomains;
|
||||||
return existingOrganization;
|
return existingOrganization;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.SharedWeb.Utilities;
|
using Bit.SharedWeb.Utilities;
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
|
||||||
namespace Bit.Admin.AdminConsole.Models;
|
namespace Bit.Admin.AdminConsole.Models;
|
||||||
|
|
||||||
|
@ -124,6 +124,10 @@
|
|||||||
<input type="checkbox" class="form-check-input" asp-for="UseSso" disabled='@(canEditPlan ? null : "disabled")'>
|
<input type="checkbox" class="form-check-input" asp-for="UseSso" disabled='@(canEditPlan ? null : "disabled")'>
|
||||||
<label class="form-check-label" asp-for="UseSso"></label>
|
<label class="form-check-label" asp-for="UseSso"></label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" asp-for="UseOrganizationDomains" disabled='@(canEditPlan ? null : "disabled")'>
|
||||||
|
<label class="form-check-label" asp-for="UseOrganizationDomains"></label>
|
||||||
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector" disabled='@(canEditPlan ? null : "disabled")'>
|
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector" disabled='@(canEditPlan ? null : "disabled")'>
|
||||||
<label class="form-check-label" asp-for="UseKeyConnector"></label>
|
<label class="form-check-label" asp-for="UseKeyConnector"></label>
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
document.getElementById('@(nameof(Model.UseGroups))').checked = plan.hasGroups;
|
document.getElementById('@(nameof(Model.UseGroups))').checked = plan.hasGroups;
|
||||||
document.getElementById('@(nameof(Model.UsePolicies))').checked = plan.hasPolicies;
|
document.getElementById('@(nameof(Model.UsePolicies))').checked = plan.hasPolicies;
|
||||||
document.getElementById('@(nameof(Model.UseSso))').checked = plan.hasSso;
|
document.getElementById('@(nameof(Model.UseSso))').checked = plan.hasSso;
|
||||||
|
document.getElementById('@(nameof(Model.UseOrganizationDomains))').checked = plan.hasOrganizationDomains;
|
||||||
document.getElementById('@(nameof(Model.UseScim))').checked = plan.hasScim;
|
document.getElementById('@(nameof(Model.UseScim))').checked = plan.hasScim;
|
||||||
document.getElementById('@(nameof(Model.UseDirectory))').checked = plan.hasDirectory;
|
document.getElementById('@(nameof(Model.UseDirectory))').checked = plan.hasDirectory;
|
||||||
document.getElementById('@(nameof(Model.UseEvents))').checked = plan.hasEvents;
|
document.getElementById('@(nameof(Model.UseEvents))').checked = plan.hasEvents;
|
||||||
|
@ -7,7 +7,7 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Bit.Admin.Billing.Models;
|
using Bit.Admin.Billing.Models;
|
||||||
using Bit.Admin.Enums;
|
using Bit.Admin.Enums;
|
||||||
using Bit.Admin.Utilities;
|
using Bit.Admin.Utilities;
|
||||||
using Bit.Core.Billing.Migration.Models;
|
using Bit.Core.Billing.Providers.Migration.Models;
|
||||||
using Bit.Core.Billing.Migration.Services;
|
using Bit.Core.Billing.Providers.Migration.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.Billing.Entities;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
|
||||||
namespace Bit.Admin.Billing.Models;
|
namespace Bit.Admin.Billing.Models;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@model Bit.Core.Billing.Migration.Models.ProviderMigrationResult
|
@model Bit.Core.Billing.Providers.Migration.Models.ProviderMigrationResult
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Results";
|
ViewData["Title"] = "Results";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@model Bit.Core.Billing.Migration.Models.ProviderMigrationResult[]
|
@model Bit.Core.Billing.Providers.Migration.Models.ProviderMigrationResult[]
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Results";
|
ViewData["Title"] = "Results";
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ using Bit.Admin.Enums;
|
|||||||
using Bit.Admin.Models;
|
using Bit.Admin.Models;
|
||||||
using Bit.Admin.Services;
|
using Bit.Admin.Services;
|
||||||
using Bit.Admin.Utilities;
|
using Bit.Admin.Utilities;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -89,7 +88,7 @@ public class UsersController : Controller
|
|||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
|
||||||
|
|
||||||
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var verifiedDomain = await AccountDeprovisioningEnabled(user.Id);
|
var verifiedDomain = await _userService.IsClaimedByAnyOrganizationAsync(user.Id);
|
||||||
return View(UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers, verifiedDomain));
|
return View(UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers, verifiedDomain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +105,7 @@ public class UsersController : Controller
|
|||||||
var billingInfo = await _paymentService.GetBillingAsync(user);
|
var billingInfo = await _paymentService.GetBillingAsync(user);
|
||||||
var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user);
|
var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user);
|
||||||
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var verifiedDomain = await AccountDeprovisioningEnabled(user.Id);
|
var verifiedDomain = await _userService.IsClaimedByAnyOrganizationAsync(user.Id);
|
||||||
var deviceVerificationRequired = await _userService.ActiveNewDeviceVerificationException(user.Id);
|
var deviceVerificationRequired = await _userService.ActiveNewDeviceVerificationException(user.Id);
|
||||||
|
|
||||||
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain, deviceVerificationRequired));
|
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain, deviceVerificationRequired));
|
||||||
@ -178,12 +177,4 @@ public class UsersController : Controller
|
|||||||
await _userService.ToggleNewDeviceVerificationException(user.Id);
|
await _userService.ToggleNewDeviceVerificationException(user.Id);
|
||||||
return RedirectToAction("Edit", new { id });
|
return RedirectToAction("Edit", new { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Feature flag to be removed in PM-14207
|
|
||||||
private async Task<bool?> AccountDeprovisioningEnabled(Guid userId)
|
|
||||||
{
|
|
||||||
return _featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
|
||||||
? await _userService.IsClaimedByAnyOrganizationAsync(userId)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public class ChargeBraintreeModel : IValidatableObject
|
|||||||
{
|
{
|
||||||
if (Id != null)
|
if (Id != null)
|
||||||
{
|
{
|
||||||
if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u') ||
|
if (Id.Length != 36 || (Id[0] != 'o' && Id[0] != 'u' && Id[0] != 'p') ||
|
||||||
!Guid.TryParse(Id.Substring(1, 32), out var guid))
|
!Guid.TryParse(Id.Substring(1, 32), out var guid))
|
||||||
{
|
{
|
||||||
yield return new ValidationResult("Customer Id is not a valid format.");
|
yield return new ValidationResult("Customer Id is not a valid format.");
|
||||||
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
|||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Bit.Admin.Services;
|
using Bit.Admin.Services;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Migration;
|
using Bit.Core.Billing.Providers.Migration;
|
||||||
|
|
||||||
#if !OSS
|
#if !OSS
|
||||||
using Bit.Commercial.Core.Utilities;
|
using Bit.Commercial.Core.Utilities;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Jobs;
|
using Bit.Core.Jobs;
|
||||||
using Bit.Core.Tools.Repositories;
|
using Bit.Core.Tools.Repositories;
|
||||||
using Bit.Core.Tools.Services;
|
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace Bit.Admin.Tools.Jobs;
|
namespace Bit.Admin.Tools.Jobs;
|
||||||
@ -32,10 +32,10 @@ public class DeleteSendsJob : BaseJob
|
|||||||
}
|
}
|
||||||
using (var scope = _serviceProvider.CreateScope())
|
using (var scope = _serviceProvider.CreateScope())
|
||||||
{
|
{
|
||||||
var sendService = scope.ServiceProvider.GetRequiredService<ISendService>();
|
var nonAnonymousSendCommand = scope.ServiceProvider.GetRequiredService<INonAnonymousSendCommand>();
|
||||||
foreach (var send in sends)
|
foreach (var send in sends)
|
||||||
{
|
{
|
||||||
await sendService.DeleteSendAsync(send);
|
await nonAnonymousSendCommand.DeleteSendAsync(send);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
207
src/Admin/package-lock.json
generated
207
src/Admin/package-lock.json
generated
@ -9,18 +9,18 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.6",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"toastr": "2.1.4"
|
"toastr": "2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"expose-loader": "5.0.0",
|
"expose-loader": "5.0.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"sass": "1.85.0",
|
"sass": "1.88.0",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.99.8",
|
||||||
"webpack-cli": "5.1.4"
|
"webpack-cli": "5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -456,13 +456,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.14",
|
"version": "22.15.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
|
||||||
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
|
"integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
@ -749,9 +749,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bootstrap": {
|
"node_modules/bootstrap": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
|
||||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -782,9 +782,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.4",
|
"version": "4.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
|
||||||
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
|
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -802,10 +802,10 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001716",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"electron-to-chromium": "^1.5.149",
|
||||||
"node-releases": "^2.0.19",
|
"node-releases": "^2.0.19",
|
||||||
"update-browserslist-db": "^1.1.1"
|
"update-browserslist-db": "^1.1.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@ -822,9 +822,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001707",
|
"version": "1.0.30001718",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
|
||||||
"integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==",
|
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -976,9 +976,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.128",
|
"version": "1.5.155",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
|
||||||
"integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==",
|
"integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@ -1010,9 +1010,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-module-lexer": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||||
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
|
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -1084,9 +1084,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expose-loader": {
|
"node_modules/expose-loader": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.1.tgz",
|
||||||
"integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==",
|
"integrity": "sha512-5YPZuszN/eWND/B+xuq5nIpb/l5TV1HYmdO6SubYtHv+HenVw9/6bn33Mm5reY8DNid7AVtbARvyUD34edfCtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1107,13 +1107,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||||
@ -1249,9 +1242,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz",
|
||||||
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
|
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -1755,16 +1748,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
@ -1878,9 +1861,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.85.0",
|
"version": "1.88.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz",
|
||||||
"integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==",
|
"integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1899,9 +1882,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass-loader": {
|
"node_modules/sass-loader": {
|
||||||
"version": "16.0.4",
|
"version": "16.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz",
|
||||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
"integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1940,9 +1923,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1960,9 +1943,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.1",
|
"version": "7.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -2079,9 +2062,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
|
||||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2089,14 +2072,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.39.0",
|
"version": "5.39.2",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz",
|
||||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
"integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.8.2",
|
"acorn": "^8.14.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
@ -2165,9 +2148,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@ -2202,16 +2185,6 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uri-js": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@ -2220,9 +2193,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2234,14 +2207,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.97.1",
|
"version": "5.99.8",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz",
|
||||||
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
|
"integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.7",
|
"@types/eslint-scope": "^3.7.7",
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"@webassemblyjs/ast": "^1.14.1",
|
"@webassemblyjs/ast": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||||
@ -2258,9 +2232,9 @@
|
|||||||
"loader-runner": "^4.2.0",
|
"loader-runner": "^4.2.0",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"neo-async": "^2.6.2",
|
"neo-async": "^2.6.2",
|
||||||
"schema-utils": "^3.2.0",
|
"schema-utils": "^4.3.2",
|
||||||
"tapable": "^2.1.1",
|
"tapable": "^2.1.1",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.11",
|
||||||
"watchpack": "^2.4.1",
|
"watchpack": "^2.4.1",
|
||||||
"webpack-sources": "^3.2.3"
|
"webpack-sources": "^3.2.3"
|
||||||
},
|
},
|
||||||
@ -2361,59 +2335,6 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/ajv": {
|
|
||||||
"version": "6.12.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.1",
|
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
|
||||||
"json-schema-traverse": "^0.4.1",
|
|
||||||
"uri-js": "^4.2.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/ajv-keywords": {
|
|
||||||
"version": "3.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^6.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/json-schema-traverse": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/webpack/node_modules/schema-utils": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/json-schema": "^7.0.8",
|
|
||||||
"ajv": "^6.12.5",
|
|
||||||
"ajv-keywords": "^3.5.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/webpack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@ -8,18 +8,18 @@
|
|||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.6",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"toastr": "2.1.4"
|
"toastr": "2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"expose-loader": "5.0.0",
|
"expose-loader": "5.0.1",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"sass": "1.85.0",
|
"sass": "1.88.0",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.99.8",
|
||||||
"webpack-cli": "5.1.4"
|
"webpack-cli": "5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -137,7 +135,6 @@ public class OrganizationDomainController : Controller
|
|||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("domain/sso/verified")]
|
[HttpPost("domain/sso/verified")]
|
||||||
[RequireFeature(FeatureFlagKeys.VerifiedSsoDomainEndpoint)]
|
|
||||||
public async Task<VerifiedOrganizationDomainSsoDetailsResponseModel> GetVerifiedOrgDomainSsoDetailsAsync(
|
public async Task<VerifiedOrganizationDomainSsoDetailsResponseModel> GetVerifiedOrgDomainSsoDetailsAsync(
|
||||||
[FromBody] OrganizationDomainSsoDetailsRequestModel model)
|
[FromBody] OrganizationDomainSsoDetailsRequestModel model)
|
||||||
{
|
{
|
||||||
|
@ -616,7 +616,6 @@ public class OrganizationUsersController : Controller
|
|||||||
new OrganizationUserBulkResponseModel(r.OrganizationUserId, r.ErrorMessage)));
|
new OrganizationUserBulkResponseModel(r.OrganizationUserId, r.ErrorMessage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
|
|
||||||
[HttpDelete("{id}/delete-account")]
|
[HttpDelete("{id}/delete-account")]
|
||||||
[HttpPost("{id}/delete-account")]
|
[HttpPost("{id}/delete-account")]
|
||||||
public async Task DeleteAccount(Guid orgId, Guid id)
|
public async Task DeleteAccount(Guid orgId, Guid id)
|
||||||
@ -635,7 +634,6 @@ public class OrganizationUsersController : Controller
|
|||||||
await _deleteClaimedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id);
|
await _deleteClaimedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
|
|
||||||
[HttpDelete("delete-account")]
|
[HttpDelete("delete-account")]
|
||||||
[HttpPost("delete-account")]
|
[HttpPost("delete-account")]
|
||||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeleteAccount(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeleteAccount(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||||
@ -760,11 +758,6 @@ public class OrganizationUsersController : Controller
|
|||||||
|
|
||||||
private async Task<IDictionary<Guid, bool>> GetClaimedByOrganizationStatusAsync(Guid orgId, IEnumerable<Guid> userIds)
|
private async Task<IDictionary<Guid, bool>> GetClaimedByOrganizationStatusAsync(Guid orgId, IEnumerable<Guid> userIds)
|
||||||
{
|
{
|
||||||
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
|
||||||
{
|
|
||||||
return userIds.ToDictionary(kvp => kvp, kvp => false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var usersOrganizationClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgId, userIds);
|
var usersOrganizationClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgId, userIds);
|
||||||
return usersOrganizationClaimedStatus;
|
return usersOrganizationClaimedStatus;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ using Bit.Core.Auth.Services;
|
|||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -279,8 +279,7 @@ public class OrganizationsController : Controller
|
|||||||
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
|
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
if ((await _userService.GetOrganizationsClaimingUserAsync(user.Id)).Any(x => x.Id == id))
|
||||||
&& (await _userService.GetOrganizationsClaimingUserAsync(user.Id)).Any(x => x.Id == id))
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Claimed user account cannot leave claiming organization. Contact your organization administrator for additional details.");
|
throw new BadRequestException("Claimed user account cannot leave claiming organization. Contact your organization administrator for additional details.");
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using Bit.Api.AdminConsole.Models.Response.Helpers;
|
using Bit.Api.AdminConsole.Models.Response.Helpers;
|
||||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||||
@ -79,7 +78,7 @@ public class PoliciesController : Controller
|
|||||||
return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });
|
return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg)
|
if (policy.Type is PolicyType.SingleOrg)
|
||||||
{
|
{
|
||||||
return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery);
|
return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
using Bit.Api.Billing.Models.Requests;
|
using Bit.Api.Billing.Models.Requests;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
@ -75,6 +75,8 @@ public class OrganizationCreateRequestModel : IValidatableObject
|
|||||||
|
|
||||||
public string InitiationPath { get; set; }
|
public string InitiationPath { get; set; }
|
||||||
|
|
||||||
|
public bool SkipTrial { get; set; }
|
||||||
|
|
||||||
public virtual OrganizationSignup ToOrganizationSignup(User user)
|
public virtual OrganizationSignup ToOrganizationSignup(User user)
|
||||||
{
|
{
|
||||||
var orgSignup = new OrganizationSignup
|
var orgSignup = new OrganizationSignup
|
||||||
@ -107,6 +109,7 @@ public class OrganizationCreateRequestModel : IValidatableObject
|
|||||||
BillingAddressCountry = BillingAddressCountry,
|
BillingAddressCountry = BillingAddressCountry,
|
||||||
},
|
},
|
||||||
InitiationPath = InitiationPath,
|
InitiationPath = InitiationPath,
|
||||||
|
SkipTrial = SkipTrial
|
||||||
};
|
};
|
||||||
|
|
||||||
Keys?.ToOrganizationSignup(orgSignup);
|
Keys?.ToOrganizationSignup(orgSignup);
|
||||||
|
@ -64,6 +64,7 @@ public class OrganizationResponseModel : ResponseModel
|
|||||||
LimitItemDeletion = organization.LimitItemDeletion;
|
LimitItemDeletion = organization.LimitItemDeletion;
|
||||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||||
UseRiskInsights = organization.UseRiskInsights;
|
UseRiskInsights = organization.UseRiskInsights;
|
||||||
|
UseOrganizationDomains = organization.UseOrganizationDomains;
|
||||||
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +112,7 @@ public class OrganizationResponseModel : ResponseModel
|
|||||||
public bool LimitItemDeletion { get; set; }
|
public bool LimitItemDeletion { get; set; }
|
||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
public bool UseAdminSponsoredFamilies { get; set; }
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
ProviderName = organization.ProviderName;
|
ProviderName = organization.ProviderName;
|
||||||
ProviderType = organization.ProviderType;
|
ProviderType = organization.ProviderType;
|
||||||
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
||||||
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
|
IsAdminInitiated = organization.IsAdminInitiated ?? false;
|
||||||
|
FamilySponsorshipAvailable = (FamilySponsorshipFriendlyName == null || IsAdminInitiated) &&
|
||||||
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
|
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
|
||||||
.UsersCanSponsor(organization);
|
.UsersCanSponsor(organization);
|
||||||
ProductTierType = organization.PlanType.GetProductTier();
|
ProductTierType = organization.PlanType.GetProductTier();
|
||||||
@ -72,6 +73,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||||
UserIsClaimedByOrganization = organizationIdsClaimingUser.Contains(organization.OrganizationId);
|
UserIsClaimedByOrganization = organizationIdsClaimingUser.Contains(organization.OrganizationId);
|
||||||
UseRiskInsights = organization.UseRiskInsights;
|
UseRiskInsights = organization.UseRiskInsights;
|
||||||
|
UseOrganizationDomains = organization.UseOrganizationDomains;
|
||||||
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
||||||
|
|
||||||
if (organization.SsoConfig != null)
|
if (organization.SsoConfig != null)
|
||||||
@ -135,7 +137,6 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obsolete.
|
/// Obsolete.
|
||||||
///
|
|
||||||
/// See <see cref="UserIsClaimedByOrganization"/>
|
/// See <see cref="UserIsClaimedByOrganization"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Please use UserIsClaimedByOrganization instead. This property will be removed in a future version.")]
|
[Obsolete("Please use UserIsClaimedByOrganization instead. This property will be removed in a future version.")]
|
||||||
@ -145,16 +146,15 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
set => UserIsClaimedByOrganization = value;
|
set => UserIsClaimedByOrganization = value;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the organization claims the user.
|
/// Indicates if the user is claimed by the organization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// An organization claims a user if the user's email domain is verified by the organization and the user is a member of it.
|
/// A user is claimed by an organization if the user's email domain is verified by the organization and the user is a member.
|
||||||
/// The organization must be enabled and able to have verified domains.
|
/// The organization must be enabled and able to have verified domains.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>
|
|
||||||
/// False if the Account Deprovisioning feature flag is disabled.
|
|
||||||
/// </returns>
|
|
||||||
public bool UserIsClaimedByOrganization { get; set; }
|
public bool UserIsClaimedByOrganization { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
public bool UseAdminSponsoredFamilies { get; set; }
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
|
public bool IsAdminInitiated { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
|||||||
LimitItemDeletion = organization.LimitItemDeletion;
|
LimitItemDeletion = organization.LimitItemDeletion;
|
||||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||||
UseRiskInsights = organization.UseRiskInsights;
|
UseRiskInsights = organization.UseRiskInsights;
|
||||||
|
UseOrganizationDomains = organization.UseOrganizationDomains;
|
||||||
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -518,9 +518,8 @@ public class AccountsController : Controller
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If Account Deprovisioning is enabled, we need to check if the user is claimed by any organization.
|
// Check if the user is claimed by any organization.
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
if (await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
|
||||||
&& await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details.");
|
throw new BadRequestException("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details.");
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Bit.Api.Billing.Models.Responses;
|
using Bit.Api.Billing.Models.Responses;
|
||||||
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Billing.Tax.Requests;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.Billing.Models.Api.Requests.Organizations;
|
using Bit.Core.Billing.Tax.Requests;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
@ -8,7 +8,9 @@ using Bit.Core;
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Models.Sales;
|
using Bit.Core.Billing.Models.Sales;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Billing.Tax.Models;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -291,14 +293,20 @@ public class OrganizationBillingController(
|
|||||||
sale.Organization.PlanType = plan.Type;
|
sale.Organization.PlanType = plan.Type;
|
||||||
sale.Organization.Plan = plan.Name;
|
sale.Organization.Plan = plan.Name;
|
||||||
sale.SubscriptionSetup.SkipTrial = true;
|
sale.SubscriptionSetup.SkipTrial = true;
|
||||||
await organizationBillingService.Finalize(sale);
|
|
||||||
|
if (organizationSignup.PaymentMethodType == null || string.IsNullOrEmpty(organizationSignup.PaymentToken))
|
||||||
|
{
|
||||||
|
return Error.BadRequest("A payment method is required to restart the subscription.");
|
||||||
|
}
|
||||||
var org = await organizationRepository.GetByIdAsync(organizationId);
|
var org = await organizationRepository.GetByIdAsync(organizationId);
|
||||||
Debug.Assert(org is not null, "This organization has already been found via this same ID, this should be fine.");
|
Debug.Assert(org is not null, "This organization has already been found via this same ID, this should be fine.");
|
||||||
if (organizationSignup.PaymentMethodType != null)
|
var paymentSource = new TokenizedPaymentSource(organizationSignup.PaymentMethodType.Value, organizationSignup.PaymentToken);
|
||||||
|
var taxInformation = TaxInformation.From(organizationSignup.TaxInfo);
|
||||||
|
await organizationBillingService.Finalize(sale);
|
||||||
|
var updatedOrg = await organizationRepository.GetByIdAsync(organizationId);
|
||||||
|
if (updatedOrg != null)
|
||||||
{
|
{
|
||||||
var paymentSource = new TokenizedPaymentSource(organizationSignup.PaymentMethodType.Value, organizationSignup.PaymentToken);
|
await organizationBillingService.UpdatePaymentMethod(updatedOrg, paymentSource, taxInformation);
|
||||||
var taxInformation = TaxInformation.From(organizationSignup.TaxInfo);
|
|
||||||
await organizationBillingService.UpdatePaymentMethod(org, paymentSource, taxInformation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedResults.Ok();
|
return TypedResults.Ok();
|
||||||
|
@ -222,6 +222,20 @@ public class OrganizationSponsorshipsController : Controller
|
|||||||
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize("Application")]
|
||||||
|
[HttpDelete("{sponsoringOrgId}/{sponsoredFriendlyName}/revoke")]
|
||||||
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
public async Task AdminInitiatedRevokeSponsorshipAsync(Guid sponsoringOrgId, string sponsoredFriendlyName)
|
||||||
|
{
|
||||||
|
var sponsorships = await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(sponsoringOrgId);
|
||||||
|
var existingOrgSponsorship = sponsorships.FirstOrDefault(s => s.FriendlyName != null && s.FriendlyName.Equals(sponsoredFriendlyName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (existingOrgSponsorship == null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("The specified sponsored organization could not be found under the given sponsoring organization.");
|
||||||
|
}
|
||||||
|
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize("Application")]
|
[Authorize("Application")]
|
||||||
[HttpDelete("sponsored/{sponsoredOrgId}")]
|
[HttpDelete("sponsored/{sponsoredOrgId}")]
|
||||||
[HttpPost("sponsored/{sponsoredOrgId}/remove")]
|
[HttpPost("sponsored/{sponsoredOrgId}/remove")]
|
||||||
@ -271,8 +285,11 @@ public class OrganizationSponsorshipsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sponsorships = await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(sponsoringOrgId);
|
var sponsorships = await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(sponsoringOrgId);
|
||||||
return new ListResponseModel<OrganizationSponsorshipInvitesResponseModel>(sponsorships.Select(s =>
|
return new ListResponseModel<OrganizationSponsorshipInvitesResponseModel>(
|
||||||
new OrganizationSponsorshipInvitesResponseModel(new OrganizationSponsorshipData(s))));
|
sponsorships
|
||||||
|
.Where(s => s.IsAdminInitiated)
|
||||||
|
.Select(s => new OrganizationSponsorshipInvitesResponseModel(new OrganizationSponsorshipData(s)))
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,28 +109,6 @@ public class OrganizationsController(
|
|||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id:guid}/payment")]
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
|
||||||
public async Task PostPayment(Guid id, [FromBody] PaymentRequestModel model)
|
|
||||||
{
|
|
||||||
if (!await currentContext.EditPaymentMethods(id))
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await organizationService.ReplacePaymentMethodAsync(id, model.PaymentToken,
|
|
||||||
model.PaymentMethodType.Value, new TaxInfo
|
|
||||||
{
|
|
||||||
BillingAddressLine1 = model.Line1,
|
|
||||||
BillingAddressLine2 = model.Line2,
|
|
||||||
BillingAddressState = model.State,
|
|
||||||
BillingAddressCity = model.City,
|
|
||||||
BillingAddressPostalCode = model.PostalCode,
|
|
||||||
BillingAddressCountry = model.Country,
|
|
||||||
TaxIdNumber = model.TaxId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{id:guid}/upgrade")]
|
[HttpPost("{id:guid}/upgrade")]
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
public async Task<PaymentResponseModel> PostUpgrade(Guid id, [FromBody] OrganizationUpgradeRequestModel model)
|
public async Task<PaymentResponseModel> PostUpgrade(Guid id, [FromBody] OrganizationUpgradeRequestModel model)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
using Bit.Api.Billing.Models.Requests;
|
using Bit.Api.Billing.Models.Requests;
|
||||||
using Bit.Api.Billing.Models.Responses;
|
using Bit.Api.Billing.Models.Responses;
|
||||||
|
using Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Models;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Models;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Billing.Tax.Models;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Models.BitStripe;
|
using Bit.Core.Models.BitStripe;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -147,13 +150,33 @@ public class ProviderBillingController(
|
|||||||
|
|
||||||
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
|
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
|
||||||
|
|
||||||
|
var getProviderPriceFromStripe = featureService.IsEnabled(FeatureFlagKeys.PM21383_GetProviderPriceFromStripe);
|
||||||
|
|
||||||
var configuredProviderPlans = await Task.WhenAll(providerPlans.Select(async providerPlan =>
|
var configuredProviderPlans = await Task.WhenAll(providerPlans.Select(async providerPlan =>
|
||||||
{
|
{
|
||||||
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
|
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
|
||||||
|
|
||||||
|
decimal unitAmount;
|
||||||
|
|
||||||
|
if (getProviderPriceFromStripe)
|
||||||
|
{
|
||||||
|
var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, plan.Type);
|
||||||
|
var price = await stripeAdapter.PriceGetAsync(priceId);
|
||||||
|
|
||||||
|
unitAmount = price.UnitAmountDecimal.HasValue
|
||||||
|
? price.UnitAmountDecimal.Value / 100M
|
||||||
|
: plan.PasswordManager.ProviderPortalSeatPrice;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unitAmount = plan.PasswordManager.ProviderPortalSeatPrice;
|
||||||
|
}
|
||||||
|
|
||||||
return new ConfiguredProviderPlan(
|
return new ConfiguredProviderPlan(
|
||||||
providerPlan.Id,
|
providerPlan.Id,
|
||||||
providerPlan.ProviderId,
|
providerPlan.ProviderId,
|
||||||
plan,
|
plan,
|
||||||
|
unitAmount,
|
||||||
providerPlan.SeatMinimum ?? 0,
|
providerPlan.SeatMinimum ?? 0,
|
||||||
providerPlan.PurchasedSeats ?? 0,
|
providerPlan.PurchasedSeats ?? 0,
|
||||||
providerPlan.AllocatedSeats ?? 0);
|
providerPlan.AllocatedSeats ?? 0);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Tax.Services;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
36
src/Api/Billing/Controllers/TaxController.cs
Normal file
36
src/Api/Billing/Controllers/TaxController.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Bit.Api.Billing.Models.Requests;
|
||||||
|
using Bit.Core.Billing.Tax.Commands;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Bit.Api.Billing.Controllers;
|
||||||
|
|
||||||
|
[Authorize("Application")]
|
||||||
|
[Route("tax")]
|
||||||
|
public class TaxController(
|
||||||
|
IPreviewTaxAmountCommand previewTaxAmountCommand) : BaseBillingController
|
||||||
|
{
|
||||||
|
[HttpPost("preview-amount/organization-trial")]
|
||||||
|
public async Task<IResult> PreviewTaxAmountForOrganizationTrialAsync(
|
||||||
|
[FromBody] PreviewTaxAmountForOrganizationTrialRequestBody requestBody)
|
||||||
|
{
|
||||||
|
var parameters = new OrganizationTrialParameters
|
||||||
|
{
|
||||||
|
PlanType = requestBody.PlanType,
|
||||||
|
ProductType = requestBody.ProductType,
|
||||||
|
TaxInformation = new OrganizationTrialParameters.TaxInformationDTO
|
||||||
|
{
|
||||||
|
Country = requestBody.TaxInformation.Country,
|
||||||
|
PostalCode = requestBody.TaxInformation.PostalCode,
|
||||||
|
TaxId = requestBody.TaxInformation.TaxId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await previewTaxAmountCommand.Run(parameters);
|
||||||
|
|
||||||
|
return result.Match<IResult>(
|
||||||
|
taxAmount => TypedResults.Ok(new { TaxAmount = taxAmount }),
|
||||||
|
badRequest => Error.BadRequest(badRequest.TranslationKey),
|
||||||
|
unhandled => Error.ServerError(unhandled.TranslationKey));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Api.Billing.Models.Requests;
|
||||||
|
|
||||||
|
public class PreviewTaxAmountForOrganizationTrialRequestBody
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public PlanType PlanType { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public ProductType ProductType { get; set; }
|
||||||
|
|
||||||
|
[Required] public TaxInformationDTO TaxInformation { get; set; } = null!;
|
||||||
|
|
||||||
|
public class TaxInformationDTO
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Country { get; set; } = null!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string PostalCode { get; set; } = null!;
|
||||||
|
|
||||||
|
public string? TaxId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Tax.Models;
|
||||||
|
|
||||||
namespace Bit.Api.Billing.Models.Requests;
|
namespace Bit.Api.Billing.Models.Requests;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
|
using Bit.Core.Billing.Tax.Models;
|
||||||
|
|
||||||
namespace Bit.Api.Billing.Models.Responses;
|
namespace Bit.Api.Billing.Models.Responses;
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
|
using Bit.Core.Billing.Providers.Models;
|
||||||
|
using Bit.Core.Billing.Tax.Models;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Api.Billing.Models.Responses;
|
namespace Bit.Api.Billing.Models.Responses;
|
||||||
@ -34,7 +36,7 @@ public record ProviderSubscriptionResponse(
|
|||||||
.Select(providerPlan =>
|
.Select(providerPlan =>
|
||||||
{
|
{
|
||||||
var plan = providerPlan.Plan;
|
var plan = providerPlan.Plan;
|
||||||
var cost = (providerPlan.SeatMinimum + providerPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice;
|
var cost = (providerPlan.SeatMinimum + providerPlan.PurchasedSeats) * providerPlan.Price;
|
||||||
var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence;
|
var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence;
|
||||||
return new ProviderPlanResponse(
|
return new ProviderPlanResponse(
|
||||||
plan.Name,
|
plan.Name,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Tax.Models;
|
||||||
|
|
||||||
namespace Bit.Api.Billing.Models.Responses;
|
namespace Bit.Api.Billing.Models.Responses;
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
using Bit.Api.Models.Request.Organizations;
|
using Bit.Api.AdminConsole.Authorization.Requirements;
|
||||||
|
using Bit.Api.Models.Request.Organizations;
|
||||||
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
|
||||||
|
using Bit.Core.Models.Data.Organizations.OrganizationSponsorships;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -22,6 +26,7 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
|
|||||||
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
|
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
public SelfHostedOrganizationSponsorshipsController(
|
public SelfHostedOrganizationSponsorshipsController(
|
||||||
ICreateSponsorshipCommand offerSponsorshipCommand,
|
ICreateSponsorshipCommand offerSponsorshipCommand,
|
||||||
@ -30,7 +35,8 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
|
|||||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IFeatureService featureService
|
IFeatureService featureService,
|
||||||
|
IAuthorizationService authorizationService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_offerSponsorshipCommand = offerSponsorshipCommand;
|
_offerSponsorshipCommand = offerSponsorshipCommand;
|
||||||
@ -40,6 +46,7 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
|
|||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
|
_authorizationService = authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
|
[HttpPost("{sponsoringOrgId}/families-for-enterprise")]
|
||||||
@ -84,4 +91,41 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
|
|||||||
|
|
||||||
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{sponsoringOrgId}/{sponsoredFriendlyName}/revoke")]
|
||||||
|
public async Task AdminInitiatedRevokeSponsorshipAsync(Guid sponsoringOrgId, string sponsoredFriendlyName)
|
||||||
|
{
|
||||||
|
var sponsorships = await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(sponsoringOrgId);
|
||||||
|
var existingOrgSponsorship = sponsorships.FirstOrDefault(s => s.FriendlyName != null && s.FriendlyName.Equals(sponsoredFriendlyName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (existingOrgSponsorship == null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("The specified sponsored organization could not be found under the given sponsoring organization.");
|
||||||
|
}
|
||||||
|
await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize("Application")]
|
||||||
|
[HttpGet("{orgId}/sponsored")]
|
||||||
|
public async Task<ListResponseModel<OrganizationSponsorshipInvitesResponseModel>> GetSponsoredOrganizations(Guid orgId)
|
||||||
|
{
|
||||||
|
var sponsoringOrg = await _organizationRepository.GetByIdAsync(orgId);
|
||||||
|
if (sponsoringOrg == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var authorizationResult = await _authorizationService.AuthorizeAsync(User, orgId, new ManageUsersRequirement());
|
||||||
|
if (!authorizationResult.Succeeded)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sponsorships = await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(orgId);
|
||||||
|
return new ListResponseModel<OrganizationSponsorshipInvitesResponseModel>(
|
||||||
|
sponsorships
|
||||||
|
.Where(s => s.IsAdminInitiated)
|
||||||
|
.Select(s => new OrganizationSponsorshipInvitesResponseModel(new OrganizationSponsorshipData(s)))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,20 @@ namespace Bit.Api.Tools.Models.Response;
|
|||||||
|
|
||||||
public class MemberCipherDetailsResponseModel
|
public class MemberCipherDetailsResponseModel
|
||||||
{
|
{
|
||||||
|
public Guid? UserGuid { get; set; }
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public bool UsesKeyConnector { get; set; }
|
public bool UsesKeyConnector { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A distinct list of the cipher ids associated with
|
/// A distinct list of the cipher ids associated with
|
||||||
/// the organization member
|
/// the organization member
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> CipherIds { get; set; }
|
public IEnumerable<string> CipherIds { get; set; }
|
||||||
|
|
||||||
public MemberCipherDetailsResponseModel(MemberAccessCipherDetails memberAccessCipherDetails)
|
public MemberCipherDetailsResponseModel(MemberAccessCipherDetails memberAccessCipherDetails)
|
||||||
{
|
{
|
||||||
|
this.UserGuid = memberAccessCipherDetails.UserGuid;
|
||||||
this.UserName = memberAccessCipherDetails.UserName;
|
this.UserName = memberAccessCipherDetails.UserName;
|
||||||
this.Email = memberAccessCipherDetails.Email;
|
this.Email = memberAccessCipherDetails.Email;
|
||||||
this.UsesKeyConnector = memberAccessCipherDetails.UsesKeyConnector;
|
this.UsesKeyConnector = memberAccessCipherDetails.UsesKeyConnector;
|
||||||
|
@ -12,17 +12,17 @@ namespace Bit.Api.KeyManagement.Validators;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>
|
public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>
|
||||||
{
|
{
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendAuthorizationService _sendAuthorizationService;
|
||||||
private readonly ISendRepository _sendRepository;
|
private readonly ISendRepository _sendRepository;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instantiates a new <see cref="SendRotationValidator"/>
|
/// Instantiates a new <see cref="SendRotationValidator"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sendService">Enables conversion of <see cref="SendWithIdRequestModel"/> to <see cref="Send"/></param>
|
/// <param name="sendAuthorizationService">Enables conversion of <see cref="SendWithIdRequestModel"/> to <see cref="Send"/></param>
|
||||||
/// <param name="sendRepository">Retrieves all user <see cref="Send"/>s</param>
|
/// <param name="sendRepository">Retrieves all user <see cref="Send"/>s</param>
|
||||||
public SendRotationValidator(ISendService sendService, ISendRepository sendRepository)
|
public SendRotationValidator(ISendAuthorizationService sendAuthorizationService, ISendRepository sendRepository)
|
||||||
{
|
{
|
||||||
_sendService = sendService;
|
_sendAuthorizationService = sendAuthorizationService;
|
||||||
_sendRepository = sendRepository;
|
_sendRepository = sendRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRe
|
|||||||
throw new BadRequestException("All existing sends must be included in the rotation.");
|
throw new BadRequestException("All existing sends must be included in the rotation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(send.ToSend(existing, _sendService));
|
result.Add(send.ToSend(existing, _sendAuthorizationService));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -32,6 +32,7 @@ public class PlanResponseModel : ResponseModel
|
|||||||
HasTotp = plan.HasTotp;
|
HasTotp = plan.HasTotp;
|
||||||
Has2fa = plan.Has2fa;
|
Has2fa = plan.Has2fa;
|
||||||
HasSso = plan.HasSso;
|
HasSso = plan.HasSso;
|
||||||
|
HasOrganizationDomains = plan.HasOrganizationDomains;
|
||||||
HasResetPassword = plan.HasResetPassword;
|
HasResetPassword = plan.HasResetPassword;
|
||||||
UsersGetPremium = plan.UsersGetPremium;
|
UsersGetPremium = plan.UsersGetPremium;
|
||||||
UpgradeSortOrder = plan.UpgradeSortOrder;
|
UpgradeSortOrder = plan.UpgradeSortOrder;
|
||||||
@ -71,6 +72,7 @@ public class PlanResponseModel : ResponseModel
|
|||||||
public bool Has2fa { get; set; }
|
public bool Has2fa { get; set; }
|
||||||
public bool HasApi { get; set; }
|
public bool HasApi { get; set; }
|
||||||
public bool HasSso { get; set; }
|
public bool HasSso { get; set; }
|
||||||
|
public bool HasOrganizationDomains { get; set; }
|
||||||
public bool HasResetPassword { get; set; }
|
public bool HasResetPassword { get; set; }
|
||||||
public bool UsersGetPremium { get; set; }
|
public bool UsersGetPremium { get; set; }
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ using Bit.Core.Services;
|
|||||||
using Bit.Core.Tools.ImportFeatures;
|
using Bit.Core.Tools.ImportFeatures;
|
||||||
using Bit.Core.Tools.ReportFeatures;
|
using Bit.Core.Tools.ReportFeatures;
|
||||||
using Bit.Core.Auth.Models.Api.Request;
|
using Bit.Core.Auth.Models.Api.Request;
|
||||||
|
using Bit.Core.Tools.SendFeatures;
|
||||||
|
|
||||||
#if !OSS
|
#if !OSS
|
||||||
using Bit.Commercial.Core.SecretsManager;
|
using Bit.Commercial.Core.SecretsManager;
|
||||||
@ -186,6 +187,7 @@ public class Startup
|
|||||||
services.AddPhishingDomainServices(globalSettings);
|
services.AddPhishingDomainServices(globalSettings);
|
||||||
|
|
||||||
services.AddBillingQueries();
|
services.AddBillingQueries();
|
||||||
|
services.AddSendServices();
|
||||||
|
|
||||||
// Authorization Handlers
|
// Authorization Handlers
|
||||||
services.AddAuthorizationHandlers();
|
services.AddAuthorizationHandlers();
|
||||||
|
@ -12,6 +12,8 @@ using Bit.Core.Settings;
|
|||||||
using Bit.Core.Tools.Enums;
|
using Bit.Core.Tools.Enums;
|
||||||
using Bit.Core.Tools.Models.Data;
|
using Bit.Core.Tools.Models.Data;
|
||||||
using Bit.Core.Tools.Repositories;
|
using Bit.Core.Tools.Repositories;
|
||||||
|
using Bit.Core.Tools.SendFeatures;
|
||||||
|
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||||
using Bit.Core.Tools.Services;
|
using Bit.Core.Tools.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -25,8 +27,10 @@ public class SendsController : Controller
|
|||||||
{
|
{
|
||||||
private readonly ISendRepository _sendRepository;
|
private readonly ISendRepository _sendRepository;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendAuthorizationService _sendAuthorizationService;
|
||||||
private readonly ISendFileStorageService _sendFileStorageService;
|
private readonly ISendFileStorageService _sendFileStorageService;
|
||||||
|
private readonly IAnonymousSendCommand _anonymousSendCommand;
|
||||||
|
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
|
||||||
private readonly ILogger<SendsController> _logger;
|
private readonly ILogger<SendsController> _logger;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
@ -34,7 +38,9 @@ public class SendsController : Controller
|
|||||||
public SendsController(
|
public SendsController(
|
||||||
ISendRepository sendRepository,
|
ISendRepository sendRepository,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
ISendService sendService,
|
ISendAuthorizationService sendAuthorizationService,
|
||||||
|
IAnonymousSendCommand anonymousSendCommand,
|
||||||
|
INonAnonymousSendCommand nonAnonymousSendCommand,
|
||||||
ISendFileStorageService sendFileStorageService,
|
ISendFileStorageService sendFileStorageService,
|
||||||
ILogger<SendsController> logger,
|
ILogger<SendsController> logger,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
@ -42,13 +48,16 @@ public class SendsController : Controller
|
|||||||
{
|
{
|
||||||
_sendRepository = sendRepository;
|
_sendRepository = sendRepository;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_sendService = sendService;
|
_sendAuthorizationService = sendAuthorizationService;
|
||||||
|
_anonymousSendCommand = anonymousSendCommand;
|
||||||
|
_nonAnonymousSendCommand = nonAnonymousSendCommand;
|
||||||
_sendFileStorageService = sendFileStorageService;
|
_sendFileStorageService = sendFileStorageService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Anonymous endpoints
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("access/{id}")]
|
[HttpPost("access/{id}")]
|
||||||
public async Task<IActionResult> Access(string id, [FromBody] SendAccessRequestModel model)
|
public async Task<IActionResult> Access(string id, [FromBody] SendAccessRequestModel model)
|
||||||
@ -61,18 +70,19 @@ public class SendsController : Controller
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
|
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
|
||||||
var (send, passwordRequired, passwordInvalid) =
|
var send = await _sendRepository.GetByIdAsync(guid);
|
||||||
await _sendService.AccessAsync(guid, model.Password);
|
SendAccessResult sendAuthResult =
|
||||||
if (passwordRequired)
|
await _sendAuthorizationService.AccessAsync(send, model.Password);
|
||||||
|
if (sendAuthResult.Equals(SendAccessResult.PasswordRequired))
|
||||||
{
|
{
|
||||||
return new UnauthorizedResult();
|
return new UnauthorizedResult();
|
||||||
}
|
}
|
||||||
if (passwordInvalid)
|
if (sendAuthResult.Equals(SendAccessResult.PasswordInvalid))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException("Invalid password.");
|
throw new BadRequestException("Invalid password.");
|
||||||
}
|
}
|
||||||
if (send == null)
|
if (sendAuthResult.Equals(SendAccessResult.Denied))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -106,19 +116,19 @@ public class SendsController : Controller
|
|||||||
throw new BadRequestException("Could not locate send");
|
throw new BadRequestException("Could not locate send");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (url, passwordRequired, passwordInvalid) = await _sendService.GetSendFileDownloadUrlAsync(send, fileId,
|
var (url, result) = await _anonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId,
|
||||||
model.Password);
|
model.Password);
|
||||||
|
|
||||||
if (passwordRequired)
|
if (result.Equals(SendAccessResult.PasswordRequired))
|
||||||
{
|
{
|
||||||
return new UnauthorizedResult();
|
return new UnauthorizedResult();
|
||||||
}
|
}
|
||||||
if (passwordInvalid)
|
if (result.Equals(SendAccessResult.PasswordInvalid))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException("Invalid password.");
|
throw new BadRequestException("Invalid password.");
|
||||||
}
|
}
|
||||||
if (send == null)
|
if (result.Equals(SendAccessResult.Denied))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
@ -130,6 +140,45 @@ public class SendsController : Controller
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("file/validate/azure")]
|
||||||
|
public async Task<ObjectResult> AzureValidateFile()
|
||||||
|
{
|
||||||
|
return await ApiHelpers.HandleAzureEvents(Request, new Dictionary<string, Func<EventGridEvent, Task>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"Microsoft.Storage.BlobCreated", async (eventGridEvent) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var blobName = eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1];
|
||||||
|
var sendId = AzureSendFileStorageService.SendIdFromBlobName(blobName);
|
||||||
|
var send = await _sendRepository.GetByIdAsync(new Guid(sendId));
|
||||||
|
if (send == null)
|
||||||
|
{
|
||||||
|
if (_sendFileStorageService is AzureSendFileStorageService azureSendFileStorageService)
|
||||||
|
{
|
||||||
|
await azureSendFileStorageService.DeleteBlobAsync(blobName);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _nonAnonymousSendCommand.ConfirmFileSize(send);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Non-anonymous endpoints
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<SendResponseModel> Get(string id)
|
public async Task<SendResponseModel> Get(string id)
|
||||||
{
|
{
|
||||||
@ -157,8 +206,8 @@ public class SendsController : Controller
|
|||||||
{
|
{
|
||||||
model.ValidateCreation();
|
model.ValidateCreation();
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var send = model.ToSend(userId, _sendService);
|
var send = model.ToSend(userId, _sendAuthorizationService);
|
||||||
await _sendService.SaveSendAsync(send);
|
await _nonAnonymousSendCommand.SaveSendAsync(send);
|
||||||
return new SendResponseModel(send, _globalSettings);
|
return new SendResponseModel(send, _globalSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,15 +224,15 @@ public class SendsController : Controller
|
|||||||
throw new BadRequestException("Invalid content. File size hint is required.");
|
throw new BadRequestException("Invalid content. File size hint is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.FileLength.Value > SendService.MAX_FILE_SIZE)
|
if (model.FileLength.Value > Constants.FileSize501mb)
|
||||||
{
|
{
|
||||||
throw new BadRequestException($"Max file size is {SendService.MAX_FILE_SIZE_READABLE}.");
|
throw new BadRequestException($"Max file size is {SendFileSettingHelper.MAX_FILE_SIZE_READABLE}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
model.ValidateCreation();
|
model.ValidateCreation();
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var (send, data) = model.ToSend(userId, model.File.FileName, _sendService);
|
var (send, data) = model.ToSend(userId, model.File.FileName, _sendAuthorizationService);
|
||||||
var uploadUrl = await _sendService.SaveFileSendAsync(send, data, model.FileLength.Value);
|
var uploadUrl = await _nonAnonymousSendCommand.SaveFileSendAsync(send, data, model.FileLength.Value);
|
||||||
return new SendFileUploadDataResponseModel
|
return new SendFileUploadDataResponseModel
|
||||||
{
|
{
|
||||||
Url = uploadUrl,
|
Url = uploadUrl,
|
||||||
@ -230,41 +279,7 @@ public class SendsController : Controller
|
|||||||
var send = await _sendRepository.GetByIdAsync(new Guid(id));
|
var send = await _sendRepository.GetByIdAsync(new Guid(id));
|
||||||
await Request.GetFileAsync(async (stream) =>
|
await Request.GetFileAsync(async (stream) =>
|
||||||
{
|
{
|
||||||
await _sendService.UploadFileToExistingSendAsync(stream, send);
|
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("file/validate/azure")]
|
|
||||||
public async Task<ObjectResult> AzureValidateFile()
|
|
||||||
{
|
|
||||||
return await ApiHelpers.HandleAzureEvents(Request, new Dictionary<string, Func<EventGridEvent, Task>>
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"Microsoft.Storage.BlobCreated", async (eventGridEvent) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var blobName = eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1];
|
|
||||||
var sendId = AzureSendFileStorageService.SendIdFromBlobName(blobName);
|
|
||||||
var send = await _sendRepository.GetByIdAsync(new Guid(sendId));
|
|
||||||
if (send == null)
|
|
||||||
{
|
|
||||||
if (_sendFileStorageService is AzureSendFileStorageService azureSendFileStorageService)
|
|
||||||
{
|
|
||||||
await azureSendFileStorageService.DeleteBlobAsync(blobName);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _sendService.ValidateSendFile(send);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +294,7 @@ public class SendsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _sendService.SaveSendAsync(model.ToSend(send, _sendService));
|
await _nonAnonymousSendCommand.SaveSendAsync(model.ToSend(send, _sendAuthorizationService));
|
||||||
return new SendResponseModel(send, _globalSettings);
|
return new SendResponseModel(send, _globalSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +309,7 @@ public class SendsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
send.Password = null;
|
send.Password = null;
|
||||||
await _sendService.SaveSendAsync(send);
|
await _nonAnonymousSendCommand.SaveSendAsync(send);
|
||||||
return new SendResponseModel(send, _globalSettings);
|
return new SendResponseModel(send, _globalSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,6 +323,8 @@ public class SendsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _sendService.DeleteSendAsync(send);
|
await _nonAnonymousSendCommand.DeleteSendAsync(send);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -36,31 +36,31 @@ public class SendRequestModel
|
|||||||
public bool? Disabled { get; set; }
|
public bool? Disabled { get; set; }
|
||||||
public bool? HideEmail { get; set; }
|
public bool? HideEmail { get; set; }
|
||||||
|
|
||||||
public Send ToSend(Guid userId, ISendService sendService)
|
public Send ToSend(Guid userId, ISendAuthorizationService sendAuthorizationService)
|
||||||
{
|
{
|
||||||
var send = new Send
|
var send = new Send
|
||||||
{
|
{
|
||||||
Type = Type,
|
Type = Type,
|
||||||
UserId = (Guid?)userId
|
UserId = (Guid?)userId
|
||||||
};
|
};
|
||||||
ToSend(send, sendService);
|
ToSend(send, sendAuthorizationService);
|
||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Send, SendFileData) ToSend(Guid userId, string fileName, ISendService sendService)
|
public (Send, SendFileData) ToSend(Guid userId, string fileName, ISendAuthorizationService sendAuthorizationService)
|
||||||
{
|
{
|
||||||
var send = ToSendBase(new Send
|
var send = ToSendBase(new Send
|
||||||
{
|
{
|
||||||
Type = Type,
|
Type = Type,
|
||||||
UserId = (Guid?)userId
|
UserId = (Guid?)userId
|
||||||
}, sendService);
|
}, sendAuthorizationService);
|
||||||
var data = new SendFileData(Name, Notes, fileName);
|
var data = new SendFileData(Name, Notes, fileName);
|
||||||
return (send, data);
|
return (send, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Send ToSend(Send existingSend, ISendService sendService)
|
public Send ToSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
|
||||||
{
|
{
|
||||||
existingSend = ToSendBase(existingSend, sendService);
|
existingSend = ToSendBase(existingSend, sendAuthorizationService);
|
||||||
switch (existingSend.Type)
|
switch (existingSend.Type)
|
||||||
{
|
{
|
||||||
case SendType.File:
|
case SendType.File:
|
||||||
@ -125,7 +125,7 @@ public class SendRequestModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Send ToSendBase(Send existingSend, ISendService sendService)
|
private Send ToSendBase(Send existingSend, ISendAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
existingSend.Key = Key;
|
existingSend.Key = Key;
|
||||||
existingSend.ExpirationDate = ExpirationDate;
|
existingSend.ExpirationDate = ExpirationDate;
|
||||||
@ -133,7 +133,7 @@ public class SendRequestModel
|
|||||||
existingSend.MaxAccessCount = MaxAccessCount;
|
existingSend.MaxAccessCount = MaxAccessCount;
|
||||||
if (!string.IsNullOrWhiteSpace(Password))
|
if (!string.IsNullOrWhiteSpace(Password))
|
||||||
{
|
{
|
||||||
existingSend.Password = sendService.HashPassword(Password);
|
existingSend.Password = authorizationService.HashPassword(Password);
|
||||||
}
|
}
|
||||||
existingSend.Disabled = Disabled.GetValueOrDefault();
|
existingSend.Disabled = Disabled.GetValueOrDefault();
|
||||||
existingSend.HideEmail = HideEmail.GetValueOrDefault();
|
existingSend.HideEmail = HideEmail.GetValueOrDefault();
|
||||||
|
@ -151,6 +151,16 @@ public class CiphersController : Controller
|
|||||||
public async Task<CipherResponseModel> Post([FromBody] CipherRequestModel model)
|
public async Task<CipherResponseModel> Post([FromBody] CipherRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.EncryptedFor != user.Id)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var cipher = model.ToCipherDetails(user.Id);
|
var cipher = model.ToCipherDetails(user.Id);
|
||||||
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
||||||
{
|
{
|
||||||
@ -170,6 +180,16 @@ public class CiphersController : Controller
|
|||||||
public async Task<CipherResponseModel> PostCreate([FromBody] CipherCreateRequestModel model)
|
public async Task<CipherResponseModel> PostCreate([FromBody] CipherCreateRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.Cipher.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.Cipher.EncryptedFor != user.Id)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var cipher = model.Cipher.ToCipherDetails(user.Id);
|
var cipher = model.Cipher.ToCipherDetails(user.Id);
|
||||||
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
if (cipher.OrganizationId.HasValue && !await _currentContext.OrganizationUser(cipher.OrganizationId.Value))
|
||||||
{
|
{
|
||||||
@ -192,6 +212,16 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.Cipher.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.Cipher.EncryptedFor != userId)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _cipherService.SaveAsync(cipher, userId, model.Cipher.LastKnownRevisionDate, model.CollectionIds, true, false);
|
await _cipherService.SaveAsync(cipher, userId, model.Cipher.LastKnownRevisionDate, model.CollectionIds, true, false);
|
||||||
|
|
||||||
var response = new CipherMiniResponseModel(cipher, _globalSettings, false);
|
var response = new CipherMiniResponseModel(cipher, _globalSettings, false);
|
||||||
@ -209,6 +239,15 @@ public class CiphersController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.EncryptedFor != user.Id)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValidateClientVersionForFido2CredentialSupport(cipher);
|
ValidateClientVersionForFido2CredentialSupport(cipher);
|
||||||
|
|
||||||
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id)).Select(c => c.CollectionId).ToList();
|
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(user.Id, id)).Select(c => c.CollectionId).ToList();
|
||||||
@ -237,6 +276,15 @@ public class CiphersController : Controller
|
|||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);
|
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.EncryptedFor != userId)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValidateClientVersionForFido2CredentialSupport(cipher);
|
ValidateClientVersionForFido2CredentialSupport(cipher);
|
||||||
|
|
||||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||||
@ -315,26 +363,10 @@ public class CiphersController : Controller
|
|||||||
{
|
{
|
||||||
var org = _currentContext.GetOrganization(organizationId);
|
var org = _currentContext.GetOrganization(organizationId);
|
||||||
|
|
||||||
// If we're not an "admin", we don't need to check the ciphers
|
// If we're not an "admin" or if we're not a provider user we don't need to check the ciphers
|
||||||
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }))
|
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
|
||||||
{
|
{
|
||||||
// Are we a provider user? If so, we need to be sure we're not restricted
|
return false;
|
||||||
// Once the feature flag is removed, this check can be combined with the above
|
|
||||||
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
|
||||||
{
|
|
||||||
// Provider is restricted from editing ciphers, so we're not an "admin"
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider is unrestricted, so we're an "admin", don't return early
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not a provider or admin
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We know we're an "admin", now check the ciphers explicitly (in case admins are restricted)
|
// We know we're an "admin", now check the ciphers explicitly (in case admins are restricted)
|
||||||
@ -350,26 +382,10 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
var org = _currentContext.GetOrganization(organizationId);
|
var org = _currentContext.GetOrganization(organizationId);
|
||||||
|
|
||||||
// If we're not an "admin", we don't need to check the ciphers
|
// If we're not an "admin" or if we're a provider user we don't need to check the ciphers
|
||||||
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }))
|
if (org is not ({ Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or { Permissions.EditAnyCollection: true }) || await _currentContext.ProviderUserForOrgAsync(organizationId))
|
||||||
{
|
{
|
||||||
// Are we a provider user? If so, we need to be sure we're not restricted
|
return false;
|
||||||
// Once the feature flag is removed, this check can be combined with the above
|
|
||||||
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
|
||||||
{
|
|
||||||
// Provider is restricted from editing ciphers, so we're not an "admin"
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider is unrestricted, so we're an "admin", don't return early
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not a provider or admin
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user can edit all ciphers for the organization, just check they all belong to the org
|
// If the user can edit all ciphers for the organization, just check they all belong to the org
|
||||||
@ -462,10 +478,10 @@ public class CiphersController : Controller
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider users can edit all ciphers if RestrictProviderAccess is disabled
|
// Provider users cannot edit ciphers
|
||||||
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
||||||
{
|
{
|
||||||
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -485,10 +501,10 @@ public class CiphersController : Controller
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider users can only access organization ciphers if RestrictProviderAccess is disabled
|
// Provider users cannot access organization ciphers
|
||||||
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
||||||
{
|
{
|
||||||
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -508,10 +524,10 @@ public class CiphersController : Controller
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider users can only access all ciphers if RestrictProviderAccess is disabled
|
// Provider users cannot access ciphers
|
||||||
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
if (await _currentContext.ProviderUserForOrgAsync(organizationId))
|
||||||
{
|
{
|
||||||
return !_featureService.IsEnabled(FeatureFlagKeys.RestrictProviderAccess);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -690,6 +706,15 @@ public class CiphersController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
if (model.Cipher.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (model.Cipher.EncryptedFor != user.Id)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValidateClientVersionForFido2CredentialSupport(cipher);
|
ValidateClientVersionForFido2CredentialSupport(cipher);
|
||||||
|
|
||||||
var original = cipher.Clone();
|
var original = cipher.Clone();
|
||||||
@ -1051,6 +1076,18 @@ public class CiphersController : Controller
|
|||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: false);
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: false);
|
||||||
var ciphersDict = ciphers.ToDictionary(c => c.Id);
|
var ciphersDict = ciphers.ToDictionary(c => c.Id);
|
||||||
|
|
||||||
|
// Validate the model was encrypted for the posting user
|
||||||
|
foreach (var cipher in model.Ciphers)
|
||||||
|
{
|
||||||
|
if (cipher.EncryptedFor != null)
|
||||||
|
{
|
||||||
|
if (cipher.EncryptedFor != userId)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var shareCiphers = new List<(Cipher, DateTime?)>();
|
var shareCiphers = new List<(Cipher, DateTime?)>();
|
||||||
foreach (var cipher in model.Ciphers)
|
foreach (var cipher in model.Ciphers)
|
||||||
{
|
{
|
||||||
@ -1086,9 +1123,8 @@ public class CiphersController : Controller
|
|||||||
throw new BadRequestException(ModelState);
|
throw new BadRequestException(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Account Deprovisioning is enabled, we need to check if the user is claimed by any organization.
|
// Check if the user is claimed by any organization.
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
if (await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
|
||||||
&& await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Cannot purge accounts owned by an organization. Contact your organization administrator for additional details.");
|
throw new BadRequestException("Cannot purge accounts owned by an organization. Contact your organization administrator for additional details.");
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@ namespace Bit.Api.Vault.Models.Request;
|
|||||||
|
|
||||||
public class CipherRequestModel
|
public class CipherRequestModel
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Id of the user that encrypted the cipher. It should always represent a UserId.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? EncryptedFor { get; set; }
|
||||||
public CipherType Type { get; set; }
|
public CipherType Type { get; set; }
|
||||||
|
|
||||||
[StringLength(36)]
|
[StringLength(36)]
|
||||||
|
@ -63,6 +63,12 @@ public class FreshdeskController : Controller
|
|||||||
note += $"<li>Region: {_billingSettings.FreshDesk.Region}</li>";
|
note += $"<li>Region: {_billingSettings.FreshDesk.Region}</li>";
|
||||||
var customFields = new Dictionary<string, object>();
|
var customFields = new Dictionary<string, object>();
|
||||||
var user = await _userRepository.GetByEmailAsync(ticketContactEmail);
|
var user = await _userRepository.GetByEmailAsync(ticketContactEmail);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
note += $"<li>No user found: {ticketContactEmail}</li>";
|
||||||
|
await CreateNote(ticketId, note);
|
||||||
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var userLink = $"{_globalSettings.BaseServiceUri.Admin}/users/edit/{user.Id}";
|
var userLink = $"{_globalSettings.BaseServiceUri.Admin}/users/edit/{user.Id}";
|
||||||
@ -121,18 +127,7 @@ public class FreshdeskController : Controller
|
|||||||
Content = JsonContent.Create(updateBody),
|
Content = JsonContent.Create(updateBody),
|
||||||
};
|
};
|
||||||
await CallFreshdeskApiAsync(updateRequest);
|
await CallFreshdeskApiAsync(updateRequest);
|
||||||
|
await CreateNote(ticketId, note);
|
||||||
var noteBody = new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "body", $"<ul>{note}</ul>" },
|
|
||||||
{ "private", true }
|
|
||||||
};
|
|
||||||
var noteRequest = new HttpRequestMessage(HttpMethod.Post,
|
|
||||||
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId))
|
|
||||||
{
|
|
||||||
Content = JsonContent.Create(noteBody),
|
|
||||||
};
|
|
||||||
await CallFreshdeskApiAsync(noteRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResult();
|
return new OkResult();
|
||||||
@ -208,6 +203,21 @@ public class FreshdeskController : Controller
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateNote(string ticketId, string note)
|
||||||
|
{
|
||||||
|
var noteBody = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "body", $"<ul>{note}</ul>" },
|
||||||
|
{ "private", true }
|
||||||
|
};
|
||||||
|
var noteRequest = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId))
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(noteBody),
|
||||||
|
};
|
||||||
|
await CallFreshdeskApiAsync(noteRequest);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddAnswerNoteToTicketAsync(string note, string ticketId)
|
private async Task AddAnswerNoteToTicketAsync(string note, string ticketId)
|
||||||
{
|
{
|
||||||
// if there is no content, then we don't need to add a note
|
// if there is no content, then we don't need to add a note
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using Bit.Billing.Constants;
|
using Bit.Billing.Constants;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Entities;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Providers.Entities;
|
||||||
|
using Bit.Core.Billing.Providers.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
|
||||||
using Bit.Core.Billing.Services.Contracts;
|
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -25,8 +25,7 @@ public class UpcomingInvoiceHandler(
|
|||||||
IStripeEventService stripeEventService,
|
IStripeEventService stripeEventService,
|
||||||
IStripeEventUtilityService stripeEventUtilityService,
|
IStripeEventUtilityService stripeEventUtilityService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
IValidateSponsorshipCommand validateSponsorshipCommand,
|
IValidateSponsorshipCommand validateSponsorshipCommand)
|
||||||
IAutomaticTaxFactory automaticTaxFactory)
|
|
||||||
: IUpcomingInvoiceHandler
|
: IUpcomingInvoiceHandler
|
||||||
{
|
{
|
||||||
public async Task HandleAsync(Event parsedEvent)
|
public async Task HandleAsync(Event parsedEvent)
|
||||||
@ -46,6 +45,8 @@ public class UpcomingInvoiceHandler(
|
|||||||
|
|
||||||
var (organizationId, userId, providerId) = stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata);
|
var (organizationId, userId, providerId) = stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata);
|
||||||
|
|
||||||
|
var setNonUSBusinessUseToReverseCharge = featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
if (organizationId.HasValue)
|
if (organizationId.HasValue)
|
||||||
{
|
{
|
||||||
var organization = await organizationRepository.GetByIdAsync(organizationId.Value);
|
var organization = await organizationRepository.GetByIdAsync(organizationId.Value);
|
||||||
@ -55,7 +56,7 @@ public class UpcomingInvoiceHandler(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await TryEnableAutomaticTaxAsync(subscription);
|
await AlignOrganizationTaxConcernsAsync(organization, subscription, parsedEvent.Id, setNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
||||||
|
|
||||||
@ -100,7 +101,25 @@ public class UpcomingInvoiceHandler(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await TryEnableAutomaticTaxAsync(subscription);
|
if (!subscription.AutomaticTax.Enabled && subscription.Customer.HasRecognizedTaxLocation())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await stripeFacade.UpdateSubscription(subscription.Id,
|
||||||
|
new SubscriptionUpdateOptions
|
||||||
|
{
|
||||||
|
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(
|
||||||
|
exception,
|
||||||
|
"Failed to set user's ({UserID}) subscription to automatic tax while processing event with ID {EventID}",
|
||||||
|
user.Id,
|
||||||
|
parsedEvent.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (user.Premium)
|
if (user.Premium)
|
||||||
{
|
{
|
||||||
@ -116,7 +135,7 @@ public class UpcomingInvoiceHandler(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await TryEnableAutomaticTaxAsync(subscription);
|
await AlignProviderTaxConcernsAsync(provider, subscription, parsedEvent.Id, setNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
await SendUpcomingInvoiceEmailsAsync(new List<string> { provider.BillingEmail }, invoice);
|
await SendUpcomingInvoiceEmailsAsync(new List<string> { provider.BillingEmail }, invoice);
|
||||||
}
|
}
|
||||||
@ -139,50 +158,123 @@ public class UpcomingInvoiceHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TryEnableAutomaticTaxAsync(Subscription subscription)
|
private async Task AlignOrganizationTaxConcernsAsync(
|
||||||
|
Organization organization,
|
||||||
|
Subscription subscription,
|
||||||
|
string eventId,
|
||||||
|
bool setNonUSBusinessUseToReverseCharge)
|
||||||
{
|
{
|
||||||
if (featureService.IsEnabled(FeatureFlagKeys.PM19147_AutomaticTaxImprovements))
|
var nonUSBusinessUse =
|
||||||
{
|
organization.PlanType.GetProductTier() != ProductTierType.Families &&
|
||||||
var automaticTaxParameters = new AutomaticTaxFactoryParameters(subscription.Items.Select(x => x.Price.Id));
|
subscription.Customer.Address.Country != "US";
|
||||||
var automaticTaxStrategy = await automaticTaxFactory.CreateAsync(automaticTaxParameters);
|
|
||||||
var updateOptions = automaticTaxStrategy.GetUpdateOptions(subscription);
|
|
||||||
|
|
||||||
if (updateOptions == null)
|
bool setAutomaticTaxToEnabled;
|
||||||
|
|
||||||
|
if (setNonUSBusinessUseToReverseCharge)
|
||||||
|
{
|
||||||
|
if (nonUSBusinessUse && subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||||
{
|
{
|
||||||
return;
|
try
|
||||||
|
{
|
||||||
|
await stripeFacade.UpdateCustomer(subscription.CustomerId,
|
||||||
|
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(
|
||||||
|
exception,
|
||||||
|
"Failed to set organization's ({OrganizationID}) to reverse tax exemption while processing event with ID {EventID}",
|
||||||
|
organization.Id,
|
||||||
|
eventId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await stripeFacade.UpdateSubscription(subscription.Id, updateOptions);
|
setAutomaticTaxToEnabled = true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (subscription.AutomaticTax.Enabled ||
|
|
||||||
!subscription.Customer.HasBillingLocation() ||
|
|
||||||
await IsNonTaxableNonUSBusinessUseSubscription(subscription))
|
|
||||||
{
|
{
|
||||||
return;
|
setAutomaticTaxToEnabled =
|
||||||
|
subscription.Customer.HasRecognizedTaxLocation() &&
|
||||||
|
(subscription.Customer.Address.Country == "US" ||
|
||||||
|
(nonUSBusinessUse && subscription.Customer.TaxIds.Any()));
|
||||||
}
|
}
|
||||||
|
|
||||||
await stripeFacade.UpdateSubscription(subscription.Id,
|
if (!subscription.AutomaticTax.Enabled && setAutomaticTaxToEnabled)
|
||||||
new SubscriptionUpdateOptions
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
DefaultTaxRates = [],
|
await stripeFacade.UpdateSubscription(subscription.Id,
|
||||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
new SubscriptionUpdateOptions
|
||||||
});
|
{
|
||||||
|
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(
|
||||||
|
exception,
|
||||||
|
"Failed to set organization's ({OrganizationID}) subscription to automatic tax while processing event with ID {EventID}",
|
||||||
|
organization.Id,
|
||||||
|
eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
private async Task AlignProviderTaxConcernsAsync(
|
||||||
|
Provider provider,
|
||||||
|
Subscription subscription,
|
||||||
|
string eventId,
|
||||||
|
bool setNonUSBusinessUseToReverseCharge)
|
||||||
|
{
|
||||||
|
bool setAutomaticTaxToEnabled;
|
||||||
|
|
||||||
async Task<bool> IsNonTaxableNonUSBusinessUseSubscription(Subscription localSubscription)
|
if (setNonUSBusinessUseToReverseCharge)
|
||||||
{
|
{
|
||||||
var familyPriceIds = (await Task.WhenAll(
|
if (subscription.Customer.Address.Country != "US" && subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019),
|
{
|
||||||
pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually)))
|
try
|
||||||
.Select(plan => plan.PasswordManager.StripePlanId);
|
{
|
||||||
|
await stripeFacade.UpdateCustomer(subscription.CustomerId,
|
||||||
|
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(
|
||||||
|
exception,
|
||||||
|
"Failed to set provider's ({ProviderID}) to reverse tax exemption while processing event with ID {EventID}",
|
||||||
|
provider.Id,
|
||||||
|
eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return localSubscription.Customer.Address.Country != "US" &&
|
setAutomaticTaxToEnabled = true;
|
||||||
localSubscription.Metadata.ContainsKey(StripeConstants.MetadataKeys.OrganizationId) &&
|
}
|
||||||
!localSubscription.Items.Select(item => item.Price.Id).Intersect(familyPriceIds).Any() &&
|
else
|
||||||
!localSubscription.Customer.TaxIds.Any();
|
{
|
||||||
|
setAutomaticTaxToEnabled =
|
||||||
|
subscription.Customer.HasRecognizedTaxLocation() &&
|
||||||
|
(subscription.Customer.Address.Country == "US" ||
|
||||||
|
subscription.Customer.TaxIds.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subscription.AutomaticTax.Enabled && setAutomaticTaxToEnabled)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await stripeFacade.UpdateSubscription(subscription.Id,
|
||||||
|
new SubscriptionUpdateOptions
|
||||||
|
{
|
||||||
|
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(
|
||||||
|
exception,
|
||||||
|
"Failed to set provider's ({ProviderID}) subscription to automatic tax while processing event with ID {EventID}",
|
||||||
|
provider.Id,
|
||||||
|
eventId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,11 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the organization can claim domains, which unlocks additional enterprise features
|
||||||
|
/// </summary>
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If set to true, admins can initiate organization-issued sponsorships.
|
/// If set to true, admins can initiate organization-issued sponsorships.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -319,5 +324,7 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
|||||||
SmSeats = license.SmSeats;
|
SmSeats = license.SmSeats;
|
||||||
SmServiceAccounts = license.SmServiceAccounts;
|
SmServiceAccounts = license.SmServiceAccounts;
|
||||||
UseRiskInsights = license.UseRiskInsights;
|
UseRiskInsights = license.UseRiskInsights;
|
||||||
|
UseOrganizationDomains = license.UseOrganizationDomains;
|
||||||
|
UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ public class OrganizationAbility
|
|||||||
LimitItemDeletion = organization.LimitItemDeletion;
|
LimitItemDeletion = organization.LimitItemDeletion;
|
||||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||||
UseRiskInsights = organization.UseRiskInsights;
|
UseRiskInsights = organization.UseRiskInsights;
|
||||||
|
UseOrganizationDomains = organization.UseOrganizationDomains;
|
||||||
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,5 +47,6 @@ public class OrganizationAbility
|
|||||||
public bool LimitItemDeletion { get; set; }
|
public bool LimitItemDeletion { get; set; }
|
||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
public bool UseAdminSponsoredFamilies { get; set; }
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -59,5 +59,7 @@ public class OrganizationUserOrganizationDetails
|
|||||||
public bool LimitItemDeletion { get; set; }
|
public bool LimitItemDeletion { get; set; }
|
||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
public bool UseAdminSponsoredFamilies { get; set; }
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
|
public bool? IsAdminInitiated { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,7 @@ public class SelfHostedOrganizationDetails : Organization
|
|||||||
AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems,
|
AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems,
|
||||||
Status = Status,
|
Status = Status,
|
||||||
UseRiskInsights = UseRiskInsights,
|
UseRiskInsights = UseRiskInsights,
|
||||||
|
UseAdminSponsoredFamilies = UseAdminSponsoredFamilies,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ public class ProviderUserOrganizationDetails
|
|||||||
public bool LimitItemDeletion { get; set; }
|
public bool LimitItemDeletion { get; set; }
|
||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public bool UseOrganizationDomains { get; set; }
|
||||||
public bool UseAdminSponsoredFamilies { get; set; }
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
public ProviderType ProviderType { get; set; }
|
public ProviderType ProviderType { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ public class VerifyOrganizationDomainCommand(
|
|||||||
IDnsResolverService dnsResolverService,
|
IDnsResolverService dnsResolverService,
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
IFeatureService featureService,
|
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
ISavePolicyCommand savePolicyCommand,
|
ISavePolicyCommand savePolicyCommand,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
@ -125,11 +124,8 @@ public class VerifyOrganizationDomainCommand(
|
|||||||
|
|
||||||
private async Task DomainVerificationSideEffectsAsync(OrganizationDomain domain, IActingUser actingUser)
|
private async Task DomainVerificationSideEffectsAsync(OrganizationDomain domain, IActingUser actingUser)
|
||||||
{
|
{
|
||||||
if (featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser);
|
||||||
{
|
await SendVerifiedDomainUserEmailAsync(domain);
|
||||||
await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser);
|
|
||||||
await SendVerifiedDomainUserEmailAsync(domain);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) =>
|
private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) =>
|
||||||
|
@ -24,9 +24,7 @@ public class GetOrganizationUsersClaimedStatusQuery : IGetOrganizationUsersClaim
|
|||||||
// Users can only be claimed by an Organization that is enabled and can have organization domains
|
// Users can only be claimed by an Organization that is enabled and can have organization domains
|
||||||
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
|
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
|
||||||
|
|
||||||
// TODO: Replace "UseSso" with a new organization ability like "UseOrganizationDomains" (PM-11622).
|
if (organizationAbility is { Enabled: true, UseOrganizationDomains: true })
|
||||||
// Verified domains were tied to SSO, so we currently check the "UseSso" organization ability.
|
|
||||||
if (organizationAbility is { Enabled: true, UseSso: true })
|
|
||||||
{
|
{
|
||||||
// Get all organization users with claimed domains by the organization
|
// Get all organization users with claimed domains by the organization
|
||||||
var organizationUsersWithClaimedDomain = await _organizationUserRepository.GetManyByOrganizationWithClaimedDomainsAsync(organizationId);
|
var organizationUsersWithClaimedDomain = await _organizationUserRepository.GetManyByOrganizationWithClaimedDomainsAsync(organizationId);
|
||||||
|
@ -159,7 +159,7 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
throw new BadRequestException(RemoveAdminByCustomUserErrorMessage);
|
throw new BadRequestException(RemoveAdminByCustomUserErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null)
|
if (deletingUserId.HasValue && eventSystemUser == null)
|
||||||
{
|
{
|
||||||
var claimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgUser.OrganizationId, new[] { orgUser.Id });
|
var claimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgUser.OrganizationId, new[] { orgUser.Id });
|
||||||
if (claimedStatus.TryGetValue(orgUser.Id, out var isClaimed) && isClaimed)
|
if (claimedStatus.TryGetValue(orgUser.Id, out var isClaimed) && isClaimed)
|
||||||
@ -214,7 +214,7 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var claimedStatus = _featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null
|
var claimedStatus = deletingUserId.HasValue && eventSystemUser == null
|
||||||
? await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(organizationId, filteredUsers.Select(u => u.Id))
|
? await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(organizationId, filteredUsers.Select(u => u.Id))
|
||||||
: filteredUsers.ToDictionary(u => u.Id, u => false);
|
: filteredUsers.ToDictionary(u => u.Id, u => false);
|
||||||
var result = new List<(OrganizationUser OrganizationUser, string ErrorMessage)>();
|
var result = new List<(OrganizationUser OrganizationUser, string ErrorMessage)>();
|
||||||
|
@ -104,7 +104,8 @@ public class CloudOrganizationSignUpCommand(
|
|||||||
RevisionDate = DateTime.UtcNow,
|
RevisionDate = DateTime.UtcNow,
|
||||||
Status = OrganizationStatusType.Created,
|
Status = OrganizationStatusType.Created,
|
||||||
UsePasswordManager = true,
|
UsePasswordManager = true,
|
||||||
UseSecretsManager = signup.UseSecretsManager
|
UseSecretsManager = signup.UseSecretsManager,
|
||||||
|
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (signup.UseSecretsManager)
|
if (signup.UseSecretsManager)
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.Billing.Pricing;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Tools.Enums;
|
||||||
|
using Bit.Core.Tools.Models.Business;
|
||||||
|
using Bit.Core.Tools.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
|
public record ProviderClientOrganizationSignUpResponse(
|
||||||
|
Organization Organization,
|
||||||
|
Collection DefaultCollection);
|
||||||
|
|
||||||
|
public interface IProviderClientOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sign up a new client organization for a provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="signup">The signup information.</param>
|
||||||
|
/// <returns>A tuple containing the new organization and its default collection.</returns>
|
||||||
|
Task<ProviderClientOrganizationSignUpResponse> SignUpClientOrganizationAsync(OrganizationSignup signup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
public const string PlanNullErrorMessage = "Password Manager Plan was null.";
|
||||||
|
public const string PlanDisabledErrorMessage = "Password Manager Plan is disabled.";
|
||||||
|
public const string AdditionalSeatsNegativeErrorMessage = "You can't subtract Password Manager seats!";
|
||||||
|
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly IPricingClient _pricingClient;
|
||||||
|
private readonly IReferenceEventService _referenceEventService;
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
|
|
||||||
|
public ProviderClientOrganizationSignUpCommand(
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
IPricingClient pricingClient,
|
||||||
|
IReferenceEventService referenceEventService,
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
|
IApplicationCacheService applicationCacheService,
|
||||||
|
ICollectionRepository collectionRepository)
|
||||||
|
{
|
||||||
|
_currentContext = currentContext;
|
||||||
|
_pricingClient = pricingClient;
|
||||||
|
_referenceEventService = referenceEventService;
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
|
_applicationCacheService = applicationCacheService;
|
||||||
|
_collectionRepository = collectionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProviderClientOrganizationSignUpResponse> SignUpClientOrganizationAsync(OrganizationSignup signup)
|
||||||
|
{
|
||||||
|
var plan = await _pricingClient.GetPlanOrThrow(signup.Plan);
|
||||||
|
|
||||||
|
ValidatePlan(plan, signup.AdditionalSeats);
|
||||||
|
|
||||||
|
var organization = new Organization
|
||||||
|
{
|
||||||
|
// Pre-generate the org id so that we can save it with the Stripe subscription.
|
||||||
|
Id = CoreHelpers.GenerateComb(),
|
||||||
|
Name = signup.Name,
|
||||||
|
BillingEmail = signup.BillingEmail,
|
||||||
|
PlanType = plan!.Type,
|
||||||
|
Seats = signup.AdditionalSeats,
|
||||||
|
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||||
|
MaxStorageGb = 1,
|
||||||
|
UsePolicies = plan.HasPolicies,
|
||||||
|
UseSso = plan.HasSso,
|
||||||
|
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||||
|
UseGroups = plan.HasGroups,
|
||||||
|
UseEvents = plan.HasEvents,
|
||||||
|
UseDirectory = plan.HasDirectory,
|
||||||
|
UseTotp = plan.HasTotp,
|
||||||
|
Use2fa = plan.Has2fa,
|
||||||
|
UseApi = plan.HasApi,
|
||||||
|
UseResetPassword = plan.HasResetPassword,
|
||||||
|
SelfHost = plan.HasSelfHost,
|
||||||
|
UsersGetPremium = plan.UsersGetPremium,
|
||||||
|
UseCustomPermissions = plan.HasCustomPermissions,
|
||||||
|
UseScim = plan.HasScim,
|
||||||
|
Plan = plan.Name,
|
||||||
|
Gateway = GatewayType.Stripe,
|
||||||
|
ReferenceData = signup.Owner.ReferenceData,
|
||||||
|
Enabled = true,
|
||||||
|
LicenseKey = CoreHelpers.SecureRandomString(20),
|
||||||
|
PublicKey = signup.PublicKey,
|
||||||
|
PrivateKey = signup.PrivateKey,
|
||||||
|
CreationDate = DateTime.UtcNow,
|
||||||
|
RevisionDate = DateTime.UtcNow,
|
||||||
|
Status = OrganizationStatusType.Created,
|
||||||
|
UsePasswordManager = true,
|
||||||
|
// Secrets Manager not available for purchase with Consolidated Billing.
|
||||||
|
UseSecretsManager = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
var returnValue = await SignUpAsync(organization, signup.CollectionName);
|
||||||
|
|
||||||
|
await _referenceEventService.RaiseEventAsync(
|
||||||
|
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
||||||
|
{
|
||||||
|
PlanName = plan.Name,
|
||||||
|
PlanType = plan.Type,
|
||||||
|
Seats = returnValue.Organization.Seats,
|
||||||
|
SignupInitiationPath = signup.InitiationPath,
|
||||||
|
Storage = returnValue.Organization.MaxStorageGb,
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidatePlan(Plan plan, int additionalSeats)
|
||||||
|
{
|
||||||
|
if (plan is null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(PlanNullErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.Disabled)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(PlanDisabledErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalSeats < 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(AdditionalSeatsNegativeErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private helper method to create a new organization.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<ProviderClientOrganizationSignUpResponse> SignUpAsync(
|
||||||
|
Organization organization, string collectionName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _organizationRepository.CreateAsync(organization);
|
||||||
|
await _organizationApiKeyRepository.CreateAsync(new OrganizationApiKey
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
ApiKey = CoreHelpers.SecureRandomString(30),
|
||||||
|
Type = OrganizationApiKeyType.Default,
|
||||||
|
RevisionDate = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||||
|
|
||||||
|
Collection defaultCollection = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(collectionName))
|
||||||
|
{
|
||||||
|
defaultCollection = new Collection
|
||||||
|
{
|
||||||
|
Name = collectionName,
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
CreationDate = organization.CreationDate,
|
||||||
|
RevisionDate = organization.CreationDate
|
||||||
|
};
|
||||||
|
|
||||||
|
await _collectionRepository.CreateAsync(defaultCollection, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProviderClientOrganizationSignUpResponse(organization, defaultCollection);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (organization.Id != default)
|
||||||
|
{
|
||||||
|
await _organizationRepository.DeleteAsync(organization);
|
||||||
|
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,16 +61,9 @@ public class SingleOrgPolicyValidator : IPolicyValidator
|
|||||||
{
|
{
|
||||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||||
{
|
{
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
var currentUser = _currentContext.UserId ?? Guid.Empty;
|
||||||
{
|
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
|
||||||
var currentUser = _currentContext.UserId ?? Guid.Empty;
|
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
|
||||||
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
|
|
||||||
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await RemoveNonCompliantUsersAsync(policyUpdate.OrganizationId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,42 +109,6 @@ public class SingleOrgPolicyValidator : IPolicyValidator
|
|||||||
_mailService.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), x.Email)));
|
_mailService.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), x.Email)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemoveNonCompliantUsersAsync(Guid organizationId)
|
|
||||||
{
|
|
||||||
// Remove non-compliant users
|
|
||||||
var savingUserId = _currentContext.UserId;
|
|
||||||
// Note: must get OrganizationUserUserDetails so that Email is always populated from the User object
|
|
||||||
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId);
|
|
||||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
|
||||||
if (org == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException(OrganizationNotFoundErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
var removableOrgUsers = orgUsers.Where(ou =>
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited &&
|
|
||||||
ou.Status != OrganizationUserStatusType.Revoked &&
|
|
||||||
ou.Type != OrganizationUserType.Owner &&
|
|
||||||
ou.Type != OrganizationUserType.Admin &&
|
|
||||||
ou.UserId != savingUserId
|
|
||||||
).ToList();
|
|
||||||
|
|
||||||
var userOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(
|
|
||||||
removableOrgUsers.Select(ou => ou.UserId!.Value));
|
|
||||||
foreach (var orgUser in removableOrgUsers)
|
|
||||||
{
|
|
||||||
if (userOrgs.Any(ou => ou.UserId == orgUser.UserId
|
|
||||||
&& ou.OrganizationId != org.Id
|
|
||||||
&& ou.Status != OrganizationUserStatusType.Invited))
|
|
||||||
{
|
|
||||||
await _removeOrganizationUserCommand.RemoveUserAsync(organizationId, orgUser.Id, savingUserId);
|
|
||||||
|
|
||||||
await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(
|
|
||||||
org.DisplayName(), orgUser.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (policyUpdate is not { Enabled: true })
|
if (policyUpdate is not { Enabled: true })
|
||||||
@ -165,8 +122,7 @@ public class SingleOrgPolicyValidator : IPolicyValidator
|
|||||||
return validateDecryptionErrorMessage;
|
return validateDecryptionErrorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
if (await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId))
|
||||||
&& await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId))
|
|
||||||
{
|
{
|
||||||
return ClaimedDomainSingleOrganizationRequiredErrorMessage;
|
return ClaimedDomainSingleOrganizationRequiredErrorMessage;
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
|
||||||
private readonly IFeatureService _featureService;
|
|
||||||
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
||||||
|
|
||||||
public const string NonCompliantMembersWillLoseAccessMessage = "Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.";
|
public const string NonCompliantMembersWillLoseAccessMessage = "Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.";
|
||||||
@ -38,8 +36,6 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
|
||||||
IFeatureService featureService,
|
|
||||||
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand)
|
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand)
|
||||||
{
|
{
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -47,8 +43,6 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
|
||||||
_featureService = featureService;
|
|
||||||
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,16 +50,9 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
{
|
{
|
||||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||||
{
|
{
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
var currentUser = _currentContext.UserId ?? Guid.Empty;
|
||||||
{
|
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
|
||||||
var currentUser = _currentContext.UserId ?? Guid.Empty;
|
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
|
||||||
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
|
|
||||||
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await RemoveNonCompliantUsersAsync(policyUpdate.OrganizationId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,40 +108,6 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
_mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), x.Email)));
|
_mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), x.Email)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemoveNonCompliantUsersAsync(Guid organizationId)
|
|
||||||
{
|
|
||||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
|
||||||
var savingUserId = _currentContext.UserId;
|
|
||||||
|
|
||||||
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId);
|
|
||||||
var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers);
|
|
||||||
var removableOrgUsers = orgUsers.Where(ou =>
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked &&
|
|
||||||
ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin &&
|
|
||||||
ou.UserId != savingUserId);
|
|
||||||
|
|
||||||
// Reorder by HasMasterPassword to prioritize checking users without a master if they have 2FA enabled
|
|
||||||
foreach (var orgUser in removableOrgUsers.OrderBy(ou => ou.HasMasterPassword))
|
|
||||||
{
|
|
||||||
var userTwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == orgUser.Id)
|
|
||||||
.twoFactorIsEnabled;
|
|
||||||
if (!userTwoFactorEnabled)
|
|
||||||
{
|
|
||||||
if (!orgUser.HasMasterPassword)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(
|
|
||||||
"Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _removeOrganizationUserCommand.RemoveUserAsync(organizationId, orgUser.Id,
|
|
||||||
savingUserId);
|
|
||||||
|
|
||||||
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
|
|
||||||
org!.DisplayName(), orgUser.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MembersWithNoMasterPasswordWillLoseAccess(
|
private static bool MembersWithNoMasterPasswordWillLoseAccess(
|
||||||
IEnumerable<OrganizationUserUserDetails> orgUserDetails,
|
IEnumerable<OrganizationUserUserDetails> orgUserDetails,
|
||||||
IEnumerable<(OrganizationUserUserDetails user, bool isTwoFactorEnabled)> organizationUsersTwoFactorEnabled) =>
|
IEnumerable<(OrganizationUserUserDetails user, bool isTwoFactorEnabled)> organizationUsersTwoFactorEnabled) =>
|
||||||
|
@ -11,8 +11,6 @@ namespace Bit.Core.Services;
|
|||||||
|
|
||||||
public interface IOrganizationService
|
public interface IOrganizationService
|
||||||
{
|
{
|
||||||
Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, PaymentMethodType paymentMethodType,
|
|
||||||
TaxInfo taxInfo);
|
|
||||||
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
|
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
|
||||||
Task ReinstateSubscriptionAsync(Guid organizationId);
|
Task ReinstateSubscriptionAsync(Guid organizationId);
|
||||||
Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb);
|
Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb);
|
||||||
@ -20,9 +18,6 @@ public interface IOrganizationService
|
|||||||
Task AutoAddSeatsAsync(Organization organization, int seatsToAdd);
|
Task AutoAddSeatsAsync(Organization organization, int seatsToAdd);
|
||||||
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
||||||
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
||||||
#nullable enable
|
|
||||||
Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup);
|
|
||||||
#nullable disable
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new organization on a self-hosted instance
|
/// Create a new organization on a self-hosted instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -93,16 +93,8 @@ public class OrganizationDomainService : IOrganizationDomainService
|
|||||||
//Send email to administrators
|
//Send email to administrators
|
||||||
if (adminEmails.Count > 0)
|
if (adminEmails.Count > 0)
|
||||||
{
|
{
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
await _mailService.SendUnclaimedOrganizationDomainEmailAsync(adminEmails,
|
||||||
{
|
domain.OrganizationId.ToString(), domain.DomainName);
|
||||||
await _mailService.SendUnclaimedOrganizationDomainEmailAsync(adminEmails,
|
|
||||||
domain.OrganizationId.ToString(), domain.DomainName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _mailService.SendUnverifiedOrganizationDomainEmailAsync(adminEmails,
|
|
||||||
domain.OrganizationId.ToString(), domain.DomainName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName);
|
_logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName);
|
||||||
|
@ -144,27 +144,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
|
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
|
||||||
PaymentMethodType paymentMethodType, TaxInfo taxInfo)
|
|
||||||
{
|
|
||||||
var organization = await GetOrgById(organizationId);
|
|
||||||
if (organization == null)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _paymentService.SaveTaxInfoAsync(organization, taxInfo);
|
|
||||||
var updated = await _paymentService.UpdatePaymentMethodAsync(
|
|
||||||
organization,
|
|
||||||
paymentMethodType,
|
|
||||||
paymentToken,
|
|
||||||
taxInfo);
|
|
||||||
if (updated)
|
|
||||||
{
|
|
||||||
await ReplaceAndUpdateCacheAsync(organization);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null)
|
public async Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null)
|
||||||
{
|
{
|
||||||
var organization = await GetOrgById(organizationId);
|
var organization = await GetOrgById(organizationId);
|
||||||
@ -431,65 +410,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup)
|
|
||||||
{
|
|
||||||
var plan = await _pricingClient.GetPlanOrThrow(signup.Plan);
|
|
||||||
|
|
||||||
ValidatePlan(plan, signup.AdditionalSeats, "Password Manager");
|
|
||||||
|
|
||||||
var organization = new Organization
|
|
||||||
{
|
|
||||||
// Pre-generate the org id so that we can save it with the Stripe subscription.
|
|
||||||
Id = CoreHelpers.GenerateComb(),
|
|
||||||
Name = signup.Name,
|
|
||||||
BillingEmail = signup.BillingEmail,
|
|
||||||
PlanType = plan!.Type,
|
|
||||||
Seats = signup.AdditionalSeats,
|
|
||||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
|
||||||
MaxStorageGb = 1,
|
|
||||||
UsePolicies = plan.HasPolicies,
|
|
||||||
UseSso = plan.HasSso,
|
|
||||||
UseGroups = plan.HasGroups,
|
|
||||||
UseEvents = plan.HasEvents,
|
|
||||||
UseDirectory = plan.HasDirectory,
|
|
||||||
UseTotp = plan.HasTotp,
|
|
||||||
Use2fa = plan.Has2fa,
|
|
||||||
UseApi = plan.HasApi,
|
|
||||||
UseResetPassword = plan.HasResetPassword,
|
|
||||||
SelfHost = plan.HasSelfHost,
|
|
||||||
UsersGetPremium = plan.UsersGetPremium,
|
|
||||||
UseCustomPermissions = plan.HasCustomPermissions,
|
|
||||||
UseScim = plan.HasScim,
|
|
||||||
Plan = plan.Name,
|
|
||||||
Gateway = GatewayType.Stripe,
|
|
||||||
ReferenceData = signup.Owner.ReferenceData,
|
|
||||||
Enabled = true,
|
|
||||||
LicenseKey = CoreHelpers.SecureRandomString(20),
|
|
||||||
PublicKey = signup.PublicKey,
|
|
||||||
PrivateKey = signup.PrivateKey,
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Status = OrganizationStatusType.Created,
|
|
||||||
UsePasswordManager = true,
|
|
||||||
// Secrets Manager not available for purchase with Consolidated Billing.
|
|
||||||
UseSecretsManager = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
var returnValue = await SignUpAsync(organization, default, signup.OwnerKey, signup.CollectionName, false);
|
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Seats = returnValue.Item1.Seats,
|
|
||||||
SignupInitiationPath = signup.InitiationPath,
|
|
||||||
Storage = returnValue.Item1.MaxStorageGb,
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||||
{
|
{
|
||||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||||
@ -570,6 +490,8 @@ public class OrganizationService : IOrganizationService
|
|||||||
SmSeats = license.SmSeats,
|
SmSeats = license.SmSeats,
|
||||||
SmServiceAccounts = license.SmServiceAccounts,
|
SmServiceAccounts = license.SmServiceAccounts,
|
||||||
UseRiskInsights = license.UseRiskInsights,
|
UseRiskInsights = license.UseRiskInsights,
|
||||||
|
UseOrganizationDomains = license.UseOrganizationDomains,
|
||||||
|
UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies,
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
|
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
|
||||||
|
@ -2,9 +2,24 @@
|
|||||||
|
|
||||||
public enum EmergencyAccessStatusType : byte
|
public enum EmergencyAccessStatusType : byte
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The user has been invited to be an emergency contact.
|
||||||
|
/// </summary>
|
||||||
Invited = 0,
|
Invited = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// The invited user, "grantee", has accepted the request to be an emergency contact.
|
||||||
|
/// </summary>
|
||||||
Accepted = 1,
|
Accepted = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// The inviting user, "grantor", has approved the grantee's acceptance.
|
||||||
|
/// </summary>
|
||||||
Confirmed = 2,
|
Confirmed = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// The grantee has initiated the recovery process.
|
||||||
|
/// </summary>
|
||||||
RecoveryInitiated = 3,
|
RecoveryInitiated = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// The grantee has excercised their emergency access.
|
||||||
|
/// </summary>
|
||||||
RecoveryApproved = 4,
|
RecoveryApproved = 4,
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Core.Auth.Entities;
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Vault.Models.Data;
|
using Bit.Core.Vault.Models.Data;
|
||||||
|
|
||||||
@ -20,6 +21,15 @@ public interface IEmergencyAccessService
|
|||||||
Task InitiateAsync(Guid id, User initiatingUser);
|
Task InitiateAsync(Guid id, User initiatingUser);
|
||||||
Task ApproveAsync(Guid id, User approvingUser);
|
Task ApproveAsync(Guid id, User approvingUser);
|
||||||
Task RejectAsync(Guid id, User rejectingUser);
|
Task RejectAsync(Guid id, User rejectingUser);
|
||||||
|
/// <summary>
|
||||||
|
/// This request is made by the Grantee user to fetch the policies <see cref="Policy"/> for the Grantor User.
|
||||||
|
/// The Grantor User has to be the owner of the organization. <see cref="OrganizationUserType"/>
|
||||||
|
/// If the Grantor user has OrganizationUserType.Owner then the policies for the _Grantor_ user
|
||||||
|
/// are returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">EmergencyAccess.Id being acted on</param>
|
||||||
|
/// <param name="requestingUser">User making the request, this is the Grantee</param>
|
||||||
|
/// <returns>null if the GrantorUser is not an organization owner; A list of policies otherwise.</returns>
|
||||||
Task<ICollection<Policy>> GetPoliciesAsync(Guid id, User requestingUser);
|
Task<ICollection<Policy>> GetPoliciesAsync(Guid id, User requestingUser);
|
||||||
Task<(EmergencyAccess, User)> TakeoverAsync(Guid id, User initiatingUser);
|
Task<(EmergencyAccess, User)> TakeoverAsync(Guid id, User initiatingUser);
|
||||||
Task PasswordAsync(Guid id, User user, string newMasterPasswordHash, string key);
|
Task PasswordAsync(Guid id, User user, string newMasterPasswordHash, string key);
|
||||||
|
@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@ -16,7 +15,6 @@ using Bit.Core.Tokens;
|
|||||||
using Bit.Core.Vault.Models.Data;
|
using Bit.Core.Vault.Models.Data;
|
||||||
using Bit.Core.Vault.Repositories;
|
using Bit.Core.Vault.Repositories;
|
||||||
using Bit.Core.Vault.Services;
|
using Bit.Core.Vault.Services;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Services;
|
namespace Bit.Core.Auth.Services;
|
||||||
|
|
||||||
@ -31,8 +29,6 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IPasswordHasher<User> _passwordHasher;
|
|
||||||
private readonly IOrganizationService _organizationService;
|
|
||||||
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _dataProtectorTokenizer;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
|
|
||||||
@ -45,9 +41,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
ICipherService cipherService,
|
ICipherService cipherService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IPasswordHasher<User> passwordHasher,
|
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IOrganizationService organizationService,
|
|
||||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
|
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer,
|
||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
|
||||||
{
|
{
|
||||||
@ -59,9 +53,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
_cipherService = cipherService;
|
_cipherService = cipherService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_passwordHasher = passwordHasher;
|
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_organizationService = organizationService;
|
|
||||||
_dataProtectorTokenizer = dataProtectorTokenizer;
|
_dataProtectorTokenizer = dataProtectorTokenizer;
|
||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
}
|
}
|
||||||
@ -126,7 +118,12 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
throw new BadRequestException("Emergency Access not valid.");
|
throw new BadRequestException("Emergency Access not valid.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_dataProtectorTokenizer.TryUnprotect(token, out var data) && data.IsValid(emergencyAccessId, user.Email))
|
if (!_dataProtectorTokenizer.TryUnprotect(token, out var data))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.IsValid(emergencyAccessId, user.Email))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Invalid token.");
|
throw new BadRequestException("Invalid token.");
|
||||||
}
|
}
|
||||||
@ -140,6 +137,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
throw new BadRequestException("Invitation already accepted.");
|
throw new BadRequestException("Invitation already accepted.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO PM-21687
|
||||||
|
// Might not be reachable since the Tokenable.IsValid() does an email comparison
|
||||||
if (string.IsNullOrWhiteSpace(emergencyAccess.Email) ||
|
if (string.IsNullOrWhiteSpace(emergencyAccess.Email) ||
|
||||||
!emergencyAccess.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
!emergencyAccess.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -163,6 +162,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
public async Task DeleteAsync(Guid emergencyAccessId, Guid grantorId)
|
public async Task DeleteAsync(Guid emergencyAccessId, Guid grantorId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||||
|
// TODO PM-19438/PM-21687
|
||||||
|
// Not sure why the GrantorId and the GranteeId are supposed to be the same?
|
||||||
if (emergencyAccess == null || (emergencyAccess.GrantorId != grantorId && emergencyAccess.GranteeId != grantorId))
|
if (emergencyAccess == null || (emergencyAccess.GrantorId != grantorId && emergencyAccess.GranteeId != grantorId))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Emergency Access not valid.");
|
throw new BadRequestException("Emergency Access not valid.");
|
||||||
@ -171,9 +172,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
|
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAcccessId, string key, Guid confirmingUserId)
|
public async Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid confirmingUserId)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAcccessId);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||||
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
|
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
|
||||||
emergencyAccess.GrantorId != confirmingUserId)
|
emergencyAccess.GrantorId != confirmingUserId)
|
||||||
{
|
{
|
||||||
@ -224,7 +225,6 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
public async Task InitiateAsync(Guid id, User initiatingUser)
|
public async Task InitiateAsync(Guid id, User initiatingUser)
|
||||||
{
|
{
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
|
||||||
|
|
||||||
if (emergencyAccess == null || emergencyAccess.GranteeId != initiatingUser.Id ||
|
if (emergencyAccess == null || emergencyAccess.GranteeId != initiatingUser.Id ||
|
||||||
emergencyAccess.Status != EmergencyAccessStatusType.Confirmed)
|
emergencyAccess.Status != EmergencyAccessStatusType.Confirmed)
|
||||||
{
|
{
|
||||||
@ -285,6 +285,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
|
|
||||||
public async Task<ICollection<Policy>> GetPoliciesAsync(Guid id, User requestingUser)
|
public async Task<ICollection<Policy>> GetPoliciesAsync(Guid id, User requestingUser)
|
||||||
{
|
{
|
||||||
|
// TODO PM-21687
|
||||||
|
// Should we look up policies here or just verify the EmergencyAccess is correct
|
||||||
|
// and handle policy logic else where? Should this be a query/Command?
|
||||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
|
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(id);
|
||||||
|
|
||||||
if (!IsValidRequest(emergencyAccess, requestingUser, EmergencyAccessType.Takeover))
|
if (!IsValidRequest(emergencyAccess, requestingUser, EmergencyAccessType.Takeover))
|
||||||
@ -295,7 +298,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
|
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
|
||||||
|
|
||||||
var grantorOrganizations = await _organizationUserRepository.GetManyByUserAsync(grantor.Id);
|
var grantorOrganizations = await _organizationUserRepository.GetManyByUserAsync(grantor.Id);
|
||||||
var isOrganizationOwner = grantorOrganizations.Any<OrganizationUser>(organization => organization.Type == OrganizationUserType.Owner);
|
var isOrganizationOwner = grantorOrganizations
|
||||||
|
.Any(organization => organization.Type == OrganizationUserType.Owner);
|
||||||
|
|
||||||
var policies = isOrganizationOwner ? await _policyRepository.GetManyByUserIdAsync(grantor.Id) : null;
|
var policies = isOrganizationOwner ? await _policyRepository.GetManyByUserIdAsync(grantor.Id) : null;
|
||||||
|
|
||||||
return policies;
|
return policies;
|
||||||
@ -311,7 +316,8 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
|
var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId);
|
||||||
|
// TODO PM-21687
|
||||||
|
// Redundant check of the EmergencyAccessType -> checked in IsValidRequest() ln 308
|
||||||
if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector)
|
if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot takeover an account that is using Key Connector.");
|
throw new BadRequestException("You cannot takeover an account that is using Key Connector.");
|
||||||
@ -336,7 +342,9 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
grantor.LastPasswordChangeDate = grantor.RevisionDate;
|
grantor.LastPasswordChangeDate = grantor.RevisionDate;
|
||||||
grantor.Key = key;
|
grantor.Key = key;
|
||||||
// Disable TwoFactor providers since they will otherwise block logins
|
// Disable TwoFactor providers since they will otherwise block logins
|
||||||
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
|
grantor.SetTwoFactorProviders([]);
|
||||||
|
// Disable New Device Verification since it will otherwise block logins
|
||||||
|
grantor.VerifyDevices = false;
|
||||||
await _userRepository.ReplaceAsync(grantor);
|
await _userRepository.ReplaceAsync(grantor);
|
||||||
|
|
||||||
// Remove grantor from all organizations unless Owner
|
// Remove grantor from all organizations unless Owner
|
||||||
@ -421,12 +429,22 @@ public class EmergencyAccessService : IEmergencyAccessService
|
|||||||
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
|
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string NameOrEmail(User user)
|
private static string NameOrEmail(User user)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(user.Name) ? user.Email : user.Name;
|
return string.IsNullOrWhiteSpace(user.Name) ? user.Email : user.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidRequest(EmergencyAccess availableAccess, User requestingUser, EmergencyAccessType requestedAccessType)
|
|
||||||
|
/*
|
||||||
|
* Checks if EmergencyAccess Object is null
|
||||||
|
* Checks the requesting user is the same as the granteeUser (So we are checking for proper grantee action)
|
||||||
|
* Status _must_ equal RecoveryApproved (This means the grantor has invited, the grantee has accepted, and the grantor has approved so the shared key exists but hasn't been exercised yet)
|
||||||
|
* request type must equal the type of access requested (View or Takeover)
|
||||||
|
*/
|
||||||
|
private static bool IsValidRequest(
|
||||||
|
EmergencyAccess availableAccess,
|
||||||
|
User requestingUser,
|
||||||
|
EmergencyAccessType requestedAccessType)
|
||||||
{
|
{
|
||||||
return availableAccess != null &&
|
return availableAccess != null &&
|
||||||
availableAccess.GranteeId == requestingUser.Id &&
|
availableAccess.GranteeId == requestingUser.Id &&
|
||||||
|
@ -108,6 +108,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
var result = await _userService.CreateUserAsync(user, masterPasswordHash);
|
var result = await _userService.CreateUserAsync(user, masterPasswordHash);
|
||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
|
var sentWelcomeEmail = false;
|
||||||
if (!string.IsNullOrEmpty(user.ReferenceData))
|
if (!string.IsNullOrEmpty(user.ReferenceData))
|
||||||
{
|
{
|
||||||
var referenceData = JsonConvert.DeserializeObject<Dictionary<string, object>>(user.ReferenceData);
|
var referenceData = JsonConvert.DeserializeObject<Dictionary<string, object>>(user.ReferenceData);
|
||||||
@ -115,6 +116,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
{
|
{
|
||||||
var initiationPath = value.ToString();
|
var initiationPath = value.ToString();
|
||||||
await SendAppropriateWelcomeEmailAsync(user, initiationPath);
|
await SendAppropriateWelcomeEmailAsync(user, initiationPath);
|
||||||
|
sentWelcomeEmail = true;
|
||||||
if (!string.IsNullOrEmpty(initiationPath))
|
if (!string.IsNullOrEmpty(initiationPath))
|
||||||
{
|
{
|
||||||
await _referenceEventService.RaiseEventAsync(
|
await _referenceEventService.RaiseEventAsync(
|
||||||
@ -128,6 +130,11 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sentWelcomeEmail)
|
||||||
|
{
|
||||||
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user