1
0
mirror of https://github.com/bitwarden/server synced 2026-01-03 09:03:44 +00:00

Split Organization.LimitCollectionCreationDeletion into two separate business rules (#4730)

* Add feature flag

* Promoted the new Entiy Framework properties

* Deprecate the old property

* Update references

* Fix mispelling

* Re-add contextual comment regarding dropped license properties

* Add back deleted assertion for deprecated property

* Add back removed fixture property assignment

* Improve feature toggling scenerios for self hosted org creation/update

* Unblock `PutCollectionManagement` for self host

* Simplify logic of a couple of conditionals

* Feature toggle route unblocking

* Adjust logic collection creation/deletion authorization handler

* Create tests

* Fix bug caught by tests

* Fix bugs caught during manual testing

* Remove remark about license
This commit is contained in:
Addison Beck
2024-10-18 11:00:01 -04:00
committed by GitHub
parent 4fec7cadb7
commit 91409a45f0
22 changed files with 701 additions and 69 deletions

View File

@@ -520,9 +520,16 @@ public class OrganizationsController : Controller
}
[HttpPut("{id}/collection-management")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<OrganizationResponseModel> PutCollectionManagement(Guid id, [FromBody] OrganizationCollectionManagementUpdateRequestModel model)
{
if (
_globalSettings.SelfHosted &&
!_featureService.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
)
{
throw new BadRequestException("Only allowed when not self hosted.");
}
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
@@ -534,7 +541,7 @@ public class OrganizationsController : Controller
throw new NotFoundException();
}
await _organizationService.UpdateAsync(model.ToOrganization(organization), eventType: EventType.Organization_CollectionManagement_Updated);
await _organizationService.UpdateAsync(model.ToOrganization(organization, _featureService), eventType: EventType.Organization_CollectionManagement_Updated);
return new OrganizationResponseModel(organization);
}
}

View File

@@ -55,6 +55,9 @@ public class OrganizationResponseModel : ResponseModel
SmServiceAccounts = organization.SmServiceAccounts;
MaxAutoscaleSmSeats = organization.MaxAutoscaleSmSeats;
MaxAutoscaleSmServiceAccounts = organization.MaxAutoscaleSmServiceAccounts;
LimitCollectionCreation = organization.LimitCollectionCreation;
LimitCollectionDeletion = organization.LimitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
}
@@ -98,6 +101,9 @@ public class OrganizationResponseModel : ResponseModel
public int? SmServiceAccounts { get; set; }
public int? MaxAutoscaleSmSeats { get; set; }
public int? MaxAutoscaleSmServiceAccounts { get; set; }
public bool LimitCollectionCreation { get; set; }
public bool LimitCollectionDeletion { get; set; }
// Deperectated: https://bitwarden.atlassian.net/browse/PM-10863
public bool LimitCollectionCreationDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
}

View File

@@ -65,6 +65,9 @@ public class ProfileOrganizationResponseModel : ResponseModel
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;
AccessSecretsManager = organization.AccessSecretsManager;
LimitCollectionCreation = organization.LimitCollectionCreation;
LimitCollectionDeletion = organization.LimitCollectionDeletion;
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
UserIsManagedByOrganization = organizationIdsManagingUser.Contains(organization.OrganizationId);
@@ -124,6 +127,9 @@ public class ProfileOrganizationResponseModel : ResponseModel
public DateTime? FamilySponsorshipValidUntil { get; set; }
public bool? FamilySponsorshipToDelete { get; set; }
public bool AccessSecretsManager { get; set; }
public bool LimitCollectionCreation { get; set; }
public bool LimitCollectionDeletion { get; set; }
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
public bool LimitCollectionCreationDeletion { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
/// <summary>

View File

@@ -44,6 +44,9 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
ProviderId = organization.ProviderId;
ProviderName = organization.ProviderName;
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
LimitCollectionCreation = organization.LimitCollectionCreation;
LimitCollectionDeletion = organization.LimitCollectionDeletion;
// https://bitwarden.atlassian.net/browse/PM-10863
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
}

View File

