1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-27 13:05:23 +01:00

[AC-2646] Remove FC MVP dead code from Core (#4281)

* chore: remove fc refs in CreateGroup and UpdateGroup commands, refs AC-2646

* chore: remove fc refs and update interface to represent usage/get rid of double enumeration warnings, refs AC-2646

* chore: remove org/provider service fc callers, refs AC-2646

* chore: remove collection service fc callers, refs AC-2646

* chore: remove cipher service import ciphers fc callers, refs AC-2646

* fix: UpdateOrganizationUserCommandTests collections to list, refs AC-2646

* fix: update CreateGroupCommandTests, refs AC-2646

* fix: adjust UpdateGroupCommandTests, refs AC-2646

* fix: adjust UpdateOrganizationUserCommandTests for FC always true, refs AC-2646

* fix: update CollectionServiceTests, refs AC-2646

* fix: remove unnecessary test with fc disabled, refs AC-2646

* fix: update tests to account for AccessAll removal and Manager removal, refs AC-2646

* chore: remove dependence on FC flag for tests, refs AC-2646
This commit is contained in:
Vincent Salucci 2024-07-12 12:25:04 -05:00 committed by GitHub
parent 25dc0c9178
commit 02b3453cd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 167 additions and 218 deletions

View File

@ -544,9 +544,9 @@ public class ProviderService : IProviderService
await _providerOrganizationRepository.CreateAsync(providerOrganization);
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Created);
// If using Flexible Collections, give the owner Can Manage access over the default collection
// Give the owner Can Manage access over the default collection
// The orgUser is not available when the org is created so we have to do it here as part of the invite
var defaultOwnerAccess = organization.FlexibleCollections && defaultCollection != null
var defaultOwnerAccess = defaultCollection != null
?
[
new CollectionAccessSelection
@ -566,10 +566,6 @@ public class ProviderService : IProviderService
new OrganizationUserInvite
{
Emails = new[] { clientOwnerEmail },
// If using Flexible Collections, AccessAll is deprecated and set to false.
// If not using Flexible Collections, set AccessAll to true (previous behavior)
AccessAll = !organization.FlexibleCollections,
Type = OrganizationUserType.Owner,
Permissions = null,
Collections = defaultOwnerAccess,

View File

@ -20,7 +20,6 @@ public class ScimUserRequestModel : BaseScimUserModel
// Permissions cannot be set via SCIM so we use default values
Type = OrganizationUserType.User,
AccessAll = false,
Collections = new List<CollectionAccessSelection>(),
Groups = new List<Guid>()
};

View File

@ -637,8 +637,7 @@ public class ProviderServiceTests
t.First().Item1.Emails.Count() == 1 &&
t.First().Item1.Emails.First() == clientOwnerEmail &&
t.First().Item1.Type == OrganizationUserType.Owner &&
t.First().Item1.AccessAll &&
!t.First().Item1.Collections.Any() &&
t.First().Item1.Collections.Count() == 1 &&
t.First().Item2 == null));
}
@ -717,13 +716,12 @@ public class ProviderServiceTests
t.First().Item1.Emails.Count() == 1 &&
t.First().Item1.Emails.First() == clientOwnerEmail &&
t.First().Item1.Type == OrganizationUserType.Owner &&
t.First().Item1.AccessAll &&
!t.First().Item1.Collections.Any() &&
t.First().Item1.Collections.Count() == 1 &&
t.First().Item2 == null));
}
[Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData]
public async Task CreateOrganizationAsync_WithFlexibleCollections_SetsAccessAllToFalse
[Theory, OrganizationCustomize, BitAutoData]
public async Task CreateOrganizationAsync_SetsAccessAllToFalse
(Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail,
User user, SutProvider<ProviderService> sutProvider, Collection defaultCollection)
{
@ -747,7 +745,6 @@ public class ProviderServiceTests
t.First().Item1.Emails.Count() == 1 &&
t.First().Item1.Emails.First() == clientOwnerEmail &&
t.First().Item1.Type == OrganizationUserType.Owner &&
t.First().Item1.AccessAll == false &&
t.First().Item1.Collections.Single().Id == defaultCollection.Id &&
!t.First().Item1.Collections.Single().HidePasswords &&
!t.First().Item1.Collections.Single().ReadOnly &&

View File

@ -43,7 +43,6 @@ public class PostUserCommandTests
Arg.Is<OrganizationUserInvite>(i =>
i.Emails.Single().Equals(scimUserRequestModel.PrimaryEmail.ToLowerInvariant()) &&
i.Type == OrganizationUserType.User &&
!i.AccessAll &&
!i.Collections.Any() &&
!i.Groups.Any() &&
i.AccessSecretsManager), externalId)
@ -56,7 +55,6 @@ public class PostUserCommandTests
Arg.Is<OrganizationUserInvite>(i =>
i.Emails.Single().Equals(scimUserRequestModel.PrimaryEmail.ToLowerInvariant()) &&
i.Type == OrganizationUserType.User &&
!i.AccessAll &&
!i.Collections.Any() &&
!i.Groups.Any() &&
i.AccessSecretsManager), externalId);

