@@ -424,6 +424,59 @@ private async Task<bool> CanAccessUnassignedCiphersAsync(Guid organizationId)
424
424
return false ;
425
425
}
426
426
427
+ /// <summary>
428
+ /// TODO: Move this to its own authorization handler or equivalent service - AC-2062
429
+ /// </summary>
430
+ private async Task < bool > CanModifyCipherCollectionsAsync ( Guid organizationId , IEnumerable < Guid > cipherIds )
431
+ {
432
+ // If the user can edit all ciphers for the organization, just check they all belong to the org
433
+ if ( await CanEditAllCiphersAsync ( organizationId ) )
434
+ {
435
+ // TODO: This can likely be optimized to only query the requested ciphers and then checking they belong to the org
436
+ var orgCiphers = ( await _cipherRepository . GetManyByOrganizationIdAsync ( organizationId ) ) . ToDictionary ( c => c . Id ) ;
437
+
438
+ // Ensure all requested ciphers are in orgCiphers
439
+ if ( cipherIds . Any ( c => ! orgCiphers . ContainsKey ( c ) ) )
440
+ {
441
+ return false ;
442
+ }
443
+
444
+ return true ;
445
+ }
446
+
447
+ // The user cannot access any ciphers for the organization, we're done
448
+ if ( ! await CanAccessOrganizationCiphersAsync ( organizationId ) )
449
+ {
450
+ return false ;
451
+ }
452
+
453
+ var userId = _userService . GetProperUserId ( User ) . Value ;
454
+ // Select all editable ciphers for this user belonging to the organization
455
+ var editableOrgCipherList = ( await _cipherRepository . GetManyByUserIdAsync ( userId , true ) )
456
+ . Where ( c => c . OrganizationId == organizationId && c . UserId == null && c . Edit && c . ViewPassword ) . ToList ( ) ;
457
+
458
+ // Special case for unassigned ciphers
459
+ if ( await CanAccessUnassignedCiphersAsync ( organizationId ) )
460
+ {
461
+ var unassignedCiphers =
462
+ ( await _cipherRepository . GetManyUnassignedOrganizationDetailsByOrganizationIdAsync (
463
+ organizationId ) ) ;
464
+
465
+ // Users that can access unassigned ciphers can also edit them
466
+ editableOrgCipherList . AddRange ( unassignedCiphers . Select ( c => new CipherDetails ( c ) { Edit = true } ) ) ;
467
+ }
468
+
469
+ var editableOrgCiphers = editableOrgCipherList
470
+ . ToDictionary ( c => c . Id ) ;
471
+
472
+ if ( cipherIds . Any ( c => ! editableOrgCiphers . ContainsKey ( c ) ) )
473
+ {
474
+ return false ;
475
+ }
476
+
477
+ return true ;
478
+ }
479
+
427
480
/// <summary>
428
481
/// TODO: Move this to its own authorization handler or equivalent service - AC-2062
429
482
/// </summary>
@@ -579,7 +632,7 @@ public async Task<OptionalCipherDetailsResponseModel> PutCollections_vNext(Guid
579
632
var userId = _userService . GetProperUserId ( User ) . Value ;
580
633
var cipher = await GetByIdAsync ( id , userId ) ;
581
634
if ( cipher == null || ! cipher . OrganizationId . HasValue ||
582
- ! await _currentContext . OrganizationUser ( cipher . OrganizationId . Value ) )
635
+ ! await _currentContext . OrganizationUser ( cipher . OrganizationId . Value ) || ! cipher . ViewPassword )
583
636
{
584
637
throw new NotFoundException ( ) ;
585
638
}
@@ -634,7 +687,7 @@ public async Task<CipherMiniDetailsResponseModel> PutCollectionsAdmin(string id,
634
687
[ HttpPost ( "bulk-collections" ) ]
635
688
public async Task PostBulkCollections ( [ FromBody ] CipherBulkUpdateCollectionsRequestModel model )
636
689
{
637
- if ( ! await CanEditCiphersAsync ( model . OrganizationId , model . CipherIds ) ||
690
+ if ( ! await CanModifyCipherCollectionsAsync ( model . OrganizationId , model . CipherIds ) ||
638
691
! await CanEditItemsInCollections ( model . OrganizationId , model . CollectionIds ) )
639
692
{
640
693
throw new NotFoundException ( ) ;
0 commit comments