@ -170,6 +170,7 @@ public class CipherService : ICipherService
@@ -170,6 +170,7 @@ public class CipherService : ICipherService
{
ValidateCipherLastKnownRevisionDateAsync ( cipher , lastKnownRevisionDate ) ;
cipher . RevisionDate = DateTime . UtcNow ;
await ValidateChangeInCollectionsAsync ( cipher , collectionIds , savingUserId ) ;
await ValidateViewPasswordUserAsync ( cipher ) ;
await _ cipherRepository . ReplaceAsync ( cipher ) ;
await _ eventService . LogCipherEventAsync ( cipher , Bit . Core . Enums . EventType . Cipher_Updated ) ;
@ -539,6 +540,7 @@ public class CipherService : ICipherService
@@ -539,6 +540,7 @@ public class CipherService : ICipherService
try
{
await ValidateCipherCanBeShared ( cipher , sharingUserId , organizationId , lastKnownRevisionDate ) ;
await ValidateChangeInCollectionsAsync ( cipher , collectionIds , sharingUserId ) ;
// Sproc will not save this UserId on the cipher. It is used limit scope of the collectionIds.
cipher . UserId = sharingUserId ;
@ -678,6 +680,7 @@ public class CipherService : ICipherService
@@ -678,6 +680,7 @@ public class CipherService : ICipherService
{
throw new BadRequestException ( "Cipher must belong to an organization." ) ;
}
await ValidateChangeInCollectionsAsync ( cipher , collectionIds , savingUserId ) ;
cipher . RevisionDate = DateTime . UtcNow ;
@ -820,6 +823,15 @@ public class CipherService : ICipherService
@@ -820,6 +823,15 @@ public class CipherService : ICipherService
return restoringCiphers ;
}
public async Task ValidateBulkCollectionAssignmentAsync ( IEnumerable < Guid > collectionIds , IEnumerable < Guid > cipherIds , Guid userId )
{
foreach ( var cipherId in cipherIds )
{
var cipher = await _ cipherRepository . GetByIdAsync ( cipherId ) ;
await ValidateChangeInCollectionsAsync ( cipher , collectionIds , userId ) ;
}
}
private async Task < bool > UserCanEditAsync ( Cipher cipher , Guid userId )
{
if ( ! cipher . OrganizationId . HasValue & & cipher . UserId . HasValue & & cipher . UserId . Value = = userId )
@ -1038,6 +1050,44 @@ public class CipherService : ICipherService
@@ -1038,6 +1050,44 @@ public class CipherService : ICipherService
}
}
// Validates that a cipher is not being added to a default collection when it is only currently only in shared collections
private async Task ValidateChangeInCollectionsAsync ( Cipher updatedCipher , IEnumerable < Guid > newCollectionIds , Guid userId )
{
if ( updatedCipher . Id = = Guid . Empty | | ! updatedCipher . OrganizationId . HasValue )
{
return ;
}
var currentCollectionsForCipher = await _ collectionCipherRepository . GetManyByUserIdCipherIdAsync ( userId , updatedCipher . Id ) ;
if ( ! currentCollectionsForCipher . Any ( ) )
{
// When a cipher is not currently in any collections it can be assigned to any type of collection
return ;
}
var currentCollections = await _ collectionRepository . GetManyByManyIdsAsync ( currentCollectionsForCipher . Select ( c = > c . CollectionId ) ) ;
var currentCollectionsContainDefault = currentCollections . Any ( c = > c . Type = = CollectionType . DefaultUserCollection ) ;
// When the current cipher already contains the default collection, no check is needed for if they added or removed
// a default collection, because it is already there.
if ( currentCollectionsContainDefault )
{
return ;
}
var newCollections = await _ collectionRepository . GetManyByManyIdsAsync ( newCollectionIds ) ;
var newCollectionsContainDefault = newCollections . Any ( c = > c . Type = = CollectionType . DefaultUserCollection ) ;
if ( newCollectionsContainDefault )
{
// User is trying to add the default collection when the cipher is only in shared collections
throw new BadRequestException ( "The cipher(s) cannot be assigned to a default collection when only assigned to non-default collections." ) ;
}
}
private string SerializeCipherData ( CipherData data )
{
return data switch