View File

@ -19,7 +19,11 @@ public class OrganizationUser : ITableObject<Guid>, IExternal
public string? ResetPasswordKey { get; set; }
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }
public bool AccessAll { get; set; }
/// <summary>
/// AccessAll is deprecated and should always be left as false. Scheduled for removal.
/// </summary>
public bool AccessAll { get; set; } = false;
[MaxLength(300)]
public string? ExternalId { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;

View File

@ -7,7 +7,6 @@ public class OrganizationUserInvite
{
public IEnumerable<string> Emails { get; set; }
public Enums.OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; }
public IEnumerable<CollectionAccessSelection> Collections { get; set; }
@ -19,7 +18,6 @@ public class OrganizationUserInvite
{
Emails = requestModel.Emails;
Type = requestModel.Type;
AccessAll = requestModel.AccessAll;
AccessSecretsManager = requestModel.AccessSecretsManager;
Collections = requestModel.Collections;
Groups = requestModel.Groups;

View File

@ -6,7 +6,6 @@ public class OrganizationUserInviteData
{
public IEnumerable<string> Emails { get; set; }
public OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public IEnumerable<CollectionAccessSelection> Collections { get; set; }
public IEnumerable<Guid> Groups { get; set; }

View File

@ -115,8 +115,6 @@ public class CreateGroupCommand : ICreateGroupCommand
throw new BadRequestException("This organization cannot use groups.");
}
if (organization.FlexibleCollections)
{
if (group.AccessAll)
{
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead.");
@ -128,5 +126,4 @@ public class CreateGroupCommand : ICreateGroupCommand
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
}
}
}
}

View File

@ -109,8 +109,6 @@ public class UpdateGroupCommand : IUpdateGroupCommand
throw new BadRequestException("This organization cannot use groups.");
}
if (organization.FlexibleCollections)
{
if (group.AccessAll)
{
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead.");
@ -122,5 +120,4 @@ public class UpdateGroupCommand : IUpdateGroupCommand
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
}
}
}
}

View File

@ -6,5 +6,5 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interface
public interface IUpdateOrganizationUserCommand
{
Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid>? groups);
Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, List<CollectionAccessSelection> collections, IEnumerable<Guid>? groups);
}

View File

@ -37,7 +37,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
}
public async Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId,
IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid>? groups)
List<CollectionAccessSelection>? collections, IEnumerable<Guid>? groups)
{
if (user.Id.Equals(default(Guid)))
{
@ -59,14 +59,12 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
throw new BadRequestException("Organization must have at least one confirmed owner.");
}
// If the organization is using Flexible Collections, prevent use of any deprecated permissions
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
if (organization.FlexibleCollections && user.AccessAll)
if (user.AccessAll)
{
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead.");
}
if (organization.FlexibleCollections && collections?.Any() == true)
if (collections?.Count > 0)
{
var invalidAssociations = collections.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations.Any())
@ -74,7 +72,6 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
}
}
// End Flexible Collections
// Only autoscale (if required) after all validation has passed so that we know it's a valid request before
// updating Stripe
@ -83,17 +80,13 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1);
if (additionalSmSeatsRequired > 0)
{
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
var update = new SecretsManagerSubscriptionUpdate(organization, true)
.AdjustSeats(additionalSmSeatsRequired);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
}
}
if (user.AccessAll)
{
// We don't need any collections if we're flagged to have all access.
collections = new List<CollectionAccessSelection>();
}
await _organizationUserRepository.ReplaceAsync(user, collections);
if (groups != null)

View File

