@@ -14,7 +14,6 @@ const {
1414 PutObjectAclCommand,
1515 GetBucketPolicyCommand,
1616 PutBucketPolicyCommand,
17- DeleteBucketPolicyCommand,
1817 PutObjectCommand,
1918 PutBucketEncryptionCommand,
2019 PutObjectTaggingCommand,
@@ -479,42 +478,74 @@ const objectStorageService = {
479478 * for an object
480479 * will also remove this type of ACL set by other applications
481480 */
482- async removePublicACL ( data , key ) {
483- const existingAcl = await this . _getS3Client ( data ) . send ( new GetObjectAclCommand ( {
484- Bucket : data . bucket ,
485- Key : key
486- } ) ) ;
487- const existingGrants = existingAcl . Grants ;
488- const hasPublicAcl = existingGrants . some ( grant => grant . Grantee ?. URI === ALLUSERS && grant . Permission === 'READ' ) ;
489- if ( hasPublicAcl ) {
490- await this . _getS3Client ( data ) . send ( new PutObjectAclCommand ( {
491- AccessControlPolicy : {
492- Grants : existingGrants
493- . filter ( grant => grant . Grantee ?. URI !== ALLUSERS && grant . Permission !== 'READ' )
494- . map ( grant => {
495- return {
496- Grantee : grant . Grantee ,
497- Permission : grant . Permission ,
498- } ;
499- } ) ,
500- Owner : existingAcl . Owner
501- } ,
502- Bucket : data . bucket ,
503- Key : key ,
504- } ) ) ;
505- return true ;
481+ async removePublicACL ( bucketId , path , recursive = false ) {
482+ try {
483+ const data = await utils . getBucket ( bucketId ) ;
484+ const key = utils . stripDelimit ( path ) ;
485+ let objects = [ ] ;
486+
487+ // list all objects at and below provided path (key)
488+ if ( recursive ) {
489+ const s3Response = await this . listAllObjectVersions ( { filePath : key , bucketId : bucketId , precisePath : false } ) ;
490+ `Found ${ s3Response . Versions . length } object versions and ${ s3Response . DeleteMarkers . length }
491+ delete markers in S3 for bucketId ${ data . bucketId } ` , { function : 'syncBucketRecursive' } ) ;
492+ const s3Keys = [ ...new Set ( [
493+ ...s3Response . DeleteMarkers . map ( object => object . Key ) ,
494+ ...s3Response . Versions . map ( object => object . Key ) ,
495+ ] ) ] ;
496+ objects = s3Keys
497+ } else {
498+ objects = [ key ] ;
499+ }
500+ log . info ( `Removing ACL's from ${ objects . length } objects` , { function : 'removePublicACL' } ) ;
501+
502+ // for each object update existing ACL to remove READ on ALLUSERS
503+ for ( const objKey of objects ) {
504+ const existingAcl = await this . _getS3Client ( data ) . send ( new GetObjectAclCommand ( {
505+ Bucket : data . bucket ,
506+ Key : objKey
507+ } ) ) ;
508+ const existingGrants = existingAcl . Grants ;
509+ const hasPublicAcl = existingGrants . some (
510+ grant => grant . Grantee ?. URI === ALLUSERS && grant . Permission === 'READ'
511+ ) ;
512+ if ( hasPublicAcl ) {
513+ await this . _getS3Client ( data ) . send ( new PutObjectAclCommand ( {
514+ AccessControlPolicy : {
515+ Grants : existingGrants
516+ . filter ( grant => grant . Grantee ?. URI !== ALLUSERS && grant . Permission !== 'READ' )
517+ . map ( grant => {
518+ return {
519+ Grantee : grant . Grantee ,
520+ Permission : grant . Permission ,
521+ } ;
522+ } ) ,
523+ Owner : existingAcl . Owner
524+ } ,
525+ Bucket : data . bucket ,
526+ Key : objKey ,
527+ } ) ) ;
528+ return true ;
529+ }
530+ }
531+ } catch ( error ) {
532+ console . log ( 'Error in removePublicACL:' , error ) ;
533+ throw error ;
506534 }
507535 } ,
508536
509537 /**
510538 * @function updatePublic
511539 * Updates the S3 Bucket Policies for the given resource
540+ * any non-COMS Bucket Policies (Sid starts with `coms::`) are preserved
541+ * NOTE: This function will remove legacy ACL's for all resources at or below path when setting to private
542+ * to ensure objects are not inadvertently left public due to ACL settings
512543 * @param {string } path the path of the resource
513544 * @param {boolean } whether given resource should be set to public
514545 * @param {string } bucketId of COMS bucket for the resource
515546 * @returns {Promise<object> } The S3 PutBucketPolicyCommand response
516547 */
517- async updatePublic ( { path, public : publicFlag = false , bucketId } ) {
548+ async updatePublic ( { path, public : setAsPublic = false , bucketId } ) {
518549 const data = await utils . getBucket ( bucketId ) ;
519550 const isPrefix = data . key + '/' === path ;
520551 const resource = data . bucket + '/' + path ;
@@ -529,21 +560,22 @@ const objectStorageService = {
529560 log . debug ( 'No existing policy found' , { function : 'updatePublic' } ) ;
530561 }
531562
532- // If COMS policy found for any parent prefix, throw error. no exceptions to inherited policies permitted
563+ // If COMS policy found for any parent prefix, throw error, exceptions from inherited policies are not permitted
533564 const parentPolicy = existingStatement . find ( policy =>
534- `coms::${ resource } ` . startsWith ( policy . Sid ) && `coms::${ resource } ` !== policy . Sid ) ;
565+ `coms::${ resource } ` . startsWith ( policy . Sid ) &&
566+ `coms::${ resource } ` !== policy . Sid ) ;
535567 if ( parentPolicy ) {
568+ console . log ( 'Parent policy found, cannot override public status set on parent folder' ) ;
536569 throw new Error ( `Unable to override Public status set on folder: ${ parentPolicy . Resource } ` ) ;
537570 }
538571
539572 // --- Update policy
540- // if making public add Allow rule for this resource
541- if ( publicFlag ) {
542-
543- // include any existing non-coms-managed rules for this resource or below
544- const newStatement = existingStatement
545- . filter ( policy => ! policy . Sid . startsWith ( `coms::${ resource } ` ) ) ;
546-
573+ // keep non-coms and sibling policies
574+ let newStatement = existingStatement . filter ( policy => {
575+ return ! policy . Sid . startsWith ( utils . stripResourcePath ( `coms::${ resource } ` ) ) ;
576+ } ) ;
577+ // if making public add Allow rule for this resource (and below)
578+ if ( setAsPublic ) {
547579 const resourceKey = isPrefix ? resource + '*' : resource ; // prefixes/need/trailing/wildcard/*
548580 newStatement
549581 . push ( {
@@ -553,28 +585,17 @@ const objectStorageService = {
553585 Principal : '*' ,
554586 Sid : 'coms::' + resource ,
555587 } ) ;
556-
557- // put new policy
558- if ( newStatement . length > 0 ) {
559- s3Response = await this . _getS3Client ( data ) . send ( new PutBucketPolicyCommand ( {
560- Bucket : data . bucket ,
561- Policy : JSON . stringify ( { Version : '2012-10-17' , Statement : newStatement } )
562- } ) ) ;
563- }
564588 }
565-
566- // else setting to private
567- // note: logic is confusing. But here, if setting to private, we are only deleting a COMS policy
568- else if ( existingStatement . length > 0 ) {
569- await this . _getS3Client ( data ) . send ( new DeleteBucketPolicyCommand ( { Bucket : data . bucket } ) ) ;
570- s3Response = [ ] ;
589+ if ( newStatement . length > 0 ) {
590+ s3Response = await this . _getS3Client ( data ) . send ( new PutBucketPolicyCommand ( {
591+ Bucket : data . bucket ,
592+ Policy : JSON . stringify ( { Version : '2012-10-17' , Statement : newStatement } )
593+ } ) ) ;
571594 }
572595
573- // If updating a single file,
574- // Remove public ACL for file if present (phase out previous implementation)
575- if ( isPrefix === false ) {
576- await this . removePublicACL ( data , path ) ;
577- }
596+ // -- remove all ACL's for this resource and below as precaution to user
597+ // if updating a single object or setting a folder private,
598+ if ( ! isPrefix || ! setAsPublic ) await this . removePublicACL ( bucketId , path , isPrefix ) ;
578599
579600 return s3Response ;
580601 } ,
0 commit comments