1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-28 13:15:12 +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 _providerOrganizationRepository.CreateAsync(providerOrganization);
await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Created); 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 // 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 new CollectionAccessSelection
@ -566,10 +566,6 @@ public class ProviderService : IProviderService
new OrganizationUserInvite new OrganizationUserInvite
{ {
Emails = new[] { clientOwnerEmail }, 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, Type = OrganizationUserType.Owner,
Permissions = null, Permissions = null,
Collections = defaultOwnerAccess, Collections = defaultOwnerAccess,

View File

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

View File

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

View File

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

View File

@ -19,7 +19,11 @@ public class OrganizationUser : ITableObject<Guid>, IExternal
public string? ResetPasswordKey { get; set; } public string? ResetPasswordKey { get; set; }
public OrganizationUserStatusType Status { get; set; } public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { 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)] [MaxLength(300)]
public string? ExternalId { get; set; } public string? ExternalId { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;

View File

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

View File

@ -6,7 +6,6 @@ public class OrganizationUserInviteData
{ {
public IEnumerable<string> Emails { get; set; } public IEnumerable<string> Emails { get; set; }
public OrganizationUserType? Type { get; set; } public OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; } public bool AccessSecretsManager { get; set; }
public IEnumerable<CollectionAccessSelection> Collections { get; set; } public IEnumerable<CollectionAccessSelection> Collections { get; set; }
public IEnumerable<Guid> Groups { 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."); throw new BadRequestException("This organization cannot use groups.");
} }
if (organization.FlexibleCollections)
{
if (group.AccessAll) if (group.AccessAll)
{ {
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); 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."); 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."); throw new BadRequestException("This organization cannot use groups.");
} }
if (organization.FlexibleCollections)
{
if (group.AccessAll) if (group.AccessAll)
{ {
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); 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."); 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 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, 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))) 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."); throw new BadRequestException("Organization must have at least one confirmed owner.");
} }
// If the organization is using Flexible Collections, prevent use of any deprecated permissions if (user.AccessAll)
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
if (organization.FlexibleCollections && user.AccessAll)
{ {
throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead."); 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)); var invalidAssociations = collections.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations.Any()) 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."); 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 // Only autoscale (if required) after all validation has passed so that we know it's a valid request before
// updating Stripe // updating Stripe
@ -83,17 +80,13 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1); var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1);
if (additionalSmSeatsRequired > 0) if (additionalSmSeatsRequired > 0)
{ {
var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId);
var update = new SecretsManagerSubscriptionUpdate(organization, true) var update = new SecretsManagerSubscriptionUpdate(organization, true)
.AdjustSeats(additionalSmSeatsRequired); .AdjustSeats(additionalSmSeatsRequired);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update); 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); await _organizationUserRepository.ReplaceAsync(user, collections);
if (groups != null) if (groups != null)

View File

