1
0
mirror of https://github.com/bitwarden/server synced 2026-02-26 01:13:35 +00:00

[PM-32131] Add UseMyItems organization ability (#7014)

Purpose: UseMyItems is a new organization ability / plan flag
which is automatically enabled where UsePolicies is enabled,
but can be selectively disabled to disable My Items creation
when the Organization Data Ownership policy is turned on.

- new organization table column with all sprocs and views updated
- data migration to enable the feature for all organizations that already use policies (replicating existing behaviour)
- data and api models updated
- added to organization license file so it can be preserved in self-hosted instances
- note that we don't have a plan feature defined for this yet, so it is set based on UsePolicies to match the migration logic. Billing Team have a ticket to add this
This commit is contained in:
Thomas Rittson
2026-02-25 12:52:28 +10:00
committed by GitHub
parent 8f54ac306c
commit e3c392badb
54 changed files with 22386 additions and 21 deletions

View File

@@ -216,6 +216,7 @@ If you believe you need to change the version for a valid reason, please discuss
UseAdminSponsoredFamilies = false,
UseDisableSmAdsForUsers = false,
UsePhishingBlocker = false,
UseMyItems = false,
};
}

View File

@@ -92,8 +92,7 @@ public class UpdateOrganizationLicenseCommandTests
"Id", "MaxStorageGb", "Issued", "Refresh", "Version", "Trial", "LicenseType",
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires",
"ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion",
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems",
"UseOrganizationDomains", "UseAdminSponsoredFamilies", "UseAutomaticUserConfirmation", "UsePhishingBlocker", "UseDisableSmAdsForUsers") &&
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems") &&
// Same property but different name, use explicit mapping
org.ExpirationDate == license.Expires));
}
@@ -169,7 +168,8 @@ public class UpdateOrganizationLicenseCommandTests
new(OrganizationLicenseConstants.ExpirationWithoutGracePeriod, DateTime.Now.AddMonths(12).ToString("O")),
new(OrganizationLicenseConstants.Trial, "false"),
new(OrganizationLicenseConstants.LimitCollectionCreationDeletion, "true"),
new(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems, "true")
new(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems, "true"),
new(OrganizationLicenseConstants.UseMyItems, "true")
};
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims));
@@ -236,7 +236,8 @@ public class UpdateOrganizationLicenseCommandTests
org.UseAdminSponsoredFamilies == true &&
org.UseAutomaticUserConfirmation == true &&
org.UseDisableSmAdsForUsers == true &&
org.UsePhishingBlocker == true));
org.UsePhishingBlocker == true &&
org.UseMyItems));
}
finally
{

View File

@@ -0,0 +1,76 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Repositories;
using Bit.Infrastructure.IntegrationTest.Services;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Migrations;
public class UseMyItemsDataMigrationTests
{
private const string _migrationName = "UseMyItemsDataMigration";
[Theory, DatabaseData(MigrationName = _migrationName)]
public async Task Migration_WithUsePoliciesEnabled_SetsUseMyItemsToTrue(
IOrganizationRepository organizationRepository,
IMigrationTesterService migrationTester)
{
// Arrange
var organization = await SetupOrganization(organizationRepository, usePolicies: true);
// Verify initial state
Assert.True(organization.UsePolicies);
Assert.False(organization.UseMyItems);
// Act
migrationTester.ApplyMigration();
// Assert
var migratedOrganization = await organizationRepository.GetByIdAsync(organization.Id);
Assert.NotNull(migratedOrganization);
Assert.True(migratedOrganization.UsePolicies);
Assert.True(migratedOrganization.UseMyItems);
}
[Theory, DatabaseData(MigrationName = _migrationName)]
public async Task Migration_WithUsePoliciesDisabled_LeavesUseMyItemsFalse(
IOrganizationRepository organizationRepository,
IMigrationTesterService migrationTester)
{
// Arrange
var organization = await SetupOrganization(organizationRepository, usePolicies: false);
// Verify initial state
Assert.False(organization.UsePolicies);
Assert.False(organization.UseMyItems);
// Act
migrationTester.ApplyMigration();
// Assert
var migratedOrganization = await organizationRepository.GetByIdAsync(organization.Id);
Assert.NotNull(migratedOrganization);
Assert.False(migratedOrganization.UsePolicies);
Assert.False(migratedOrganization.UseMyItems);
}
/// <summary>
/// Helper method to create a test organization with specified UsePolicies value.
/// UseMyItems is always initialized to false to simulate pre-migration state.
/// </summary>
private static async Task<Organization> SetupOrganization(
IOrganizationRepository organizationRepository,
bool usePolicies,
string identifier = "test")
{
// CreateTestOrganizationAsync sets UsePolicies = true by default
var organization = await organizationRepository.CreateTestOrganizationAsync(identifier: identifier);
// Override to test both true and false scenarios
organization.UsePolicies = usePolicies;
organization.UseMyItems = false; // Simulate pre-migration state
await organizationRepository.ReplaceAsync(organization);
return organization;
}
}