Skip to content
7 changes: 5 additions & 2 deletions src/Api/AdminConsole/Controllers/OrganizationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class OrganizationsController : Controller
private readonly IPricingClient _pricingClient;
private readonly IOrganizationUpdateKeysCommand _organizationUpdateKeysCommand;
private readonly IOrganizationUpdateCommand _organizationUpdateCommand;
private readonly IOrganizationUpdateCollectionManagementCommand _organizationUpdateCollectionManagementCommand;

public OrganizationsController(
IOrganizationRepository organizationRepository,
Expand All @@ -88,7 +89,8 @@ public OrganizationsController(
IOrganizationDeleteCommand organizationDeleteCommand,
IPricingClient pricingClient,
IOrganizationUpdateKeysCommand organizationUpdateKeysCommand,
IOrganizationUpdateCommand organizationUpdateCommand)
IOrganizationUpdateCommand organizationUpdateCommand,
IOrganizationUpdateCollectionManagementCommand organizationUpdateCollectionManagementCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
Expand All @@ -112,6 +114,7 @@ public OrganizationsController(
_pricingClient = pricingClient;
_organizationUpdateKeysCommand = organizationUpdateKeysCommand;
_organizationUpdateCommand = organizationUpdateCommand;
_organizationUpdateCollectionManagementCommand = organizationUpdateCollectionManagementCommand;
}

[HttpGet("{id}")]
Expand Down Expand Up @@ -547,7 +550,7 @@ public async Task<OrganizationResponseModel> PutCollectionManagement(Guid id, [F
throw new NotFoundException();
}

var organization = await _organizationService.UpdateCollectionManagementSettingsAsync(id, model.ToSettings());
var organization = await _organizationUpdateCollectionManagementCommand.UpdateAsync(id, model.ToSettings());
var plan = await _pricingClient.GetPlan(organization.PlanType);
return new OrganizationResponseModel(organization, plan);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ο»Ώusing Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;

public interface IOrganizationUpdateCollectionManagementCommand
{
/// <summary>
/// Updates an organization's collection management settings.
/// </summary>
/// <param name="organizationId">The unique identifier of the organization to update.</param>
/// <param name="settings">The collection management settings to apply.</param>
/// <returns>The updated organization.</returns>
Task<Organization> UpdateAsync(Guid organizationId, OrganizationCollectionManagementSettings settings);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
ο»Ώusing Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;

public class OrganizationUpdateCollectionManagementCommand : IOrganizationUpdateCollectionManagementCommand
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IEventService _eventService;
private readonly IPushNotificationService _pushNotificationService;

public OrganizationUpdateCollectionManagementCommand(
IOrganizationRepository organizationRepository,
IApplicationCacheService applicationCacheService,
IEventService eventService,
IPushNotificationService pushNotificationService)
{
_organizationRepository = organizationRepository;
_applicationCacheService = applicationCacheService;
_eventService = eventService;
_pushNotificationService = pushNotificationService;
}

public async Task<Organization> UpdateAsync(Guid organizationId, OrganizationCollectionManagementSettings settings)
{
var existingOrganization = await _organizationRepository.GetByIdAsync(organizationId);
if (existingOrganization == null)
{
throw new NotFoundException();
}

var loggingActions = CreateCollectionManagementLoggingActions(existingOrganization, settings);

existingOrganization.LimitCollectionCreation = settings.LimitCollectionCreation;
existingOrganization.LimitCollectionDeletion = settings.LimitCollectionDeletion;
existingOrganization.LimitItemDeletion = settings.LimitItemDeletion;
existingOrganization.AllowAdminAccessToAllCollectionItems = settings.AllowAdminAccessToAllCollectionItems;
existingOrganization.RevisionDate = DateTime.UtcNow;

await _organizationRepository.ReplaceAsync(existingOrganization);
await _applicationCacheService.UpsertOrganizationAbilityAsync(existingOrganization);

if (loggingActions.Any())
{
await Task.WhenAll(loggingActions.Select(action => action()));
}

await _pushNotificationService.PushSyncOrganizationCollectionManagementSettingsAsync(existingOrganization);

return existingOrganization;
}

private List<Func<Task>> CreateCollectionManagementLoggingActions(
Organization existingOrganization, OrganizationCollectionManagementSettings settings)
{
var loggingActions = new List<Func<Task>>();

if (existingOrganization.LimitCollectionCreation != settings.LimitCollectionCreation)
{
var eventType = settings.LimitCollectionCreation
? EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled
: EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.LimitCollectionDeletion != settings.LimitCollectionDeletion)
{
var eventType = settings.LimitCollectionDeletion
? EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled
: EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.LimitItemDeletion != settings.LimitItemDeletion)
{
var eventType = settings.LimitItemDeletion
? EventType.Organization_CollectionManagement_LimitItemDeletionEnabled
: EventType.Organization_CollectionManagement_LimitItemDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.AllowAdminAccessToAllCollectionItems != settings.AllowAdminAccessToAllCollectionItems)
{
var eventType = settings.AllowAdminAccessToAllCollectionItems
? EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled
: EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

return loggingActions;
}
}
2 changes: 0 additions & 2 deletions src/Core/AdminConsole/Services/IOrganizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#nullable disable

using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
Expand All @@ -20,7 +19,6 @@ public interface IOrganizationService
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
Task UpdateAsync(Organization organization, bool updateBilling = false);
Task<Organization> UpdateCollectionManagementSettingsAsync(Guid organizationId, OrganizationCollectionManagementSettings settings);
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
Expand Down Expand Up @@ -389,35 +388,6 @@ await _stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId,
}
}

public async Task<Organization> UpdateCollectionManagementSettingsAsync(Guid organizationId, OrganizationCollectionManagementSettings settings)
{
var existingOrganization = await _organizationRepository.GetByIdAsync(organizationId);
if (existingOrganization == null)
{
throw new NotFoundException();
}

// Create logging actions based on what will change
var loggingActions = CreateCollectionManagementLoggingActions(existingOrganization, settings);

existingOrganization.LimitCollectionCreation = settings.LimitCollectionCreation;
existingOrganization.LimitCollectionDeletion = settings.LimitCollectionDeletion;
existingOrganization.LimitItemDeletion = settings.LimitItemDeletion;
existingOrganization.AllowAdminAccessToAllCollectionItems = settings.AllowAdminAccessToAllCollectionItems;
existingOrganization.RevisionDate = DateTime.UtcNow;

await ReplaceAndUpdateCacheAsync(existingOrganization);

if (loggingActions.Any())
{
await Task.WhenAll(loggingActions.Select(action => action()));
}

await _pushNotificationService.PushSyncOrganizationCollectionManagementSettingsAsync(existingOrganization);

return existingOrganization;
}

public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type)
{
if (!type.ToString().Contains("Organization"))
Expand Down Expand Up @@ -1122,43 +1092,4 @@ public static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusTyp
return status;
}

