diff --git a/lib/api/apiUtils/authorization/permissionChecks.js b/lib/api/apiUtils/authorization/permissionChecks.js index 73a4d6167e..53ddf792c2 100644 --- a/lib/api/apiUtils/authorization/permissionChecks.js +++ b/lib/api/apiUtils/authorization/permissionChecks.js @@ -12,19 +12,20 @@ const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS function checkBucketAcls(bucket, requestType, canonicalID, mainApiCall) { // Same logic applies on the Versioned APIs, so let's simplify it. - const requestTypeParsed = requestType.endsWith('Version') - ? requestType.slice(0, -7) : requestType; + const requestTypeParsed = requestType.endsWith('Version') ? + requestType.slice(0, -7) : requestType; if (bucket.getOwner() === canonicalID) { return true; } // Backward compatibility const arrayOfAllowed = [ + 'objectGetTagging', 'objectPutTagging', 'objectPutLegalHold', 'objectPutRetention', ]; if (mainApiCall === 'objectGet') { - if (requestTypeParsed === 'objectGetTagging') { + if (arrayOfAllowed.includes(requestTypeParsed)) { return true; } } @@ -196,9 +197,9 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIs // allow public reads on buckets that are whitelisted for anonymous reads // TODO: remove this after bucket policies are implemented const bucketAcl = bucket.getAcl(); - const allowPublicReads = publicReadBuckets.includes(bucket.getName()) - && bucketAcl.Canned === 'public-read' - && (requestType === 'objectGet' || requestType === 'objectHead'); + const allowPublicReads = publicReadBuckets.includes(bucket.getName()) && + bucketAcl.Canned === 'public-read' && + (requestType === 'objectGet' || requestType === 'objectHead'); if (allowPublicReads) { return true; } @@ -355,70 +356,33 @@ function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, actionI return Object.keys(results).every(key => results[key] === true); } - -function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) { +function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) { if (!Array.isArray(requestTypes)) { // eslint-disable-next-line no-param-reassign requestTypes = [requestTypes]; } - // By default, all missing actions are defined as allowed from IAM, to be - // backward compatible - if (!actionImplicitDenies) { + if (iamAuthzResults === false) { // eslint-disable-next-line no-param-reassign - actionImplicitDenies = {}; + iamAuthzResults = {}; } + // By default, all missing actions are defined as allowed from IAM, to be + // backward compatible requestTypes.forEach(requestType => { if (actionImplicitDenies[requestType] === undefined) { // eslint-disable-next-line no-param-reassign actionImplicitDenies[requestType] = false; } }); + const results = {}; - const mainApiCall = requestTypes[0]; requestTypes.forEach(_requestType => { - const parsedMethodName = _requestType.endsWith('Version') - ? _requestType.slice(0, -7) : _requestType; - const bucketOwner = bucket.getOwner(); - if (!objectMD) { - // User is already authorized on the bucket for FULL_CONTROL or WRITE or - // bucket has canned ACL public-read-write - if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') { - results[_requestType] = actionImplicitDenies[_requestType] === false; - return; - } - // check bucket has read access - // 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions - results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, - actionImplicitDenies, log, request); - return; - } - let requesterIsNotUser = true; let arn = null; - let isUserUnauthenticated = false; if (authInfo) { - requesterIsNotUser = !authInfo.isRequesterAnIAMUser(); arn = authInfo.getArn(); - isUserUnauthenticated = arn === undefined; - } - if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) { - results[_requestType] = actionImplicitDenies[_requestType] === false; - return; } - // account is authorized if: - // - requesttype is included in bucketOwnerActions and - // - account is the bucket owner - // - requester is account, not user - if (bucketOwnerActions.includes(parsedMethodName) - && (bucketOwner === canonicalID) - && requesterIsNotUser) { - results[_requestType] = actionImplicitDenies[_requestType] === false; - return; - } - const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName, - canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall); const bucketPolicy = bucket.getBucketPolicy(); if (!bucketPolicy) { - results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission; + results[_requestType] = actionImplicitDenies[_requestType] === false; return; } const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType, @@ -433,40 +397,76 @@ function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, results[_requestType] = true; return; } - results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission; + results[_requestType] = actionImplicitDenies[_requestType] === false; }); // final result is true if all the results are true return Object.keys(results).every(key => results[key] === true); } -function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) { +function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) { if (!Array.isArray(requestTypes)) { // eslint-disable-next-line no-param-reassign requestTypes = [requestTypes]; } - if (actionImplicitDenies === false) { + // By default, all missing actions are defined as allowed from IAM, to be + // backward compatible + if (!actionImplicitDenies) { // eslint-disable-next-line no-param-reassign actionImplicitDenies = {}; } - // By default, all missing actions are defined as allowed from IAM, to be - // backward compatible requestTypes.forEach(requestType => { if (actionImplicitDenies[requestType] === undefined) { // eslint-disable-next-line no-param-reassign actionImplicitDenies[requestType] = false; } }); - const results = {}; + const mainApiCall = requestTypes[0]; requestTypes.forEach(_requestType => { + const parsedMethodName = _requestType.endsWith('Version') ? + _requestType.slice(0, -7) : _requestType; + const bucketOwner = bucket.getOwner(); + if (!objectMD) { + // User is already authorized on the bucket for FULL_CONTROL or WRITE or + // bucket has canned ACL public-read-write + if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') { + results[_requestType] = actionImplicitDenies[_requestType] === false; + return; + } + // check bucket has read access + // 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions + results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, + actionImplicitDenies, log, request); + return; + } + let requesterIsNotUser = true; let arn = null; + let isUserUnauthenticated = false; if (authInfo) { + requesterIsNotUser = !authInfo.isRequesterAnIAMUser(); arn = authInfo.getArn(); + isUserUnauthenticated = arn === undefined; + } + if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) { + results[_requestType] = actionImplicitDenies[_requestType] === false; + return; + } + // account is authorized if: + // - requesttype is included in bucketOwnerActions and + // - account is the bucket owner + // - requester is account, not user + if (bucketOwnerActions.includes(parsedMethodName) + && (bucketOwner === canonicalID) + && requesterIsNotUser) { + results[_requestType] = actionImplicitDenies[_requestType] === false; + return; } + const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName, + canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall); const bucketPolicy = bucket.getBucketPolicy(); if (!bucketPolicy) { - results[_requestType] = actionImplicitDenies[_requestType] === false; + results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission; return; } const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType, @@ -481,7 +481,7 @@ function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo results[_requestType] = true; return; } - results[_requestType] = actionImplicitDenies[_requestType] === false; + results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission; }); // final result is true if all the results are true @@ -539,10 +539,10 @@ function isLifecycleSession(arn) { module.exports = { isBucketAuthorized, + evaluateBucketPolicyWithIAM, isObjAuthorized, checkBucketAcls, checkObjectAcls, validatePolicyResource, isLifecycleSession, - evaluateBucketPolicyWithIAM, }; diff --git a/lib/api/apiUtils/object/abortMultipartUpload.js b/lib/api/apiUtils/object/abortMultipartUpload.js index 2a9c999c96..13ff44adc0 100644 --- a/lib/api/apiUtils/object/abortMultipartUpload.js +++ b/lib/api/apiUtils/object/abortMultipartUpload.js @@ -14,7 +14,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log, bucketName, objectKey, uploadId, - preciseRequestType: 'multipartDelete', + preciseRequestType: request.apiMethods || 'multipartDelete', request, }; // For validating the request at the destinationBucket level diff --git a/lib/api/bucketDelete.js b/lib/api/bucketDelete.js index 636dcff151..378bcd2e74 100644 --- a/lib/api/bucketDelete.js +++ b/lib/api/bucketDelete.js @@ -27,7 +27,7 @@ function bucketDelete(authInfo, request, log, cb) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketDelete', + requestType: request.apiMethods || 'bucketDelete', request, }; diff --git a/lib/api/bucketDeleteCors.js b/lib/api/bucketDeleteCors.js index 007c229a03..439974bb0a 100644 --- a/lib/api/bucketDeleteCors.js +++ b/lib/api/bucketDeleteCors.js @@ -33,7 +33,7 @@ function bucketDeleteCors(authInfo, request, log, callback) { } log.trace('found bucket in metadata'); - if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, + if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo, request.actionImplicitDenies, log, request)) { log.debug('access denied for user on bucket', { requestType, diff --git a/lib/api/bucketDeleteEncryption.js b/lib/api/bucketDeleteEncryption.js index 5ec5442da1..1d208e3863 100644 --- a/lib/api/bucketDeleteEncryption.js +++ b/lib/api/bucketDeleteEncryption.js @@ -21,7 +21,7 @@ function bucketDeleteEncryption(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketDeleteEncryption', + requestType: request.apiMethods || 'bucketDeleteEncryption', request, }; diff --git a/lib/api/bucketDeleteLifecycle.js b/lib/api/bucketDeleteLifecycle.js index c1e7e9fc66..893239f226 100644 --- a/lib/api/bucketDeleteLifecycle.js +++ b/lib/api/bucketDeleteLifecycle.js @@ -17,7 +17,7 @@ function bucketDeleteLifecycle(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketDeleteLifecycle', + requestType: request.apiMethods || 'bucketDeleteLifecycle', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketDeletePolicy.js b/lib/api/bucketDeletePolicy.js index 0c509af630..1796cee7c2 100644 --- a/lib/api/bucketDeletePolicy.js +++ b/lib/api/bucketDeletePolicy.js @@ -16,7 +16,7 @@ function bucketDeletePolicy(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketDeletePolicy', + requestType: request.apiMethods || 'bucketDeletePolicy', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketDeleteReplication.js b/lib/api/bucketDeleteReplication.js index 5fb58783bd..f7ce36377d 100644 --- a/lib/api/bucketDeleteReplication.js +++ b/lib/api/bucketDeleteReplication.js @@ -17,7 +17,7 @@ function bucketDeleteReplication(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketDeleteReplication', + requestType: request.apiMethods || 'bucketDeleteReplication', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketDeleteTagging.js b/lib/api/bucketDeleteTagging.js new file mode 100644 index 0000000000..f6990b6365 --- /dev/null +++ b/lib/api/bucketDeleteTagging.js @@ -0,0 +1,55 @@ +const collectCorsHeaders = require('../utilities/collectCorsHeaders'); +const { metadataValidateBucket } = require('../metadata/metadataUtils'); +const { pushMetric } = require('../utapi/utilities'); +const metadata = require('../metadata/wrapper'); +const util = require('node:util'); +const monitoring = require('../utilities/metrics'); + +/** + * Bucket Delete Tagging - Delete a bucket's Tagging + * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info + * @param {object} request - http request object + * @param {object} log - Werelogs logger + * @param {function} callback - callback to server + * @return {undefined} + */ +async function bucketDeleteTagging(authInfo, request, log, callback) { + const bucketName = request.bucketName; + let error = null; + log.debug('processing request', { method: 'bucketDeleteTagging', bucketName }); + + let bucket; + const metadataValidateBucketPromise = util.promisify(metadataValidateBucket); + let updateBucketPromise = util.promisify(metadata.updateBucket); + // necessary to bind metadata as updateBucket calls 'this', causing undefined otherwise + updateBucketPromise = updateBucketPromise.bind(metadata); + const metadataValParams = { + authInfo, + bucketName, + requestType: request.apiMethods || 'bucketDeleteTagging', + }; + + try { + bucket = await metadataValidateBucketPromise(metadataValParams, request.actionImplicitDenies, log); + bucket.setTags([]); + // eslint-disable-next-line no-unused-expressions + await updateBucketPromise(bucket.getName(), bucket, log); + pushMetric('deleteBucketTagging', log, { + authInfo, + bucket: bucketName, + }); + monitoring.promMetrics( + 'DELETE', bucketName, '200', 'deleteBucketTagging'); + } catch (err) { + error = err; + log.error('error processing request', { error: err, + method: 'deleteBucketTagging', bucketName }); + monitoring.promMetrics('DELETE', bucketName, err.code, + 'deleteBucketTagging'); + } + const corsHeaders = collectCorsHeaders(request.headers.origin, + request.method, bucket); + return callback(error, corsHeaders); +} + +module.exports = bucketDeleteTagging; diff --git a/lib/api/bucketDeleteWebsite.js b/lib/api/bucketDeleteWebsite.js index 74a0c415ca..426b3c2483 100644 --- a/lib/api/bucketDeleteWebsite.js +++ b/lib/api/bucketDeleteWebsite.js @@ -25,7 +25,7 @@ function bucketDeleteWebsite(authInfo, request, log, callback) { } log.trace('found bucket in metadata'); - if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, + if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo, request.actionImplicitDenies, log, request)) { log.debug('access denied for user on bucket', { requestType, diff --git a/lib/api/bucketGet.js b/lib/api/bucketGet.js index 674303ae54..199a63d34b 100644 --- a/lib/api/bucketGet.js +++ b/lib/api/bucketGet.js @@ -322,7 +322,7 @@ function bucketGet(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGet', + requestType: request.apiMethods || 'bucketGet', request, }; const listParams = { diff --git a/lib/api/bucketGetACL.js b/lib/api/bucketGetACL.js index 8faff12551..a4a4ac7564 100644 --- a/lib/api/bucketGetACL.js +++ b/lib/api/bucketGetACL.js @@ -43,7 +43,7 @@ function bucketGetACL(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetACL', + requestType: request.apiMethods || 'bucketGetACL', request, }; const grantInfo = { diff --git a/lib/api/bucketGetCors.js b/lib/api/bucketGetCors.js index 11b1ebf3d8..f01a344086 100644 --- a/lib/api/bucketGetCors.js +++ b/lib/api/bucketGetCors.js @@ -34,7 +34,7 @@ function bucketGetCors(authInfo, request, log, callback) { const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket); - if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, + if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo, request.actionImplicitDenies, log, request)) { log.debug('access denied for user on bucket', { requestType, diff --git a/lib/api/bucketGetEncryption.js b/lib/api/bucketGetEncryption.js index 2e6f371ba4..97fbd498f6 100644 --- a/lib/api/bucketGetEncryption.js +++ b/lib/api/bucketGetEncryption.js @@ -22,7 +22,7 @@ function bucketGetEncryption(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetEncryption', + requestType: request.apiMethods || 'bucketGetEncryption', request, }; diff --git a/lib/api/bucketGetLifecycle.js b/lib/api/bucketGetLifecycle.js index a8f8cecb29..9d4f820b1e 100644 --- a/lib/api/bucketGetLifecycle.js +++ b/lib/api/bucketGetLifecycle.js @@ -20,7 +20,7 @@ function bucketGetLifecycle(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetLifecycle', + requestType: request.apiMethods || 'bucketGetLifecycle', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketGetLocation.js b/lib/api/bucketGetLocation.js index 0b65879ece..2614b36740 100644 --- a/lib/api/bucketGetLocation.js +++ b/lib/api/bucketGetLocation.js @@ -36,7 +36,7 @@ function bucketGetLocation(authInfo, request, log, callback) { const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket); - if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, + if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo, request.actionImplicitDenies, log, request)) { log.debug('access denied for account on bucket', { requestType, diff --git a/lib/api/bucketGetNotification.js b/lib/api/bucketGetNotification.js index cf701cc361..4f59ad079d 100644 --- a/lib/api/bucketGetNotification.js +++ b/lib/api/bucketGetNotification.js @@ -37,7 +37,7 @@ function bucketGetNotification(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetNotification', + requestType: request.apiMethods || 'bucketGetNotification', request, }; diff --git a/lib/api/bucketGetObjectLock.js b/lib/api/bucketGetObjectLock.js index 9303ccffc2..0fb488e396 100644 --- a/lib/api/bucketGetObjectLock.js +++ b/lib/api/bucketGetObjectLock.js @@ -33,7 +33,7 @@ function bucketGetObjectLock(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetObjectLock', + requestType: request.apiMethods || 'bucketGetObjectLock', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketGetPolicy.js b/lib/api/bucketGetPolicy.js index 7cdd0c9a99..56b42fd261 100644 --- a/lib/api/bucketGetPolicy.js +++ b/lib/api/bucketGetPolicy.js @@ -17,7 +17,7 @@ function bucketGetPolicy(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetPolicy', + requestType: request.apiMethods || 'bucketGetPolicy', request, }; diff --git a/lib/api/bucketGetReplication.js b/lib/api/bucketGetReplication.js index 48a9cadf40..56f19bb43f 100644 --- a/lib/api/bucketGetReplication.js +++ b/lib/api/bucketGetReplication.js @@ -20,7 +20,7 @@ function bucketGetReplication(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetReplication', + requestType: request.apiMethods || 'bucketGetReplication', request, }; return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketGetTagging.js b/lib/api/bucketGetTagging.js new file mode 100644 index 0000000000..d0e9d6646e --- /dev/null +++ b/lib/api/bucketGetTagging.js @@ -0,0 +1,112 @@ +const { metadataValidateBucket } = require('../metadata/metadataUtils'); +const util = require('node:util'); +const collectCorsHeaders = require('../utilities/collectCorsHeaders'); +const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner'); +const { pushMetric } = require('../utapi/utilities'); +const monitoring = require('../utilities/metrics'); +const { errors, s3middleware } = require('arsenal'); +const escapeForXml = s3middleware.escapeForXml; + +// Sample XML response: +/* + + + + string + string + + + string + string + + + +*/ + +/** + * @typedef Tag + * @type {object} + * @property {string} Value - Value of the tag. + * @property {string} Key - Key of the tag. + */ +/** + * Convert Versioning Configuration object of a bucket into xml format. + * @param {array.} tags - set of bucket tag + * @return {string} - the converted xml string of the versioning configuration + */ +function tagsToXml(tags) { + const xml = []; + + xml.push(''); + + tags.forEach(tag => { + xml.push(''); + xml.push(`${escapeForXml(tag.Key)}`); + xml.push(`${escapeForXml(tag.Value)}`); + xml.push(''); + }); + + xml.push(''); + + return xml.join(''); +} + +/** + * bucketGetVersioning - Return Versioning Configuration for bucket + * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info + * @param {object} request - http request object + * @param {object} log - Werelogs logger + * @param {function} callback - callback to respond to http request + * with either error code or xml response body + * @return {undefined} + */ +async function bucketGetTagging(authInfo, request, log, callback) { + log.debug('processing request', { method: 'bucketGetTagging' }); + + const { bucketName, headers } = request; + const metadataValidateBucketPromise = util.promisify(metadataValidateBucket); + const checkExpectedBucketOwnerPromise = util.promisify(checkExpectedBucketOwner); + + const metadataValParams = { + authInfo, + bucketName, + requestType: request.apiMethods || 'bucketGetTagging', + request, + }; + + let bucket; + let xml = null; + + try { + bucket = await metadataValidateBucketPromise(metadataValParams, request.actionImplicitDenies, log); + // eslint-disable-next-line no-unused-expressions + await checkExpectedBucketOwnerPromise(headers, bucket, log); + const tags = bucket.getTags(); + if (!tags || !tags.length) { + log.debug('bucket TagSet does not exist', { + method: 'bucketGetTagging', + }); + throw errors.NoSuchTagSet; + } + xml = tagsToXml(tags); + pushMetric('getBucketTagging', log, { + authInfo, + bucket: bucketName, + }); + const corsHeaders = collectCorsHeaders(request.headers.origin, + request.method, bucket); + monitoring.promMetrics( + 'GET', bucketName, '200', 'getBucketTagging'); + return callback(null, xml, corsHeaders); + } catch (err) { + const corsHeaders = collectCorsHeaders(request.headers.origin, + request.method, bucket); + log.debug('error processing request', + { method: 'bucketGetTagging', error: err }); + monitoring.promMetrics('GET', bucketName, err.code, + 'getBucketTagging'); + return callback(err, corsHeaders); + } +} + +module.exports = bucketGetTagging; diff --git a/lib/api/bucketGetVersioning.js b/lib/api/bucketGetVersioning.js index 9ec1c9a1b4..abf4d88b76 100644 --- a/lib/api/bucketGetVersioning.js +++ b/lib/api/bucketGetVersioning.js @@ -53,7 +53,7 @@ function bucketGetVersioning(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketGetVersioning', + requestType: request.apiMethods || 'bucketGetVersioning', request, }; diff --git a/lib/api/bucketGetWebsite.js b/lib/api/bucketGetWebsite.js index e47e98fe48..2a15e58a1e 100644 --- a/lib/api/bucketGetWebsite.js +++ b/lib/api/bucketGetWebsite.js @@ -34,7 +34,7 @@ function bucketGetWebsite(authInfo, request, log, callback) { const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket); - if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, + if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo, request.actionImplicitDenies, log, request)) { log.debug('access denied for user on bucket', { requestType, diff --git a/lib/api/bucketHead.js b/lib/api/bucketHead.js index df33bee472..f74728b9d7 100644 --- a/lib/api/bucketHead.js +++ b/lib/api/bucketHead.js @@ -18,7 +18,7 @@ function bucketHead(authInfo, request, log, callback) { const metadataValParams = { authInfo, bucketName, - requestType: 'bucketHead', + requestType: request.apiMethods || 'bucketHead', request, }; metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => { diff --git a/lib/api/bucketPutACL.js b/lib/api/bucketPutACL.js index e62eb12db7..bdbc784a71 100644 --- a/lib/api/bucketPutACL.js +++ b/lib/api/bucketPutACL.js @@ -101,10 +101,12 @@ function bucketPutACL(authInfo, request, log, callback) { acl: newCannedACL, method: 'bucketPutACL', }); + monitoring.promMetrics('PUT', bucketName, 400, 'bucketPutACL'); return next(errors.InvalidArgument); } if (!aclUtils.checkGrantHeaderValidity(request.headers)) { log.trace('invalid acl header'); + monitoring.promMetrics('PUT', bucketName, 400, 'bucketPutACL'); return next(errors.InvalidArgument); } return next(null, bucket); diff --git a/lib/api/bucketPutTagging.js b/lib/api/bucketPutTagging.js new file mode 100644 index 0000000000..0d1442b3c1 --- /dev/null +++ b/lib/api/bucketPutTagging.js @@ -0,0 +1,83 @@ +const { waterfall } = require('async'); +const { s3middleware } = require('arsenal'); + + +const collectCorsHeaders = require('../utilities/collectCorsHeaders'); +const { metadataValidateBucket } = require('../metadata/metadataUtils'); +const metadata = require('../metadata/wrapper'); +const { pushMetric } = require('../utapi/utilities'); +const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner'); +const monitoring = require('../utilities/metrics'); +const { parseTagXml } = s3middleware.tagging; + +/** + * Format of xml request: + + + + + string + string + + + + */ + +/** + * Bucket Put Tagging - Create or update bucket Tagging + * @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info + * @param {object} request - http request object + * @param {object} log - Werelogs logger + * @param {function} callback - callback to server + * @return {undefined} + */ +function bucketPutTagging(authInfo, request, log, callback) { + log.debug('processing request', { method: 'bucketPutTagging' }); + + const { bucketName, headers } = request; + const metadataValParams = { + authInfo, + bucketName, + requestType: request.apiMethods || 'bucketPutTagging', + }; + let bucket = null; + return waterfall([ + next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, + (err, b) => { + bucket = b; + return next(err); + }), + next => checkExpectedBucketOwner(headers, bucket, log, next), + next => parseTagXml(request.post, log, next), + (tags, next) => { + const tagArray = []; + Object.keys(tags).forEach(key => { + tagArray.push({ Value: tags[key], Key: key }); + }); + bucket.setTags(tagArray); + metadata.updateBucket(bucket.getName(), bucket, log, err => + next(err)); + }, + ], err => { + const corsHeaders = collectCorsHeaders(request.headers.origin, + request.method, bucket); + if (err) { + log.debug('error processing request', { + error: err, + method: 'bucketPutTagging', + }); + monitoring.promMetrics('PUT', bucketName, err.code, + 'putBucketTagging'); + } else { + monitoring.promMetrics( + 'PUT', bucketName, '200', 'putBucketTagging'); + pushMetric('putBucketTagging', log, { + authInfo, + bucket: bucketName, + }); + } + return callback(err, corsHeaders); + }); +} + +module.exports = bucketPutTagging; diff --git a/lib/api/completeMultipartUpload.js b/lib/api/completeMultipartUpload.js index 841441e156..1c01eebbfb 100644 --- a/lib/api/completeMultipartUpload.js +++ b/lib/api/completeMultipartUpload.js @@ -118,7 +118,7 @@ function completeMultipartUpload(authInfo, request, log, callback) { bucketName, // Required permissions for this action // at the destinationBucket level are same as objectPut - requestType: 'objectPut', + requestType: request.apiMethods || 'objectPut', }; metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log, next); }, diff --git a/lib/api/initiateMultipartUpload.js b/lib/api/initiateMultipartUpload.js index 50ed82d531..58079c5912 100644 --- a/lib/api/initiateMultipartUpload.js +++ b/lib/api/initiateMultipartUpload.js @@ -92,7 +92,7 @@ function initiateMultipartUpload(authInfo, request, log, callback) { authInfo, bucketName, // Required permissions for this action are same as objectPut - requestType: 'objectPut', + requestType: request.apiMethods || 'objectPut', request, }; const accountCanonicalID = authInfo.getCanonicalID(); diff --git a/lib/api/listMultipartUploads.js b/lib/api/listMultipartUploads.js index 5f50d17650..9bdb184843 100644 --- a/lib/api/listMultipartUploads.js +++ b/lib/api/listMultipartUploads.js @@ -95,8 +95,8 @@ function listMultipartUploads(authInfo, request, log, callback) { // to list the multipart uploads so we have provided here that // the authorization to list multipart uploads is the same // as listing objects in a bucket. - requestType: 'bucketGet', - preciseRequestType: 'listMultipartUploads', + requestType: request.apiMethods || 'bucketGet', + preciseRequestType: request.apiMethods || 'listMultipartUploads', request, }; diff --git a/lib/api/listParts.js b/lib/api/listParts.js index 0a5e227482..8e2e1760e7 100644 --- a/lib/api/listParts.js +++ b/lib/api/listParts.js @@ -95,7 +95,7 @@ function listParts(authInfo, request, log, callback) { bucketName, objectKey, uploadId, - preciseRequestType: 'listParts', + preciseRequestType: request.apiMethods || 'listParts', request, }; // For validating the request at the destinationBucket level diff --git a/lib/api/multiObjectDelete.js b/lib/api/multiObjectDelete.js index 6f540ce40f..2448eaa5c8 100644 --- a/lib/api/multiObjectDelete.js +++ b/lib/api/multiObjectDelete.js @@ -203,6 +203,8 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, 'null' : versionIdUtils.decode(entry.versionId); } if (decodedVersionId instanceof Error) { + monitoring.promMetrics('DELETE', bucketName, 404, + 'multiObjectDelete'); return callback(errors.NoSuchVersion); } return callback(null, decodedVersionId); @@ -213,6 +215,8 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, versionId, log, (err, objMD) => { // if general error from metadata return error if (err) { + monitoring.promMetrics('DELETE', bucketName, err.code, + 'multiObjectDelete'); return callback(err); } if (!objMD) { @@ -280,14 +284,16 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, return callback(null, objMD, versionId); }, - (objMD, versionId, callback) => - preprocessingVersioningDelete(bucketName, bucket, objMD, - versionId, log, (err, options) => callback(err, options, - objMD)), - (options, objMD, callback) => { + (objMD, versionId, callback) => { + const options = preprocessingVersioningDelete( + bucketName, bucket, objMD, versionId, config.nullVersionCompatMode); const deleteInfo = {}; if (options && options.deleteData) { deleteInfo.deleted = true; + if (objMD.uploadId) { + // eslint-disable-next-line + options.replayId = objMD.uploadId; + } return services.deleteObject(bucketName, objMD, entry.key, options, log, err => callback(err, objMD, deleteInfo)); @@ -365,11 +371,15 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request, function multiObjectDelete(authInfo, request, log, callback) { log.debug('processing request', { method: 'multiObjectDelete' }); if (!request.post) { + monitoring.promMetrics('DELETE', request.bucketName, 400, + 'multiObjectDelete'); return callback(errors.MissingRequestBodyError); } const md5 = crypto.createHash('md5') .update(request.post, 'utf8').digest('base64'); if (md5 !== request.headers['content-md5']) { + monitoring.promMetrics('DELETE', request.bucketName, 400, + 'multiObjectDelete'); return callback(errors.BadDigest); } @@ -545,6 +555,8 @@ function multiObjectDelete(authInfo, request, log, callback) { const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket); if (err) { + monitoring.promMetrics('DELETE', bucketName, err.code, + 'multiObjectDelete'); return callback(err, null, corsHeaders); } const xml = _formatXML(quietSetting, errorResults, @@ -563,6 +575,10 @@ function multiObjectDelete(authInfo, request, log, callback) { removedDeleteMarkers, isDelete: true, }); + monitoring.promMetrics('DELETE', bucketName, '200', + 'multiObjectDelete', + Number.parseInt(totalContentLengthDeleted, 10), null, null, + numOfObjectsRemoved); return callback(null, xml, corsHeaders); }); } @@ -570,4 +586,4 @@ function multiObjectDelete(authInfo, request, log, callback) { module.exports = { getObjMetadataAndDelete, multiObjectDelete, -}; +}; \ No newline at end of file diff --git a/lib/api/objectDelete.js b/lib/api/objectDelete.js index ee47a83cd5..cd278581b5 100644 --- a/lib/api/objectDelete.js +++ b/lib/api/objectDelete.js @@ -49,7 +49,7 @@ function objectDelete(authInfo, request, log, cb) { bucketName, objectKey, versionId: reqVersionId, - requestType: 'objectDelete', + requestType: request.apiMethods || 'objectDelete', request, }; diff --git a/lib/api/objectDeleteTagging.js b/lib/api/objectDeleteTagging.js index f9aa5ad809..3ce8233406 100644 --- a/lib/api/objectDeleteTagging.js +++ b/lib/api/objectDeleteTagging.js @@ -40,8 +40,9 @@ function objectDeleteTagging(authInfo, request, log, callback) { authInfo, bucketName, objectKey, - requestType: 'objectDeleteTagging', versionId: reqVersionId, + getDeleteMarker: true, + requestType: request.apiMethods || 'objectDeleteTagging', request, }; diff --git a/lib/api/objectGet.js b/lib/api/objectGet.js index 75c7bd13b6..e03eef919a 100644 --- a/lib/api/objectGet.js +++ b/lib/api/objectGet.js @@ -44,7 +44,8 @@ function objectGet(authInfo, request, returnTagCount, log, callback) { bucketName, objectKey, versionId, - requestType: 'objectGet', + getDeleteMarker: true, + requestType: request.apiMethods || 'objectGet', request, }; diff --git a/lib/api/objectGetACL.js b/lib/api/objectGetACL.js index d96f0a62d3..274d10ad30 100644 --- a/lib/api/objectGetACL.js +++ b/lib/api/objectGetACL.js @@ -58,7 +58,7 @@ function objectGetACL(authInfo, request, log, callback) { bucketName, objectKey, versionId, - requestType: 'objectGetACL', + requestType: request.apiMethods || 'objectGetACL', request, }; const grantInfo = { diff --git a/lib/api/objectGetLegalHold.js b/lib/api/objectGetLegalHold.js index a450314a2b..8a76fb0c4c 100644 --- a/lib/api/objectGetLegalHold.js +++ b/lib/api/objectGetLegalHold.js @@ -37,8 +37,8 @@ function objectGetLegalHold(authInfo, request, log, callback) { authInfo, bucketName, objectKey, - requestType: 'objectGetLegalHold', versionId, + requestType: request.apiMethods || 'objectGetLegalHold', request, }; diff --git a/lib/api/objectGetRetention.js b/lib/api/objectGetRetention.js index 825843ac4b..a9111c718f 100644 --- a/lib/api/objectGetRetention.js +++ b/lib/api/objectGetRetention.js @@ -37,8 +37,8 @@ function objectGetRetention(authInfo, request, log, callback) { authInfo, bucketName, objectKey, - requestType: 'objectGetRetention', versionId: reqVersionId, + requestType: request.apiMethods || 'objectGetRetention', request, }; diff --git a/lib/api/objectGetTagging.js b/lib/api/objectGetTagging.js index 98657156a0..22cdb0fb9b 100644 --- a/lib/api/objectGetTagging.js +++ b/lib/api/objectGetTagging.js @@ -37,11 +37,10 @@ function objectGetTagging(authInfo, request, log, callback) { authInfo, bucketName, objectKey, - requestType: 'objectGetTagging', versionId: reqVersionId, + requestType: request.apiMethods || 'objectGetTagging', request, }; - return async.waterfall([ next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log, (err, bucket, objectMD) => { diff --git a/lib/api/objectHead.js b/lib/api/objectHead.js index 38977bad88..d57d65b630 100644 --- a/lib/api/objectHead.js +++ b/lib/api/objectHead.js @@ -44,7 +44,8 @@ function objectHead(authInfo, request, log, callback) { bucketName, objectKey, versionId, - requestType: 'objectHead', + getDeleteMarker: true, + requestType: request.apiMethods || 'objectHead', request, }; diff --git a/lib/api/objectPutACL.js b/lib/api/objectPutACL.js index d0bb3b3fd8..389b83a4fa 100644 --- a/lib/api/objectPutACL.js +++ b/lib/api/objectPutACL.js @@ -82,6 +82,7 @@ function objectPutACL(authInfo, request, log, cb) { bucketName, objectKey, versionId: reqVersionId, + getDeleteMarker: true, requestType: request.apiMethods || 'objectPutACL', }; diff --git a/lib/api/objectPutCopyPart.js b/lib/api/objectPutCopyPart.js index 5572c4149d..1c8fe9fbc9 100644 --- a/lib/api/objectPutCopyPart.js +++ b/lib/api/objectPutCopyPart.js @@ -43,6 +43,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket, bucketName: sourceBucket, objectKey: sourceObject, versionId: reqVersionId, + getDeleteMarker: true, requestType: 'objectGet', request, }; @@ -58,7 +59,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket, // Note that keys in the query object retain their case, so // request.query.uploadId must be called with that exact // capitalization - const { uploadId } = request.query; + const uploadId = request.query.uploadId; const valPutParams = { authInfo, bucketName: destBucketName, diff --git a/lib/api/objectPutLegalHold.js b/lib/api/objectPutLegalHold.js index 7f04d4197f..51d5e05ef0 100644 --- a/lib/api/objectPutLegalHold.js +++ b/lib/api/objectPutLegalHold.js @@ -41,6 +41,7 @@ function objectPutLegalHold(authInfo, request, log, callback) { bucketName, objectKey, versionId, + getDeleteMarker: true, requestType: request.apiMethods || 'objectPutLegalHold', request, }; diff --git a/lib/api/objectPutRetention.js b/lib/api/objectPutRetention.js index 8084e26438..41d4072bd3 100644 --- a/lib/api/objectPutRetention.js +++ b/lib/api/objectPutRetention.js @@ -42,6 +42,7 @@ function objectPutRetention(authInfo, request, log, callback) { bucketName, objectKey, versionId: reqVersionId, + getDeleteMarker: true, requestType: request.apiMethods || 'objectPutRetention', request, }; diff --git a/lib/api/objectPutTagging.js b/lib/api/objectPutTagging.js index 8e857926d6..8db01cfda1 100644 --- a/lib/api/objectPutTagging.js +++ b/lib/api/objectPutTagging.js @@ -42,6 +42,7 @@ function objectPutTagging(authInfo, request, log, callback) { bucketName, objectKey, versionId: reqVersionId, + getDeleteMarker: true, requestType: request.apiMethods || 'objectPutTagging', request, }; diff --git a/lib/api/websiteGet.js b/lib/api/websiteGet.js index 0bbfbbedba..9c88e4f03a 100644 --- a/lib/api/websiteGet.js +++ b/lib/api/websiteGet.js @@ -47,7 +47,7 @@ function _errorActions(err, errorDocument, routingRules, } // return the default error message if the object is private // rather than sending a stored error file - if (!isObjAuthorized(bucket, errObjMD, 'objectGet', + if (!isObjAuthorized(bucket, errObjMD, request.apiMethods || 'objectGet', constants.publicId, null, request.actionImplicitDenies, log)) { log.trace('errorObj not authorized', { error: err }); return callback(err, true, null, corsHeaders); @@ -144,8 +144,8 @@ function websiteGet(request, log, callback) { log.trace('error retrieving object metadata', { error: err }); let returnErr = err; - const bucketAuthorized = isBucketAuthorized(bucket, - 'bucketGet', constants.publicId, null, request.actionImplicitDenies, log, request); + const bucketAuthorized = isBucketAuthorized(bucket, request.apiMethods || 'bucketGet', + constants.publicId, null, request.actionImplicitDenies, log, request); // if index object does not exist and bucket is private AWS // returns 403 - AccessDenied error. if (err.is.NoSuchKey && !bucketAuthorized) { @@ -156,7 +156,7 @@ function websiteGet(request, log, callback) { bucket, reqObjectKey, corsHeaders, request, log, callback); } - if (!isObjAuthorized(bucket, objMD, 'objectGet', + if (!isObjAuthorized(bucket, objMD, request.apiMethods || 'objectGet', constants.publicId, null, request.actionImplicitDenies, log, request)) { const err = errors.AccessDenied; log.trace('request not authorized', { error: err }); diff --git a/lib/api/websiteHead.js b/lib/api/websiteHead.js index 493ce8dad6..31684b460a 100644 --- a/lib/api/websiteHead.js +++ b/lib/api/websiteHead.js @@ -103,8 +103,8 @@ function websiteHead(request, log, callback) { log.trace('error retrieving object metadata', { error: err }); let returnErr = err; - const bucketAuthorized = isBucketAuthorized(bucket, - 'bucketGet', constants.publicId, null, request.actionImplicitDenies, log, request); + const bucketAuthorized = isBucketAuthorized(bucket, request.apiMethods || 'bucketGet', + constants.publicId, null, request.actionImplicitDenies, log, request); // if index object does not exist and bucket is private AWS // returns 403 - AccessDenied error. if (err.is.NoSuchKey && !bucketAuthorized) { @@ -113,7 +113,7 @@ function websiteHead(request, log, callback) { return _errorActions(returnErr, routingRules, reqObjectKey, corsHeaders, log, callback); } - if (!isObjAuthorized(bucket, objMD, 'objectGet', + if (!isObjAuthorized(bucket, objMD, request.apiMethods || 'objectGet', constants.publicId, null, request.actionImplicitDenies, log, request)) { const err = errors.AccessDenied; log.trace('request not authorized', { error: err }); diff --git a/lib/metadata/metadataUtils.js b/lib/metadata/metadataUtils.js index 1bdee2169e..186beec154 100644 --- a/lib/metadata/metadataUtils.js +++ b/lib/metadata/metadataUtils.js @@ -110,6 +110,25 @@ function metadataGetObject(bucketName, objectKey, versionId, log, cb) { }); } +/** + * Validate that a bucket is accessible and authorized to the user, + * return a specific error code otherwise + * + * @param {BucketInfo} bucket - bucket info + * @param {object} params - function parameters + * @param {AuthInfo} params.authInfo - AuthInfo class instance, requester's info + * @param {string} params.requestType - type of request + * @param {string} [params.preciseRequestType] - precise type of request + * @param {object} params.request - http request object + * @param {object} actionImplicitDenies - authorization results showing if an action is implictly denied + * @param {RequestLogger} log - request logger + * @return {ArsenalError|null} returns a validation error, or null if validation OK + * The following errors may be returned: + * - NoSuchBucket: bucket is shielded + * - MethodNotAllowed: requester is not bucket owner and asking for a + * bucket policy operation + * - AccessDenied: bucket is not authorized + */ function validateBucket(bucket, params, actionImplicitDenies, log) { const { authInfo, preciseRequestType, request } = params; let requestType = params.requestType; @@ -154,25 +173,40 @@ function validateBucket(bucket, params, actionImplicitDenies, log) { * @return {undefined} - and call callback with params err, bucket md */ function metadataValidateBucketAndObj(params, actionImplicitDenies, log, callback) { - const { authInfo, bucketName, objectKey, versionId, request } = params; + const { authInfo, bucketName, objectKey, versionId, getDeleteMarker, request } = params; let requestType = params.requestType; if (!Array.isArray(requestType)) { requestType = [requestType]; } async.waterfall([ - next => metadataGetBucketAndObject(bucketName, - objectKey, versionId, log, (err, bucket, objMD) => { - if (err) { - // if some implicit actionImplicitDenies, return AccessDenied - // before leaking any state information - if (actionImplicitDenies && Object.values(actionImplicitDenies).some(v => v === true)) { - return next(errors.AccessDenied); - } - return next(err); + next => { + // versionId may be 'null', which asks metadata to fetch the null key specifically + const getOptions = { versionId }; + if (getDeleteMarker) { + getOptions.getDeleteMarker = true; + } + return metadata.getBucketAndObjectMD(bucketName, objectKey, getOptions, log, (err, getResult) => { + if (err) { + // if some implicit actionImplicitDenies, return AccessDenied + // before leaking any state information + if (actionImplicitDenies && Object.values(actionImplicitDenies).some(v => v === true)) { + return next(errors.AccessDenied); } - return next(null, bucket, objMD); - }), - (bucket, objMD, next) => { + return next(err); + } + return next(null, getResult); + }); + }, + (getResult, next) => { + const bucket = getResult.bucket ? + BucketInfo.deSerialize(getResult.bucket) : undefined; + if (!bucket) { + log.debug('bucketAttrs is undefined', { + bucket: bucketName, + method: 'metadataValidateBucketAndObj', + }); + return next(errors.NoSuchBucket); + } const validationError = validateBucket(bucket, params, actionImplicitDenies, log); if (validationError) { return next(validationError, bucket); @@ -240,8 +274,8 @@ function metadataGetBucket(requestType, bucketName, log, cb) { * @return {undefined} - and call callback with params err, bucket md */ function metadataValidateBucket(params, actionImplicitDenies, log, callback) { - const { bucketName, requestType } = params; - return metadataGetBucket(requestType, bucketName, log, (err, bucket) => { + const { bucketName } = params; + return metadata.getBucket(bucketName, log, (err, bucket) => { if (err) { return callback(err); } diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 60016ee9a2..d21be91c73 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -1272,7 +1272,7 @@ function routeBackbeat(clientIP, request, response, log) { objectKey: request.objectKey, authInfo: userInfo, versionId, - requestType: 'ReplicateObject', + requestType: request.apiMethods || 'ReplicateObject', request, }; return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log, next); diff --git a/tests/unit/api/bucketPutACL.js b/tests/unit/api/bucketPutACL.js index 29e629fd17..6c3a76ad5e 100644 --- a/tests/unit/api/bucketPutACL.js +++ b/tests/unit/api/bucketPutACL.js @@ -437,30 +437,6 @@ describe('putBucketACL API', () => { }); }); - it('should return an error if multiple AccessControlList section', done => { - const testACLRequest = { - bucketName, - namespace, - headers: { host: `${bucketName}.s3.amazonaws.com` }, - post: '' + - '' + - '79a59df900b949e55d96a1e698fbaced' + - 'fd6e09d98eacf8f8d5218e7cd47ef2be' + - 'OwnerDisplayName' + - '' + - '', - url: '/?acl', - query: { acl: '' }, - actionImplicitDenies: false, - }; - - bucketPutACL(authInfo, testACLRequest, log, err => { - assert.deepStrictEqual(err, errors.MalformedACLError); - done(); - }); - }); - it('should return an error if multiple AccessControlList section', done => { const testACLRequest = { bucketName, @@ -496,7 +472,7 @@ describe('putBucketACL API', () => { + '', url: '/?acl', query: { acl: '' }, - iamAuthzResults: false, + actionImplicitDenies: false, }; bucketPutACL(authInfo, testACLRequest, log, err => {