@ -743,10 +743,6 @@ public class OrganizationService : IOrganizationService
AccessSecretsManager = organization.UseSecretsManager,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed,
// If using Flexible Collections, AccessAll is deprecated and set to false.
// If not using Flexible Collections, set AccessAll to true (previous behavior)
AccessAll = !organization.FlexibleCollections,
CreationDate = organization.CreationDate,
RevisionDate = organization.CreationDate
};
@ -771,9 +767,9 @@ public class OrganizationService : IOrganizationService
RevisionDate = organization.CreationDate
};
// If using Flexible Collections, give the owner Can Manage access over the default collection
// Give the owner Can Manage access over the default collection
List<CollectionAccessSelection> defaultOwnerAccess = null;
if (orgUser != null && organization.FlexibleCollections)
if (orgUser != null)
{
defaultOwnerAccess =
[new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }];
@ -965,16 +961,12 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("This method can only be used to invite a single user.");
}
// Validate Collection associations if org is using latest collection enhancements
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
if (organizationAbility?.FlexibleCollections ?? false)
{
// Validate Collection associations
var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations?.Any() ?? false)
{
throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.");
}
}
var results = await InviteUsersAsync(organizationId, invitingUserId, systemUser,
new (OrganizationUserInvite, string)[] { (invite, externalId) });
@ -1038,13 +1030,6 @@ public class OrganizationService : IOrganizationService
throw new NotFoundException();
}
// If the organization is using Flexible Collections, prevent use of any deprecated permissions
if (organization.FlexibleCollections && invites.Any(i => i.invite.AccessAll))
{
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead.");
}
// End Flexible Collections
var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase);
@ -1087,8 +1072,8 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("Organization must have at least one confirmed owner.");
}
var orgUsers = new List<OrganizationUser>();
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
var orgUsersWithoutCollections = new List<OrganizationUser>();
var orgUsersWithCollections = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>();
var orgUserInvitedCount = 0;
var exceptions = new List<Exception>();
@ -1114,7 +1099,6 @@ public class OrganizationService : IOrganizationService
Key = null,
Type = invite.Type.Value,
Status = OrganizationUserStatusType.Invited,
AccessAll = invite.AccessAll,
AccessSecretsManager = invite.AccessSecretsManager,
ExternalId = externalId,
CreationDate = DateTime.UtcNow,
@ -1126,13 +1110,13 @@ public class OrganizationService : IOrganizationService
orgUser.SetPermissions(invite.Permissions ?? new Permissions());
}
if (!orgUser.AccessAll && invite.Collections.Any())
if (invite.Collections.Any())
{
limitedCollectionOrgUsers.Add((orgUser, invite.Collections));
orgUsersWithCollections.Add((orgUser, invite.Collections));
}
else
{
orgUsers.Add(orgUser);
orgUsersWithoutCollections.Add(orgUser);
}
if (invite.Groups != null && invite.Groups.Any())
@ -1155,10 +1139,14 @@ public class OrganizationService : IOrganizationService
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
}
var allOrgUsers = orgUsersWithoutCollections
.Concat(orgUsersWithCollections.Select(u => u.Item1))
.ToList();
try
{
await _organizationUserRepository.CreateManyAsync(orgUsers);
foreach (var (orgUser, collections) in limitedCollectionOrgUsers)
await _organizationUserRepository.CreateManyAsync(orgUsersWithoutCollections);
foreach (var (orgUser, collections) in orgUsersWithCollections)
{
await _organizationUserRepository.CreateAsync(orgUser, collections);
}
@ -1180,7 +1168,7 @@ public class OrganizationService : IOrganizationService
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate);
}
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization);
await SendInvitesAsync(allOrgUsers, organization);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
@ -1191,7 +1179,7 @@ public class OrganizationService : IOrganizationService
catch (Exception e)
{
// Revert any added users.
var invitedOrgUserIds = orgUsers.Select(u => u.Id).Concat(limitedCollectionOrgUsers.Select(u => u.Item1.Id));
var invitedOrgUserIds = allOrgUsers.Select(ou => ou.Id);
await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds);
var currentOrganization = await _organizationRepository.GetByIdAsync(organization.Id);
@ -1220,7 +1208,7 @@ public class OrganizationService : IOrganizationService
throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
}
return (orgUsers, events);
return (allOrgUsers, events);
}
public async Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId,
@ -1811,7 +1799,6 @@ public class OrganizationService : IOrganizationService
{
Emails = new List<string> { user.Email },
Type = OrganizationUserType.User,
AccessAll = false,
Collections = new List<CollectionAccessSelection>(),
AccessSecretsManager = hasStandaloneSecretsManager
};
@ -2519,10 +2506,6 @@ public class OrganizationService : IOrganizationService
Key = null,
Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Invited,
// If using Flexible Collections, AccessAll is deprecated and set to false.
// If not using Flexible Collections, set AccessAll to true (previous behavior)
AccessAll = !organization.FlexibleCollections,
};
await _organizationUserRepository.CreateAsync(ownerOrganizationUser);