@ -743,10 +743,6 @@ public class OrganizationService : IOrganizationService
AccessSecretsManager = organization.UseSecretsManager, AccessSecretsManager = organization.UseSecretsManager,
Type = OrganizationUserType.Owner, Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed, 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, CreationDate = organization.CreationDate,
RevisionDate = organization.CreationDate RevisionDate = organization.CreationDate
}; };
@ -771,9 +767,9 @@ public class OrganizationService : IOrganizationService
RevisionDate = organization.CreationDate 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; List<CollectionAccessSelection> defaultOwnerAccess = null;
if (orgUser != null && organization.FlexibleCollections) if (orgUser != null)
{ {
defaultOwnerAccess = defaultOwnerAccess =
[new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }]; [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."); throw new BadRequestException("This method can only be used to invite a single user.");
} }
// Validate Collection associations if org is using latest collection enhancements // Validate Collection associations
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
if (organizationAbility?.FlexibleCollections ?? false)
{
var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords));
if (invalidAssociations?.Any() ?? false) 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."); 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, var results = await InviteUsersAsync(organizationId, invitingUserId, systemUser,
new (OrganizationUserInvite, string)[] { (invite, externalId) }); new (OrganizationUserInvite, string)[] { (invite, externalId) });
@ -1038,13 +1030,6 @@ public class OrganizationService : IOrganizationService
throw new NotFoundException(); 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( var existingEmails = new HashSet<string>(await _organizationUserRepository.SelectKnownEmailsAsync(
organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); 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."); throw new BadRequestException("Organization must have at least one confirmed owner.");
} }
var orgUsers = new List<OrganizationUser>(); var orgUsersWithoutCollections = new List<OrganizationUser>();
var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>(); var orgUsersWithCollections = new List<(OrganizationUser, IEnumerable<CollectionAccessSelection>)>();
var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>(); var orgUserGroups = new List<(OrganizationUser, IEnumerable<Guid>)>();
var orgUserInvitedCount = 0; var orgUserInvitedCount = 0;
var exceptions = new List<Exception>(); var exceptions = new List<Exception>();
@ -1114,7 +1099,6 @@ public class OrganizationService : IOrganizationService
Key = null, Key = null,
Type = invite.Type.Value, Type = invite.Type.Value,
Status = OrganizationUserStatusType.Invited, Status = OrganizationUserStatusType.Invited,
AccessAll = invite.AccessAll,
AccessSecretsManager = invite.AccessSecretsManager, AccessSecretsManager = invite.AccessSecretsManager,
ExternalId = externalId, ExternalId = externalId,
CreationDate = DateTime.UtcNow, CreationDate = DateTime.UtcNow,
@ -1126,13 +1110,13 @@ public class OrganizationService : IOrganizationService
orgUser.SetPermissions(invite.Permissions ?? new Permissions()); 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 else
{ {
orgUsers.Add(orgUser); orgUsersWithoutCollections.Add(orgUser);
} }
if (invite.Groups != null && invite.Groups.Any()) 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); throw new AggregateException("One or more errors occurred while inviting users.", exceptions);
} }
var allOrgUsers = orgUsersWithoutCollections
.Concat(orgUsersWithCollections.Select(u => u.Item1))
.ToList();
try try
{ {
await _organizationUserRepository.CreateManyAsync(orgUsers); await _organizationUserRepository.CreateManyAsync(orgUsersWithoutCollections);
foreach (var (orgUser, collections) in limitedCollectionOrgUsers) foreach (var (orgUser, collections) in orgUsersWithCollections)
{ {
await _organizationUserRepository.CreateAsync(orgUser, collections); await _organizationUserRepository.CreateAsync(orgUser, collections);
} }
@ -1180,7 +1168,7 @@ public class OrganizationService : IOrganizationService
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate); await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate);
} }
await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization); await SendInvitesAsync(allOrgUsers, organization);
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext) new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
@ -1191,7 +1179,7 @@ public class OrganizationService : IOrganizationService
catch (Exception e) catch (Exception e)
{ {
// Revert any added users. // 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); await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds);
var currentOrganization = await _organizationRepository.GetByIdAsync(organization.Id); 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); 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, 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 }, Emails = new List<string> { user.Email },
Type = OrganizationUserType.User, Type = OrganizationUserType.User,
AccessAll = false,
Collections = new List<CollectionAccessSelection>(), Collections = new List<CollectionAccessSelection>(),
AccessSecretsManager = hasStandaloneSecretsManager AccessSecretsManager = hasStandaloneSecretsManager
}; };
@ -2519,10 +2506,6 @@ public class OrganizationService : IOrganizationService
Key = null, Key = null,
Type = OrganizationUserType.Owner, Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Invited, 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); await _organizationUserRepository.CreateAsync(ownerOrganizationUser);

View File

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

View File

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

View File

@ -138,6 +138,9 @@ internal class OrganizationInvite : ICustomization
.With(ou => ou.Permissions, PermissionsBlob)); .With(ou => ou.Permissions, PermissionsBlob));
fixture.Customize<OrganizationUserInvite>(composer => composer fixture.Customize<OrganizationUserInvite>(composer => composer
.With(oi => oi.Type, InviteeUserType)); .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] [SutProviderCustomize]
public class CreateGroupCommandTests 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) 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.Sut.CreateGroupAsync(group, organization);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group); await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group);
@ -32,9 +35,21 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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.Sut.CreateGroupAsync(group, organization, collections);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group, collections); await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group, collections);
@ -44,9 +59,12 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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.Sut.CreateGroupAsync(group, organization, eventSystemUser);
await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group); await sutProvider.GetDependency<IGroupRepository>().Received(1).CreateAsync(group);
@ -56,9 +74,12 @@ public class CreateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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)); var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateGroupAsync(group, null, eventSystemUser));
Assert.Contains("Organization not found", exception.Message); Assert.Contains("Organization not found", exception.Message);
@ -68,9 +89,12 @@ public class CreateGroupCommandTests
await sutProvider.GetDependency<IReferenceEventService>().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); 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) 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)); var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser));
Assert.Contains("This organization cannot use groups", exception.Message); Assert.Contains("This organization cannot use groups", exception.Message);
@ -80,8 +104,8 @@ public class CreateGroupCommandTests
await sutProvider.GetDependency<IReferenceEventService>().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); await sutProvider.GetDependency<IReferenceEventService>().DidNotReceiveWithAnyArgs().RaiseEventAsync(default);
} }
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] [Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task CreateGroup_WithFlexibleCollections_WithAccessAll_Throws( public async Task CreateGroup_WithAccessAll_Throws(
SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group) SutProvider<CreateGroupCommand> sutProvider, Organization organization, Group group)
{ {
group.AccessAll = true; group.AccessAll = true;

View File

@ -17,9 +17,12 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups;
[SutProviderCustomize] [SutProviderCustomize]
public class UpdateGroupCommandTests 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) 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.Sut.UpdateGroupAsync(group, organization);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group); await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group);
@ -27,9 +30,21 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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.Sut.UpdateGroupAsync(group, organization, collections);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group, collections); await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group, collections);
@ -37,9 +52,12 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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.Sut.UpdateGroupAsync(group, organization, eventSystemUser);
await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group); await sutProvider.GetDependency<IGroupRepository>().Received(1).ReplaceAsync(group);
@ -47,7 +65,7 @@ public class UpdateGroupCommandTests
AssertHelper.AssertRecent(group.RevisionDate); 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) 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)); 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); 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) 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)); 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); await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default);
} }
[Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] [Theory, OrganizationCustomize(UseGroups = true), BitAutoData]
public async Task UpdateGroup_WithFlexibleCollections_WithAccessAll_Throws( public async Task UpdateGroup_WithAccessAll_Throws(
SutProvider<UpdateGroupCommand> sutProvider, Organization organization, Group group) SutProvider<UpdateGroupCommand> sutProvider, Organization organization, Group group)
{ {
group.AccessAll = true; group.AccessAll = true;
organization.FlexibleCollections = true;
var exception = var exception =
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization)); await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization));