private List<Func<Task>> CreateCollectionManagementLoggingActions(
Organization existingOrganization, OrganizationCollectionManagementSettings settings)
{
var loggingActions = new List<Func<Task>>();

if (existingOrganization.LimitCollectionCreation != settings.LimitCollectionCreation)
{
var eventType = settings.LimitCollectionCreation
? EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled
: EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.LimitCollectionDeletion != settings.LimitCollectionDeletion)
{
var eventType = settings.LimitCollectionDeletion
? EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled
: EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.LimitItemDeletion != settings.LimitItemDeletion)
{
var eventType = settings.LimitItemDeletion
? EventType.Organization_CollectionManagement_LimitItemDeletionEnabled
: EventType.Organization_CollectionManagement_LimitItemDeletionDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

if (existingOrganization.AllowAdminAccessToAllCollectionItems != settings.AllowAdminAccessToAllCollectionItems)
{
var eventType = settings.AllowAdminAccessToAllCollectionItems
? EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled
: EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled;
loggingActions.Add(() => _eventService.LogOrganizationEventAsync(existingOrganization, eventType));
}

return loggingActions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ private static void AddOrganizationUpdateCommands(this IServiceCollection servic
{
services.AddScoped<IOrganizationUpdateKeysCommand, OrganizationUpdateKeysCommand>();
services.AddScoped<IOrganizationUpdateCommand, OrganizationUpdateCommand>();
services.AddScoped<IOrganizationUpdateCollectionManagementCommand, OrganizationUpdateCollectionManagementCommand>();
}

private static void AddOrganizationEnableCommands(this IServiceCollection services) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ public async Task PutCollectionManagement_ValidRequest_Success(
var plan = MockPlans.Get(PlanType.EnterpriseAnnually);
sutProvider.GetDependency<IPricingClient>().GetPlan(Arg.Any<PlanType>()).Returns(plan);

sutProvider.GetDependency<IOrganizationService>()
.UpdateCollectionManagementSettingsAsync(
sutProvider.GetDependency<IOrganizationUpdateCollectionManagementCommand>()
.UpdateAsync(
organization.Id,
Arg.Is<OrganizationCollectionManagementSettings>(s =>
s.LimitCollectionCreation == model.LimitCollectionCreation &&
Expand All @@ -219,9 +219,9 @@ public async Task PutCollectionManagement_ValidRequest_Success(
await sutProvider.Sut.PutCollectionManagement(organization.Id, model);

// Assert
await sutProvider.GetDependency<IOrganizationService>()
await sutProvider.GetDependency<IOrganizationUpdateCollectionManagementCommand>()
.Received(1)
.UpdateCollectionManagementSettingsAsync(
.UpdateAsync(
organization.Id,
Arg.Is<OrganizationCollectionManagementSettings>(s =>
s.LimitCollectionCreation == model.LimitCollectionCreation &&
Expand Down
Loading
Loading