View File

@ -55,8 +55,6 @@ public class CollectionService : ICollectionService
var groupsList = groups?.ToList();
var usersList = users?.ToList();
if (org.FlexibleCollections)
{
// Cannot use Manage with ReadOnly/HidePasswords permissions
var invalidAssociations = groupsList?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations?.Any() ?? false)
@ -75,7 +73,6 @@ public class CollectionService : ICollectionService
"At least one member or group must have can manage permission.");
}
}
}
if (collection.Id == default(Guid))
{

View File

@ -788,8 +788,6 @@ public class CipherService : ICipherService
{
collection.SetNewId();
newCollections.Add(collection);
if (org.FlexibleCollections)
{
newCollectionUsers.Add(new CollectionUser
{
CollectionId = collection.Id,
@ -798,7 +796,6 @@ public class CipherService : ICipherService
});
}
}
}
// Create associations based on the newly assigned ids
var collectionCiphers = new List<CollectionCipher>();

View File

@ -138,6 +138,9 @@ internal class OrganizationInvite : ICustomization
.With(ou => ou.Permissions, PermissionsBlob));
fixture.Customize<OrganizationUserInvite>(composer => composer
.With(oi => oi.Type, InviteeUserType));
// Set Manage to false, this ensures it doesn't conflict with the other properties during validation
fixture.Customize<CollectionAccessSelection>(composer => composer
.With(c => c.Manage, false));
}
}

View File