@@ -1,15 +1,29 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Services;
namespace Bit.Api.Models.Request.Organizations;
public class OrganizationCollectionManagementUpdateRequestModel
{
public bool LimitCollectionCreation { get; set; }
public bool LimitCollectionDeletion { get; set; }
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
public bool LimitCreateDeleteOwnerAdmin { get; set; }
public bool AllowAdminAccessToAllCollectionItems { get; set; }
public virtual Organization ToOrganization(Organization existingOrganization)
public virtual Organization ToOrganization(Organization existingOrganization, IFeatureService featureService)
{
existingOrganization.LimitCollectionCreationDeletion = LimitCreateDeleteOwnerAdmin;
if (featureService.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit))
{
existingOrganization.LimitCollectionCreation = LimitCollectionCreation;
existingOrganization.LimitCollectionDeletion = LimitCollectionDeletion;
}
else
{
existingOrganization.LimitCollectionCreationDeletion = LimitCreateDeleteOwnerAdmin || LimitCollectionCreation || LimitCollectionDeletion;
}
existingOrganization.AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems;
return existingOrganization;
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using System.Diagnostics;
using Bit.Core;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -101,7 +102,7 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
break;
case null:
// requirement isn't actually nullable but since we use the
// requirement isn't actually nullable but since we use the
// not null when trick it makes the compiler think that requirement
// could actually be nullable.
throw new UnreachableException();
@@ -123,10 +124,24 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
return true;
}
// If the limit collection management setting is disabled, allow any user to create collections
if (await GetOrganizationAbilityAsync(org) is { LimitCollectionCreationDeletion: false })
if (_featureService.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit))
{
return true;
var userIsMemberOfOrg = org is not null;
var limitCollectionCreationEnabled = await GetOrganizationAbilityAsync(org) is { LimitCollectionCreation: true };
var userIsOrgOwnerOrAdmin = org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin };
// If the limit collection management setting is disabled, allow any user to create collections
if (userIsMemberOfOrg && (!limitCollectionCreationEnabled || userIsOrgOwnerOrAdmin))
{
return true;
}
}
else
{
// If the limit collection management setting is disabled, allow any user to create collections
if (await GetOrganizationAbilityAsync(org) is { LimitCollectionCreationDeletion: false })
{
return true;
}
}
// Allow provider users to create collections if they are a provider for the target organization
@@ -246,21 +261,35 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler<BulkC
return true;
}
// If AllowAdminAccessToAllCollectionItems is true, Owners and Admins can delete any collection, regardless of LimitCollectionCreationDeletion setting
// If AllowAdminAccessToAllCollectionItems is true, Owners and Admins can delete any collection, regardless of LimitCollectionDeletion setting
if (await AllowAdminAccessToAllCollectionItems(org) && org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin })
{
return true;
}
// If LimitCollectionCreationDeletion is false, AllowAdminAccessToAllCollectionItems setting is irrelevant.
// Ensure acting user has manage permissions for all collections being deleted
// If LimitCollectionCreationDeletion is true, only Owners and Admins can delete collections they manage
var organizationAbility = await GetOrganizationAbilityAsync(org);
var canDeleteManagedCollections = organizationAbility is { LimitCollectionCreationDeletion: false } ||
org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin };
if (canDeleteManagedCollections && await CanManageCollectionsAsync(resources, org))
if (_featureService.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit))
{
return true;
var userIsMemberOfOrg = org is not null;
var limitCollectionDeletionEnabled = await GetOrganizationAbilityAsync(org) is { LimitCollectionDeletion: true };
var userIsOrgOwnerOrAdmin = org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin };
// If the limit collection management setting is disabled, allow any user to delete collections
if (userIsMemberOfOrg && (!limitCollectionDeletionEnabled || userIsOrgOwnerOrAdmin) && await CanManageCollectionsAsync(resources, org))
{
return true;
}
}
else
{
// If LimitCollectionCreationDeletion is false, AllowAdminAccessToAllCollectionItems setting is irrelevant.
// Ensure acting user has manage permissions for all collections being deleted
// If LimitCollectionCreationDeletion is true, only Owners and Admins can delete collections they manage
var organizationAbility = await GetOrganizationAbilityAsync(org);
var canDeleteManagedCollections = organizationAbility is { LimitCollectionCreationDeletion: false } ||
org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin };
if (canDeleteManagedCollections && await CanManageCollectionsAsync(resources, org))
{
return true;
}
}
// Allow providers to delete collections if they are a provider for the target organization