mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[AC-2026] Add flexible collections opt-in endpoint (#3643)
Stored procedure to be added in AC-1682
This commit is contained in:
parent
0deb13791a
commit
10f590b4e7
@ -815,6 +815,39 @@ public class OrganizationsController : Controller
|
||||
return new OrganizationResponseModel(organization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrates user, collection, and group data to the new Flexible Collections permissions scheme,
|
||||
/// then sets organization.FlexibleCollections to true to enable these new features for the organization.
|
||||
/// This is irreversible.
|
||||
/// </summary>
|
||||
/// <param name="organizationId"></param>
|
||||
/// <exception cref="NotFoundException"></exception>
|
||||
[HttpPost("{id}/enable-collection-enhancements")]
|
||||
[RequireFeature(FeatureFlagKeys.FlexibleCollectionsMigration)]
|
||||
public async Task EnableCollectionEnhancements(Guid id)
|
||||
{
|
||||
if (!await _currentContext.OrganizationOwner(id))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (organization.FlexibleCollections)
|
||||
{
|
||||
throw new BadRequestException("Organization has already been migrated to the new collection enhancements");
|
||||
}
|
||||
|
||||
await _organizationRepository.EnableCollectionEnhancements(id);
|
||||
|
||||
organization.FlexibleCollections = true;
|
||||
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
|
||||
}
|
||||
|
||||
private async Task TryGrantOwnerAccessToSecretsManagerAsync(Guid organizationId, Guid userId)
|
||||
{
|
||||
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
||||
|
@ -15,4 +15,5 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||
Task<SelfHostedOrganizationDetails> GetSelfHostedOrganizationDetailsById(Guid id);
|
||||
Task<ICollection<Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take);
|
||||
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
||||
Task EnableCollectionEnhancements(Guid organizationId);
|
||||
}
|
||||
|
@ -106,8 +106,15 @@ public static class FeatureFlagKeys
|
||||
public const string ItemShare = "item-share";
|
||||
public const string KeyRotationImprovements = "key-rotation-improvements";
|
||||
public const string DuoRedirect = "duo-redirect";
|
||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
||||
/// <summary>
|
||||
/// Enables flexible collections improvements for new organizations on creation
|
||||
/// </summary>
|
||||
public const string FlexibleCollectionsSignup = "flexible-collections-signup";
|
||||
/// <summary>
|
||||
/// Exposes a migration button in the web vault which allows users to migrate an existing organization to
|
||||
/// flexible collections
|
||||
/// </summary>
|
||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
||||
|
||||
public static List<string> GetAllKeys()
|
||||
{
|
||||
|
@ -169,4 +169,16 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task EnableCollectionEnhancements(Guid organizationId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"[dbo].[Organization_EnableCollectionEnhancements]",
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure,
|
||||
commandTimeout: 180);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,4 +267,9 @@ public class OrganizationRepository : Repository<Core.AdminConsole.Entities.Orga
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public Task EnableCollectionEnhancements(Guid organizationId)
|
||||
{
|
||||
throw new NotImplementedException("Collection enhancements migration is not yet supported for Entity Framework.");
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Controllers;
|
||||
|
||||
@ -353,4 +353,47 @@ public class OrganizationsControllerTests : IDisposable
|
||||
.SignUpAsync(organization, model.AdditionalSmSeats, model.AdditionalServiceAccounts);
|
||||
await _organizationUserRepository.DidNotReceiveWithAnyArgs().ReplaceAsync(Arg.Any<OrganizationUser>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task EnableCollectionEnhancements_Success(Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = false;
|
||||
_currentContext.OrganizationOwner(organization.Id).Returns(true);
|
||||
_organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
await _sut.EnableCollectionEnhancements(organization.Id);
|
||||
|
||||
await _organizationRepository.Received(1).EnableCollectionEnhancements(organization.Id);
|
||||
await _organizationService.Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Id == organization.Id &&
|
||||
o.FlexibleCollections));
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task EnableCollectionEnhancements_WhenNotOwner_Throws(Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = false;
|
||||
_currentContext.OrganizationOwner(organization.Id).Returns(false);
|
||||
_organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await _sut.EnableCollectionEnhancements(organization.Id));
|
||||
|
||||
await _organizationRepository.DidNotReceiveWithAnyArgs().EnableCollectionEnhancements(Arg.Any<Guid>());
|
||||
await _organizationService.DidNotReceiveWithAnyArgs().ReplaceAndUpdateCacheAsync(Arg.Any<Organization>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task EnableCollectionEnhancements_WhenAlreadyMigrated_Throws(Organization organization)
|
||||
{
|
||||
organization.FlexibleCollections = true;
|
||||
_currentContext.OrganizationOwner(organization.Id).Returns(true);
|
||||
_organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await _sut.EnableCollectionEnhancements(organization.Id));
|
||||
Assert.Contains("has already been migrated", exception.Message);
|
||||
|
||||
await _organizationRepository.DidNotReceiveWithAnyArgs().EnableCollectionEnhancements(Arg.Any<Guid>());
|
||||
await _organizationService.DidNotReceiveWithAnyArgs().ReplaceAndUpdateCacheAsync(Arg.Any<Organization>());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user