View File

@ -21,7 +21,7 @@ public class UpdateOrganizationUserCommandTests
{ {
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UpdateUserAsync_NoUserId_Throws(OrganizationUser user, Guid? savingUserId, 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); user.Id = default(Guid);
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
@ -34,7 +34,7 @@ public class UpdateOrganizationUserCommandTests
Organization organization, Organization organization,
OrganizationUser oldUserData, OrganizationUser oldUserData,
OrganizationUser newUserData, OrganizationUser newUserData,
ICollection<CollectionAccessSelection> collections, List<CollectionAccessSelection> collections,
IEnumerable<Guid> groups, IEnumerable<Guid> groups,
Permissions permissions, Permissions permissions,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser, [OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser,
@ -46,6 +46,19 @@ public class UpdateOrganizationUserCommandTests
organizationRepository.GetByIdAsync(organization.Id).Returns(organization); 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.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId; newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id; newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id;
@ -78,16 +91,15 @@ public class UpdateOrganizationUserCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UpdateUserAsync_WithFlexibleCollections_WithAccessAll_Throws( public async Task UpdateUserAsync_WithAccessAll_Throws(
Organization organization, Organization organization,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData, [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData,
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser newUserData, [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser newUserData,
[OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser, [OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser,
ICollection<CollectionAccessSelection> collections, List<CollectionAccessSelection> collections,
IEnumerable<Guid> groups, IEnumerable<Guid> groups,
SutProvider<UpdateOrganizationUserCommand> sutProvider) SutProvider<UpdateOrganizationUserCommand> sutProvider)
{ {
organization.FlexibleCollections = true;
newUserData.Id = oldUserData.Id; newUserData.Id = oldUserData.Id;
newUserData.UserId = oldUserData.UserId; newUserData.UserId = oldUserData.UserId;
newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organization.Id; newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organization.Id;

View File

@ -730,7 +730,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
} }
[Theory] [Theory]
[OrganizationCustomize(FlexibleCollections = false)]
[BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.User)] [BitAutoData(OrganizationUserType.User)]
@ -843,7 +842,7 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
[OrganizationInviteCustomize( [OrganizationInviteCustomize(
InviteeUserType = OrganizationUserType.User, InviteeUserType = OrganizationUserType.User,
InvitorUserType = OrganizationUserType.Custom InvitorUserType = OrganizationUserType.Custom
), OrganizationCustomize(FlexibleCollections = false), BitAutoData] ), OrganizationCustomize, BitAutoData]
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId, public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId,
OrganizationUser invitor, OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, [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); .ReturnsForAnyArgs(Task.FromResult(0)).AndDoes(x => organization.SmSeats += invitedSmUsers);
// Throw error at the end of the try block // 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 // OrgUser is reverted
// Note: we don't know what their guids are so comparing length is the best we can do // 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, private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser,
SutProvider<OrganizationService> sutProvider) SutProvider<OrganizationService> sutProvider)
{ {
@ -2336,6 +2315,15 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
return Task.FromResult<ICollection<Guid>>(orgUsers.Select(u => u.Id).ToList()); 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 // 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 class CollectionAccessSelectionCustomization : ICustomization
{ {
public bool Manage { get; set; } public bool Manage { get; set; }
public bool ReadOnly { get; set; }
public bool HidePasswords { get; set; }
public CollectionAccessSelectionCustomization(bool manage) public CollectionAccessSelectionCustomization(bool manage)
{ {
Manage = manage; Manage = manage;
ReadOnly = !manage;
HidePasswords = !manage;
} }
public void Customize(IFixture fixture) public void Customize(IFixture fixture)
{ {
fixture.Customize<CollectionAccessSelection>(composer => composer 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] [Theory, BitAutoData]
public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection, 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) Organization organization, SutProvider<CollectionService> sutProvider)
{ {
collection.Id = default; collection.Id = default;

View File

@ -26,63 +26,7 @@ namespace Bit.Core.Test.Services;
public class CipherServiceTests public class CipherServiceTests
{ {
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task ImportCiphersAsync_IntoOrganization_WithFlexibleCollectionsDisabled_Success( public async Task ImportCiphersAsync_IntoOrganization_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(
Organization organization, Organization organization,
Guid importingUserId, Guid importingUserId,
OrganizationUser importingOrganizationUser, OrganizationUser importingOrganizationUser,