@ -20,9 +20,12 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups;
[SutProviderCustomize]
public class CreateGroupCommandTests
{
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_Success(SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
await sutProvider.Sut.CreateGroupAsync(group, organization);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group);
@ -32,9 +35,21 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_WithCollections_Success(SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group, List<CollectionAccessSelection> collections)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
// Arrange list of collections to make sure Manage is mutually exclusive
for (var i = 0; i < collections.Count; i++)
{
var cas = collections[i];
cas.Manage = i != collections.Count - 1;
cas.HidePasswords = i == collections.Count - 1;
cas.ReadOnly = i == collections.Count - 1;
}
await sutProvider.Sut.CreateGroupAsync(group, organization, collections);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group, collections);
@ -44,9 +59,12 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_WithEventSystemUser_Success(SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group);
@ -56,9 +74,12 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_WithNullOrganization_Throws(SutProvider<CreateGroupCommand> sutProvider, Group group, EventSystemUser eventSystemUser)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateGroupAsync(group, null, eventSystemUser));
Assert.Contains("Organization not found", exception.Message);
@ -68,9 +89,12 @@ public class CreateGroupCommandTests
await sutProvider.GetDependency<IReferenceEventService>().DidNotReceiveWithAnyArgs().RaiseEventAsync(default);
}
[Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = false), BitAutoData]
public async Task CreateGroup_WithUseGroupsAsFalse_Throws(SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser));
Assert.Contains("This organization cannot use groups", exception.Message);
@ -80,8 +104,8 @@ public class CreateGroupCommandTests
await sutProvider.GetDependency<IReferenceEventService>().DidNotReceiveWithAnyArgs().RaiseEventAsync(default);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData]
public async Task CreateGroup_WithFlexibleCollections_WithAccessAll_Throws(
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_WithAccessAll_Throws(
SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group)
{
group.AccessAll = true;

View File

@ -17,9 +17,12 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups;
[SutProviderCustomize]
public class UpdateGroupCommandTests
{
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_Success(SutProvider<UpdateGroupCommand> sutProvider, Group group, Organization organization)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
await sutProvider.Sut.UpdateGroupAsync(group, organization);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group);
@ -27,9 +30,21 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_WithCollections_Success(SutProvider<UpdateGroupCommand> sutProvider, Group group, Organization organization, List<CollectionAccessSelection> collections)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
// Arrange list of collections to make sure Manage is mutually exclusive
for (var i = 0; i < collections.Count; i++)
{
var cas = collections[i];
cas.Manage = i != collections.Count - 1;
cas.HidePasswords = i == collections.Count - 1;
cas.ReadOnly = i == collections.Count - 1;
}
await sutProvider.Sut.UpdateGroupAsync(group, organization, collections);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group, collections);
@ -37,9 +52,12 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_WithEventSystemUser_Success(SutProvider<UpdateGroupCommand> sutProvider, Group group, Organization organization, EventSystemUser eventSystemUser)
{
// Deprecated with Flexible Collections
group.AccessAll = false;
await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group);
@ -47,7 +65,7 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_WithNullOrganization_Throws(SutProvider<UpdateGroupCommand> sutProvider, Group group, EventSystemUser eventSystemUser)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateGroupAsync(group, null, eventSystemUser));
@ -58,7 +76,7 @@ public class UpdateGroupCommandTests
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
[Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData]
[Theory, OrganizationCustomize(UseGroups = false), BitAutoData]
public async Task UpdateGroup_WithUseGroupsAsFalse_Throws(SutProvider<UpdateGroupCommand> sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser));
@ -69,12 +87,11 @@ public class UpdateGroupCommandTests
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
}
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData]
public async Task UpdateGroup_WithFlexibleCollections_WithAccessAll_Throws(
[Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_WithAccessAll_Throws(
SutProvider<UpdateGroupCommand> sutProvider, Organization organization, Group group)
{
group.AccessAll = true;
organization.FlexibleCollections = true;
var exception =
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization));

View File

@ -21,7 +21,7 @@ public class UpdateOrganizationUserCommandTests
{
[Theory, BitAutoData]
public async Task UpdateUserAsync_NoUserId_Throws(OrganizationUser user, Guid? savingUserId,
ICollection<CollectionAccessSelection> collections, IEnumerable<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider)
List<CollectionAccessSelection> collections, IEnumerable<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
user.Id = default(Guid);
var exception = await Assert.ThrowsAsync<BadRequestException>(
@ -34,7 +34,7 @@ public class UpdateOrganizationUserCommandTests
Organization organization,
OrganizationUser oldUserData,
OrganizationUser newUserData,
ICollection<CollectionAccessSelection> collections,
List<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
Permissions permissions,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
@ -46,6 +46,19 @@ public class UpdateOrganizationUserCommandTests
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
// Deprecated with Flexible Collections
oldUserData.AccessAll = false;
newUserData.AccessAll = false;
// Arrange list of collections to make sure Manage is mutually exclusive
for (var i = 0; i < collections.Count; i++)
{
var cas = collections[i];
cas.Manage = i != collections.Count - 1;
cas.HidePasswords = i == collections.Count - 1;
cas.ReadOnly = i == collections.Count - 1;
}
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
@ -78,16 +91,15 @@ public class UpdateOrganizationUserCommandTests
}
[Theory, BitAutoData]
public async Task UpdateUserAsync_WithFlexibleCollections_WithAccessAll_Throws(
public async Task UpdateUserAsync_WithAccessAll_Throws(
Organization organization,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser newUserData,
[OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser,
ICollection<CollectionAccessSelection> collections,
List<CollectionAccessSelection> collections,
IEnumerable<Guid> groups,
SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
organization.FlexibleCollections = true;
newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organization.Id;

View File

@ -730,7 +730,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
[Theory]
[OrganizationCustomize(FlexibleCollections = false)]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.User)]
@ -843,7 +842,7 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
[OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Custom
), OrganizationCustomize(FlexibleCollections = false), BitAutoData]
), OrganizationCustomize, BitAutoData]
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
@ -1152,9 +1151,11 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
.ReturnsForAnyArgs(Task.FromResult(0)).AndDoes(x => organization.SmSeats += invitedSmUsers);
// Throw error at the end of the try block
sutProvider.GetDependency<IReferenceEventService>().RaiseEventAsync(default).ThrowsForAnyArgs<BadRequestException>();
sutProvider.GetDependency<IReferenceEventService>().RaiseEventAsync(default)
.ThrowsForAnyArgs<BadRequestException>();
await Assert.ThrowsAsync<AggregateException>(async () => await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites));
await Assert.ThrowsAsync<AggregateException>(async () =>
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites));
// OrgUser is reverted
// Note: we don't know what their guids are so comparing length is the best we can do
@ -1182,28 +1183,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
});
}
[Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData]
public async Task InviteUsers_WithFlexibleCollections_WithAccessAll_Throws(Organization organization,
OrganizationUserInvite invite, OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
{
invite.Type = OrganizationUserType.User;
invite.AccessAll = true;
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<ICurrentContext>()
.ManageUsers(organization.Id)
.Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null,
new (OrganizationUserInvite, string)[] { (invite, null) }));
Assert.Contains("accessall property has been deprecated", exception.Message.ToLowerInvariant());
}
private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider)
{
@ -2336,6 +2315,15 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
return Task.FromResult<ICollection<Guid>>(orgUsers.Select(u => u.Id).ToList());
}
);
organizationUserRepository.CreateAsync(Arg.Any<OrganizationUser>(), Arg.Any<IEnumerable<CollectionAccessSelection>>()).Returns(
info =>
{
var orgUser = info.Arg<OrganizationUser>();
orgUser.Id = Guid.NewGuid();
return Task.FromResult<Guid>(orgUser.Id);
}
);
}
// Must set real guids in order for dictionary of guids to not throw aggregate exceptions

