mirror of
https://github.com/bitwarden/server.git
synced 2025-05-19 18:44:32 -05:00
[PM-12491] Create Organization disable command (#5348)
* Add command interface and implementation for disabling organizations * Register organization disable command for dependency injection * Add unit tests for OrganizationDisableCommand * Refactor subscription handlers to use IOrganizationDisableCommand for disabling organizations * Remove DisableAsync method from IOrganizationService and its implementation in OrganizationService * Remove IOrganizationService dependency from SubscriptionDeletedHandler * Remove commented TODO for sending email to owners in OrganizationDisableCommand
This commit is contained in:
parent
0f10ca52b4
commit
d15c1faa74
@ -1,4 +1,5 @@
|
||||
using Bit.Billing.Constants;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Services;
|
||||
using Event = Stripe.Event;
|
||||
namespace Bit.Billing.Services.Implementations;
|
||||
@ -6,20 +7,20 @@ namespace Bit.Billing.Services.Implementations;
|
||||
public class SubscriptionDeletedHandler : ISubscriptionDeletedHandler
|
||||
{
|
||||
private readonly IStripeEventService _stripeEventService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
||||
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
||||
|
||||
public SubscriptionDeletedHandler(
|
||||
IStripeEventService stripeEventService,
|
||||
IOrganizationService organizationService,
|
||||
IUserService userService,
|
||||
IStripeEventUtilityService stripeEventUtilityService)
|
||||
IStripeEventUtilityService stripeEventUtilityService,
|
||||
IOrganizationDisableCommand organizationDisableCommand)
|
||||
{
|
||||
_stripeEventService = stripeEventService;
|
||||
_organizationService = organizationService;
|
||||
_userService = userService;
|
||||
_stripeEventUtilityService = stripeEventUtilityService;
|
||||
_organizationDisableCommand = organizationDisableCommand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -44,7 +45,7 @@ public class SubscriptionDeletedHandler : ISubscriptionDeletedHandler
|
||||
subscription.CancellationDetails.Comment != providerMigrationCancellationComment &&
|
||||
!subscription.CancellationDetails.Comment.Contains(addedToProviderCancellationComment))
|
||||
{
|
||||
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
||||
await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
||||
}
|
||||
else if (userId.HasValue)
|
||||
{
|
||||
|
@ -26,6 +26,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
||||
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
||||
|
||||
public SubscriptionUpdatedHandler(
|
||||
IStripeEventService stripeEventService,
|
||||
@ -38,7 +39,8 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
IOrganizationRepository organizationRepository,
|
||||
ISchedulerFactory schedulerFactory,
|
||||
IFeatureService featureService,
|
||||
IOrganizationEnableCommand organizationEnableCommand)
|
||||
IOrganizationEnableCommand organizationEnableCommand,
|
||||
IOrganizationDisableCommand organizationDisableCommand)
|
||||
{
|
||||
_stripeEventService = stripeEventService;
|
||||
_stripeEventUtilityService = stripeEventUtilityService;
|
||||
@ -51,6 +53,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
_schedulerFactory = schedulerFactory;
|
||||
_featureService = featureService;
|
||||
_organizationEnableCommand = organizationEnableCommand;
|
||||
_organizationDisableCommand = organizationDisableCommand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -67,7 +70,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
case StripeSubscriptionStatus.Unpaid or StripeSubscriptionStatus.IncompleteExpired
|
||||
when organizationId.HasValue:
|
||||
{
|
||||
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
||||
await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
||||
if (subscription.Status == StripeSubscriptionStatus.Unpaid &&
|
||||
subscription.LatestInvoice is { BillingReason: "subscription_cycle" or "subscription_create" })
|
||||
{
|
||||
|
@ -0,0 +1,14 @@
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for disabling organizations.
|
||||
/// </summary>
|
||||
public interface IOrganizationDisableCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Disables an organization with an optional expiration date.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization to disable.</param>
|
||||
/// <param name="expirationDate">Optional date when the disable status should expire.</param>
|
||||
Task DisableAsync(Guid organizationId, DateTime? expirationDate);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
public class OrganizationDisableCommand : IOrganizationDisableCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
|
||||
public OrganizationDisableCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IApplicationCacheService applicationCacheService)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
}
|
||||
|
||||
public async Task DisableAsync(Guid organizationId, DateTime? expirationDate)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
if (organization is { Enabled: true })
|
||||
{
|
||||
organization.Enabled = false;
|
||||
organization.ExpirationDate = expirationDate;
|
||||
organization.RevisionDate = DateTime.UtcNow;
|
||||
|
||||
await _organizationRepository.ReplaceAsync(organization);
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ public interface IOrganizationService
|
||||
/// </summary>
|
||||
Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(OrganizationLicense license, User owner,
|
||||
string ownerKey, string collectionName, string publicKey, string privateKey);
|
||||
Task DisableAsync(Guid organizationId, DateTime? expirationDate);
|
||||
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
||||
Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated);
|
||||
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||
|
@ -686,20 +686,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisableAsync(Guid organizationId, DateTime? expirationDate)
|
||||
{
|
||||
var org = await GetOrgById(organizationId);
|
||||
if (org != null && org.Enabled)
|
||||
{
|
||||
org.Enabled = false;
|
||||
org.ExpirationDate = expirationDate;
|
||||
org.RevisionDate = DateTime.UtcNow;
|
||||
await ReplaceAndUpdateCacheAsync(org);
|
||||
|
||||
// TODO: send email to owners?
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate)
|
||||
{
|
||||
var org = await GetOrgById(organizationId);
|
||||
|
@ -55,6 +55,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationSignUpCommands();
|
||||
services.AddOrganizationDeleteCommands();
|
||||
services.AddOrganizationEnableCommands();
|
||||
services.AddOrganizationDisableCommands();
|
||||
services.AddOrganizationAuthCommands();
|
||||
services.AddOrganizationUserCommands();
|
||||
services.AddOrganizationUserCommandsQueries();
|
||||
@ -73,6 +74,9 @@ public static class OrganizationServiceCollectionExtensions
|
||||
private static void AddOrganizationEnableCommands(this IServiceCollection services) =>
|
||||
services.AddScoped<IOrganizationEnableCommand, OrganizationEnableCommand>();
|
||||
|
||||
private static void AddOrganizationDisableCommands(this IServiceCollection services) =>
|
||||
services.AddScoped<IOrganizationDisableCommand, OrganizationDisableCommand>();
|
||||
|
||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICreateOrganizationConnectionCommand, CreateOrganizationConnectionCommand>();
|
||||
|
@ -0,0 +1,79 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationDisableCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableAsync_WhenOrganizationEnabled_DisablesSuccessfully(
|
||||
Organization organization,
|
||||
DateTime expirationDate,
|
||||
SutProvider<OrganizationDisableCommand> sutProvider)
|
||||
{
|
||||
organization.Enabled = true;
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
await sutProvider.Sut.DisableAsync(organization.Id, expirationDate);
|
||||
|
||||
Assert.False(organization.Enabled);
|
||||
Assert.Equal(expirationDate, organization.ExpirationDate);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(organization);
|
||||
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableAsync_WhenOrganizationNotFound_DoesNothing(
|
||||
Guid organizationId,
|
||||
DateTime expirationDate,
|
||||
SutProvider<OrganizationDisableCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns((Organization)null);
|
||||
|
||||
await sutProvider.Sut.DisableAsync(organizationId, expirationDate);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<Organization>());
|
||||
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.UpsertOrganizationAbilityAsync(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableAsync_WhenOrganizationAlreadyDisabled_DoesNothing(
|
||||
Organization organization,
|
||||
DateTime expirationDate,
|
||||
SutProvider<OrganizationDisableCommand> sutProvider)
|
||||
{
|
||||
organization.Enabled = false;
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
await sutProvider.Sut.DisableAsync(organization.Id, expirationDate);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<Organization>());
|
||||
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.DidNotReceive()
|
||||
.UpsertOrganizationAbilityAsync(Arg.Any<Organization>());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user