View File

@ -8,16 +8,22 @@ namespace Bit.Core.Test.AutoFixture;
public class CollectionAccessSelectionCustomization : ICustomization
{
public bool Manage { get; set; }
public bool ReadOnly { get; set; }
public bool HidePasswords { get; set; }
public CollectionAccessSelectionCustomization(bool manage)
{
Manage = manage;
ReadOnly = !manage;
HidePasswords = !manage;
}
public void Customize(IFixture fixture)
{
fixture.Customize<CollectionAccessSelection>(composer => composer
.With(o => o.Manage, Manage));
.With(o => o.Manage, Manage)
.With(o => o.ReadOnly, ReadOnly)
.With(o => o.HidePasswords, HidePasswords));
}
}

View File

@ -77,7 +77,7 @@ public class CollectionServiceTest
[Theory, BitAutoData]
public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection,
IEnumerable<CollectionAccessSelection> groups, [CollectionAccessSelectionCustomize(true)] IEnumerable<CollectionAccessSelection> users,
[CollectionAccessSelectionCustomize] IEnumerable<CollectionAccessSelection> groups, [CollectionAccessSelectionCustomize(true)] IEnumerable<CollectionAccessSelection> users,
Organization organization, SutProvider<CollectionService> sutProvider)
{
collection.Id = default;

View File

@ -26,63 +26,7 @@ namespace Bit.Core.Test.Services;
public class CipherServiceTests
{
[Theory, BitAutoData]
public async Task ImportCiphersAsync_IntoOrganization_WithFlexibleCollectionsDisabled_Success(
Organization organization,
Guid importingUserId,
OrganizationUser importingOrganizationUser,
List<Collection> collections,
List<CipherDetails> ciphers,
SutProvider<CipherService> sutProvider)
{
organization.MaxCollections = null;
organization.FlexibleCollections = false;
importingOrganizationUser.OrganizationId = organization.Id;
foreach (var collection in collections)
{
collection.OrganizationId = organization.Id;
}
foreach (var cipher in ciphers)
{
cipher.OrganizationId = organization.Id;
}
KeyValuePair<int, int>[] collectionRelationships = {
new(0, 0),
new(1, 1),
new(2, 2)
};
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(organization.Id, importingUserId)
.Returns(importingOrganizationUser);
// Set up a collection that already exists in the organization
sutProvider.GetDependency<ICollectionRepository>()
.GetManyByOrganizationIdAsync(organization.Id)
.Returns(new List<Collection> { collections[0] });
await sutProvider.Sut.ImportCiphersAsync(collections, ciphers, collectionRelationships, importingUserId);
await sutProvider.GetDependency<ICipherRepository>().Received(1).CreateAsync(
ciphers,
Arg.Is<IEnumerable<Collection>>(cols => cols.Count() == collections.Count - 1 &&
!cols.Any(c => c.Id == collections[0].Id) && // Check that the collection that already existed in the organization was not added
cols.All(c => collections.Any(x => c.Name == x.Name))),
Arg.Is<IEnumerable<CollectionCipher>>(c => c.Count() == ciphers.Count),
Arg.Is<IEnumerable<CollectionUser>>(i => !i.Any()));
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
await sutProvider.GetDependency<IReferenceEventService>().Received(1).RaiseEventAsync(
Arg.Is<ReferenceEvent>(e => e.Type == ReferenceEventType.VaultImported));
}
[Theory, BitAutoData]
public async Task ImportCiphersAsync_IntoOrganization_WithFlexibleCollectionsEnabled_Success(
public async Task ImportCiphersAsync_IntoOrganization_Success(
Organization organization,
Guid importingUserId,
OrganizationUser importingOrganizationUser,