Compare commits

...

50 Commits

Author SHA1 Message Date
williamlardier 975498e913
wip 2023-09-21 10:56:58 +02:00
williamlardier 4e24fbc6e8
wip 2023-09-21 10:56:42 +02:00
williamlardier 4e4d37d161
checkpoint 2023-09-20 19:22:31 +02:00
williamlardier d272c2dfab
checkpoint 2023-09-20 19:18:00 +02:00
williamlardier fd12d5c016
wip 2023-09-20 19:07:10 +02:00
williamlardier 4ebccd294d
wip 2023-09-20 18:51:57 +02:00
williamlardier 64c384056c
testing 2023-09-20 18:51:47 +02:00
williamlardier c76cbc92bd
testing 2023-09-20 18:51:13 +02:00
williamlardier 083083b64b
checkpoint 2023-09-20 18:19:05 +02:00
williamlardier b05ac4d63d
test bb 2023-09-20 17:07:47 +02:00
williamlardier e3ca1d1326
wip 2023-09-20 16:46:12 +02:00
williamlardier 573c460454
wip 2023-09-20 16:42:18 +02:00
williamlardier 238393e6d7
Handle implcit deny results from IAM.
The API is started if IAM returns Allow or an implicit Deny.
In these cases, we add a new boolean to the request that
serves as a context when checking the Bucket/Object ACL or
the Bucket policies. Then, we implement the same authorization
logic as AWS, where an implicit deny from IAM and an Allow from
the Bucket Policy should allow the request.
2023-09-20 15:30:34 +02:00
Will Toozs c82ce08fdb
permission fix: no allow on user from account by default 2023-09-19 11:00:34 +02:00
Will Toozs 9bf9130ca9
change variable name 2023-09-18 18:38:27 +02:00
Will Toozs b4a986ec3a
fixup: remove skips 2023-09-18 18:38:27 +02:00
Will Toozs 8725888bbd
fixup: remove skips 2023-09-18 18:38:27 +02:00
Will Toozs 111ecf875b
CLDSRV-431: updatye other API impDeny test logic 2023-09-18 18:38:27 +02:00
Will Toozs 15ddc02c25
CLDSRV-431: implment other API impDeny logic 2023-09-18 18:38:26 +02:00
Will Toozs 482e6576b1
variable name change 2023-09-18 18:34:16 +02:00
Will Toozs 5f3fa488d5
fixup: skips 2023-09-18 18:34:16 +02:00
Will Toozs e035d5550b
fixup: skips 2023-09-18 18:34:15 +02:00
Will Toozs b119898ed1
CLDSRV-429: update get apis tests with impDenylogic 2023-09-18 18:34:15 +02:00
Will Toozs e9d5c17707
CLDSRV-429: update get apis with impDeny logic 2023-09-18 18:34:15 +02:00
Will Toozs a7ce187499
update variable name 2023-09-18 18:31:12 +02:00
Will Toozs 6f7b2d6638
fixup: skips 2023-09-18 18:31:12 +02:00
Will Toozs f10cacb033
CLDSRV-430: update delete API tests for impDeny logic 2023-09-18 18:31:11 +02:00
Will Toozs 9bf18d806a
CLDSRV-430: add delete API implicit deny logic 2023-09-18 18:31:11 +02:00
Will Toozs dfa80a41ed
update variable name 2023-09-18 18:28:35 +02:00
Will Toozs 9f753af13a
lint 2023-09-18 18:28:35 +02:00
Will Toozs c214bb8ee1
lint 2023-09-18 18:28:34 +02:00
Will Toozs 3fcd479639
test skips 2023-09-18 18:28:34 +02:00
Will Toozs 1bb2f11f6c
lint 2023-09-18 18:28:34 +02:00
Will Toozs bfe9d34547
skip irrelevant apis 2023-09-18 18:28:33 +02:00
Will Toozs cc8f8a7213
api updates 2023-09-18 18:28:29 +02:00
Will Toozs f53a8ea8c7
linting 2023-09-18 18:25:10 +02:00
Will Toozs 1d55be193a
fixup: missing constants change 2023-09-18 18:25:09 +02:00
Will Toozs 00ba0d9377
CLDSRV-428: put apis updated for implicit deny 2023-09-18 18:25:09 +02:00
Will Toozs fe6578b276
change variable name 2023-09-18 18:24:09 +02:00
Will Toozs da526b5092
fixup: retrocompatibility changes 2023-09-18 18:24:09 +02:00
Will Toozs 159906dc74
fixup: retrocompatibility changes 2023-09-18 18:24:09 +02:00
Will Toozs 3ff2b5e9ed
fixup: retrocompatibility changes 2023-09-18 18:24:08 +02:00
Will Toozs f69f72363b
fixup: retrocompatibility changes 2023-09-18 18:24:08 +02:00
Will Toozs 7f9562bfda
fixup: lint 2023-09-18 18:24:08 +02:00
Will Toozs c01898f1a0
CLDSRV-427: update bucket/object perm checks to account for implicit …
…denies
2023-09-18 18:24:08 +02:00
Will Toozs 0f64e7c337
CLDSRV-426: add tests for ACL permission check updates 2023-09-18 18:23:45 +02:00
Will Toozs de334d16e9
CLDSRV-426: update ACL permission checks for implicitDeny logic 2023-09-18 18:23:44 +02:00
Will Toozs e99bb1e1cc
change variable names for clarity 2023-09-15 17:52:03 +02:00
Will Toozs cd6320c432
edit: update arsenal package 2023-09-12 11:14:45 +02:00
Will Toozs 3bc66f8cea
CLDSRV-424: api call updated with implicit deny logic 2023-09-11 18:05:21 +02:00
130 changed files with 2616 additions and 1550 deletions

View File

@ -153,6 +153,8 @@ const constants = {
'objectDeleteTagging', 'objectDeleteTagging',
'objectGetTagging', 'objectGetTagging',
'objectPutTagging', 'objectPutTagging',
'objectPutLegalHold',
'objectPutRetention',
], ],
// response header to be sent when there are invalid // response header to be sent when there are invalid
// user metadata in the object's metadata // user metadata in the object's metadata

View File

@ -107,6 +107,7 @@ const api = {
// no need to check auth on website or cors preflight requests // no need to check auth on website or cors preflight requests
if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' || if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' ||
apiMethod === 'corsPreflight') { apiMethod === 'corsPreflight') {
request.actionImplicitDenies = false;
return this[apiMethod](request, log, callback); return this[apiMethod](request, log, callback);
} }
@ -129,15 +130,25 @@ const api = {
const requestContexts = prepareRequestContexts(apiMethod, request, const requestContexts = prepareRequestContexts(apiMethod, request,
sourceBucket, sourceObject, sourceVersionId); sourceBucket, sourceObject, sourceVersionId);
// Extract all the _apiMethods and store them in an array
const apiMethods = requestContexts ? requestContexts.map(context => context._apiMethod) : [];
// Attach the names to the current request
// eslint-disable-next-line no-param-reassign
request.apiMethods = apiMethods;
function checkAuthResults(authResults) { function checkAuthResults(authResults) {
let returnTagCount = true; let returnTagCount = true;
const isImplicitDeny = {};
let isOnlyImplicitDeny = true;
if (apiMethod === 'objectGet') { if (apiMethod === 'objectGet') {
// first item checks s3:GetObject(Version) action // first item checks s3:GetObject(Version) action
if (!authResults[0].isAllowed) { if (!authResults[0].isAllowed && !authResults[0].isImplicit) {
log.trace('get object authorization denial from Vault'); log.trace('get object authorization denial from Vault');
return errors.AccessDenied; return errors.AccessDenied;
} }
// TODO add support for returnTagCount in the bucket policy
// checks
isImplicitDeny[authResults[0].action] = authResults[0].isImplicit;
// second item checks s3:GetObject(Version)Tagging action // second item checks s3:GetObject(Version)Tagging action
if (!authResults[1].isAllowed) { if (!authResults[1].isAllowed) {
log.trace('get tagging authorization denial ' + log.trace('get tagging authorization denial ' +
@ -146,13 +157,25 @@ const api = {
} }
} else { } else {
for (let i = 0; i < authResults.length; i++) { for (let i = 0; i < authResults.length; i++) {
if (!authResults[i].isAllowed) { isImplicitDeny[authResults[i].action] = true;
if (!authResults[i].isAllowed && !authResults[i].isImplicit) {
// Any explicit deny rejects the current API call
log.trace('authorization denial from Vault'); log.trace('authorization denial from Vault');
return errors.AccessDenied; return errors.AccessDenied;
} else if (authResults[i].isAllowed) {
// If the action is allowed, the result is not implicit
// Deny.
isImplicitDeny[authResults[i].action] = false;
isOnlyImplicitDeny = false;
} }
} }
} }
return returnTagCount; // These two APIs cannot use ACLs or Bucket Policies, hence, any
// implicit deny from vault must be treated as an explicit deny.
if ((apiMethod === 'bucketPut' || apiMethod === 'serviceGet') && isOnlyImplicitDeny) {
return errors.AccessDenied;
}
return { returnTagCount, isImplicitDeny };
} }
return async.waterfall([ return async.waterfall([
@ -230,7 +253,16 @@ const api = {
if (checkedResults instanceof Error) { if (checkedResults instanceof Error) {
return callback(checkedResults); return callback(checkedResults);
} }
returnTagCount = checkedResults; returnTagCount = checkedResults.returnTagCount;
request.actionImplicitDenies = checkedResults.isImplicitDeny;
} else {
// create an object of keys apiMethods with all values to false:
// for backward compatibility, all apiMethods are allowed by default
// thus it is explicitly allowed, so implicit deny is false
request.actionImplicitDenies = apiMethods.reduce((acc, curr) => {
acc[curr] = false;
return acc;
}, {});
} }
if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') { if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') {
request._response = response; request._response = response;

View File

@ -1,29 +1,51 @@
const { evaluators, actionMaps, RequestContext } = require('arsenal').policies; const { evaluators, actionMaps, RequestContext } = require('arsenal').policies;
const constants = require('../../../../constants'); const constants = require('../../../../constants');
const { allAuthedUsersId, bucketOwnerActions, logId, publicId, const {
assumedRoleArnResourceType, backbeatLifecycleSessionName } = constants; allAuthedUsersId, bucketOwnerActions, logId, publicId,
assumedRoleArnResourceType, backbeatLifecycleSessionName,
} = constants;
// whitelist buckets to allow public read on objects // whitelist buckets to allow public read on objects
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ? const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : []; ? process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
function checkBucketAcls(bucket, requestType, canonicalID) { 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;
if (bucket.getOwner() === canonicalID) { if (bucket.getOwner() === canonicalID) {
return true; return true;
} }
// Backward compatibility
const arrayOfAllowed = [
'objectGetTagging',
'objectPutTagging',
'objectPutLegalHold',
'objectPutRetention',
];
if (mainApiCall === 'objectGet') {
if (arrayOfAllowed.includes(requestTypeParsed)) {
return true;
}
}
if (mainApiCall === 'objectPut') {
if (arrayOfAllowed.includes(requestTypeParsed)) {
return true;
}
}
const bucketAcl = bucket.getAcl(); const bucketAcl = bucket.getAcl();
if (requestType === 'bucketGet' || requestType === 'bucketHead') { if (requestTypeParsed === 'bucketGet' || requestTypeParsed === 'bucketHead') {
if (bucketAcl.Canned === 'public-read' if (bucketAcl.Canned === 'public-read'
|| bucketAcl.Canned === 'public-read-write' || bucketAcl.Canned === 'public-read-write'
|| (bucketAcl.Canned === 'authenticated-read' || (bucketAcl.Canned === 'authenticated-read'
&& canonicalID !== publicId)) { && canonicalID !== publicId)) {
return true; return true;
} else if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 } if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.READ.indexOf(canonicalID) > -1) { || bucketAcl.READ.indexOf(canonicalID) > -1) {
return true; return true;
} else if (bucketAcl.READ.indexOf(publicId) > -1 } if (bucketAcl.READ.indexOf(publicId) > -1
|| (bucketAcl.READ.indexOf(allAuthedUsersId) > -1 || (bucketAcl.READ.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -32,13 +54,13 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
return true; return true;
} }
} }
if (requestType === 'bucketGetACL') { if (requestTypeParsed === 'bucketGetACL') {
if ((bucketAcl.Canned === 'log-delivery-write' if ((bucketAcl.Canned === 'log-delivery-write'
&& canonicalID === logId) && canonicalID === logId)
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.READ_ACP.indexOf(canonicalID) > -1) { || bucketAcl.READ_ACP.indexOf(canonicalID) > -1) {
return true; return true;
} else if (bucketAcl.READ_ACP.indexOf(publicId) > -1 } if (bucketAcl.READ_ACP.indexOf(publicId) > -1
|| (bucketAcl.READ_ACP.indexOf(allAuthedUsersId) > -1 || (bucketAcl.READ_ACP.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -48,11 +70,11 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
} }
} }
if (requestType === 'bucketPutACL') { if (requestTypeParsed === 'bucketPutACL') {
if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) { || bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
return true; return true;
} else if (bucketAcl.WRITE_ACP.indexOf(publicId) > -1 } if (bucketAcl.WRITE_ACP.indexOf(publicId) > -1
|| (bucketAcl.WRITE_ACP.indexOf(allAuthedUsersId) > -1 || (bucketAcl.WRITE_ACP.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -62,16 +84,12 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
} }
} }
if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) { if (requestTypeParsed === 'objectDelete' || requestTypeParsed === 'objectPut') {
return true;
}
if (requestType === 'objectDelete' || requestType === 'objectPut') {
if (bucketAcl.Canned === 'public-read-write' if (bucketAcl.Canned === 'public-read-write'
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1 || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|| bucketAcl.WRITE.indexOf(canonicalID) > -1) { || bucketAcl.WRITE.indexOf(canonicalID) > -1) {
return true; return true;
} else if (bucketAcl.WRITE.indexOf(publicId) > -1 } if (bucketAcl.WRITE.indexOf(publicId) > -1
|| (bucketAcl.WRITE.indexOf(allAuthedUsersId) > -1 || (bucketAcl.WRITE.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (bucketAcl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -86,11 +104,12 @@ function checkBucketAcls(bucket, requestType, canonicalID) {
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket // objectPutACL, objectGetACL, objectHead or objectGet, the bucket
// authorization check should just return true so can move on to check // authorization check should just return true so can move on to check
// rights at the object level. // rights at the object level.
return (requestType === 'objectPutACL' || requestType === 'objectGetACL' || return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL'
requestType === 'objectGet' || requestType === 'objectHead'); || requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead');
} }
function checkObjectAcls(bucket, objectMD, requestType, canonicalID) { function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIsNotUser,
isUserUnauthenticated, mainApiCall) {
const bucketOwner = bucket.getOwner(); const bucketOwner = bucket.getOwner();
// acls don't distinguish between users and accounts, so both should be allowed // acls don't distinguish between users and accounts, so both should be allowed
if (bucketOwnerActions.includes(requestType) if (bucketOwnerActions.includes(requestType)
@ -100,6 +119,15 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
if (objectMD['owner-id'] === canonicalID) { if (objectMD['owner-id'] === canonicalID) {
return true; return true;
} }
// Backward compatibility
if (mainApiCall === 'objectGet') {
if ((isUserUnauthenticated || (requesterIsNotUser && bucketOwner === objectMD['owner-id']))
&& requestType === 'objectGetTagging') {
return true;
}
}
if (!objectMD.acl) { if (!objectMD.acl) {
return false; return false;
} }
@ -110,15 +138,15 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|| (objectMD.acl.Canned === 'authenticated-read' || (objectMD.acl.Canned === 'authenticated-read'
&& canonicalID !== publicId)) { && canonicalID !== publicId)) {
return true; return true;
} else if (objectMD.acl.Canned === 'bucket-owner-read' } if (objectMD.acl.Canned === 'bucket-owner-read'
&& bucketOwner === canonicalID) { && bucketOwner === canonicalID) {
return true; return true;
} else if ((objectMD.acl.Canned === 'bucket-owner-full-control' } if ((objectMD.acl.Canned === 'bucket-owner-full-control'
&& bucketOwner === canonicalID) && bucketOwner === canonicalID)
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1 || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|| objectMD.acl.READ.indexOf(canonicalID) > -1) { || objectMD.acl.READ.indexOf(canonicalID) > -1) {
return true; return true;
} else if (objectMD.acl.READ.indexOf(publicId) > -1 } if (objectMD.acl.READ.indexOf(publicId) > -1
|| (objectMD.acl.READ.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.READ.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -140,7 +168,7 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1 || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|| objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) { || objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) {
return true; return true;
} else if (objectMD.acl.WRITE_ACP.indexOf(publicId) > -1 } if (objectMD.acl.WRITE_ACP.indexOf(publicId) > -1
|| (objectMD.acl.WRITE_ACP.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.WRITE_ACP.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -156,7 +184,7 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1 || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|| objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) { || objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) {
return true; return true;
} else if (objectMD.acl.READ_ACP.indexOf(publicId) > -1 } if (objectMD.acl.READ_ACP.indexOf(publicId) > -1
|| (objectMD.acl.READ_ACP.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.READ_ACP.indexOf(allAuthedUsersId) > -1
&& canonicalID !== publicId) && canonicalID !== publicId)
|| (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1 || (objectMD.acl.FULL_CONTROL.indexOf(allAuthedUsersId) > -1
@ -246,9 +274,10 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
let permission = 'defaultDeny'; let permission = 'defaultDeny';
// if requester is user within bucket owner account, actions should be // if requester is user within bucket owner account, actions should be
// allowed unless explicitly denied (assumes allowed by IAM policy) // allowed unless explicitly denied (assumes allowed by IAM policy)
if (bucketOwner === canonicalID) { // Update: manual testing on the 18/9/2023 found this not to be the case
permission = 'allow'; // if (bucketOwner === canonicalID) {
} // permission = 'allow';
// }
let copiedStatement = JSON.parse(JSON.stringify(policy.Statement)); let copiedStatement = JSON.parse(JSON.stringify(policy.Statement));
while (copiedStatement.length > 0) { while (copiedStatement.length > 0) {
const s = copiedStatement[0]; const s = copiedStatement[0];
@ -268,75 +297,195 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
return permission; return permission;
} }
function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request) { function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
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 mainApiCall = requestTypes[0];
const results = {};
requestTypes.forEach(_requestType => {
// Check to see if user is authorized to perform a // Check to see if user is authorized to perform a
// particular action on bucket based on ACLs. // particular action on bucket based on ACLs.
// TODO: Add IAM checks // TODO: Add IAM checks
let requesterIsNotUser = true; let requesterIsNotUser = true;
let arn = null; let arn = null;
if (authInfo) { if (authInfo) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser(); requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
arn = authInfo.getArn(); arn = authInfo.getArn();
} }
// if the bucket owner is an account, users should not have default access // if the bucket owner is an account, users should not have default access
if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) { if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
return true; results[_requestType] = actionImplicitDenies[_requestType] === false;
} return;
const aclPermission = checkBucketAcls(bucket, requestType, canonicalID); }
const bucketPolicy = bucket.getBucketPolicy(); const aclPermission = checkBucketAcls(bucket, _requestType, canonicalID, mainApiCall);
if (!bucketPolicy) { const bucketPolicy = bucket.getBucketPolicy();
return aclPermission; if (!bucketPolicy) {
} results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, return;
canonicalID, arn, bucket.getOwner(), log, request); }
if (bucketPolicyPermission === 'explicitDeny') { const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
return false; canonicalID, arn, bucket.getOwner(), log, request);
} if (bucketPolicyPermission === 'explicitDeny') {
return (aclPermission || (bucketPolicyPermission === 'allow')); results[_requestType] = false;
return;
}
// If the bucket policy returns an allow, we accept the request, as the
// IAM response here is either Allow or implicit deny.
if (bucketPolicyPermission === 'allow') {
results[_requestType] = true;
return;
}
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
});
// final result is true if all the results are true
return Object.keys(results).every(key => results[key] === true);
} }
function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log, request) { function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
const bucketOwner = bucket.getOwner(); if (!Array.isArray(requestTypes)) {
if (!objectMD) { // eslint-disable-next-line no-param-reassign
// User is already authorized on the bucket for FULL_CONTROL or WRITE or requestTypes = [requestTypes];
// bucket has canned ACL public-read-write }
if (requestType === 'objectPut' || requestType === 'objectDelete') { if (iamAuthzResults === false) {
return true; // eslint-disable-next-line no-param-reassign
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;
} }
// check bucket has read access });
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
return isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request); const results = {};
requestTypes.forEach(_requestType => {
let arn = null;
if (authInfo) {
arn = authInfo.getArn();
}
const bucketPolicy = bucket.getBucketPolicy();
if (!bucketPolicy) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
canonicalID, arn, bucket.getOwner(), log, request);
if (bucketPolicyPermission === 'explicitDeny') {
results[_requestType] = false;
return;
}
// If the bucket policy returns an allow, we accept the request, as the
// IAM response here is either Allow or implicit deny.
if (bucketPolicyPermission === 'allow') {
results[_requestType] = true;
return;
}
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 isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, actionImplicitDenies, log, request) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
} }
let requesterIsNotUser = true; // By default, all missing actions are defined as allowed from IAM, to be
let arn = null; // backward compatible
if (authInfo) { if (!actionImplicitDenies) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser(); // eslint-disable-next-line no-param-reassign
arn = authInfo.getArn(); actionImplicitDenies = {};
} }
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) { requestTypes.forEach(requestType => {
return true; if (actionImplicitDenies[requestType] === undefined) {
} // eslint-disable-next-line no-param-reassign
// account is authorized if: actionImplicitDenies[requestType] = false;
// - requesttype is included in bucketOwnerActions and }
// - account is the bucket owner });
// - requester is account, not user const results = {};
if (bucketOwnerActions.includes(requestType) const mainApiCall = requestTypes[0];
&& (bucketOwner === canonicalID) requestTypes.forEach(_requestType => {
&& requesterIsNotUser) { const parsedMethodName = _requestType.endsWith('Version') ?
return true; _requestType.slice(0, -7) : _requestType;
} const bucketOwner = bucket.getOwner();
const aclPermission = checkObjectAcls(bucket, objectMD, requestType, if (!objectMD) {
canonicalID); // User is already authorized on the bucket for FULL_CONTROL or WRITE or
const bucketPolicy = bucket.getBucketPolicy(); // bucket has canned ACL public-read-write
if (!bucketPolicy) { if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') {
return aclPermission; results[_requestType] = actionImplicitDenies[_requestType] === false;
} return;
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, }
canonicalID, arn, bucket.getOwner(), log, request); // check bucket has read access
if (bucketPolicyPermission === 'explicitDeny') { // 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
return false; results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo,
} actionImplicitDenies, log, request);
return (aclPermission || (bucketPolicyPermission === 'allow')); 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;
return;
}
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType,
canonicalID, arn, bucket.getOwner(), log, request);
if (bucketPolicyPermission === 'explicitDeny') {
results[_requestType] = false;
return;
}
// If the bucket policy returns an allow, we accept the request, as the
// IAM response here is either Allow or implicit deny.
if (bucketPolicyPermission === 'allow') {
results[_requestType] = true;
return;
}
results[_requestType] = actionImplicitDenies[_requestType] === false && aclPermission;
});
// final result is true if all the results are true
return Object.keys(results).every(key => results[key] === true);
} }
function _checkResource(resource, bucketArn) { function _checkResource(resource, bucketArn) {
@ -383,13 +532,14 @@ function isLifecycleSession(arn) {
const resourceType = resourceNames[0]; const resourceType = resourceNames[0];
const sessionName = resourceNames[resourceNames.length - 1]; const sessionName = resourceNames[resourceNames.length - 1];
return (service === 'sts' && return (service === 'sts'
resourceType === assumedRoleArnResourceType && && resourceType === assumedRoleArnResourceType
sessionName === backbeatLifecycleSessionName); && sessionName === backbeatLifecycleSessionName);
} }
module.exports = { module.exports = {
isBucketAuthorized, isBucketAuthorized,
evaluateBucketPolicyWithIAM,
isObjAuthorized, isObjAuthorized,
checkBucketAcls, checkBucketAcls,
checkObjectAcls, checkObjectAcls,

View File

@ -24,7 +24,7 @@ function _deleteMPUbucket(destinationBucketName, log, cb) {
}); });
} }
function _deleteOngoingMPUs(authInfo, bucketName, bucketMD, mpus, log, cb) { function _deleteOngoingMPUs(authInfo, bucketName, bucketMD, mpus, request, log, cb) {
async.mapLimit(mpus, 1, (mpu, next) => { async.mapLimit(mpus, 1, (mpu, next) => {
const splitterChar = mpu.key.includes(oldSplitter) ? const splitterChar = mpu.key.includes(oldSplitter) ?
oldSplitter : splitter; oldSplitter : splitter;
@ -40,7 +40,7 @@ function _deleteOngoingMPUs(authInfo, bucketName, bucketMD, mpus, log, cb) {
byteLength: partSizeSum, byteLength: partSizeSum,
}); });
next(err); next(err);
}); }, request);
}, cb); }, cb);
} }
/** /**
@ -49,11 +49,13 @@ function _deleteOngoingMPUs(authInfo, bucketName, bucketMD, mpus, log, cb) {
* @param {object} bucketMD - bucket attributes/metadata * @param {object} bucketMD - bucket attributes/metadata
* @param {string} bucketName - bucket in which objectMetadata is stored * @param {string} bucketName - bucket in which objectMetadata is stored
* @param {string} canonicalID - account canonicalID of requester * @param {string} canonicalID - account canonicalID of requester
* @param {object} request - request object given by router
* including normalized headers
* @param {object} log - Werelogs logger * @param {object} log - Werelogs logger
* @param {function} cb - callback from async.waterfall in bucketDelete * @param {function} cb - callback from async.waterfall in bucketDelete
* @return {undefined} * @return {undefined}
*/ */
function deleteBucket(authInfo, bucketMD, bucketName, canonicalID, log, cb) { function deleteBucket(authInfo, bucketMD, bucketName, canonicalID, request, log, cb) {
log.trace('deleting bucket from metadata'); log.trace('deleting bucket from metadata');
assert.strictEqual(typeof bucketName, 'string'); assert.strictEqual(typeof bucketName, 'string');
assert.strictEqual(typeof canonicalID, 'string'); assert.strictEqual(typeof canonicalID, 'string');
@ -100,7 +102,7 @@ function deleteBucket(authInfo, bucketMD, bucketName, canonicalID, log, cb) {
} }
if (objectsListRes.Contents.length) { if (objectsListRes.Contents.length) {
return _deleteOngoingMPUs(authInfo, bucketName, return _deleteOngoingMPUs(authInfo, bucketName,
bucketMD, objectsListRes.Contents, log, err => { bucketMD, objectsListRes.Contents, request, log, err => {
if (err) { if (err) {
return next(err); return next(err);
} }

View File

@ -14,7 +14,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
bucketName, bucketName,
objectKey, objectKey,
uploadId, uploadId,
preciseRequestType: 'multipartDelete', preciseRequestType: request.apiMethods || 'multipartDelete',
request, request,
}; };
// For validating the request at the destinationBucket level // For validating the request at the destinationBucket level
@ -22,10 +22,11 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
// but the requestType is the more general 'objectDelete' // but the requestType is the more general 'objectDelete'
const metadataValParams = Object.assign({}, metadataValMPUparams); const metadataValParams = Object.assign({}, metadataValMPUparams);
metadataValParams.requestType = 'objectPut'; metadataValParams.requestType = 'objectPut';
const authzIdentityResult = request ? request.actionImplicitDenies : true;
async.waterfall([ async.waterfall([
function checkDestBucketVal(next) { function checkDestBucketVal(next) {
metadataValidateBucketAndObj(metadataValParams, log, metadataValidateBucketAndObj(metadataValParams, authzIdentityResult, log,
(err, destinationBucket) => { (err, destinationBucket) => {
if (err) { if (err) {
return next(err, destinationBucket); return next(err, destinationBucket);
@ -56,9 +57,15 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
function abortExternalMpu(mpuBucket, mpuOverviewObj, destBucket, function abortExternalMpu(mpuBucket, mpuOverviewObj, destBucket,
next) { next) {
const location = mpuOverviewObj.controllingLocationConstraint; const location = mpuOverviewObj.controllingLocationConstraint;
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
return data.abortMPU(objectKey, uploadId, location, bucketName, return data.abortMPU(objectKey, uploadId, location, bucketName,
request, destBucket, locationConstraintCheck, log, request, destBucket, locationConstraintCheck, log,
(err, skipDataDelete) => { (err, skipDataDelete) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket);
} }

View File

@ -27,11 +27,11 @@ function bucketDelete(authInfo, request, log, cb) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketDelete', requestType: request.apiMethods || 'bucketDelete',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucketMD) => { (err, bucketMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucketMD); request.method, bucketMD);
@ -43,7 +43,7 @@ function bucketDelete(authInfo, request, log, cb) {
log.trace('passed checks', log.trace('passed checks',
{ method: 'metadataValidateBucket' }); { method: 'metadataValidateBucket' });
return deleteBucket(authInfo, bucketMD, bucketName, return deleteBucket(authInfo, bucketMD, bucketName,
authInfo.getCanonicalID(), log, err => { authInfo.getCanonicalID(), request, log, err => {
if (err) { if (err) {
return cb(err, corsHeaders); return cb(err, corsHeaders);
} }

View File

@ -33,7 +33,8 @@ function bucketDeleteCors(authInfo, request, log, callback) {
} }
log.trace('found bucket in metadata'); log.trace('found bucket in metadata');
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { log.debug('access denied for user on bucket', {
requestType, requestType,
method: 'bucketDeleteCors', method: 'bucketDeleteCors',

View File

@ -21,12 +21,12 @@ function bucketDeleteEncryption(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketDeleteEncryption', requestType: request.apiMethods || 'bucketDeleteEncryption',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucket(metadataValParams, log, next), next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, next),
(bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)), (bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)),
(bucket, next) => { (bucket, next) => {
const sseConfig = bucket.getServerSideEncryption(); const sseConfig = bucket.getServerSideEncryption();

View File

@ -17,10 +17,10 @@ function bucketDeleteLifecycle(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketDeleteLifecycle', requestType: request.apiMethods || 'bucketDeleteLifecycle',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -16,10 +16,10 @@ function bucketDeletePolicy(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketDeletePolicy', requestType: request.apiMethods || 'bucketDeletePolicy',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -17,10 +17,10 @@ function bucketDeleteReplication(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketDeleteReplication', requestType: request.apiMethods || 'bucketDeleteReplication',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -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;

View File

@ -25,7 +25,8 @@ function bucketDeleteWebsite(authInfo, request, log, callback) {
} }
log.trace('found bucket in metadata'); log.trace('found bucket in metadata');
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { log.debug('access denied for user on bucket', {
requestType, requestType,
method: 'bucketDeleteWebsite', method: 'bucketDeleteWebsite',

View File

@ -322,7 +322,7 @@ function bucketGet(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGet', requestType: request.apiMethods || 'bucketGet',
request, request,
}; };
const listParams = { const listParams = {
@ -345,7 +345,7 @@ function bucketGet(authInfo, request, log, callback) {
listParams.marker = params.marker; listParams.marker = params.marker;
} }
metadataValidateBucket(metadataValParams, log, (err, bucket) => { metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {

View File

@ -43,7 +43,7 @@ function bucketGetACL(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetACL', requestType: request.apiMethods || 'bucketGetACL',
request, request,
}; };
const grantInfo = { const grantInfo = {
@ -54,7 +54,7 @@ function bucketGetACL(authInfo, request, log, callback) {
}, },
}; };
metadataValidateBucket(metadataValParams, log, (err, bucket) => { metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {

View File

@ -34,7 +34,8 @@ function bucketGetCors(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { log.debug('access denied for user on bucket', {
requestType, requestType,
method: 'bucketGetCors', method: 'bucketGetCors',

View File

@ -22,12 +22,12 @@ function bucketGetEncryption(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetEncryption', requestType: request.apiMethods || 'bucketGetEncryption',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucket(metadataValParams, log, next), next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, next),
(bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)), (bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)),
(bucket, next) => { (bucket, next) => {
// If sseInfo is present but the `mandatory` flag is not set // If sseInfo is present but the `mandatory` flag is not set

View File

@ -20,10 +20,10 @@ function bucketGetLifecycle(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetLifecycle', requestType: request.apiMethods || 'bucketGetLifecycle',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -36,7 +36,8 @@ function bucketGetLocation(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for account on bucket', { log.debug('access denied for account on bucket', {
requestType, requestType,
method: 'bucketGetLocation', method: 'bucketGetLocation',

View File

@ -37,11 +37,11 @@ function bucketGetNotification(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetNotification', requestType: request.apiMethods || 'bucketGetNotification',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -33,10 +33,10 @@ function bucketGetObjectLock(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetObjectLock', requestType: request.apiMethods || 'bucketGetObjectLock',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -17,11 +17,11 @@ function bucketGetPolicy(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetPolicy', requestType: request.apiMethods || 'bucketGetPolicy',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

View File

@ -20,10 +20,10 @@ function bucketGetReplication(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetReplication', requestType: request.apiMethods || 'bucketGetReplication',
request, request,
}; };
return metadataValidateBucket(metadataValParams, log, (err, bucket) => { return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket); const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) { if (err) {
log.debug('error processing request', { log.debug('error processing request', {

112
lib/api/bucketGetTagging.js Normal file
View File

@ -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:
/*
<Tagging>
<TagSet>
<Tag>
<Key>string</Key>
<Value>string</Value>
</Tag>
<Tag>
<Key>string</Key>
<Value>string</Value>
</Tag>
</TagSet>
</Tagging>
*/
/**
* @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.<Tag>} tags - set of bucket tag
* @return {string} - the converted xml string of the versioning configuration
*/
function tagsToXml(tags) {
const xml = [];
xml.push('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Tagging><TagSet>');
tags.forEach(tag => {
xml.push('<Tag>');
xml.push(`<Key>${escapeForXml(tag.Key)}</Key>`);
xml.push(`<Value>${escapeForXml(tag.Value)}</Value>`);
xml.push('</Tag>');
});
xml.push('</TagSet></Tagging>');
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;

View File

@ -53,11 +53,11 @@ function bucketGetVersioning(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketGetVersioning', requestType: request.apiMethods || 'bucketGetVersioning',
request, request,
}; };
metadataValidateBucket(metadataValParams, log, (err, bucket) => { metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {

View File

@ -34,7 +34,8 @@ function bucketGetWebsite(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { log.debug('access denied for user on bucket', {
requestType, requestType,
method: 'bucketGetWebsite', method: 'bucketGetWebsite',

View File

@ -18,10 +18,10 @@ function bucketHead(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketHead', requestType: request.apiMethods || 'bucketHead',
request, request,
}; };
metadataValidateBucket(metadataValParams, log, (err, bucket) => { metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {

View File

@ -43,7 +43,7 @@ const { pushMetric } = require('../utapi/utilities');
function bucketPutACL(authInfo, request, log, callback) { function bucketPutACL(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutACL' }); log.debug('processing request', { method: 'bucketPutACL' });
const bucketName = request.bucketName; const { bucketName } = request;
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
const newCannedACL = request.headers['x-amz-acl']; const newCannedACL = request.headers['x-amz-acl'];
const possibleCannedACL = [ const possibleCannedACL = [
@ -53,17 +53,6 @@ function bucketPutACL(authInfo, request, log, callback) {
'authenticated-read', 'authenticated-read',
'log-delivery-write', 'log-delivery-write',
]; ];
if (newCannedACL && possibleCannedACL.indexOf(newCannedACL) === -1) {
log.trace('invalid canned acl argument', {
acl: newCannedACL,
method: 'bucketPutACL',
});
return callback(errors.InvalidArgument);
}
if (!aclUtils.checkGrantHeaderValidity(request.headers)) {
log.trace('invalid acl header');
return callback(errors.InvalidArgument);
}
const possibleGroups = [constants.allAuthedUsersId, const possibleGroups = [constants.allAuthedUsersId,
constants.publicId, constants.publicId,
constants.logId, constants.logId,
@ -71,7 +60,7 @@ function bucketPutACL(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutACL', requestType: request.apiMethods || 'bucketPutACL',
request, request,
}; };
const possibleGrants = ['FULL_CONTROL', 'WRITE', const possibleGrants = ['FULL_CONTROL', 'WRITE',
@ -85,34 +74,43 @@ function bucketPutACL(authInfo, request, log, callback) {
READ_ACP: [], READ_ACP: [],
}; };
const grantReadHeader = const grantReadHeader = aclUtils.parseGrant(request.headers[
aclUtils.parseGrant(request.headers[ 'x-amz-grant-read'], 'READ');
'x-amz-grant-read'], 'READ'); const grantWriteHeader = aclUtils.parseGrant(request.headers['x-amz-grant-write'], 'WRITE');
const grantWriteHeader = const grantReadACPHeader = aclUtils.parseGrant(request.headers['x-amz-grant-read-acp'],
aclUtils.parseGrant(request.headers['x-amz-grant-write'], 'WRITE'); 'READ_ACP');
const grantReadACPHeader = const grantWriteACPHeader = aclUtils.parseGrant(request.headers['x-amz-grant-write-acp'],
aclUtils.parseGrant(request.headers['x-amz-grant-read-acp'], 'WRITE_ACP');
'READ_ACP'); const grantFullControlHeader = aclUtils.parseGrant(request.headers['x-amz-grant-full-control'],
const grantWriteACPHeader = 'FULL_CONTROL');
aclUtils.parseGrant(request.headers['x-amz-grant-write-acp'],
'WRITE_ACP');
const grantFullControlHeader =
aclUtils.parseGrant(request.headers['x-amz-grant-full-control'],
'FULL_CONTROL');
return async.waterfall([ return async.waterfall([
function waterfall1(next) { function waterfall1(next) {
metadataValidateBucket(metadataValParams, log, metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => { (err, bucket) => {
if (err) { if (err) {
log.trace('request authorization failed', { log.trace('request authorization failed', {
error: err, error: err,
method: 'metadataValidateBucket', method: 'metadataValidateBucket',
}); });
return next(err, bucket); return next(err, bucket);
} }
return next(null, bucket); // if the API call is allowed, ensure that the parameters are valid
}); if (newCannedACL && possibleCannedACL.indexOf(newCannedACL) === -1) {
log.trace('invalid canned acl argument', {
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);
});
}, },
function waterfall2(bucket, next) { function waterfall2(bucket, next) {
// If not setting acl through headers, parse body // If not setting acl through headers, parse body
@ -179,7 +177,7 @@ function bucketPutACL(authInfo, request, log, callback) {
if (!skip && granteeType === 'Group') { if (!skip && granteeType === 'Group') {
if (possibleGroups.indexOf(grantee.URI[0]) < 0) { if (possibleGroups.indexOf(grantee.URI[0]) < 0) {
log.trace('invalid user group', log.trace('invalid user group',
{ userGroup: grantee.URI[0] }); { userGroup: grantee.URI[0] });
return next(errors.InvalidArgument, bucket); return next(errors.InvalidArgument, bucket);
} }
return usersIdentifiedByGroup.push({ return usersIdentifiedByGroup.push({
@ -193,22 +191,23 @@ function bucketPutACL(authInfo, request, log, callback) {
} else { } else {
// If no canned ACL and no parsed xml, loop // If no canned ACL and no parsed xml, loop
// through the access headers // through the access headers
const allGrantHeaders = const allGrantHeaders = [].concat(grantReadHeader, grantWriteHeader,
[].concat(grantReadHeader, grantWriteHeader,
grantReadACPHeader, grantWriteACPHeader, grantReadACPHeader, grantWriteACPHeader,
grantFullControlHeader); grantFullControlHeader);
usersIdentifiedByEmail = allGrantHeaders.filter(item => usersIdentifiedByEmail = allGrantHeaders.filter(item => item
item && item.userIDType.toLowerCase() === 'emailaddress'); && item.userIDType.toLowerCase() === 'emailaddress');
usersIdentifiedByGroup = allGrantHeaders usersIdentifiedByGroup = allGrantHeaders
.filter(itm => itm && itm.userIDType .filter(itm => itm && itm.userIDType
.toLowerCase() === 'uri'); .toLowerCase() === 'uri');
for (let i = 0; i < usersIdentifiedByGroup.length; i++) { for (let i = 0; i < usersIdentifiedByGroup.length; i++) {
const userGroup = usersIdentifiedByGroup[i].identifier; const userGroup = usersIdentifiedByGroup[i].identifier;
if (possibleGroups.indexOf(userGroup) < 0) { if (possibleGroups.indexOf(userGroup) < 0) {
log.trace('invalid user group', { userGroup, log.trace('invalid user group', {
method: 'bucketPutACL' }); userGroup,
method: 'bucketPutACL',
});
return next(errors.InvalidArgument, bucket); return next(errors.InvalidArgument, bucket);
} }
} }
@ -241,8 +240,8 @@ function bucketPutACL(authInfo, request, log, callback) {
return vault.getCanonicalIds(justEmails, log, return vault.getCanonicalIds(justEmails, log,
(err, results) => { (err, results) => {
if (err) { if (err) {
log.trace('error looking up canonical ids', { log.trace('error looking up canonical ids',
error: err, method: 'vault.getCanonicalIDs' }); { error: err, method: 'vault.getCanonicalIDs' });
return next(err, bucket); return next(err, bucket);
} }
const reconstructedUsersIdentifiedByEmail = aclUtils const reconstructedUsersIdentifiedByEmail = aclUtils
@ -251,7 +250,8 @@ function bucketPutACL(authInfo, request, log, callback) {
const allUsers = [].concat( const allUsers = [].concat(
reconstructedUsersIdentifiedByEmail, reconstructedUsersIdentifiedByEmail,
usersIdentifiedByID, usersIdentifiedByID,
usersIdentifiedByGroup); usersIdentifiedByGroup,
);
const revisedAddACLParams = aclUtils const revisedAddACLParams = aclUtils
.sortHeaderGrants(allUsers, addACLParams); .sortHeaderGrants(allUsers, addACLParams);
return next(null, bucket, revisedAddACLParams); return next(null, bucket, revisedAddACLParams);
@ -259,9 +259,9 @@ function bucketPutACL(authInfo, request, log, callback) {
} }
const allUsers = [].concat( const allUsers = [].concat(
usersIdentifiedByID, usersIdentifiedByID,
usersIdentifiedByGroup); usersIdentifiedByGroup,
const revisedAddACLParams = );
aclUtils.sortHeaderGrants(allUsers, addACLParams); const revisedAddACLParams = aclUtils.sortHeaderGrants(allUsers, addACLParams);
return next(null, bucket, revisedAddACLParams); return next(null, bucket, revisedAddACLParams);
}, },
function waterfall4(bucket, addACLParams, next) { function waterfall4(bucket, addACLParams, next) {
@ -272,12 +272,10 @@ function bucketPutACL(authInfo, request, log, callback) {
if (bucket.hasTransientFlag() || bucket.hasDeletedFlag()) { if (bucket.hasTransientFlag() || bucket.hasDeletedFlag()) {
log.trace('transient or deleted flag so cleaning up bucket'); log.trace('transient or deleted flag so cleaning up bucket');
bucket.setFullAcl(addACLParams); bucket.setFullAcl(addACLParams);
return cleanUpBucket(bucket, canonicalID, log, err => return cleanUpBucket(bucket, canonicalID, log, err => next(err, bucket));
next(err, bucket));
} }
// If no bucket flags, just add acl's to bucket metadata // If no bucket flags, just add acl's to bucket metadata
return acl.addACL(bucket, addACLParams, log, err => return acl.addACL(bucket, addACLParams, log, err => next(err, bucket));
next(err, bucket));
}, },
], (err, bucket) => { ], (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,

View File

@ -4,8 +4,7 @@ const { errors } = require('arsenal');
const bucketShield = require('./apiUtils/bucket/bucketShield'); const bucketShield = require('./apiUtils/bucket/bucketShield');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const { isBucketAuthorized } = const { isBucketAuthorized } = require('./apiUtils/authorization/permissionChecks');
require('./apiUtils/authorization/permissionChecks');
const metadata = require('../metadata/wrapper'); const metadata = require('../metadata/wrapper');
const { parseCorsXml } = require('./apiUtils/bucket/bucketCors'); const { parseCorsXml } = require('./apiUtils/bucket/bucketCors');
const { pushMetric } = require('../utapi/utilities'); const { pushMetric } = require('../utapi/utilities');
@ -22,7 +21,7 @@ const requestType = 'bucketPutCors';
*/ */
function bucketPutCors(authInfo, request, log, callback) { function bucketPutCors(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutCors' }); log.debug('processing request', { method: 'bucketPutCors' });
const bucketName = request.bucketName; const { bucketName } = request;
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
if (!request.post) { if (!request.post) {
@ -66,7 +65,8 @@ function bucketPutCors(authInfo, request, log, callback) {
}); });
}, },
function validateBucketAuthorization(bucket, rules, corsHeaders, next) { function validateBucketAuthorization(bucket, rules, corsHeaders, next) {
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for account on bucket', { log.debug('access denied for account on bucket', {
requestType, requestType,
}); });
@ -77,8 +77,7 @@ function bucketPutCors(authInfo, request, log, callback) {
function updateBucketMetadata(bucket, rules, corsHeaders, next) { function updateBucketMetadata(bucket, rules, corsHeaders, next) {
log.trace('updating bucket cors rules in metadata'); log.trace('updating bucket cors rules in metadata');
bucket.setCors(rules); bucket.setCors(rules);
metadata.updateBucket(bucketName, bucket, log, err => metadata.updateBucket(bucketName, bucket, log, err => next(err, corsHeaders));
next(err, corsHeaders));
}, },
], (err, corsHeaders) => { ], (err, corsHeaders) => {
if (err) { if (err) {

View File

@ -18,17 +18,17 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
*/ */
function bucketPutEncryption(authInfo, request, log, callback) { function bucketPutEncryption(authInfo, request, log, callback) {
const bucketName = request.bucketName; const { bucketName } = request;
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutEncryption', requestType: request.apiMethods || 'bucketPutEncryption',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucket(metadataValParams, log, next), next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, next),
(bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)), (bucket, next) => checkExpectedBucketOwner(request.headers, bucket, log, err => next(err, bucket)),
(bucket, next) => { (bucket, next) => {
log.trace('parsing encryption config', { method: 'bucketPutEncryption' }); log.trace('parsing encryption config', { method: 'bucketPutEncryption' });

View File

@ -1,7 +1,6 @@
const { waterfall } = require('async'); const { waterfall } = require('async');
const uuid = require('uuid/v4'); const uuid = require('uuid/v4');
const LifecycleConfiguration = const { LifecycleConfiguration } = require('arsenal').models;
require('arsenal').models.LifecycleConfiguration;
const parseXML = require('../utilities/parseXML'); const parseXML = require('../utilities/parseXML');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
@ -21,11 +20,11 @@ const { pushMetric } = require('../utapi/utilities');
function bucketPutLifecycle(authInfo, request, log, callback) { function bucketPutLifecycle(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutLifecycle' }); log.debug('processing request', { method: 'bucketPutLifecycle' });
const bucketName = request.bucketName; const { bucketName } = request;
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutLifecycle', requestType: request.apiMethods || 'bucketPutLifecycle',
request, request,
}; };
return waterfall([ return waterfall([
@ -42,7 +41,7 @@ function bucketPutLifecycle(authInfo, request, log, callback) {
return next(null, configObj); return next(null, configObj);
}); });
}, },
(lcConfig, next) => metadataValidateBucket(metadataValParams, log, (lcConfig, next) => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => { (err, bucket) => {
if (err) { if (err) {
return next(err, bucket); return next(err, bucket);
@ -54,8 +53,7 @@ function bucketPutLifecycle(authInfo, request, log, callback) {
bucket.setUid(uuid()); bucket.setUid(uuid());
} }
bucket.setLifecycleConfiguration(lcConfig); bucket.setLifecycleConfiguration(lcConfig);
metadata.updateBucket(bucket.getName(), bucket, log, err => metadata.updateBucket(bucket.getName(), bucket, log, err => next(err, bucket));
next(err, bucket));
}, },
], (err, bucket) => { ], (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,

View File

@ -19,11 +19,11 @@ const { pushMetric } = require('../utapi/utilities');
function bucketPutNotification(authInfo, request, log, callback) { function bucketPutNotification(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutNotification' }); log.debug('processing request', { method: 'bucketPutNotification' });
const bucketName = request.bucketName; const { bucketName } = request;
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutNotification', requestType: request.apiMethods || 'bucketPutNotification',
request, request,
}; };
@ -34,7 +34,7 @@ function bucketPutNotification(authInfo, request, log, callback) {
const notifConfig = notificationConfig.error ? undefined : notificationConfig; const notifConfig = notificationConfig.error ? undefined : notificationConfig;
process.nextTick(() => next(notificationConfig.error, notifConfig)); process.nextTick(() => next(notificationConfig.error, notifConfig));
}, },
(notifConfig, next) => metadataValidateBucket(metadataValParams, log, (notifConfig, next) => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => next(err, bucket, notifConfig)), (err, bucket) => next(err, bucket, notifConfig)),
(bucket, notifConfig, next) => { (bucket, notifConfig, next) => {
bucket.setNotificationConfiguration(notifConfig); bucket.setNotificationConfiguration(notifConfig);
@ -45,8 +45,10 @@ function bucketPutNotification(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {
log.trace('error processing request', { error: err, log.trace('error processing request', {
method: 'bucketPutNotification' }); error: err,
method: 'bucketPutNotification',
});
return callback(err, corsHeaders); return callback(err, corsHeaders);
} }
pushMetric('putBucketNotification', log, { pushMetric('putBucketNotification', log, {

View File

@ -1,8 +1,8 @@
const { waterfall } = require('async'); const { waterfall } = require('async');
const arsenal = require('arsenal'); const arsenal = require('arsenal');
const errors = arsenal.errors; const { errors } = arsenal;
const ObjectLockConfiguration = arsenal.models.ObjectLockConfiguration; const { ObjectLockConfiguration } = arsenal.models;
const parseXML = require('../utilities/parseXML'); const parseXML = require('../utilities/parseXML');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
@ -22,11 +22,11 @@ const { pushMetric } = require('../utapi/utilities');
function bucketPutObjectLock(authInfo, request, log, callback) { function bucketPutObjectLock(authInfo, request, log, callback) {
log.debug('processing request', { method: 'bucketPutObjectLock' }); log.debug('processing request', { method: 'bucketPutObjectLock' });
const bucketName = request.bucketName; const { bucketName } = request;
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutObjectLock', requestType: request.apiMethods || 'bucketPutObjectLock',
request, request,
}; };
return waterfall([ return waterfall([
@ -36,12 +36,12 @@ function bucketPutObjectLock(authInfo, request, log, callback) {
// if there was an error getting object lock configuration, // if there was an error getting object lock configuration,
// returned configObj will contain 'error' key // returned configObj will contain 'error' key
process.nextTick(() => { process.nextTick(() => {
const configObj = lockConfigClass. const configObj = lockConfigClass
getValidatedObjectLockConfiguration(); .getValidatedObjectLockConfiguration();
return next(configObj.error || null, configObj); return next(configObj.error || null, configObj);
}); });
}, },
(objectLockConfig, next) => metadataValidateBucket(metadataValParams, (objectLockConfig, next) => metadataValidateBucket(metadataValParams, request.actionImplicitDenies,
log, (err, bucket) => { log, (err, bucket) => {
if (err) { if (err) {
return next(err, bucket); return next(err, bucket);
@ -53,23 +53,25 @@ function bucketPutObjectLock(authInfo, request, log, callback) {
process.nextTick(() => { process.nextTick(() => {
if (!isObjectLockEnabled) { if (!isObjectLockEnabled) {
return next(errors.InvalidBucketState.customizeDescription( return next(errors.InvalidBucketState.customizeDescription(
'Object Lock configuration cannot be enabled on ' + 'Object Lock configuration cannot be enabled on '
'existing buckets'), bucket); + 'existing buckets',
), bucket);
} }
return next(null, bucket, objectLockConfig); return next(null, bucket, objectLockConfig);
}); });
}, },
(bucket, objectLockConfig, next) => { (bucket, objectLockConfig, next) => {
bucket.setObjectLockConfiguration(objectLockConfig); bucket.setObjectLockConfiguration(objectLockConfig);
metadata.updateBucket(bucket.getName(), bucket, log, err => metadata.updateBucket(bucket.getName(), bucket, log, err => next(err, bucket));
next(err, bucket));
}, },
], (err, bucket) => { ], (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {
log.trace('error processing request', { error: err, log.trace('error processing request', {
method: 'bucketPutObjectLock' }); error: err,
method: 'bucketPutObjectLock',
});
return callback(err, corsHeaders); return callback(err, corsHeaders);
} }
pushMetric('putBucketObjectLock', log, { pushMetric('putBucketObjectLock', log, {

View File

@ -17,8 +17,7 @@ const { BucketPolicy } = models;
function _checkNotImplementedPolicy(policyString) { function _checkNotImplementedPolicy(policyString) {
// bucket names and key names cannot include "", so including those // bucket names and key names cannot include "", so including those
// isolates not implemented keys // isolates not implemented keys
return policyString.includes('"Condition"') return policyString.includes('"Service"')
|| policyString.includes('"Service"')
|| policyString.includes('"Federated"'); || policyString.includes('"Federated"');
} }
@ -37,7 +36,7 @@ function bucketPutPolicy(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutPolicy', requestType: request.apiMethods || 'bucketPutPolicy',
request, request,
}; };
@ -70,7 +69,7 @@ function bucketPutPolicy(authInfo, request, log, callback) {
return next(null, bucketPolicy); return next(null, bucketPolicy);
}); });
}, },
(bucketPolicy, next) => metadataValidateBucket(metadataValParams, log, (bucketPolicy, next) => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => { (err, bucket) => {
if (err) { if (err) {
return next(err, bucket); return next(err, bucket);

View File

@ -27,7 +27,7 @@ function bucketPutReplication(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutReplication', requestType: request.apiMethods || 'bucketPutReplication',
request, request,
}; };
return waterfall([ return waterfall([
@ -36,7 +36,7 @@ function bucketPutReplication(authInfo, request, log, callback) {
// Check bucket user privileges and ensure versioning is 'Enabled'. // Check bucket user privileges and ensure versioning is 'Enabled'.
(config, next) => (config, next) =>
// TODO: Validate that destination bucket exists and has versioning. // TODO: Validate that destination bucket exists and has versioning.
metadataValidateBucket(metadataValParams, log, (err, bucket) => { metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
if (err) { if (err) {
return next(err); return next(err);
} }

View File

@ -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:
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<TagSet>
<Tag>
<Key>string</Key>
<Value>string</Value>
</Tag>
</TagSet>
</Tagging>
*/
/**
* 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;

View File

@ -87,13 +87,13 @@ function bucketPutVersioning(authInfo, request, log, callback) {
const metadataValParams = { const metadataValParams = {
authInfo, authInfo,
bucketName, bucketName,
requestType: 'bucketPutVersioning', requestType: request.apiMethods || 'bucketPutVersioning',
request, request,
}; };
return waterfall([ return waterfall([
next => _parseXML(request, log, next), next => _parseXML(request, log, next),
next => metadataValidateBucket(metadataValParams, log, next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => next(err, bucket)), // ignore extra null object, (err, bucket) => next(err, bucket)), // ignore extra null object,
(bucket, next) => parseString(request.post, (err, result) => { (bucket, next) => parseString(request.post, (err, result) => {
// just for linting; there should not be any parsing error here // just for linting; there should not be any parsing error here

View File

@ -46,7 +46,8 @@ function bucketPutWebsite(authInfo, request, log, callback) {
}); });
}, },
function validateBucketAuthorization(bucket, config, next) { function validateBucketAuthorization(bucket, config, next) {
if (!isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(bucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { log.debug('access denied for user on bucket', {
requestType, requestType,
method: 'bucketPutWebsite', method: 'bucketPutWebsite',

View File

@ -118,9 +118,9 @@ function completeMultipartUpload(authInfo, request, log, callback) {
bucketName, bucketName,
// Required permissions for this action // Required permissions for this action
// at the destinationBucket level are same as objectPut // at the destinationBucket level are same as objectPut
requestType: 'objectPut', requestType: request.apiMethods || 'objectPut',
}; };
metadataValidateBucketAndObj(metadataValParams, log, next); metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log, next);
}, },
function validateMultipart(destBucket, objMD, next) { function validateMultipart(destBucket, objMD, next) {
if (objMD) { if (objMD) {
@ -190,9 +190,14 @@ function completeMultipartUpload(authInfo, request, log, callback) {
const mdInfo = { storedParts, mpuOverviewKey, splitter }; const mdInfo = { storedParts, mpuOverviewKey, splitter };
const mpuInfo = const mpuInfo =
{ objectKey, uploadId, jsonList, bucketName, destBucket }; { objectKey, uploadId, jsonList, bucketName, destBucket };
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
return data.completeMPU(request, mpuInfo, mdInfo, location, return data.completeMPU(request, mpuInfo, mdInfo, location,
null, null, null, locationConstraintCheck, log, null, null, null, locationConstraintCheck, log,
(err, completeObjData) => { (err, completeObjData) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket);
} }

View File

@ -92,7 +92,7 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
// Required permissions for this action are same as objectPut // Required permissions for this action are same as objectPut
requestType: 'objectPut', requestType: request.apiMethods || 'objectPut',
request, request,
}; };
const accountCanonicalID = authInfo.getCanonicalID(); const accountCanonicalID = authInfo.getCanonicalID();
@ -253,7 +253,7 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
} }
async.waterfall([ async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(error, destinationBucket) => { (error, destinationBucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, destinationBucket); request.method, destinationBucket);

View File

@ -95,8 +95,8 @@ function listMultipartUploads(authInfo, request, log, callback) {
// to list the multipart uploads so we have provided here that // to list the multipart uploads so we have provided here that
// the authorization to list multipart uploads is the same // the authorization to list multipart uploads is the same
// as listing objects in a bucket. // as listing objects in a bucket.
requestType: 'bucketGet', requestType: request.apiMethods || 'bucketGet',
preciseRequestType: 'listMultipartUploads', preciseRequestType: request.apiMethods || 'listMultipartUploads',
request, request,
}; };
@ -104,7 +104,7 @@ function listMultipartUploads(authInfo, request, log, callback) {
function waterfall1(next) { function waterfall1(next) {
// Check final destination bucket for authorization rather // Check final destination bucket for authorization rather
// than multipart upload bucket // than multipart upload bucket
metadataValidateBucket(metadataValParams, log, metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => next(err, bucket)); (err, bucket) => next(err, bucket));
}, },
function getMPUBucket(bucket, next) { function getMPUBucket(bucket, next) {

View File

@ -95,7 +95,7 @@ function listParts(authInfo, request, log, callback) {
bucketName, bucketName,
objectKey, objectKey,
uploadId, uploadId,
preciseRequestType: 'listParts', preciseRequestType: request.apiMethods || 'listParts',
request, request,
}; };
// For validating the request at the destinationBucket level // For validating the request at the destinationBucket level
@ -112,7 +112,7 @@ function listParts(authInfo, request, log, callback) {
async.waterfall([ async.waterfall([
function checkDestBucketVal(next) { function checkDestBucketVal(next) {
metadataValidateBucketAndObj(metadataValParams, log, metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, destinationBucket) => { (err, destinationBucket) => {
if (err) { if (err) {
return next(err, destinationBucket, null); return next(err, destinationBucket, null);
@ -150,8 +150,13 @@ function listParts(authInfo, request, log, callback) {
mpuOverviewObj, mpuOverviewObj,
destBucket, destBucket,
}; };
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
return data.listParts(mpuInfo, request, locationConstraintCheck, return data.listParts(mpuInfo, request, locationConstraintCheck,
log, (err, backendPartList) => { log, (err, backendPartList) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) { if (err) {
return next(err, destBucket); return next(err, destBucket);
} }

View File

@ -11,11 +11,12 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper'); const metadata = require('../metadata/wrapper');
const services = require('../services'); const services = require('../services');
const vault = require('../auth/vault'); const vault = require('../auth/vault');
const { isBucketAuthorized } = const { isBucketAuthorized, evaluateBucketPolicyWithIAM } =
require('./apiUtils/authorization/permissionChecks'); require('./apiUtils/authorization/permissionChecks');
const { preprocessingVersioningDelete } const { preprocessingVersioningDelete }
= require('./apiUtils/object/versioning'); = require('./apiUtils/object/versioning');
const createAndStoreObject = require('./apiUtils/object/createAndStoreObject'); const createAndStoreObject = require('./apiUtils/object/createAndStoreObject');
const monitoring = require('../utilities/metrics');
const { metadataGetObject } = require('../metadata/metadataUtils'); const { metadataGetObject } = require('../metadata/metadataUtils');
const { config } = require('../Config'); const { config } = require('../Config');
const { hasGovernanceBypassHeader, checkUserGovernanceBypass, ObjectLockInfo } const { hasGovernanceBypassHeader, checkUserGovernanceBypass, ObjectLockInfo }
@ -202,6 +203,8 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
'null' : versionIdUtils.decode(entry.versionId); 'null' : versionIdUtils.decode(entry.versionId);
} }
if (decodedVersionId instanceof Error) { if (decodedVersionId instanceof Error) {
monitoring.promMetrics('DELETE', bucketName, 404,
'multiObjectDelete');
return callback(errors.NoSuchVersion); return callback(errors.NoSuchVersion);
} }
return callback(null, decodedVersionId); return callback(null, decodedVersionId);
@ -212,6 +215,8 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
versionId, log, (err, objMD) => { versionId, log, (err, objMD) => {
// if general error from metadata return error // if general error from metadata return error
if (err) { if (err) {
monitoring.promMetrics('DELETE', bucketName, err.code,
'multiObjectDelete');
return callback(err); return callback(err);
} }
if (!objMD) { if (!objMD) {
@ -279,14 +284,16 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
return callback(null, objMD, versionId); return callback(null, objMD, versionId);
}, },
(objMD, versionId, callback) => (objMD, versionId, callback) => {
preprocessingVersioningDelete(bucketName, bucket, objMD, const options = preprocessingVersioningDelete(
versionId, log, (err, options) => callback(err, options, bucketName, bucket, objMD, versionId, config.nullVersionCompatMode);
objMD)),
(options, objMD, callback) => {
const deleteInfo = {}; const deleteInfo = {};
if (options && options.deleteData) { if (options && options.deleteData) {
deleteInfo.deleted = true; deleteInfo.deleted = true;
if (objMD.uploadId) {
// eslint-disable-next-line
options.replayId = objMD.uploadId;
}
return services.deleteObject(bucketName, objMD, return services.deleteObject(bucketName, objMD,
entry.key, options, log, err => entry.key, options, log, err =>
callback(err, objMD, deleteInfo)); callback(err, objMD, deleteInfo));
@ -364,11 +371,15 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
function multiObjectDelete(authInfo, request, log, callback) { function multiObjectDelete(authInfo, request, log, callback) {
log.debug('processing request', { method: 'multiObjectDelete' }); log.debug('processing request', { method: 'multiObjectDelete' });
if (!request.post) { if (!request.post) {
monitoring.promMetrics('DELETE', request.bucketName, 400,
'multiObjectDelete');
return callback(errors.MissingRequestBodyError); return callback(errors.MissingRequestBodyError);
} }
const md5 = crypto.createHash('md5') const md5 = crypto.createHash('md5')
.update(request.post, 'utf8').digest('base64'); .update(request.post, 'utf8').digest('base64');
if (md5 !== request.headers['content-md5']) { if (md5 !== request.headers['content-md5']) {
monitoring.promMetrics('DELETE', request.bucketName, 400,
'multiObjectDelete');
return callback(errors.BadDigest); return callback(errors.BadDigest);
} }
@ -385,15 +396,37 @@ function multiObjectDelete(authInfo, request, log, callback) {
return next(null, quietSetting, objects); return next(null, quietSetting, objects);
}); });
}, },
function checkPolicies(quietSetting, objects, next) { function checkBucketMetadata(quietSetting, objects, next) {
const errorResults = [];
return metadata.getBucket(bucketName, log, (err, bucketMD) => {
if (err) {
log.trace('error retrieving bucket metadata', { error: err });
return next(err);
}
if (bucketShield(bucketMD, 'objectDelete')) {
return next(errors.NoSuchBucket);
}
if (!isBucketAuthorized(bucketMD, 'objectDelete', canonicalID, authInfo,
request.actionImplicitDenies, authInfo)) {
log.trace("access denied due to bucket acl's");
objects.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
});
return next(null, quietSetting, errorResults, [], bucketMD);
}
return next(null, quietSetting, errorResults, objects, bucketMD);
});
},
function checkPolicies(quietSetting, errorResults, objects, bucketMD, next) {
// track keys that are still on track to be deleted // track keys that are still on track to be deleted
const inPlay = []; const inPlay = [];
const errorResults = [];
// if request from account, no need to check policies // if request from account, no need to check policies
// all objects are inPlay so send array of object keys
// as inPlay argument
if (!authInfo.isRequesterAnIAMUser()) { if (!authInfo.isRequesterAnIAMUser()) {
return next(null, quietSetting, errorResults, objects); return next(null, quietSetting, errorResults, objects, bucketMD);
} }
// TODO: once arsenal's extractParams is separated from doAuth // TODO: once arsenal's extractParams is separated from doAuth
@ -429,7 +462,6 @@ function multiObjectDelete(authInfo, request, log, callback) {
}; };
return vault.checkPolicies(requestContextParams, authInfo.getArn(), return vault.checkPolicies(requestContextParams, authInfo.getArn(),
log, (err, authorizationResults) => { log, (err, authorizationResults) => {
// there were no policies so received a blanket AccessDenied
if (err && err.is.AccessDenied) { if (err && err.is.AccessDenied) {
objects.forEach(entry => { objects.forEach(entry => {
errorResults.push({ errorResults.push({
@ -437,7 +469,7 @@ function multiObjectDelete(authInfo, request, log, callback) {
error: errors.AccessDenied }); error: errors.AccessDenied });
}); });
// send empty array for inPlay // send empty array for inPlay
return next(null, quietSetting, errorResults, []); return next(null, quietSetting, errorResults, [], bucketMD);
} }
if (err) { if (err) {
log.trace('error checking policies', { log.trace('error checking policies', {
@ -455,6 +487,16 @@ function multiObjectDelete(authInfo, request, log, callback) {
}); });
return next(errors.InternalError); return next(errors.InternalError);
} }
// Convert authorization results into an easier to handle format
const iamAuthzResults = authorizationResults.reduce((acc, curr, idx) => {
const apiMethod = requestContextParams[idx].apiMethod;
// eslint-disable-next-line no-param-reassign
acc[apiMethod] = curr.isImplicit;
return acc;
}, {});
for (let i = 0; i < authorizationResults.length; i++) { for (let i = 0; i < authorizationResults.length; i++) {
const result = authorizationResults[i]; const result = authorizationResults[i];
// result is { isAllowed: true, // result is { isAllowed: true,
@ -470,7 +512,27 @@ function multiObjectDelete(authInfo, request, log, callback) {
key: result.arn.slice(slashIndex + 1), key: result.arn.slice(slashIndex + 1),
versionId: result.versionId, versionId: result.versionId,
}; };
if (result.isAllowed) {
// Deny immediately if there is an explicit deny
if (!result.isImplicit && !result.isAllowed) {
errorResults.push({
entry,
error: errors.AccessDenied,
});
continue;
}
// Evaluate against the bucket policies
const areAllActionsAllowed = evaluateBucketPolicyWithIAM(
bucketMD,
Object.keys(iamAuthzResults),
canonicalID,
authInfo,
iamAuthzResults,
log,
request);
if (areAllActionsAllowed) {
inPlay.push(entry); inPlay.push(entry);
} else { } else {
errorResults.push({ errorResults.push({
@ -479,50 +541,9 @@ function multiObjectDelete(authInfo, request, log, callback) {
}); });
} }
} }
return next(null, quietSetting, errorResults, inPlay); return next(null, quietSetting, errorResults, inPlay, bucketMD);
}); });
}, },
function checkBucketMetadata(quietSetting, errorResults, inPlay, next) {
// if no objects in play, no need to check ACLs / get metadata,
// just move on if there is no Origin header
if (inPlay.length === 0 && !request.headers.origin) {
return next(null, quietSetting, errorResults, inPlay,
undefined);
}
return metadata.getBucket(bucketName, log, (err, bucketMD) => {
if (err) {
log.trace('error retrieving bucket metadata',
{ error: err });
return next(err);
}
// check whether bucket has transient or deleted flag
if (bucketShield(bucketMD, 'objectDelete')) {
return next(errors.NoSuchBucket);
}
// if no objects in play, no need to check ACLs
if (inPlay.length === 0) {
return next(null, quietSetting, errorResults, inPlay,
bucketMD);
}
if (!isBucketAuthorized(bucketMD, 'objectDelete', canonicalID, authInfo, log, request)) {
log.trace("access denied due to bucket acl's");
// if access denied at the bucket level, no access for
// any of the objects so all results will be error results
inPlay.forEach(entry => {
errorResults.push({
entry,
error: errors.AccessDenied,
});
});
// by sending an empty array as the inPlay array
// async.forEachLimit below will not actually
// make any calls to metadata or data but will continue on
// to the next step to build xml
return next(null, quietSetting, errorResults, [], bucketMD);
}
return next(null, quietSetting, errorResults, inPlay, bucketMD);
});
},
function getObjMetadataAndDeleteStep(quietSetting, errorResults, inPlay, function getObjMetadataAndDeleteStep(quietSetting, errorResults, inPlay,
bucket, next) { bucket, next) {
return getObjMetadataAndDelete(authInfo, canonicalID, request, return getObjMetadataAndDelete(authInfo, canonicalID, request,
@ -534,6 +555,8 @@ function multiObjectDelete(authInfo, request, log, callback) {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
if (err) { if (err) {
monitoring.promMetrics('DELETE', bucketName, err.code,
'multiObjectDelete');
return callback(err, null, corsHeaders); return callback(err, null, corsHeaders);
} }
const xml = _formatXML(quietSetting, errorResults, const xml = _formatXML(quietSetting, errorResults,
@ -552,6 +575,10 @@ function multiObjectDelete(authInfo, request, log, callback) {
removedDeleteMarkers, removedDeleteMarkers,
isDelete: true, isDelete: true,
}); });
monitoring.promMetrics('DELETE', bucketName, '200',
'multiObjectDelete',
Number.parseInt(totalContentLengthDeleted, 10), null, null,
numOfObjectsRemoved);
return callback(null, xml, corsHeaders); return callback(null, xml, corsHeaders);
}); });
} }
@ -559,4 +586,4 @@ function multiObjectDelete(authInfo, request, log, callback) {
module.exports = { module.exports = {
getObjMetadataAndDelete, getObjMetadataAndDelete,
multiObjectDelete, multiObjectDelete,
}; };

View File

@ -245,7 +245,7 @@ function objectCopy(authInfo, request, sourceBucket,
} }
return async.waterfall([ return async.waterfall([
function checkDestAuth(next) { function checkDestAuth(next) {
return metadataValidateBucketAndObj(valPutParams, log, return metadataValidateBucketAndObj(valPutParams, request.actionImplicitDenies, log,
(err, destBucketMD, destObjMD) => { (err, destBucketMD, destObjMD) => {
if (err) { if (err) {
log.debug('error validating put part of request', log.debug('error validating put part of request',
@ -263,7 +263,7 @@ function objectCopy(authInfo, request, sourceBucket,
}); });
}, },
function checkSourceAuthorization(destBucketMD, destObjMD, next) { function checkSourceAuthorization(destBucketMD, destObjMD, next) {
return metadataValidateBucketAndObj(valGetParams, log, return metadataValidateBucketAndObj(valGetParams, request.actionImplicitDenies, log,
(err, sourceBucketMD, sourceObjMD) => { (err, sourceBucketMD, sourceObjMD) => {
if (err) { if (err) {
log.debug('error validating get part of request', log.debug('error validating get part of request',
@ -408,10 +408,15 @@ function objectCopy(authInfo, request, sourceBucket,
return next(null, storeMetadataParams, dataLocator, destObjMD, return next(null, storeMetadataParams, dataLocator, destObjMD,
serverSideEncryption, destBucketMD); serverSideEncryption, destBucketMD);
} }
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
return data.copyObject(request, sourceLocationConstraintName, return data.copyObject(request, sourceLocationConstraintName,
storeMetadataParams, dataLocator, dataStoreContext, storeMetadataParams, dataLocator, dataStoreContext,
backendInfoDest, sourceBucketMD, destBucketMD, serverSideEncryption, log, backendInfoDest, sourceBucketMD, destBucketMD, serverSideEncryption, log,
(err, results) => { (err, results) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) { if (err) {
return next(err, destBucketMD); return next(err, destBucketMD);
} }

View File

@ -49,15 +49,15 @@ function objectDelete(authInfo, request, log, cb) {
bucketName, bucketName,
objectKey, objectKey,
versionId: reqVersionId, versionId: reqVersionId,
requestType: 'objectDelete', requestType: request.apiMethods || 'objectDelete',
request, request,
}; };
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
return async.waterfall([ return async.waterfall([
function validateBucketAndObj(next) { function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(valParams, log, return metadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log,
(err, bucketMD, objMD) => { (err, bucketMD, objMD) => {
if (err) { if (err) {
return next(err, bucketMD); return next(err, bucketMD);
} }

View File

@ -40,13 +40,14 @@ function objectDeleteTagging(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectDeleteTagging',
versionId: reqVersionId, versionId: reqVersionId,
getDeleteMarker: true,
requestType: request.apiMethods || 'objectDeleteTagging',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -44,11 +44,12 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
bucketName, bucketName,
objectKey, objectKey,
versionId, versionId,
requestType: 'objectGet', getDeleteMarker: true,
requestType: request.apiMethods || 'objectGet',
request, request,
}; };
return metadataValidateBucketAndObj(mdValParams, log, return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log,
(err, bucket, objMD) => { (err, bucket, objMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);

View File

@ -58,7 +58,7 @@ function objectGetACL(authInfo, request, log, callback) {
bucketName, bucketName,
objectKey, objectKey,
versionId, versionId,
requestType: 'objectGetACL', requestType: request.apiMethods || 'objectGetACL',
request, request,
}; };
const grantInfo = { const grantInfo = {
@ -71,7 +71,7 @@ function objectGetACL(authInfo, request, log, callback) {
return async.waterfall([ return async.waterfall([
function validateBucketAndObj(next) { function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(metadataValParams, log, return metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -37,13 +37,13 @@ function objectGetLegalHold(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectGetLegalHold',
versionId, versionId,
requestType: request.apiMethods || 'objectGetLegalHold',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -37,13 +37,13 @@ function objectGetRetention(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectGetRetention',
versionId: reqVersionId, versionId: reqVersionId,
requestType: request.apiMethods || 'objectGetRetention',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -37,13 +37,12 @@ function objectGetTagging(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectGetTagging',
versionId: reqVersionId, versionId: reqVersionId,
requestType: request.apiMethods || 'objectGetTagging',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -44,11 +44,12 @@ function objectHead(authInfo, request, log, callback) {
bucketName, bucketName,
objectKey, objectKey,
versionId, versionId,
requestType: 'objectHead', getDeleteMarker: true,
requestType: request.apiMethods || 'objectHead',
request, request,
}; };
return metadataValidateBucketAndObj(mdValParams, log, return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log,
(err, bucket, objMD) => { (err, bucket, objMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);

View File

@ -57,7 +57,7 @@ function objectPut(authInfo, request, streamingV4Params, log, callback) {
} }
const invalidSSEError = errors.InvalidArgument.customizeDescription( const invalidSSEError = errors.InvalidArgument.customizeDescription(
'The encryption method specified is not supported'); 'The encryption method specified is not supported');
const requestType = 'objectPut'; const requestType = request.apiMethods || 'objectPut';
const valParams = { authInfo, bucketName, objectKey, requestType, request }; const valParams = { authInfo, bucketName, objectKey, requestType, request };
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
@ -68,8 +68,7 @@ function objectPut(authInfo, request, streamingV4Params, log, callback) {
} }
log.trace('owner canonicalID to send to data', { canonicalID }); log.trace('owner canonicalID to send to data', { canonicalID });
return metadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log,
return metadataValidateBucketAndObj(valParams, log,
(err, bucket, objMD) => { (err, bucket, objMD) => {
const responseHeaders = collectCorsHeaders(headers.origin, const responseHeaders = collectCorsHeaders(headers.origin,
method, bucket); method, bucket);

View File

@ -7,8 +7,7 @@ const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const constants = require('../../constants'); const constants = require('../../constants');
const vault = require('../auth/vault'); const vault = require('../auth/vault');
const { decodeVersionId, getVersionIdResHeader } const { decodeVersionId, getVersionIdResHeader } = require('./apiUtils/object/versioning');
= require('./apiUtils/object/versioning');
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils'); const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
/* /*
@ -43,8 +42,8 @@ const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
*/ */
function objectPutACL(authInfo, request, log, cb) { function objectPutACL(authInfo, request, log, cb) {
log.debug('processing request', { method: 'objectPutACL' }); log.debug('processing request', { method: 'objectPutACL' });
const bucketName = request.bucketName; const { bucketName } = request;
const objectKey = request.objectKey; const { objectKey } = request;
const newCannedACL = request.headers['x-amz-acl']; const newCannedACL = request.headers['x-amz-acl'];
const possibleCannedACL = [ const possibleCannedACL = [
'private', 'private',
@ -82,8 +81,9 @@ function objectPutACL(authInfo, request, log, cb) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectPutACL',
versionId: reqVersionId, versionId: reqVersionId,
getDeleteMarker: true,
requestType: request.apiMethods || 'objectPutACL',
}; };
const possibleGrants = ['FULL_CONTROL', 'WRITE_ACP', 'READ', 'READ_ACP']; const possibleGrants = ['FULL_CONTROL', 'WRITE_ACP', 'READ', 'READ_ACP'];
@ -95,26 +95,26 @@ function objectPutACL(authInfo, request, log, cb) {
READ_ACP: [], READ_ACP: [],
}; };
const grantReadHeader = const grantReadHeader = aclUtils.parseGrant(request.headers['x-amz-grant-read'], 'READ');
aclUtils.parseGrant(request.headers['x-amz-grant-read'], 'READ'); const grantReadACPHeader = aclUtils.parseGrant(request.headers['x-amz-grant-read-acp'],
const grantReadACPHeader = 'READ_ACP');
aclUtils.parseGrant(request.headers['x-amz-grant-read-acp'],
'READ_ACP');
const grantWriteACPHeader = aclUtils.parseGrant( const grantWriteACPHeader = aclUtils.parseGrant(
request.headers['x-amz-grant-write-acp'], 'WRITE_ACP'); request.headers['x-amz-grant-write-acp'], 'WRITE_ACP',
);
const grantFullControlHeader = aclUtils.parseGrant( const grantFullControlHeader = aclUtils.parseGrant(
request.headers['x-amz-grant-full-control'], 'FULL_CONTROL'); request.headers['x-amz-grant-full-control'], 'FULL_CONTROL',
);
return async.waterfall([ return async.waterfall([
function validateBucketAndObj(next) { function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(metadataValParams, log, return metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
return next(err); return next(err);
} }
if (!objectMD) { if (!objectMD) {
const err = reqVersionId ? errors.NoSuchVersion : const err = reqVersionId ? errors.NoSuchVersion
errors.NoSuchKey; : errors.NoSuchKey;
return next(err, bucket); return next(err, bucket);
} }
if (objectMD.isDeleteMarker) { if (objectMD.isDeleteMarker) {
@ -202,7 +202,7 @@ function objectPutACL(authInfo, request, log, cb) {
if (!skip && granteeType === 'Group') { if (!skip && granteeType === 'Group') {
if (possibleGroups.indexOf(grantee.URI[0]) < 0) { if (possibleGroups.indexOf(grantee.URI[0]) < 0) {
log.trace('invalid user group', log.trace('invalid user group',
{ userGroup: grantee.URI[0] }); { userGroup: grantee.URI[0] });
return next(errors.InvalidArgument, bucket); return next(errors.InvalidArgument, bucket);
} }
return usersIdentifiedByGroup.push({ return usersIdentifiedByGroup.push({
@ -216,22 +216,24 @@ function objectPutACL(authInfo, request, log, cb) {
} else { } else {
// If no canned ACL and no parsed xml, loop // If no canned ACL and no parsed xml, loop
// through the access headers // through the access headers
const allGrantHeaders = const allGrantHeaders = [].concat(grantReadHeader,
[].concat(grantReadHeader,
grantReadACPHeader, grantWriteACPHeader, grantReadACPHeader, grantWriteACPHeader,
grantFullControlHeader); grantFullControlHeader);
usersIdentifiedByEmail = allGrantHeaders.filter(item => usersIdentifiedByEmail = allGrantHeaders.filter(item => item
item && item.userIDType.toLowerCase() === 'emailaddress'); && item.userIDType.toLowerCase() === 'emailaddress');
usersIdentifiedByGroup = allGrantHeaders usersIdentifiedByGroup = allGrantHeaders
.filter(itm => itm && itm.userIDType .filter(itm => itm && itm.userIDType
.toLowerCase() === 'uri'); .toLowerCase() === 'uri');
for (let i = 0; i < usersIdentifiedByGroup.length; i++) { for (let i = 0; i < usersIdentifiedByGroup.length; i += 1) {
if (possibleGroups.indexOf( if (possibleGroups.indexOf(
usersIdentifiedByGroup[i].identifier) < 0) { usersIdentifiedByGroup[i].identifier,
) < 0) {
log.trace('invalid user group', log.trace('invalid user group',
{ userGroup: usersIdentifiedByGroup[i] {
.identifier }); userGroup: usersIdentifiedByGroup[i]
.identifier,
});
return next(errors.InvalidArgument, bucket); return next(errors.InvalidArgument, bucket);
} }
} }
@ -259,18 +261,20 @@ function objectPutACL(authInfo, request, log, cb) {
const allUsers = [].concat( const allUsers = [].concat(
reconstructedUsersIdentifiedByEmail, reconstructedUsersIdentifiedByEmail,
usersIdentifiedByID, usersIdentifiedByID,
usersIdentifiedByGroup); usersIdentifiedByGroup,
);
const revisedAddACLParams = aclUtils const revisedAddACLParams = aclUtils
.sortHeaderGrants(allUsers, addACLParams); .sortHeaderGrants(allUsers, addACLParams);
return next(null, bucket, objectMD, return next(null, bucket, objectMD,
revisedAddACLParams); revisedAddACLParams);
}); },
);
} }
const allUsers = [].concat( const allUsers = [].concat(
usersIdentifiedByID, usersIdentifiedByID,
usersIdentifiedByGroup); usersIdentifiedByGroup,
const revisedAddACLParams = );
aclUtils.sortHeaderGrants(allUsers, addACLParams); const revisedAddACLParams = aclUtils.sortHeaderGrants(allUsers, addACLParams);
return next(null, bucket, objectMD, revisedAddACLParams); return next(null, bucket, objectMD, revisedAddACLParams);
}, },
function addAclsToObjMD(bucket, objectMD, ACLParams, next) { function addAclsToObjMD(bucket, objectMD, ACLParams, next) {
@ -292,8 +296,7 @@ function objectPutACL(authInfo, request, log, cb) {
} }
const verCfg = bucket.getVersioningConfiguration(); const verCfg = bucket.getVersioningConfiguration();
resHeaders['x-amz-version-id'] = resHeaders['x-amz-version-id'] = getVersionIdResHeader(verCfg, objectMD);
getVersionIdResHeader(verCfg, objectMD);
log.trace('processed request successfully in object put acl api'); log.trace('processed request successfully in object put acl api');
pushMetric('putObjectAcl', log, { pushMetric('putObjectAcl', log, {

View File

@ -1,12 +1,12 @@
const async = require('async'); const async = require('async');
const { errors, versioning, s3middleware } = require('arsenal'); const { errors, versioning, s3middleware } = require('arsenal');
const validateHeaders = s3middleware.validateConditionalHeaders; const validateHeaders = s3middleware.validateConditionalHeaders;
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const constants = require('../../constants'); const constants = require('../../constants');
const { data } = require('../data/wrapper'); const { data } = require('../data/wrapper');
const locationConstraintCheck = const locationConstraintCheck = require('./apiUtils/object/locationConstraintCheck');
require('./apiUtils/object/locationConstraintCheck');
const metadata = require('../metadata/wrapper'); const metadata = require('../metadata/wrapper');
const { pushMetric } = require('../utapi/utilities'); const { pushMetric } = require('../utapi/utilities');
const logger = require('../utilities/logger'); const logger = require('../utilities/logger');
@ -43,6 +43,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
bucketName: sourceBucket, bucketName: sourceBucket,
objectKey: sourceObject, objectKey: sourceObject,
versionId: reqVersionId, versionId: reqVersionId,
getDeleteMarker: true,
requestType: 'objectGet', requestType: 'objectGet',
request, request,
}; };
@ -59,7 +60,6 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
// request.query.uploadId must be called with that exact // request.query.uploadId must be called with that exact
// capitalization // capitalization
const uploadId = request.query.uploadId; const uploadId = request.query.uploadId;
const valPutParams = { const valPutParams = {
authInfo, authInfo,
bucketName: destBucketName, bucketName: destBucketName,
@ -89,26 +89,26 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
return async.waterfall([ return async.waterfall([
function checkDestAuth(next) { function checkDestAuth(next) {
return metadataValidateBucketAndObj(valPutParams, log, return metadataValidateBucketAndObj(valPutParams, request.actionImplicitDenies, log,
(err, destBucketMD) => { (err, destBucketMD) => {
if (err) { if (err) {
log.debug('error validating authorization for ' + log.debug('error validating authorization for '
'destination bucket', + 'destination bucket',
{ error: err }); { error: err });
return next(err, destBucketMD); return next(err, destBucketMD);
} }
const flag = destBucketMD.hasDeletedFlag() const flag = destBucketMD.hasDeletedFlag()
|| destBucketMD.hasTransientFlag(); || destBucketMD.hasTransientFlag();
if (flag) { if (flag) {
log.trace('deleted flag or transient flag ' + log.trace('deleted flag or transient flag '
'on destination bucket', { flag }); + 'on destination bucket', { flag });
return next(errors.NoSuchBucket); return next(errors.NoSuchBucket);
} }
return next(null, destBucketMD); return next(null, destBucketMD);
}); });
}, },
function checkSourceAuthorization(destBucketMD, next) { function checkSourceAuthorization(destBucketMD, next) {
return metadataValidateBucketAndObj(valGetParams, log, return metadataValidateBucketAndObj(valGetParams, request.actionImplicitDenies, log,
(err, sourceBucketMD, sourceObjMD) => { (err, sourceBucketMD, sourceObjMD) => {
if (err) { if (err) {
log.debug('error validating get part of request', log.debug('error validating get part of request',
@ -117,28 +117,26 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
} }
if (!sourceObjMD) { if (!sourceObjMD) {
log.debug('no source object', { sourceObject }); log.debug('no source object', { sourceObject });
const err = reqVersionId ? errors.NoSuchVersion : const err = reqVersionId ? errors.NoSuchVersion
errors.NoSuchKey; : errors.NoSuchKey;
return next(err, destBucketMD); return next(err, destBucketMD);
} }
let sourceLocationConstraintName = let sourceLocationConstraintName = sourceObjMD.dataStoreName;
sourceObjMD.dataStoreName;
// for backwards compatibility before storing dataStoreName // for backwards compatibility before storing dataStoreName
// TODO: handle in objectMD class // TODO: handle in objectMD class
if (!sourceLocationConstraintName && if (!sourceLocationConstraintName
sourceObjMD.location[0] && && sourceObjMD.location[0]
sourceObjMD.location[0].dataStoreName) { && sourceObjMD.location[0].dataStoreName) {
sourceLocationConstraintName = sourceLocationConstraintName = sourceObjMD.location[0].dataStoreName;
sourceObjMD.location[0].dataStoreName;
} }
if (sourceObjMD.isDeleteMarker) { if (sourceObjMD.isDeleteMarker) {
log.debug('delete marker on source object', log.debug('delete marker on source object',
{ sourceObject }); { sourceObject });
if (reqVersionId) { if (reqVersionId) {
const err = errors.InvalidRequest const err = errors.InvalidRequest
.customizeDescription('The source of a copy ' + .customizeDescription('The source of a copy '
'request may not specifically refer to a delete' + + 'request may not specifically refer to a delete'
'marker by version id.'); + 'marker by version id.');
return next(err, destBucketMD); return next(err, destBucketMD);
} }
// if user specifies a key in a versioned source bucket // if user specifies a key in a versioned source bucket
@ -146,8 +144,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
// delete marker, return NoSuchKey // delete marker, return NoSuchKey
return next(errors.NoSuchKey, destBucketMD); return next(errors.NoSuchKey, destBucketMD);
} }
const headerValResult = const headerValResult = validateHeaders(request.headers,
validateHeaders(request.headers,
sourceObjMD['last-modified'], sourceObjMD['last-modified'],
sourceObjMD['content-md5']); sourceObjMD['content-md5']);
if (headerValResult.error) { if (headerValResult.error) {
@ -162,15 +159,15 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
// If specific version requested, include copy source // If specific version requested, include copy source
// version id in response. Include in request by default // version id in response. Include in request by default
// if versioning is enabled or suspended. // if versioning is enabled or suspended.
if (sourceBucketMD.getVersioningConfiguration() || if (sourceBucketMD.getVersioningConfiguration()
reqVersionId) { || reqVersionId) {
if (sourceObjMD.isNull || !sourceObjMD.versionId) { if (sourceObjMD.isNull || !sourceObjMD.versionId) {
sourceVerId = 'null'; sourceVerId = 'null';
} else { } else {
sourceVerId = sourceVerId = versionIdUtils.encode(
versionIdUtils.encode( sourceObjMD.versionId,
sourceObjMD.versionId, config.versionIdEncodingType,
config.versionIdEncodingType); );
} }
} }
return next(null, copyLocator.dataLocator, destBucketMD, return next(null, copyLocator.dataLocator, destBucketMD,
@ -195,7 +192,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
}); });
return next(err); return next(err);
} }
let splitter = constants.splitter; let { splitter } = constants;
if (mpuBucket.getMdBucketModelVersion() < 2) { if (mpuBucket.getMdBucketModelVersion() < 2) {
splitter = constants.oldSplitter; splitter = constants.oldSplitter;
} }
@ -209,35 +206,33 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
function getMpuOverviewObject(dataLocator, destBucketMD, function getMpuOverviewObject(dataLocator, destBucketMD,
copyObjectSize, sourceVerId, splitter, copyObjectSize, sourceVerId, splitter,
sourceLocationConstraintName, next) { sourceLocationConstraintName, next) {
const mpuOverviewKey = const mpuOverviewKey = `overview${splitter}${destObjectKey}${splitter}${uploadId}`;
`overview${splitter}${destObjectKey}${splitter}${uploadId}`;
return metadata.getObjectMD(mpuBucketName, mpuOverviewKey, return metadata.getObjectMD(mpuBucketName, mpuOverviewKey,
null, log, (err, res) => { null, log, (err, res) => {
if (err) { if (err) {
// TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver
if (err.NoSuchKey) { if (err.NoSuchKey) {
return next(errors.NoSuchUpload); return next(errors.NoSuchUpload);
}
log.error('error getting overview object from ' +
'mpu bucket', {
error: err,
method: 'objectPutCopyPart::' +
'metadata.getObjectMD',
});
return next(err);
} }
const initiatorID = res.initiator.ID; log.error('error getting overview object from '
const requesterID = authInfo.isRequesterAnIAMUser() ? + 'mpu bucket', {
authInfo.getArn() : authInfo.getCanonicalID(); error: err,
if (initiatorID !== requesterID) { method: 'objectPutCopyPart::'
return next(errors.AccessDenied); + 'metadata.getObjectMD',
} });
const destObjLocationConstraint = return next(err);
res.controllingLocationConstraint; }
return next(null, dataLocator, destBucketMD, const initiatorID = res.initiator.ID;
destObjLocationConstraint, copyObjectSize, const requesterID = authInfo.isRequesterAnIAMUser()
sourceVerId, sourceLocationConstraintName, splitter); ? authInfo.getArn() : authInfo.getCanonicalID();
}); if (initiatorID !== requesterID) {
return next(errors.AccessDenied);
}
const destObjLocationConstraint = res.controllingLocationConstraint;
return next(null, dataLocator, destBucketMD,
destObjLocationConstraint, copyObjectSize,
sourceVerId, sourceLocationConstraintName, splitter);
});
}, },
function goGetData( function goGetData(
dataLocator, dataLocator,
@ -249,6 +244,9 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
splitter, splitter,
next, next,
) { ) {
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
data.uploadPartCopy( data.uploadPartCopy(
request, request,
log, log,
@ -259,31 +257,33 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
dataStoreContext, dataStoreContext,
locationConstraintCheck, locationConstraintCheck,
(error, eTag, lastModified, serverSideEncryption, locations) => { (error, eTag, lastModified, serverSideEncryption, locations) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (error) { if (error) {
if (error.message === 'skip') { if (error.message === 'skip') {
return next(skipError, destBucketMD, eTag, return next(skipError, destBucketMD, eTag,
lastModified, sourceVerId, lastModified, sourceVerId,
serverSideEncryption); serverSideEncryption);
} }
return next(error, destBucketMD); return next(error, destBucketMD);
} }
return next(null, destBucketMD, locations, eTag, return next(null, destBucketMD, locations, eTag,
copyObjectSize, sourceVerId, serverSideEncryption, copyObjectSize, sourceVerId, serverSideEncryption,
lastModified, splitter); lastModified, splitter);
}); },
);
}, },
function getExistingPartInfo(destBucketMD, locations, totalHash, function getExistingPartInfo(destBucketMD, locations, totalHash,
copyObjectSize, sourceVerId, serverSideEncryption, lastModified, copyObjectSize, sourceVerId, serverSideEncryption, lastModified,
splitter, next) { splitter, next) {
const partKey = const partKey = `${uploadId}${constants.splitter}${paddedPartNumber}`;
`${uploadId}${constants.splitter}${paddedPartNumber}`;
metadata.getObjectMD(mpuBucketName, partKey, {}, log, metadata.getObjectMD(mpuBucketName, partKey, {}, log,
(err, result) => { (err, result) => {
// If there is nothing being overwritten just move on // If there is nothing being overwritten just move on
// TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver
if (err && !err.NoSuchKey) { if (err && !err.NoSuchKey) {
log.debug('error getting current part (if any)', log.debug('error getting current part (if any)',
{ error: err }); { error: err });
return next(err); return next(err);
} }
let oldLocations; let oldLocations;
@ -294,8 +294,8 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
// Pull locations to clean up any potential orphans // Pull locations to clean up any potential orphans
// in data if object put is an overwrite of // in data if object put is an overwrite of
// already existing object with same key and part number // already existing object with same key and part number
oldLocations = Array.isArray(oldLocations) ? oldLocations = Array.isArray(oldLocations)
oldLocations : [oldLocations]; ? oldLocations : [oldLocations];
} }
return next(null, destBucketMD, locations, totalHash, return next(null, destBucketMD, locations, totalHash,
prevObjectSize, copyObjectSize, sourceVerId, prevObjectSize, copyObjectSize, sourceVerId,
@ -317,7 +317,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
locations, metaStoreParams, log, err => { locations, metaStoreParams, log, err => {
if (err) { if (err) {
log.debug('error storing new metadata', log.debug('error storing new metadata',
{ error: err, method: 'storeNewPartMetadata' }); { error: err, method: 'storeNewPartMetadata' });
return next(err); return next(err);
} }
return next(null, locations, oldLocations, destBucketMD, totalHash, return next(null, locations, oldLocations, destBucketMD, totalHash,
@ -370,7 +370,8 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
// data locations) has been stored // data locations) has been stored
if (oldLocationsToDelete) { if (oldLocationsToDelete) {
const delLog = logger.newRequestLoggerFromSerializedUids( const delLog = logger.newRequestLoggerFromSerializedUids(
log.getSerializedUids()); log.getSerializedUids(),
);
return data.batchDelete(oldLocationsToDelete, request.method, null, return data.batchDelete(oldLocationsToDelete, request.method, null,
delLog, err => { delLog, err => {
if (err) { if (err) {
@ -409,11 +410,9 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
const additionalHeaders = corsHeaders || {}; const additionalHeaders = corsHeaders || {};
if (serverSideEncryption) { if (serverSideEncryption) {
additionalHeaders['x-amz-server-side-encryption'] = additionalHeaders['x-amz-server-side-encryption'] = serverSideEncryption.algorithm;
serverSideEncryption.algorithm;
if (serverSideEncryption.algorithm === 'aws:kms') { if (serverSideEncryption.algorithm === 'aws:kms') {
additionalHeaders['x-amz-server-side-encryption-aws-kms-key-id'] additionalHeaders['x-amz-server-side-encryption-aws-kms-key-id'] = serverSideEncryption.masterKeyId;
= serverSideEncryption.masterKeyId;
} }
} }
additionalHeaders['x-amz-copy-source-version-id'] = sourceVerId; additionalHeaders['x-amz-copy-source-version-id'] = sourceVerId;

View File

@ -40,13 +40,14 @@ function objectPutLegalHold(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectPutLegalHold',
versionId, versionId,
getDeleteMarker: true,
requestType: request.apiMethods || 'objectPutLegalHold',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',

View File

@ -87,6 +87,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
const uploadId = request.query.uploadId; const uploadId = request.query.uploadId;
const mpuBucketName = `${constants.mpuBucketPrefix}${bucketName}`; const mpuBucketName = `${constants.mpuBucketPrefix}${bucketName}`;
const objectKey = request.objectKey; const objectKey = request.objectKey;
const originalIdentityImpDenies = request.actionImplicitDenies;
return async.waterfall([ return async.waterfall([
// Get the destination bucket. // Get the destination bucket.
@ -109,7 +110,8 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
// For validating the request at the destinationBucket level the // For validating the request at the destinationBucket level the
// `requestType` is the general 'objectPut'. // `requestType` is the general 'objectPut'.
const requestType = 'objectPut'; const requestType = 'objectPut';
if (!isBucketAuthorized(destinationBucket, requestType, canonicalID, authInfo, log, request)) { if (!isBucketAuthorized(destinationBucket, request.apiMethods || requestType, canonicalID, authInfo,
request.actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { requestType }); log.debug('access denied for user on bucket', { requestType });
return next(errors.AccessDenied, destinationBucket); return next(errors.AccessDenied, destinationBucket);
} }
@ -139,24 +141,24 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
// Get the MPU shadow bucket. // Get the MPU shadow bucket.
(destinationBucket, cipherBundle, next) => (destinationBucket, cipherBundle, next) =>
metadata.getBucket(mpuBucketName, log, metadata.getBucket(mpuBucketName, log,
(err, mpuBucket) => { (err, mpuBucket) => {
if (err && err.is.NoSuchBucket) { if (err && err.is.NoSuchBucket) {
return next(errors.NoSuchUpload, destinationBucket); return next(errors.NoSuchUpload, destinationBucket);
} }
if (err) { if (err) {
log.error('error getting the shadow mpu bucket', { log.error('error getting the shadow mpu bucket', {
error: err, error: err,
method: 'objectPutPart::metadata.getBucket', method: 'objectPutPart::metadata.getBucket',
}); });
return next(err, destinationBucket); return next(err, destinationBucket);
} }
let splitter = constants.splitter; let splitter = constants.splitter;
// BACKWARD: Remove to remove the old splitter // BACKWARD: Remove to remove the old splitter
if (mpuBucket.getMdBucketModelVersion() < 2) { if (mpuBucket.getMdBucketModelVersion() < 2) {
splitter = constants.oldSplitter; splitter = constants.oldSplitter;
} }
return next(null, destinationBucket, cipherBundle, splitter); return next(null, destinationBucket, cipherBundle, splitter);
}), }),
// Check authorization of the MPU shadow bucket. // Check authorization of the MPU shadow bucket.
(destinationBucket, cipherBundle, splitter, next) => { (destinationBucket, cipherBundle, splitter, next) => {
const mpuOverviewKey = _getOverviewKey(splitter, objectKey, const mpuOverviewKey = _getOverviewKey(splitter, objectKey,
@ -187,7 +189,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
// If data backend is backend that handles mpu (like real AWS), // If data backend is backend that handles mpu (like real AWS),
// no need to store part info in metadata // no need to store part info in metadata
(destinationBucket, objectLocationConstraint, cipherBundle, (destinationBucket, objectLocationConstraint, cipherBundle,
splitter, next) => { splitter, next) => {
const mpuInfo = { const mpuInfo = {
destinationBucket, destinationBucket,
size, size,
@ -196,24 +198,26 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
partNumber, partNumber,
bucketName, bucketName,
}; };
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
writeContinue(request, request._response); writeContinue(request, request._response);
return data.putPart(request, mpuInfo, streamingV4Params, return data.putPart(request, mpuInfo, streamingV4Params,
objectLocationConstraint, locationConstraintCheck, log, objectLocationConstraint, locationConstraintCheck, log,
(err, partInfo, updatedObjectLC) => { (err, partInfo, updatedObjectLC) => {
if (err) { if (err) {
return next(err, destinationBucket); return next(err, destinationBucket);
} }
// if data backend handles mpu, skip to end of waterfall // if data backend handles mpu, skip to end of waterfall
if (partInfo && partInfo.dataStoreType === 'aws_s3') { if (partInfo && partInfo.dataStoreType === 'aws_s3') {
return next(skipError, destinationBucket, return next(skipError, destinationBucket,
partInfo.dataStoreETag); partInfo.dataStoreETag);
} }
// partInfo will be null if data backend is not external // partInfo will be null if data backend is not external
// if the object location constraint undefined because // if the object location constraint undefined because
// mpu was initiated in legacy version, update it // mpu was initiated in legacy version, update it
return next(null, destinationBucket, updatedObjectLC, return next(null, destinationBucket, updatedObjectLC,
cipherBundle, splitter, partInfo); cipherBundle, splitter, partInfo);
}); });
}, },
// Get any pre-existing part. // Get any pre-existing part.
(destinationBucket, objectLocationConstraint, cipherBundle, (destinationBucket, objectLocationConstraint, cipherBundle,
@ -249,14 +253,14 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
}, },
// Store in data backend. // Store in data backend.
(destinationBucket, objectLocationConstraint, cipherBundle, (destinationBucket, objectLocationConstraint, cipherBundle,
partKey, prevObjectSize, oldLocations, partInfo, splitter, next) => { partKey, prevObjectSize, oldLocations, partInfo, splitter, next) => {
// NOTE: set oldLocations to null so we do not batchDelete for now // NOTE: set oldLocations to null so we do not batchDelete for now
if (partInfo && partInfo.dataStoreType === 'azure') { if (partInfo && partInfo.dataStoreType === 'azure') {
// skip to storing metadata // skip to storing metadata
return next(null, destinationBucket, partInfo, return next(null, destinationBucket, partInfo,
partInfo.dataStoreETag, partInfo.dataStoreETag,
cipherBundle, partKey, prevObjectSize, null, cipherBundle, partKey, prevObjectSize, null,
objectLocationConstraint, splitter); objectLocationConstraint, splitter);
} }
const objectContext = { const objectContext = {
bucketName, bucketName,
@ -282,7 +286,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
// Store data locations in metadata and delete any overwritten // Store data locations in metadata and delete any overwritten
// data if completeMPU hasn't been initiated yet. // data if completeMPU hasn't been initiated yet.
(destinationBucket, dataGetInfo, hexDigest, cipherBundle, partKey, (destinationBucket, dataGetInfo, hexDigest, cipherBundle, partKey,
prevObjectSize, oldLocations, objectLocationConstraint, splitter, next) => { prevObjectSize, oldLocations, objectLocationConstraint, splitter, next) => {
// Use an array to be consistent with objectPutCopyPart where there // Use an array to be consistent with objectPutCopyPart where there
// could be multiple locations. // could be multiple locations.
const partLocations = [dataGetInfo]; const partLocations = [dataGetInfo];
@ -317,7 +321,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
}); });
}, },
(partLocations, oldLocations, objectLocationConstraint, destinationBucket, (partLocations, oldLocations, objectLocationConstraint, destinationBucket,
hexDigest, prevObjectSize, splitter, next) => { hexDigest, prevObjectSize, splitter, next) => {
if (!oldLocations) { if (!oldLocations) {
return next(null, oldLocations, objectLocationConstraint, return next(null, oldLocations, objectLocationConstraint,
destinationBucket, hexDigest, prevObjectSize); destinationBucket, hexDigest, prevObjectSize);
@ -378,6 +382,8 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
], (err, destinationBucket, hexDigest, prevObjectSize) => { ], (err, destinationBucket, hexDigest, prevObjectSize) => {
const corsHeaders = collectCorsHeaders(request.headers.origin, const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, destinationBucket); request.method, destinationBucket);
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) { if (err) {
if (err === skipError) { if (err === skipError) {
return cb(null, hexDigest, corsHeaders); return cb(null, hexDigest, corsHeaders);

View File

@ -41,45 +41,58 @@ function objectPutRetention(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectPutRetention',
versionId: reqVersionId, versionId: reqVersionId,
getDeleteMarker: true,
requestType: request.apiMethods || 'objectPutRetention',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => {
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',
{ method: 'objectPutRetention', error: err });
return next(err);
}
if (!objectMD) {
const err = reqVersionId ? errors.NoSuchVersion :
errors.NoSuchKey;
log.trace('error no object metadata found',
{ method: 'objectPutRetention', error: err });
return next(err, bucket);
}
if (objectMD.isDeleteMarker) {
log.trace('version is a delete marker',
{ method: 'objectPutRetention' });
return next(errors.MethodNotAllowed, bucket);
}
if (!bucket.isObjectLockEnabled()) {
log.trace('object lock not enabled on bucket',
{ method: 'objectPutRetention' });
return next(errors.InvalidRequest.customizeDescription(
'Bucket is missing Object Lock Configuration'
), bucket);
}
return next(null, bucket, objectMD);
}),
(bucket, objectMD, next) => {
log.trace('parsing retention information'); log.trace('parsing retention information');
parseRetentionXml(request.post, log, parseRetentionXml(request.post, log,
(err, retentionInfo) => next(err, bucket, retentionInfo, objectMD)); (err, retentionInfo) => {
if (err) {
log.trace('error parsing retention information',
{ error: err });
return next(err);
}
const remainingDays = Math.ceil(
(new Date(retentionInfo.date) - Date.now()) / (1000 * 3600 * 24));
metadataValParams.request.objectLockRetentionDays = remainingDays;
return next(null, retentionInfo);
});
}, },
(retentionInfo, next) => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',
{ method: 'objectPutRetention', error: err });
return next(err);
}
if (!objectMD) {
const err = reqVersionId ? errors.NoSuchVersion :
errors.NoSuchKey;
log.trace('error no object metadata found',
{ method: 'objectPutRetention', error: err });
return next(err, bucket);
}
if (objectMD.isDeleteMarker) {
log.trace('version is a delete marker',
{ method: 'objectPutRetention' });
// FIXME we should return a `x-amz-delete-marker: true` header,
// see S3C-7592
return next(errors.MethodNotAllowed, bucket);
}
if (!bucket.isObjectLockEnabled()) {
log.trace('object lock not enabled on bucket',
{ method: 'objectPutRetention' });
return next(errors.InvalidRequest.customizeDescription(
'Bucket is missing Object Lock Configuration'
), bucket);
}
return next(null, bucket, retentionInfo, objectMD);
}),
(bucket, retentionInfo, objectMD, next) => { (bucket, retentionInfo, objectMD, next) => {
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers); const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
if (hasGovernanceBypass && authInfo.isRequesterAnIAMUser()) { if (hasGovernanceBypass && authInfo.isRequesterAnIAMUser()) {

View File

@ -1,8 +1,7 @@
const async = require('async'); const async = require('async');
const { errors, s3middleware } = require('arsenal'); const { errors, s3middleware } = require('arsenal');
const { decodeVersionId, getVersionIdResHeader } = const { decodeVersionId, getVersionIdResHeader } = require('./apiUtils/object/versioning');
require('./apiUtils/object/versioning');
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils'); const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities'); const { pushMetric } = require('../utapi/utilities');
@ -10,6 +9,7 @@ const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper'); const metadata = require('../metadata/wrapper');
const { data } = require('../data/wrapper'); const { data } = require('../data/wrapper');
const { parseTagXml } = s3middleware.tagging; const { parseTagXml } = s3middleware.tagging;
const REPLICATION_ACTION = 'PUT_TAGGING'; const REPLICATION_ACTION = 'PUT_TAGGING';
@ -24,8 +24,8 @@ const REPLICATION_ACTION = 'PUT_TAGGING';
function objectPutTagging(authInfo, request, log, callback) { function objectPutTagging(authInfo, request, log, callback) {
log.debug('processing request', { method: 'objectPutTagging' }); log.debug('processing request', { method: 'objectPutTagging' });
const bucketName = request.bucketName; const { bucketName } = request;
const objectKey = request.objectKey; const { objectKey } = request;
const decodedVidResult = decodeVersionId(request.query); const decodedVidResult = decodeVersionId(request.query);
if (decodedVidResult instanceof Error) { if (decodedVidResult instanceof Error) {
@ -41,13 +41,14 @@ function objectPutTagging(authInfo, request, log, callback) {
authInfo, authInfo,
bucketName, bucketName,
objectKey, objectKey,
requestType: 'objectPutTagging',
versionId: reqVersionId, versionId: reqVersionId,
getDeleteMarker: true,
requestType: request.apiMethods || 'objectPutTagging',
request, request,
}; };
return async.waterfall([ return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log, next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => { (err, bucket, objectMD) => {
if (err) { if (err) {
log.trace('request authorization failed', log.trace('request authorization failed',
@ -70,8 +71,7 @@ function objectPutTagging(authInfo, request, log, callback) {
}), }),
(bucket, objectMD, next) => { (bucket, objectMD, next) => {
log.trace('parsing tag(s)'); log.trace('parsing tag(s)');
parseTagXml(request.post, log, (err, tags) => parseTagXml(request.post, log, (err, tags) => next(err, bucket, tags, objectMD));
next(err, bucket, tags, objectMD));
}, },
(bucket, tags, objectMD, next) => { (bucket, tags, objectMD, next) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -88,13 +88,11 @@ function objectPutTagging(authInfo, request, log, callback) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
objectMD.originOp = 's3:ObjectTagging:Put'; objectMD.originOp = 's3:ObjectTagging:Put';
metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params, metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params,
log, err => log, err => next(err, bucket, objectMD));
next(err, bucket, objectMD));
}, },
(bucket, objectMD, next) => // if external backend handles tagging
// if external backend handles tagging (bucket, objectMD, next) => data.objectTagging('Put', objectKey, bucket, objectMD,
data.objectTagging('Put', objectKey, bucket, objectMD, log, err => next(err, bucket, objectMD)),
log, err => next(err, bucket, objectMD)),
], (err, bucket, objectMD) => { ], (err, bucket, objectMD) => {
const additionalResHeaders = collectCorsHeaders(request.headers.origin, const additionalResHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket); request.method, bucket);
@ -110,8 +108,7 @@ function objectPutTagging(authInfo, request, log, callback) {
location: objectMD ? objectMD.dataStoreName : undefined, location: objectMD ? objectMD.dataStoreName : undefined,
}); });
const verCfg = bucket.getVersioningConfiguration(); const verCfg = bucket.getVersioningConfiguration();
additionalResHeaders['x-amz-version-id'] = additionalResHeaders['x-amz-version-id'] = getVersionIdResHeader(verCfg, objectMD);
getVersionIdResHeader(verCfg, objectMD);
} }
return callback(err, additionalResHeaders); return callback(err, additionalResHeaders);
}); });

View File

@ -21,12 +21,13 @@ const { pushMetric } = require('../utapi/utilities');
* @param {string} objectKey - object key from request (or as translated in * @param {string} objectKey - object key from request (or as translated in
* websiteGet) * websiteGet)
* @param {object} corsHeaders - CORS-related response headers * @param {object} corsHeaders - CORS-related response headers
* @param {object} request - normalized request object
* @param {object} log - Werelogs instance * @param {object} log - Werelogs instance
* @param {function} callback - callback to function in route * @param {function} callback - callback to function in route
* @return {undefined} * @return {undefined}
*/ */
function _errorActions(err, errorDocument, routingRules, function _errorActions(err, errorDocument, routingRules,
bucket, objectKey, corsHeaders, log, callback) { bucket, objectKey, corsHeaders, request, log, callback) {
const bucketName = bucket.getName(); const bucketName = bucket.getName();
const errRoutingRule = findRoutingRule(routingRules, const errRoutingRule = findRoutingRule(routingRules,
objectKey, err.code); objectKey, err.code);
@ -46,8 +47,8 @@ function _errorActions(err, errorDocument, routingRules,
} }
// return the default error message if the object is private // return the default error message if the object is private
// rather than sending a stored error file // rather than sending a stored error file
if (!isObjAuthorized(bucket, errObjMD, 'objectGet', if (!isObjAuthorized(bucket, errObjMD, request.apiMethods || 'objectGet',
constants.publicId, null, log)) { constants.publicId, null, request.actionImplicitDenies, log)) {
log.trace('errorObj not authorized', { error: err }); log.trace('errorObj not authorized', { error: err });
return callback(err, true, null, corsHeaders); return callback(err, true, null, corsHeaders);
} }
@ -143,8 +144,8 @@ function websiteGet(request, log, callback) {
log.trace('error retrieving object metadata', log.trace('error retrieving object metadata',
{ error: err }); { error: err });
let returnErr = err; let returnErr = err;
const bucketAuthorized = isBucketAuthorized(bucket, const bucketAuthorized = isBucketAuthorized(bucket, request.apiMethods || 'bucketGet',
'bucketGet', constants.publicId, null, log, request); constants.publicId, null, request.actionImplicitDenies, log, request);
// if index object does not exist and bucket is private AWS // if index object does not exist and bucket is private AWS
// returns 403 - AccessDenied error. // returns 403 - AccessDenied error.
if (err.is.NoSuchKey && !bucketAuthorized) { if (err.is.NoSuchKey && !bucketAuthorized) {
@ -152,16 +153,16 @@ function websiteGet(request, log, callback) {
} }
return _errorActions(returnErr, return _errorActions(returnErr,
websiteConfig.getErrorDocument(), routingRules, websiteConfig.getErrorDocument(), routingRules,
bucket, reqObjectKey, corsHeaders, log, bucket, reqObjectKey, corsHeaders, request, log,
callback); callback);
} }
if (!isObjAuthorized(bucket, objMD, 'objectGet', if (!isObjAuthorized(bucket, objMD, request.apiMethods || 'objectGet',
constants.publicId, null, log, request)) { constants.publicId, null, request.actionImplicitDenies, log, request)) {
const err = errors.AccessDenied; const err = errors.AccessDenied;
log.trace('request not authorized', { error: err }); log.trace('request not authorized', { error: err });
return _errorActions(err, websiteConfig.getErrorDocument(), return _errorActions(err, websiteConfig.getErrorDocument(),
routingRules, bucket, routingRules, bucket,
reqObjectKey, corsHeaders, log, callback); reqObjectKey, corsHeaders, request, log, callback);
} }
const headerValResult = validateHeaders(request.headers, const headerValResult = validateHeaders(request.headers,
@ -171,7 +172,7 @@ function websiteGet(request, log, callback) {
log.trace('header validation error', { error: err }); log.trace('header validation error', { error: err });
return _errorActions(err, websiteConfig.getErrorDocument(), return _errorActions(err, websiteConfig.getErrorDocument(),
routingRules, bucket, reqObjectKey, routingRules, bucket, reqObjectKey,
corsHeaders, log, callback); corsHeaders, request, log, callback);
} }
// check if object to serve has website redirect header // check if object to serve has website redirect header
// Note: AWS prioritizes website configuration rules over // Note: AWS prioritizes website configuration rules over

View File

@ -103,8 +103,8 @@ function websiteHead(request, log, callback) {
log.trace('error retrieving object metadata', log.trace('error retrieving object metadata',
{ error: err }); { error: err });
let returnErr = err; let returnErr = err;
const bucketAuthorized = isBucketAuthorized(bucket, const bucketAuthorized = isBucketAuthorized(bucket, request.apiMethods || 'bucketGet',
'bucketGet', constants.publicId, null, log, request); constants.publicId, null, request.actionImplicitDenies, log, request);
// if index object does not exist and bucket is private AWS // if index object does not exist and bucket is private AWS
// returns 403 - AccessDenied error. // returns 403 - AccessDenied error.
if (err.is.NoSuchKey && !bucketAuthorized) { if (err.is.NoSuchKey && !bucketAuthorized) {
@ -113,8 +113,8 @@ function websiteHead(request, log, callback) {
return _errorActions(returnErr, routingRules, return _errorActions(returnErr, routingRules,
reqObjectKey, corsHeaders, log, callback); reqObjectKey, corsHeaders, log, callback);
} }
if (!isObjAuthorized(bucket, objMD, 'objectGet', if (!isObjAuthorized(bucket, objMD, request.apiMethods || 'objectGet',
constants.publicId, null, log, request)) { constants.publicId, null, request.actionImplicitDenies, log, request)) {
const err = errors.AccessDenied; const err = errors.AccessDenied;
log.trace('request not authorized', { error: err }); log.trace('request not authorized', { error: err });
return _errorActions(err, routingRules, reqObjectKey, return _errorActions(err, routingRules, reqObjectKey,

View File

@ -42,7 +42,6 @@ function getNullVersion(objMD, bucketName, objectKey, log, cb) {
* NOTE: If the value of `versionId` param is 'null', this function returns the * NOTE: If the value of `versionId` param is 'null', this function returns the
* master version objMD. The null version object md must be retrieved in a * master version objMD. The null version object md must be retrieved in a
* separate step using the master object md: see getNullVersion(). * separate step using the master object md: see getNullVersion().
* @param {string} requestType - type of request
* @param {string} bucketName - name of bucket * @param {string} bucketName - name of bucket
* @param {string} objectKey - name of object key * @param {string} objectKey - name of object key
* @param {string} [versionId] - version of object to retrieve * @param {string} [versionId] - version of object to retrieve
@ -50,7 +49,7 @@ function getNullVersion(objMD, bucketName, objectKey, log, cb) {
* @param {function} cb - callback * @param {function} cb - callback
* @return {undefined} - and call callback with err, bucket md and object md * @return {undefined} - and call callback with err, bucket md and object md
*/ */
function metadataGetBucketAndObject(requestType, bucketName, objectKey, function metadataGetBucketAndObject(bucketName, objectKey,
versionId, log, cb) { versionId, log, cb) {
const options = { const options = {
// if attempting to get 'null' version, must retrieve null version id // if attempting to get 'null' version, must retrieve null version id
@ -73,13 +72,6 @@ function metadataGetBucketAndObject(requestType, bucketName, objectKey,
}); });
return cb(errors.NoSuchBucket); return cb(errors.NoSuchBucket);
} }
if (bucketShield(bucket, requestType)) {
log.debug('bucket is shielded from request', {
requestType,
method: 'metadataGetBucketAndObject',
});
return cb(errors.NoSuchBucket);
}
log.trace('found bucket in metadata'); log.trace('found bucket in metadata');
return cb(null, bucket, obj); return cb(null, bucket, obj);
}); });
@ -118,6 +110,54 @@ 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;
if (bucketShield(bucket, requestType)) {
log.debug('bucket is shielded from request', {
requestType,
method: 'validateBucket',
});
return errors.NoSuchBucket;
}
// if requester is not bucket owner, bucket policy actions should be denied with
// MethodNotAllowed error
const onlyOwnerAllowed = ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'];
const canonicalID = authInfo.getCanonicalID();
if (!Array.isArray(requestType)) {
requestType = [requestType];
}
if (bucket.getOwner() !== canonicalID && requestType.some(type => onlyOwnerAllowed.includes(type))) {
return errors.MethodNotAllowed;
}
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID,
authInfo, actionImplicitDenies, log, request)) {
log.debug('access denied for user on bucket', { requestType });
return errors.AccessDenied;
}
return null;
}
/** metadataValidateBucketAndObj - retrieve bucket and object md from metadata /** metadataValidateBucketAndObj - retrieve bucket and object md from metadata
* and check if user is authorized to access them. * and check if user is authorized to access them.
* @param {object} params - function parameters * @param {object} params - function parameters
@ -127,41 +167,60 @@ function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
* @param {string} [params.versionId] - version id if getting specific version * @param {string} [params.versionId] - version id if getting specific version
* @param {string} params.requestType - type of request * @param {string} params.requestType - type of request
* @param {object} params.request - http request object * @param {object} params.request - http request object
* @param {boolean} actionImplicitDenies - identity authorization results
* @param {RequestLogger} log - request logger * @param {RequestLogger} log - request logger
* @param {function} callback - callback * @param {function} callback - callback
* @return {undefined} - and call callback with params err, bucket md * @return {undefined} - and call callback with params err, bucket md
*/ */
function metadataValidateBucketAndObj(params, log, callback) { function metadataValidateBucketAndObj(params, actionImplicitDenies, log, callback) {
const { authInfo, bucketName, objectKey, versionId, requestType, preciseRequestType, request } = params; const { authInfo, bucketName, objectKey, versionId, getDeleteMarker, request } = params;
const canonicalID = authInfo.getCanonicalID(); let requestType = params.requestType;
if (!Array.isArray(requestType)) {
requestType = [requestType];
}
async.waterfall([ async.waterfall([
function getBucketAndObjectMD(next) { next => {
return metadataGetBucketAndObject(requestType, bucketName, // versionId may be 'null', which asks metadata to fetch the null key specifically
objectKey, versionId, log, next); const getOptions = { versionId };
}, if (getDeleteMarker) {
function checkBucketAuth(bucket, objMD, next) { getOptions.getDeleteMarker = true;
// if requester is not bucket owner, bucket policy actions should be denied with
// MethodNotAllowed error
const onlyOwnerAllowed = ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'];
if (bucket.getOwner() !== canonicalID && onlyOwnerAllowed.includes(requestType)) {
return next(errors.MethodNotAllowed, bucket);
} }
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID, return metadata.getBucketAndObjectMD(bucketName, objectKey, getOptions, log, (err, getResult) => {
authInfo, log, request)) { if (err) {
log.debug('access denied for user on bucket', { requestType }); // if some implicit actionImplicitDenies, return AccessDenied
return next(errors.AccessDenied, bucket); // before leaking any state information
} if (actionImplicitDenies && Object.values(actionImplicitDenies).some(v => v === true)) {
return next(null, bucket, objMD); return next(errors.AccessDenied);
}
return next(err);
}
return next(null, getResult);
});
}, },
function handleNullVersionGet(bucket, objMD, next) { (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);
}
if (objMD && versionId === 'null') { if (objMD && versionId === 'null') {
return getNullVersion(objMD, bucketName, objectKey, log, return getNullVersion(objMD, bucketName, objectKey, log,
(err, nullVer) => next(err, bucket, nullVer)); (err, nullVer) => next(err, bucket, nullVer));
} }
return next(null, bucket, objMD); return next(null, bucket, objMD);
}, },
function checkObjectAuth(bucket, objMD, next) { (bucket, objMD, next) => {
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, log, request)) { const canonicalID = authInfo.getCanonicalID();
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, actionImplicitDenies,
log, request)) {
log.debug('access denied for user on object', { requestType }); log.debug('access denied for user on object', { requestType });
return next(errors.AccessDenied, bucket); return next(errors.AccessDenied, bucket);
} }
@ -209,34 +268,25 @@ function metadataGetBucket(requestType, bucketName, log, cb) {
* @param {string} params.bucketName - name of bucket * @param {string} params.bucketName - name of bucket
* @param {string} params.requestType - type of request * @param {string} params.requestType - type of request
* @param {string} params.request - http request object * @param {string} params.request - http request object
* @param {boolean} actionImplicitDenies - identity authorization results
* @param {RequestLogger} log - request logger * @param {RequestLogger} log - request logger
* @param {function} callback - callback * @param {function} callback - callback
* @return {undefined} - and call callback with params err, bucket md * @return {undefined} - and call callback with params err, bucket md
*/ */
function metadataValidateBucket(params, log, callback) { function metadataValidateBucket(params, actionImplicitDenies, log, callback) {
const { authInfo, bucketName, requestType, preciseRequestType, request } = params; const { bucketName } = params;
const canonicalID = authInfo.getCanonicalID(); return metadata.getBucket(bucketName, log, (err, bucket) => {
return metadataGetBucket(requestType, bucketName, log, (err, bucket) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
// if requester is not bucket owner, bucket policy actions should be denied with const validationError = validateBucket(bucket, params, actionImplicitDenies, log);
// MethodNotAllowed error return callback(validationError, bucket);
const onlyOwnerAllowed = ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'];
if (bucket.getOwner() !== canonicalID && onlyOwnerAllowed.includes(requestType)) {
return callback(errors.MethodNotAllowed, bucket);
}
// still return bucket for cors headers
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID, authInfo, log, request)) {
log.debug('access denied for user on bucket', { requestType });
return callback(errors.AccessDenied, bucket);
}
return callback(null, bucket);
}); });
} }
module.exports = { module.exports = {
metadataGetObject, metadataGetObject,
validateBucket,
metadataValidateBucketAndObj, metadataValidateBucketAndObj,
metadataValidateBucket, metadataValidateBucket,
}; };

View File

@ -1149,6 +1149,8 @@ function routeBackbeat(clientIP, request, response, log) {
// Attach the apiMethod method to the request, so it can used by monitoring in the server // Attach the apiMethod method to the request, so it can used by monitoring in the server
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
request.apiMethod = 'routeBackbeat'; request.apiMethod = 'routeBackbeat';
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = false;
log.debug('routing request', { log.debug('routing request', {
method: 'routeBackbeat', method: 'routeBackbeat',
@ -1270,10 +1272,10 @@ function routeBackbeat(clientIP, request, response, log) {
objectKey: request.objectKey, objectKey: request.objectKey,
authInfo: userInfo, authInfo: userInfo,
versionId, versionId,
requestType: 'ReplicateObject', requestType: request.apiMethods || 'ReplicateObject',
request, request,
}; };
return metadataValidateBucketAndObj(mdValParams, log, next); return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log, next);
}, },
(bucketInfo, objMd, next) => { (bucketInfo, objMd, next) => {
if (useMultipleBackend) { if (useMultipleBackend) {

View File

@ -20,7 +20,7 @@
"homepage": "https://github.com/scality/S3#readme", "homepage": "https://github.com/scality/S3#readme",
"dependencies": { "dependencies": {
"@hapi/joi": "^17.1.0", "@hapi/joi": "^17.1.0",
"arsenal": "git+https://github.com/scality/arsenal#7.10.47", "arsenal": "git+https://github.com/scality/arsenal#1d74f512c86ca34ee7e662acdc213f18c86326b0",
"async": "~2.5.0", "async": "~2.5.0",
"aws-sdk": "2.905.0", "aws-sdk": "2.905.0",
"azure-storage": "^2.1.0", "azure-storage": "^2.1.0",

View File

@ -175,7 +175,6 @@ describe('KMIP backed server-side encryption', () => {
done(); done();
}); });
}); });
it('should allow creating mpu with SSE header ' + it('should allow creating mpu with SSE header ' +
'in encrypted bucket', done => { 'in encrypted bucket', done => {
async.waterfall([ async.waterfall([

Binary file not shown.

View File

@ -54,6 +54,7 @@ const bucketPutRequest = {
url: '/', url: '/',
post: '', post: '',
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
const awsETag = 'be747eb4b75517bf6b3cf7c5fbb62f3a'; const awsETag = 'be747eb4b75517bf6b3cf7c5fbb62f3a';
@ -73,6 +74,7 @@ const completeBody = '<CompleteMultipartUpload>' +
const basicParams = { const basicParams = {
bucketName, bucketName,
namespace, namespace,
actionImplicitDenies: false,
}; };
function getObjectGetRequest(objectKey) { function getObjectGetRequest(objectKey) {
@ -270,6 +272,7 @@ function mpuSetup(location, key, cb) {
'x-amz-meta-scal-location-constraint': location }, 'x-amz-meta-scal-location-constraint': location },
url: `/${key}?uploads`, url: `/${key}?uploads`,
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
initiateMultipartUpload(authInfo, initiateRequest, log, initiateMultipartUpload(authInfo, initiateRequest, log,
(err, result) => { (err, result) => {
@ -317,7 +320,6 @@ function abortMultipleMpus(backendsInfo, callback) {
callback(); callback();
}); });
} }
describe('Multipart Upload API with AWS Backend', function mpuTestSuite() { describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
this.timeout(60000); this.timeout(60000);
@ -342,6 +344,7 @@ describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
'x-amz-meta-scal-location-constraint': `${awsLocation}` }, 'x-amz-meta-scal-location-constraint': `${awsLocation}` },
url: `/${objectKey}?uploads`, url: `/${objectKey}?uploads`,
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
initiateMultipartUpload(authInfo, initiateRequest, log, initiateMultipartUpload(authInfo, initiateRequest, log,
@ -365,6 +368,7 @@ describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
`${awsLocationMismatch}` }, `${awsLocationMismatch}` },
url: `/${objectKey}?uploads`, url: `/${objectKey}?uploads`,
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
initiateMultipartUpload(authInfo, initiateRequest, log, initiateMultipartUpload(authInfo, initiateRequest, log,
@ -389,6 +393,7 @@ describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
}, },
url: `/${objectKey}?uploads`, url: `/${objectKey}?uploads`,
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
initiateMultipartUpload(authInfo, initiateRequest, log, initiateMultipartUpload(authInfo, initiateRequest, log,
@ -612,6 +617,7 @@ describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
'x-amz-meta-scal-location-constraint': awsLocation }, 'x-amz-meta-scal-location-constraint': awsLocation },
url: `/${objectKey}?uploads`, url: `/${objectKey}?uploads`,
parsedHost: 'localhost', parsedHost: 'localhost',
actionImplicitDenies: false,
}; };
initiateMultipartUpload(authInfo, initiateRequest, log, initiateMultipartUpload(authInfo, initiateRequest, log,
err => { err => {
@ -712,6 +718,7 @@ describe('Multipart Upload API with AWS Backend', function mpuTestSuite() {
headers: { host: '/' }, headers: { host: '/' },
url: `/${bucketName}?uploads`, url: `/${bucketName}?uploads`,
query: {}, query: {},
actionImplicitDenies: false,
}; };
listMultipartUploads(authInfo, listMpuParams, log, listMultipartUploads(authInfo, listMpuParams, log,
(err, mpuListXml) => { (err, mpuListXml) => {

View File

@ -71,7 +71,6 @@ function copySetup(params, cb) {
callback), callback),
], err => cb(err)); ], err => cb(err));
} }
describe('ObjectCopy API with multiple backends', () => { describe('ObjectCopy API with multiple backends', () => {
before(() => { before(() => {
cleanup(); cleanup();

View File

@ -3,21 +3,19 @@ const async = require('async');
const { parseString } = require('xml2js'); const { parseString } = require('xml2js');
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
const { cleanup, DummyRequestLogger, makeAuthInfo } const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
= require('../unit/helpers'); const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../unit/helpers');
const { ds } = require('arsenal').storage.data.inMemory.datastore; const { ds } = require('arsenal').storage.data.inMemory.datastore;
const { bucketPut } = require('../../lib/api/bucketPut'); const { bucketPut } = require('../../lib/api/bucketPut');
const initiateMultipartUpload const initiateMultipartUpload = require('../../lib/api/initiateMultipartUpload');
= require('../../lib/api/initiateMultipartUpload');
const objectPut = require('../../lib/api/objectPut'); const objectPut = require('../../lib/api/objectPut');
const objectPutCopyPart = require('../../lib/api/objectPutCopyPart'); const objectPutCopyPart = require('../../lib/api/objectPutCopyPart');
const DummyRequest = require('../unit/DummyRequest'); const DummyRequest = require('../unit/DummyRequest');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const constants = require('../../constants'); const constants = require('../../constants');
const s3 = new AWS.S3(); const s3 = new AWS.S3();
const splitter = constants.splitter; const { splitter } = constants;
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();
const canonicalID = 'accessKey1'; const canonicalID = 'accessKey1';
const authInfo = makeAuthInfo(canonicalID); const authInfo = makeAuthInfo(canonicalID);
@ -56,14 +54,14 @@ function getAwsParamsBucketMismatch(destObjName, uploadId) {
} }
function copyPutPart(bucketLoc, mpuLoc, srcObjLoc, requestHost, cb, function copyPutPart(bucketLoc, mpuLoc, srcObjLoc, requestHost, cb,
errorPutCopyPart) { errorPutCopyPart) {
const keys = getSourceAndDestKeys(); const keys = getSourceAndDestKeys();
const { sourceObjName, destObjName } = keys; const { sourceObjName, destObjName } = keys;
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>' + const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>'
'<CreateBucketConfiguration ' + + '<CreateBucketConfiguration '
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' + + 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
`<LocationConstraint>${bucketLoc}</LocationConstraint>` + + `<LocationConstraint>${bucketLoc}</LocationConstraint>`
'</CreateBucketConfiguration>' : ''; + '</CreateBucketConfiguration>' : '';
const bucketPutReq = new DummyRequest({ const bucketPutReq = new DummyRequest({
bucketName, bucketName,
namespace, namespace,
@ -80,10 +78,13 @@ errorPutCopyPart) {
objectKey: destObjName, objectKey: destObjName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${destObjName}?uploads`, url: `/${destObjName}?uploads`,
actionImplicitDenies: false,
}; };
if (mpuLoc) { if (mpuLoc) {
initiateReq.headers = { 'host': `${bucketName}.s3.amazonaws.com`, initiateReq.headers = {
'x-amz-meta-scal-location-constraint': `${mpuLoc}` }; 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}`,
};
} }
if (requestHost) { if (requestHost) {
initiateReq.parsedHost = requestHost; initiateReq.parsedHost = requestHost;
@ -94,10 +95,13 @@ errorPutCopyPart) {
objectKey: sourceObjName, objectKey: sourceObjName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
if (srcObjLoc) { if (srcObjLoc) {
sourceObjPutParams.headers = { 'host': `${bucketName}.s3.amazonaws.com`, sourceObjPutParams.headers = {
'x-amz-meta-scal-location-constraint': `${srcObjLoc}` }; 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${srcObjLoc}`,
};
} }
const sourceObjPutReq = new DummyRequest(sourceObjPutParams, body); const sourceObjPutReq = new DummyRequest(sourceObjPutParams, body);
if (requestHost) { if (requestHost) {
@ -112,8 +116,7 @@ errorPutCopyPart) {
}); });
}, },
next => { next => {
objectPut(authInfo, sourceObjPutReq, undefined, log, err => objectPut(authInfo, sourceObjPutReq, undefined, log, err => next(err));
next(err));
}, },
next => { next => {
initiateMultipartUpload(authInfo, initiateReq, log, next); initiateMultipartUpload(authInfo, initiateReq, log, next);
@ -130,8 +133,8 @@ errorPutCopyPart) {
// Need to build request in here since do not have // Need to build request in here since do not have
// uploadId until here // uploadId until here
assert.ifError(err, 'Error putting source object or initiate MPU'); assert.ifError(err, 'Error putting source object or initiate MPU');
const testUploadId = json.InitiateMultipartUploadResult. const testUploadId = json.InitiateMultipartUploadResult
UploadId[0]; .UploadId[0];
const copyPartParams = { const copyPartParams = {
bucketName, bucketName,
namespace, namespace,
@ -172,137 +175,137 @@ function assertPartList(partList, uploadId) {
} }
describeSkipIfE2E('ObjectCopyPutPart API with multiple backends', describeSkipIfE2E('ObjectCopyPutPart API with multiple backends',
function testSuite() { function testSuite() {
this.timeout(60000); this.timeout(60000);
beforeEach(() => { beforeEach(() => {
cleanup(); cleanup();
}); });
it('should copy part to mem based on mpu location', done => { it('should copy part to mem based on mpu location', done => {
copyPutPart(fileLocation, memLocation, null, 'localhost', () => { copyPutPart(fileLocation, memLocation, null, 'localhost', () => {
// object info is stored in ds beginning at index one, // object info is stored in ds beginning at index one,
// so an array length of two means only one object // so an array length of two means only one object
// was stored in mem // was stored in mem
assert.strictEqual(ds.length, 2); assert.strictEqual(ds.length, 2);
assert.deepStrictEqual(ds[1].value, body); assert.deepStrictEqual(ds[1].value, body);
done(); done();
});
});
it('should copy part to file based on mpu location', done => {
copyPutPart(memLocation, fileLocation, null, 'localhost', () => {
assert.strictEqual(ds.length, 2);
done();
});
});
it('should copy part to AWS based on mpu location', done => {
copyPutPart(memLocation, awsLocation, null, 'localhost',
(keys, uploadId) => {
assert.strictEqual(ds.length, 2);
const awsReq = getAwsParams(keys.destObjName, uploadId);
s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` +
`You must abort MPU with upload ID ${uploadId} manually.`);
done();
});
}); });
}); });
});
it('should copy part to mem from AWS based on mpu location', done => { it('should copy part to file based on mpu location', done => {
copyPutPart(awsLocation, memLocation, null, 'localhost', () => { copyPutPart(memLocation, fileLocation, null, 'localhost', () => {
assert.strictEqual(ds.length, 2); assert.strictEqual(ds.length, 2);
assert.deepStrictEqual(ds[1].value, body); done();
done(); });
}); });
});
it('should copy part to mem based on bucket location', done => { it('should copy part to AWS based on mpu location', done => {
copyPutPart(memLocation, null, null, 'localhost', () => { copyPutPart(memLocation, awsLocation, null, 'localhost',
(keys, uploadId) => {
assert.strictEqual(ds.length, 2);
const awsReq = getAwsParams(keys.destObjName, uploadId);
s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. `
+ `You must abort MPU with upload ID ${uploadId} manually.`);
done();
});
});
});
});
it('should copy part to mem from AWS based on mpu location', done => {
copyPutPart(awsLocation, memLocation, null, 'localhost', () => {
assert.strictEqual(ds.length, 2);
assert.deepStrictEqual(ds[1].value, body);
done();
});
});
it('should copy part to mem based on bucket location', done => {
copyPutPart(memLocation, null, null, 'localhost', () => {
// ds length should be three because both source // ds length should be three because both source
// and copied objects should be in mem // and copied objects should be in mem
assert.strictEqual(ds.length, 3); assert.strictEqual(ds.length, 3);
assert.deepStrictEqual(ds[2].value, body); assert.deepStrictEqual(ds[2].value, body);
done(); done();
});
}); });
});
it('should copy part to file based on bucket location', done => { it('should copy part to file based on bucket location', done => {
copyPutPart(fileLocation, null, null, 'localhost', () => { copyPutPart(fileLocation, null, null, 'localhost', () => {
// ds should be empty because both source and // ds should be empty because both source and
// coped objects should be in file // coped objects should be in file
assert.deepStrictEqual(ds, []); assert.deepStrictEqual(ds, []);
done(); done();
});
}); });
});
it('should copy part to AWS based on bucket location', done => { it('should copy part to AWS based on bucket location', done => {
copyPutPart(awsLocation, null, null, 'localhost', (keys, uploadId) => { copyPutPart(awsLocation, null, null, 'localhost', (keys, uploadId) => {
assert.deepStrictEqual(ds, []); assert.deepStrictEqual(ds, []);
const awsReq = getAwsParams(keys.destObjName, uploadId); const awsReq = getAwsParams(keys.destObjName, uploadId);
s3.listParts(awsReq, (err, partList) => { s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId); assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => { s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` + assert.equal(err, null, `Error aborting MPU: ${err}. `
`You must abort MPU with upload ID ${uploadId} manually.`); + `You must abort MPU with upload ID ${uploadId} manually.`);
done(); done();
});
}); });
}); });
}); });
});
it('should copy part an object on AWS location that has bucketMatch ' + it('should copy part an object on AWS location that has bucketMatch '
'equals false to a mpu with a different AWS location', done => { + 'equals false to a mpu with a different AWS location', done => {
copyPutPart(null, awsLocation, awsLocationMismatch, 'localhost', copyPutPart(null, awsLocation, awsLocationMismatch, 'localhost',
(keys, uploadId) => { (keys, uploadId) => {
assert.deepStrictEqual(ds, []); assert.deepStrictEqual(ds, []);
const awsReq = getAwsParams(keys.destObjName, uploadId); const awsReq = getAwsParams(keys.destObjName, uploadId);
s3.listParts(awsReq, (err, partList) => { s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId); assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => { s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` + assert.equal(err, null, `Error aborting MPU: ${err}. `
`You must abort MPU with upload ID ${uploadId} manually.`); + `You must abort MPU with upload ID ${uploadId} manually.`);
done(); done();
});
});
}); });
});
it('should copy part an object on AWS to a mpu with a different '
+ 'AWS location that has bucketMatch equals false', done => {
copyPutPart(null, awsLocationMismatch, awsLocation, 'localhost',
(keys, uploadId) => {
assert.deepStrictEqual(ds, []);
const awsReq = getAwsParamsBucketMismatch(keys.destObjName,
uploadId);
s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. `
+ `You must abort MPU with upload ID ${uploadId} manually.`);
done();
});
});
});
});
it('should return error 403 AccessDenied copying part to a '
+ 'different AWS location without object READ access',
done => {
const errorPutCopyPart = { code: 'AccessDenied', statusCode: 403 };
copyPutPart(null, awsLocation, awsLocation2, 'localhost', done,
errorPutCopyPart);
});
it('should copy part to file based on request endpoint', done => {
copyPutPart(null, null, memLocation, 'localhost', () => {
assert.strictEqual(ds.length, 2);
done();
}); });
}); });
}); });
it('should copy part an object on AWS to a mpu with a different ' +
'AWS location that has bucketMatch equals false', done => {
copyPutPart(null, awsLocationMismatch, awsLocation, 'localhost',
(keys, uploadId) => {
assert.deepStrictEqual(ds, []);
const awsReq = getAwsParamsBucketMismatch(keys.destObjName,
uploadId);
s3.listParts(awsReq, (err, partList) => {
assertPartList(partList, uploadId);
s3.abortMultipartUpload(awsReq, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` +
`You must abort MPU with upload ID ${uploadId} manually.`);
done();
});
});
});
});
it('should return error 403 AccessDenied copying part to a ' +
'different AWS location without object READ access',
done => {
const errorPutCopyPart = { code: 'AccessDenied', statusCode: 403 };
copyPutPart(null, awsLocation, awsLocation2, 'localhost', done,
errorPutCopyPart);
});
it('should copy part to file based on request endpoint', done => {
copyPutPart(null, null, memLocation, 'localhost', () => {
assert.strictEqual(ds.length, 2);
done();
});
});
});

View File

@ -3,20 +3,17 @@ const async = require('async');
const crypto = require('crypto'); const crypto = require('crypto');
const { parseString } = require('xml2js'); const { parseString } = require('xml2js');
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const { config } = require('../../lib/Config'); const { config } = require('../../lib/Config');
const { cleanup, DummyRequestLogger, makeAuthInfo } const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../unit/helpers');
= require('../unit/helpers');
const { ds } = require('arsenal').storage.data.inMemory.datastore; const { ds } = require('arsenal').storage.data.inMemory.datastore;
const { bucketPut } = require('../../lib/api/bucketPut'); const { bucketPut } = require('../../lib/api/bucketPut');
const initiateMultipartUpload const initiateMultipartUpload = require('../../lib/api/initiateMultipartUpload');
= require('../../lib/api/initiateMultipartUpload');
const objectPutPart = require('../../lib/api/objectPutPart'); const objectPutPart = require('../../lib/api/objectPutPart');
const DummyRequest = require('../unit/DummyRequest'); const DummyRequest = require('../unit/DummyRequest');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const mdWrapper = require('../../lib/metadata/wrapper'); const mdWrapper = require('../../lib/metadata/wrapper');
const constants = require('../../constants'); const constants = require('../../constants');
const { getRealAwsConfig } = const { getRealAwsConfig } = require('../functional/aws-node-sdk/test/support/awsConfig');
require('../functional/aws-node-sdk/test/support/awsConfig');
const memLocation = 'scality-internal-mem'; const memLocation = 'scality-internal-mem';
const fileLocation = 'scality-internal-file'; const fileLocation = 'scality-internal-file';
@ -25,7 +22,7 @@ const awsLocationMismatch = 'awsbackendmismatch';
const awsConfig = getRealAwsConfig(awsLocation); const awsConfig = getRealAwsConfig(awsLocation);
const s3 = new AWS.S3(awsConfig); const s3 = new AWS.S3(awsConfig);
const splitter = constants.splitter; const { splitter } = constants;
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();
const canonicalID = 'accessKey1'; const canonicalID = 'accessKey1';
const authInfo = makeAuthInfo(canonicalID); const authInfo = makeAuthInfo(canonicalID);
@ -47,13 +44,13 @@ function _getOverviewKey(objectKey, uploadId) {
} }
function putPart(bucketLoc, mpuLoc, requestHost, cb, function putPart(bucketLoc, mpuLoc, requestHost, cb,
errorDescription) { errorDescription) {
const objectName = `objectName-${Date.now()}`; const objectName = `objectName-${Date.now()}`;
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>' + const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>'
'<CreateBucketConfiguration ' + + '<CreateBucketConfiguration '
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' + + 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
`<LocationConstraint>${bucketLoc}</LocationConstraint>` + + `<LocationConstraint>${bucketLoc}</LocationConstraint>`
'</CreateBucketConfiguration>' : ''; + '</CreateBucketConfiguration>' : '';
const bucketPutReq = { const bucketPutReq = {
bucketName, bucketName,
namespace, namespace,
@ -70,10 +67,13 @@ errorDescription) {
objectKey: objectName, objectKey: objectName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectName}?uploads`, url: `/${objectName}?uploads`,
actionImplicitDenies: false,
}; };
if (mpuLoc) { if (mpuLoc) {
initiateReq.headers = { 'host': `${bucketName}.s3.amazonaws.com`, initiateReq.headers = {
'x-amz-meta-scal-location-constraint': `${mpuLoc}` }; 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}`,
};
} }
if (requestHost) { if (requestHost) {
initiateReq.parsedHost = requestHost; initiateReq.parsedHost = requestHost;
@ -123,9 +123,9 @@ errorDescription) {
const partReq = new DummyRequest(partReqParams, body1); const partReq = new DummyRequest(partReqParams, body1);
return objectPutPart(authInfo, partReq, undefined, log, err => { return objectPutPart(authInfo, partReq, undefined, log, err => {
assert.strictEqual(err, null); assert.strictEqual(err, null);
if (bucketLoc !== awsLocation && mpuLoc !== awsLocation && if (bucketLoc !== awsLocation && mpuLoc !== awsLocation
bucketLoc !== awsLocationMismatch && && bucketLoc !== awsLocationMismatch
mpuLoc !== awsLocationMismatch) { && mpuLoc !== awsLocationMismatch) {
const keysInMPUkeyMap = []; const keysInMPUkeyMap = [];
metadata.keyMaps.get(mpuBucket).forEach((val, key) => { metadata.keyMaps.get(mpuBucket).forEach((val, key) => {
keysInMPUkeyMap.push(key); keysInMPUkeyMap.push(key);
@ -138,7 +138,7 @@ errorDescription) {
}); });
const partKey = sortedKeyMap[1]; const partKey = sortedKeyMap[1];
const partETag = metadata.keyMaps.get(mpuBucket) const partETag = metadata.keyMaps.get(mpuBucket)
.get(partKey)['content-md5']; .get(partKey)['content-md5'];
assert.strictEqual(keysInMPUkeyMap.length, 2); assert.strictEqual(keysInMPUkeyMap.length, 2);
assert.strictEqual(partETag, calculatedHash1); assert.strictEqual(partETag, calculatedHash1);
} }
@ -148,8 +148,8 @@ errorDescription) {
} }
function listAndAbort(uploadId, calculatedHash2, objectName, done) { function listAndAbort(uploadId, calculatedHash2, objectName, done) {
const awsBucket = config.locationConstraints[awsLocation]. const awsBucket = config.locationConstraints[awsLocation]
details.bucketName; .details.bucketName;
const params = { const params = {
Bucket: awsBucket, Bucket: awsBucket,
Key: objectName, Key: objectName,
@ -162,167 +162,168 @@ function listAndAbort(uploadId, calculatedHash2, objectName, done) {
assert.strictEqual(`"${calculatedHash2}"`, data.Parts[0].ETag); assert.strictEqual(`"${calculatedHash2}"`, data.Parts[0].ETag);
} }
s3.abortMultipartUpload(params, err => { s3.abortMultipartUpload(params, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` + assert.equal(err, null, `Error aborting MPU: ${err}. `
`You must abort MPU with upload ID ${uploadId} manually.`); + `You must abort MPU with upload ID ${uploadId} manually.`);
done(); done();
}); });
}); });
} }
describeSkipIfE2E('objectPutPart API with multiple backends', describeSkipIfE2E('objectPutPart API with multiple backends',
function testSuite() { function testSuite() {
this.timeout(5000); this.timeout(5000);
beforeEach(() => { beforeEach(() => {
cleanup(); cleanup();
}); });
it('should upload a part to file based on mpu location', done => { it('should upload a part to file based on mpu location', done => {
putPart(memLocation, fileLocation, 'localhost', () => { putPart(memLocation, fileLocation, 'localhost', () => {
// if ds is empty, the object is not in mem, which means it // if ds is empty, the object is not in mem, which means it
// must be in file because those are the only possibilities // must be in file because those are the only possibilities
// for unit tests // for unit tests
assert.deepStrictEqual(ds, []); assert.deepStrictEqual(ds, []);
done(); done();
});
});
it('should put a part to mem based on mpu location', done => {
putPart(fileLocation, memLocation, 'localhost', () => {
assert.deepStrictEqual(ds[1].value, body1);
done();
});
});
it('should put a part to AWS based on mpu location', done => {
putPart(fileLocation, awsLocation, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, objectName, done);
});
});
it('should replace part if two parts uploaded with same part number to AWS',
done => {
putPart(fileLocation, awsLocation, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
const partReqParams = {
bucketName,
namespace,
objectKey: objectName,
headers: { 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': awsLocation },
url: `/${objectName}?partNumber=1&uploadId=${uploadId}`,
query: {
partNumber: '1', uploadId,
},
};
const partReq = new DummyRequest(partReqParams, body2);
objectPutPart(authInfo, partReq, undefined, log, err => {
assert.equal(err, null, `Error putting second part: ${err}`);
listAndAbort(uploadId, calculatedHash2, objectName, done);
}); });
}); });
});
it('should upload part based on mpu location even if part ' + it('should put a part to mem based on mpu location', done => {
'location constraint is specified ', done => { putPart(fileLocation, memLocation, 'localhost', () => {
putPart(fileLocation, memLocation, 'localhost', () => { assert.deepStrictEqual(ds[1].value, body1);
assert.deepStrictEqual(ds[1].value, body1); done();
done(); });
}); });
});
it('should put a part to file based on bucket location', done => { it('should put a part to AWS based on mpu location', done => {
putPart(fileLocation, null, 'localhost', () => { putPart(fileLocation, awsLocation, 'localhost',
assert.deepStrictEqual(ds, []); (objectName, uploadId) => {
done(); assert.deepStrictEqual(ds, []);
}); listAndAbort(uploadId, null, objectName, done);
});
it('should put a part to mem based on bucket location', done => {
putPart(memLocation, null, 'localhost', () => {
assert.deepStrictEqual(ds[1].value, body1);
done();
});
});
it('should put a part to AWS based on bucket location', done => {
putPart(awsLocation, null, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, objectName, done);
});
});
it('should put a part to AWS based on bucket location with bucketMatch ' +
'set to true', done => {
putPart(null, awsLocation, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, objectName, done);
});
});
it('should put a part to AWS based on bucket location with bucketMatch ' +
'set to false', done => {
putPart(null, awsLocationMismatch, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, `${bucketName}/${objectName}`, done);
});
});
it('should put a part to file based on request endpoint', done => {
putPart(null, null, 'localhost', () => {
assert.deepStrictEqual(ds, []);
done();
});
});
it('should store a part even if the MPU was initiated on legacy version',
done => {
putPart('scality-internal-mem', null, 'localhost',
(objectKey, uploadId) => {
const mputOverviewKey = _getOverviewKey(objectKey, uploadId);
mdWrapper.getObjectMD(mpuBucket, mputOverviewKey, {}, log,
(err, res) => {
// remove location constraint to mimic legacy behvior
// eslint-disable-next-line no-param-reassign
res.controllingLocationConstraint = undefined;
const md5Hash = crypto.createHash('md5');
const bufferBody = Buffer.from(body1);
const calculatedHash = md5Hash.update(bufferBody).digest('hex');
const partRequest = new DummyRequest({
bucketName,
namespace,
objectKey,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectKey}?partNumber=1&uploadId=${uploadId}`,
query: { partNumber: '1', uploadId },
calculatedHash,
}, body1);
objectPutPart(authInfo, partRequest, undefined, log, err => {
assert.strictEqual(err, null);
const keysInMPUkeyMap = [];
metadata.keyMaps.get(mpuBucket).forEach((val, key) => {
keysInMPUkeyMap.push(key);
});
const sortedKeyMap = keysInMPUkeyMap.sort(a => {
if (a.slice(0, 8) === 'overview') {
return -1;
}
return 0;
});
const partKey = sortedKeyMap[1];
const partETag = metadata.keyMaps.get(mpuBucket)
.get(partKey)['content-md5'];
assert.strictEqual(keysInMPUkeyMap.length, 2);
assert.strictEqual(partETag, calculatedHash);
done();
}); });
});
it('should replace part if two parts uploaded with same part number to AWS',
done => {
putPart(fileLocation, awsLocation, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
const partReqParams = {
bucketName,
namespace,
objectKey: objectName,
headers: {
'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': awsLocation,
},
url: `/${objectName}?partNumber=1&uploadId=${uploadId}`,
query: {
partNumber: '1', uploadId,
},
};
const partReq = new DummyRequest(partReqParams, body2);
objectPutPart(authInfo, partReq, undefined, log, err => {
assert.equal(err, null, `Error putting second part: ${err}`);
listAndAbort(uploadId, calculatedHash2, objectName, done);
});
});
});
it('should upload part based on mpu location even if part '
+ 'location constraint is specified ', done => {
putPart(fileLocation, memLocation, 'localhost', () => {
assert.deepStrictEqual(ds[1].value, body1);
done();
}); });
}); });
it('should put a part to file based on bucket location', done => {
putPart(fileLocation, null, 'localhost', () => {
assert.deepStrictEqual(ds, []);
done();
});
});
it('should put a part to mem based on bucket location', done => {
putPart(memLocation, null, 'localhost', () => {
assert.deepStrictEqual(ds[1].value, body1);
done();
});
});
it('should put a part to AWS based on bucket location', done => {
putPart(awsLocation, null, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, objectName, done);
});
});
it('should put a part to AWS based on bucket location with bucketMatch '
+ 'set to true', done => {
putPart(null, awsLocation, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, objectName, done);
});
});
it('should put a part to AWS based on bucket location with bucketMatch '
+ 'set to false', done => {
putPart(null, awsLocationMismatch, 'localhost',
(objectName, uploadId) => {
assert.deepStrictEqual(ds, []);
listAndAbort(uploadId, null, `${bucketName}/${objectName}`, done);
});
});
it('should put a part to file based on request endpoint', done => {
putPart(null, null, 'localhost', () => {
assert.deepStrictEqual(ds, []);
done();
});
});
it('should store a part even if the MPU was initiated on legacy version',
done => {
putPart('scality-internal-mem', null, 'localhost',
(objectKey, uploadId) => {
const mputOverviewKey = _getOverviewKey(objectKey, uploadId);
mdWrapper.getObjectMD(mpuBucket, mputOverviewKey, {}, log,
(err, res) => {
// remove location constraint to mimic legacy behvior
// eslint-disable-next-line no-param-reassign
res.controllingLocationConstraint = undefined;
const md5Hash = crypto.createHash('md5');
const bufferBody = Buffer.from(body1);
const calculatedHash = md5Hash.update(bufferBody).digest('hex');
const partRequest = new DummyRequest({
bucketName,
namespace,
objectKey,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectKey}?partNumber=1&uploadId=${uploadId}`,
query: { partNumber: '1', uploadId },
calculatedHash,
}, body1);
objectPutPart(authInfo, partRequest, undefined, log, err => {
assert.strictEqual(err, null);
const keysInMPUkeyMap = [];
metadata.keyMaps.get(mpuBucket).forEach((val, key) => {
keysInMPUkeyMap.push(key);
});
const sortedKeyMap = keysInMPUkeyMap.sort(a => {
if (a.slice(0, 8) === 'overview') {
return -1;
}
return 0;
});
const partKey = sortedKeyMap[1];
const partETag = metadata.keyMaps.get(mpuBucket)
.get(partKey)['content-md5'];
assert.strictEqual(keysInMPUkeyMap.length, 2);
assert.strictEqual(partETag, calculatedHash);
done();
});
});
});
});
}); });
});

View File

@ -16,7 +16,7 @@ class DummyRequest extends http.IncomingMessage {
this.parsedContentLength = 0; this.parsedContentLength = 0;
} }
} }
this.actionImplicitDenies = false;
if (Array.isArray(msg)) { if (Array.isArray(msg)) {
msg.forEach(part => { msg.forEach(part => {
this.push(part); this.push(part);

View File

@ -24,6 +24,7 @@ const bucketPutReq = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const taggingUtil = new TaggingConfigTester(); const taggingUtil = new TaggingConfigTester();

View File

@ -106,7 +106,7 @@ describe('bucket authorization for bucketGet, bucketHead, ' +
} }
bucket.setCannedAcl(value.canned); bucket.setCannedAcl(value.canned);
const results = requestTypes.map(type => const results = requestTypes.map(type =>
isBucketAuthorized(bucket, type, value.id, value.auth, log)); isBucketAuthorized(bucket, type, value.id, value.auth, false, log));
assert.deepStrictEqual(results, value.response); assert.deepStrictEqual(results, value.response);
done(); done();
}); });
@ -128,13 +128,13 @@ describe('bucket authorization for bucketGetACL', () => {
it('should allow access to bucket owner', () => { it('should allow access to bucket owner', () => {
const result = isBucketAuthorized(bucket, 'bucketGetACL', const result = isBucketAuthorized(bucket, 'bucketGetACL',
ownerCanonicalId, authInfo); ownerCanonicalId, authInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
it('should allow access to user in bucket owner account', () => { it('should allow access to user in bucket owner account', () => {
const result = isBucketAuthorized(bucket, 'bucketGetACL', const result = isBucketAuthorized(bucket, 'bucketGetACL',
ownerCanonicalId, userAuthInfo); ownerCanonicalId, userAuthInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
@ -157,7 +157,7 @@ describe('bucket authorization for bucketGetACL', () => {
orders.forEach(value => { orders.forEach(value => {
it(`should allow access to ${value.it}`, done => { it(`should allow access to ${value.it}`, done => {
const noAuthResult = isBucketAuthorized(bucket, 'bucketGetACL', const noAuthResult = isBucketAuthorized(bucket, 'bucketGetACL',
value.id); value.id, null, false);
assert.strictEqual(noAuthResult, false); assert.strictEqual(noAuthResult, false);
if (value.aclParam) { if (value.aclParam) {
bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]); bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]);
@ -165,7 +165,7 @@ describe('bucket authorization for bucketGetACL', () => {
bucket.setCannedAcl(value.canned); bucket.setCannedAcl(value.canned);
} }
const authorizedResult = isBucketAuthorized(bucket, 'bucketGetACL', const authorizedResult = isBucketAuthorized(bucket, 'bucketGetACL',
value.id, value.auth); value.id, value.auth, false);
assert.strictEqual(authorizedResult, true); assert.strictEqual(authorizedResult, true);
done(); done();
}); });
@ -187,13 +187,13 @@ describe('bucket authorization for bucketPutACL', () => {
it('should allow access to bucket owner', () => { it('should allow access to bucket owner', () => {
const result = isBucketAuthorized(bucket, 'bucketPutACL', const result = isBucketAuthorized(bucket, 'bucketPutACL',
ownerCanonicalId, authInfo); ownerCanonicalId, authInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
it('should allow access to user in bucket owner account', () => { it('should allow access to user in bucket owner account', () => {
const result = isBucketAuthorized(bucket, 'bucketPutACL', const result = isBucketAuthorized(bucket, 'bucketPutACL',
ownerCanonicalId, userAuthInfo); ownerCanonicalId, userAuthInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
@ -202,11 +202,11 @@ describe('bucket authorization for bucketPutACL', () => {
it('should allow access to account if ' + it('should allow access to account if ' +
`account was granted ${value} right`, done => { `account was granted ${value} right`, done => {
const noAuthResult = isBucketAuthorized(bucket, 'bucketPutACL', const noAuthResult = isBucketAuthorized(bucket, 'bucketPutACL',
accountToVet, altAcctAuthInfo); accountToVet, altAcctAuthInfo, false);
assert.strictEqual(noAuthResult, false); assert.strictEqual(noAuthResult, false);
bucket.setSpecificAcl(accountToVet, value); bucket.setSpecificAcl(accountToVet, value);
const authorizedResult = isBucketAuthorized(bucket, 'bucketPutACL', const authorizedResult = isBucketAuthorized(bucket, 'bucketPutACL',
accountToVet, altAcctAuthInfo); accountToVet, altAcctAuthInfo, false);
assert.strictEqual(authorizedResult, true); assert.strictEqual(authorizedResult, true);
done(); done();
}); });
@ -228,13 +228,13 @@ describe('bucket authorization for bucketOwnerAction', () => {
it('should allow access to bucket owner', () => { it('should allow access to bucket owner', () => {
const result = isBucketAuthorized(bucket, 'bucketDeleteCors', const result = isBucketAuthorized(bucket, 'bucketDeleteCors',
ownerCanonicalId, authInfo); ownerCanonicalId, authInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
it('should allow access to user in bucket owner account', () => { it('should allow access to user in bucket owner account', () => {
const result = isBucketAuthorized(bucket, 'bucketDeleteCors', const result = isBucketAuthorized(bucket, 'bucketDeleteCors',
ownerCanonicalId, userAuthInfo); ownerCanonicalId, userAuthInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
@ -256,7 +256,7 @@ describe('bucket authorization for bucketOwnerAction', () => {
} }
bucket.setCannedAcl(value.canned); bucket.setCannedAcl(value.canned);
const result = isBucketAuthorized(bucket, 'bucketDeleteCors', const result = isBucketAuthorized(bucket, 'bucketDeleteCors',
value.id, value.auth); value.id, value.auth, false);
assert.strictEqual(result, false); assert.strictEqual(result, false);
done(); done();
}); });
@ -278,13 +278,13 @@ describe('bucket authorization for bucketDelete', () => {
it('should allow access to bucket owner', () => { it('should allow access to bucket owner', () => {
const result = isBucketAuthorized(bucket, 'bucketDelete', const result = isBucketAuthorized(bucket, 'bucketDelete',
ownerCanonicalId, authInfo); ownerCanonicalId, authInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
it('should allow access to user in bucket owner account', () => { it('should allow access to user in bucket owner account', () => {
const result = isBucketAuthorized(bucket, 'bucketDelete', const result = isBucketAuthorized(bucket, 'bucketDelete',
ownerCanonicalId, userAuthInfo); ownerCanonicalId, userAuthInfo, false);
assert.strictEqual(result, true); assert.strictEqual(result, true);
}); });
@ -305,7 +305,7 @@ describe('bucket authorization for bucketDelete', () => {
bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]); bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]);
} }
bucket.setCannedAcl(value.canned); bucket.setCannedAcl(value.canned);
const result = isBucketAuthorized(bucket, 'bucketDelete', value.id, value.auth); const result = isBucketAuthorized(bucket, 'bucketDelete', value.id, value.auth, false);
assert.strictEqual(result, false); assert.strictEqual(result, false);
done(); done();
}); });
@ -329,13 +329,13 @@ describe('bucket authorization for objectDelete and objectPut', () => {
it('should allow access to bucket owner', () => { it('should allow access to bucket owner', () => {
const results = requestTypes.map(type => const results = requestTypes.map(type =>
isBucketAuthorized(bucket, type, ownerCanonicalId, authInfo)); isBucketAuthorized(bucket, type, ownerCanonicalId, authInfo, false));
assert.deepStrictEqual(results, [true, true]); assert.deepStrictEqual(results, [true, true]);
}); });
it('should allow access to user in bucket owner account', () => { it('should allow access to user in bucket owner account', () => {
const results = requestTypes.map(type => const results = requestTypes.map(type =>
isBucketAuthorized(bucket, type, ownerCanonicalId, userAuthInfo)); isBucketAuthorized(bucket, type, ownerCanonicalId, userAuthInfo, false));
assert.deepStrictEqual(results, [true, true]); assert.deepStrictEqual(results, [true, true]);
}); });
@ -360,13 +360,13 @@ describe('bucket authorization for objectDelete and objectPut', () => {
it(`should allow access to ${value.it}`, done => { it(`should allow access to ${value.it}`, done => {
bucket.setCannedAcl(value.canned); bucket.setCannedAcl(value.canned);
const noAuthResults = requestTypes.map(type => const noAuthResults = requestTypes.map(type =>
isBucketAuthorized(bucket, type, value.id, value.auth)); isBucketAuthorized(bucket, type, value.id, value.auth, false));
assert.deepStrictEqual(noAuthResults, value.response); assert.deepStrictEqual(noAuthResults, value.response);
if (value.aclParam) { if (value.aclParam) {
bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]); bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]);
} }
const authResults = requestTypes.map(type => const authResults = requestTypes.map(type =>
isBucketAuthorized(bucket, type, accountToVet, altAcctAuthInfo)); isBucketAuthorized(bucket, type, accountToVet, altAcctAuthInfo, false));
assert.deepStrictEqual(authResults, [true, true]); assert.deepStrictEqual(authResults, [true, true]);
done(); done();
}); });
@ -378,10 +378,10 @@ describe('bucket authorization for objectPutACL and objectGetACL', () => {
'are done at object level', done => { 'are done at object level', done => {
const requestTypes = ['objectPutACL', 'objectGetACL']; const requestTypes = ['objectPutACL', 'objectGetACL'];
const results = requestTypes.map(type => const results = requestTypes.map(type =>
isBucketAuthorized(bucket, type, accountToVet, altAcctAuthInfo)); isBucketAuthorized(bucket, type, accountToVet, altAcctAuthInfo, false));
assert.deepStrictEqual(results, [true, true]); assert.deepStrictEqual(results, [true, true]);
const publicUserResults = requestTypes.map(type => const publicUserResults = requestTypes.map(type =>
isBucketAuthorized(bucket, type, constants.publicId)); isBucketAuthorized(bucket, type, constants.publicId, null, false));
assert.deepStrictEqual(publicUserResults, [true, true]); assert.deepStrictEqual(publicUserResults, [true, true]);
done(); done();
}); });

View File

@ -77,7 +77,6 @@ function createMPU(testRequest, initiateRequest, deleteOverviewMPUObj, cb) {
}); });
}); });
} }
describe('bucketDelete API', () => { describe('bucketDelete API', () => {
beforeEach(() => { beforeEach(() => {
cleanup(); cleanup();
@ -88,6 +87,7 @@ describe('bucketDelete API', () => {
namespace, namespace,
headers: {}, headers: {},
url: `/${bucketName}`, url: `/${bucketName}`,
actionImplicitDenies: false,
}; };
const initiateRequest = { const initiateRequest = {
@ -96,6 +96,7 @@ describe('bucketDelete API', () => {
objectKey: objectName, objectKey: objectName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectName}?uploads`, url: `/${objectName}?uploads`,
actionImplicitDenies: false,
}; };
it('should return an error if the bucket is not empty', done => { it('should return an error if the bucket is not empty', done => {

View File

@ -19,12 +19,12 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const testBucketPutCorsRequest = const testBucketPutCorsRequest =
corsUtil.createBucketCorsRequest('PUT', bucketName); corsUtil.createBucketCorsRequest('PUT', bucketName);
const testBucketDeleteCorsRequest = const testBucketDeleteCorsRequest =
corsUtil.createBucketCorsRequest('DELETE', bucketName); corsUtil.createBucketCorsRequest('DELETE', bucketName);
describe('deleteBucketCors API', () => { describe('deleteBucketCors API', () => {
beforeEach(done => { beforeEach(done => {
cleanup(); cleanup();

View File

@ -13,8 +13,8 @@ const bucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
describe('bucketDeleteEncryption API', () => { describe('bucketDeleteEncryption API', () => {
before(() => cleanup()); before(() => cleanup());

View File

@ -19,6 +19,7 @@ function _makeRequest(includeXml) {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
if (includeXml) { if (includeXml) {
request.post = '<LifecycleConfiguration ' + request.post = '<LifecycleConfiguration ' +
@ -30,7 +31,6 @@ function _makeRequest(includeXml) {
} }
return request; return request;
} }
describe('deleteBucketLifecycle API', () => { describe('deleteBucketLifecycle API', () => {
before(() => cleanup()); before(() => cleanup());
beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done)); beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done));

View File

@ -19,6 +19,7 @@ function _makeRequest(includePolicy) {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
if (includePolicy) { if (includePolicy) {
const examplePolicy = { const examplePolicy = {
@ -36,7 +37,6 @@ function _makeRequest(includePolicy) {
} }
return request; return request;
} }
describe('deleteBucketPolicy API', () => { describe('deleteBucketPolicy API', () => {
before(() => cleanup()); before(() => cleanup());
beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done)); beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done));

View File

@ -20,6 +20,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const testBucketDeleteWebsiteRequest = { const testBucketDeleteWebsiteRequest = {
bucketName, bucketName,
@ -28,10 +29,10 @@ const testBucketDeleteWebsiteRequest = {
}, },
url: '/?website', url: '/?website',
query: { website: '' }, query: { website: '' },
actionImplicitDenies: false,
}; };
const testBucketPutWebsiteRequest = Object.assign({ post: config.getXml() }, const testBucketPutWebsiteRequest = Object.assign({ post: config.getXml() },
testBucketDeleteWebsiteRequest); testBucketDeleteWebsiteRequest);
describe('deleteBucketWebsite API', () => { describe('deleteBucketWebsite API', () => {
beforeEach(done => { beforeEach(done => {
cleanup(); cleanup();

View File

@ -63,6 +63,7 @@ const baseGetRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: '/' }, headers: { host: '/' },
actionImplicitDenies: false,
}; };
const baseUrl = `/${bucketName}`; const baseUrl = `/${bucketName}`;
@ -173,7 +174,6 @@ const tests = [
}, },
}, },
]; ];
describe('bucketGet API', () => { describe('bucketGet API', () => {
beforeEach(() => { beforeEach(() => {
cleanup(); cleanup();

View File

@ -14,7 +14,6 @@ const authInfo = makeAuthInfo(accessKey);
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
const namespace = 'default'; const namespace = 'default';
const bucketName = 'bucketname'; const bucketName = 'bucketname';
describe('bucketGetACL API', () => { describe('bucketGetACL API', () => {
beforeEach(() => { beforeEach(() => {
cleanup(); cleanup();
@ -25,6 +24,7 @@ describe('bucketGetACL API', () => {
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const testGetACLRequest = { const testGetACLRequest = {
bucketName, bucketName,
@ -32,6 +32,7 @@ describe('bucketGetACL API', () => {
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
it('should get a canned private ACL', done => { it('should get a canned private ACL', done => {
@ -44,6 +45,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -76,6 +78,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -119,6 +122,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -156,6 +160,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -194,6 +199,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -248,6 +254,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
const canonicalIDforSample1 = const canonicalIDforSample1 =
'79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be'; '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be';
@ -338,6 +345,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([
@ -377,6 +385,7 @@ describe('bucketGetACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
async.waterfall([ async.waterfall([

View File

@ -16,6 +16,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function _makeCorsRequest(xml) { function _makeCorsRequest(xml) {
@ -26,6 +27,7 @@ function _makeCorsRequest(xml) {
}, },
url: '/?cors', url: '/?cors',
query: { cors: '' }, query: { cors: '' },
actionImplicitDenies: false,
}; };
if (xml) { if (xml) {
@ -55,7 +57,6 @@ function _comparePutGetXml(sampleXml, done) {
}); });
}); });
} }
describe('getBucketCors API', () => { describe('getBucketCors API', () => {
beforeEach(done => { beforeEach(done => {
cleanup(); cleanup();

View File

@ -17,8 +17,8 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
describe('getBucketLifecycle API', () => { describe('getBucketLifecycle API', () => {
before(() => cleanup()); before(() => cleanup());
beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done)); beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done));

View File

@ -16,6 +16,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const testGetLocationRequest = { const testGetLocationRequest = {
@ -25,6 +26,7 @@ const testGetLocationRequest = {
}, },
url: '/?location', url: '/?location',
query: { location: '' }, query: { location: '' },
actionImplicitDenies: false,
}; };
const locationConstraints = config.locationConstraints; const locationConstraints = config.locationConstraints;
@ -37,7 +39,6 @@ function getBucketRequestObject(location) {
'</CreateBucketConfiguration>' : undefined; '</CreateBucketConfiguration>' : undefined;
return Object.assign({ post }, testBucketPutRequest); return Object.assign({ post }, testBucketPutRequest);
} }
describe('getBucketLocation API', () => { describe('getBucketLocation API', () => {
Object.keys(locationConstraints).forEach(location => { Object.keys(locationConstraints).forEach(location => {
if (location === 'us-east-1') { if (location === 'us-east-1') {

View File

@ -15,6 +15,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function getNotificationRequest(bucketName, xml) { function getNotificationRequest(bucketName, xml) {
@ -23,6 +24,7 @@ function getNotificationRequest(bucketName, xml) {
headers: { headers: {
host: `${bucketName}.s3.amazonaws.com`, host: `${bucketName}.s3.amazonaws.com`,
}, },
actionImplicitDenies: false,
}; };
if (xml) { if (xml) {
request.post = xml; request.post = xml;
@ -52,7 +54,6 @@ function getNotificationXml() {
'</NotificationConfiguration>'; '</NotificationConfiguration>';
} }
describe('getBucketNotification API', () => { describe('getBucketNotification API', () => {
before(cleanup); before(cleanup);
beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done)); beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done));

View File

@ -14,6 +14,7 @@ const bucketPutReq = {
host: `${bucketName}.s3.amazonaws.com`, host: `${bucketName}.s3.amazonaws.com`,
}, },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const testBucketPutReqWithObjLock = { const testBucketPutReqWithObjLock = {
@ -23,6 +24,7 @@ const testBucketPutReqWithObjLock = {
'x-amz-bucket-object-lock-enabled': 'True', 'x-amz-bucket-object-lock-enabled': 'True',
}, },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function getObjectLockConfigRequest(bucketName, xml) { function getObjectLockConfigRequest(bucketName, xml) {
@ -33,6 +35,7 @@ function getObjectLockConfigRequest(bucketName, xml) {
'x-amz-bucket-object-lock-enabled': 'true', 'x-amz-bucket-object-lock-enabled': 'true',
}, },
url: '/?object-lock', url: '/?object-lock',
actionImplicitDenies: false,
}; };
if (xml) { if (xml) {
request.post = xml; request.post = xml;
@ -65,7 +68,6 @@ function getObjectLockXml(mode, type, time) {
xmlStr += xml.objLockConfigClose; xmlStr += xml.objLockConfigClose;
return xmlStr; return xmlStr;
} }
describe('bucketGetObjectLock API', () => { describe('bucketGetObjectLock API', () => {
before(done => bucketPut(authInfo, bucketPutReq, log, done)); before(done => bucketPut(authInfo, bucketPutReq, log, done));
after(cleanup); after(cleanup);
@ -79,7 +81,6 @@ describe('bucketGetObjectLock API', () => {
}); });
}); });
}); });
describe('bucketGetObjectLock API', () => { describe('bucketGetObjectLock API', () => {
before(cleanup); before(cleanup);
beforeEach(done => bucketPut(authInfo, testBucketPutReqWithObjLock, log, done)); beforeEach(done => bucketPut(authInfo, testBucketPutReqWithObjLock, log, done));

View File

@ -16,6 +16,7 @@ const testBasicRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const expectedBucketPolicy = { const expectedBucketPolicy = {
@ -34,8 +35,8 @@ const testPutPolicyRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: JSON.stringify(expectedBucketPolicy), post: JSON.stringify(expectedBucketPolicy),
actionImplicitDenies: false,
}; };
describe('getBucketPolicy API', () => { describe('getBucketPolicy API', () => {
before(() => cleanup()); before(() => cleanup());
beforeEach(done => bucketPut(authInfo, testBasicRequest, log, done)); beforeEach(done => bucketPut(authInfo, testBasicRequest, log, done));

View File

@ -53,7 +53,6 @@ function getReplicationConfig() {
], ],
}; };
} }
describe("'getReplicationConfigurationXML' function", () => { describe("'getReplicationConfigurationXML' function", () => {
it('should return XML from the bucket replication configuration', done => it('should return XML from the bucket replication configuration', done =>
getAndCheckXML(getReplicationConfig(), done)); getAndCheckXML(getReplicationConfig(), done));

View File

@ -15,6 +15,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function _makeWebsiteRequest(xml) { function _makeWebsiteRequest(xml) {
@ -25,6 +26,7 @@ function _makeWebsiteRequest(xml) {
}, },
url: '/?website', url: '/?website',
query: { website: '' }, query: { website: '' },
actionImplicitDenies: false,
}; };
if (xml) { if (xml) {
@ -53,7 +55,6 @@ function _comparePutGetXml(sampleXml, done) {
}); });
}); });
} }
describe('getBucketWebsite API', () => { describe('getBucketWebsite API', () => {
beforeEach(done => { beforeEach(done => {
cleanup(); cleanup();

View File

@ -14,6 +14,7 @@ const testRequest = {
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
describe('bucketHead API', () => { describe('bucketHead API', () => {
beforeEach(() => { beforeEach(() => {

View File

@ -244,7 +244,7 @@ describe('bucket policy authorization', () => {
describe('isBucketAuthorized with no policy set', () => { describe('isBucketAuthorized with no policy set', () => {
it('should allow access to bucket owner', done => { it('should allow access to bucket owner', done => {
const allowed = isBucketAuthorized(bucket, 'bucketPut', const allowed = isBucketAuthorized(bucket, 'bucketPut',
bucketOwnerCanonicalId, null, log); bucketOwnerCanonicalId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -252,7 +252,7 @@ describe('bucket policy authorization', () => {
it('should deny access to non-bucket owner', it('should deny access to non-bucket owner',
done => { done => {
const allowed = isBucketAuthorized(bucket, 'bucketPut', const allowed = isBucketAuthorized(bucket, 'bucketPut',
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -268,7 +268,7 @@ describe('bucket policy authorization', () => {
it('should allow access to non-bucket owner if principal is set to "*"', it('should allow access to non-bucket owner if principal is set to "*"',
done => { done => {
const allowed = isBucketAuthorized(bucket, bucAction, const allowed = isBucketAuthorized(bucket, bucAction,
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -276,7 +276,7 @@ describe('bucket policy authorization', () => {
it('should allow access to public user if principal is set to "*"', it('should allow access to public user if principal is set to "*"',
done => { done => {
const allowed = isBucketAuthorized(bucket, bucAction, const allowed = isBucketAuthorized(bucket, bucAction,
constants.publicId, null, log); constants.publicId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -287,7 +287,7 @@ describe('bucket policy authorization', () => {
newPolicy.Statement[0][t.keyToChange] = t.bucketValue; newPolicy.Statement[0][t.keyToChange] = t.bucketValue;
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isBucketAuthorized(bucket, bucAction, const allowed = isBucketAuthorized(bucket, bucAction,
t.bucketId, t.bucketAuthInfo, log); t.bucketId, t.bucketAuthInfo, false, log);
assert.equal(allowed, t.expected); assert.equal(allowed, t.expected);
done(); done();
}); });
@ -304,7 +304,7 @@ describe('bucket policy authorization', () => {
}; };
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isBucketAuthorized(bucket, bucAction, const allowed = isBucketAuthorized(bucket, bucAction,
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -312,7 +312,7 @@ describe('bucket policy authorization', () => {
it('should deny access to non-bucket owner with an unsupported action type', it('should deny access to non-bucket owner with an unsupported action type',
done => { done => {
const allowed = isBucketAuthorized(bucket, 'unsupportedAction', const allowed = isBucketAuthorized(bucket, 'unsupportedAction',
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -325,7 +325,7 @@ describe('bucket policy authorization', () => {
it('should allow access to object owner', done => { it('should allow access to object owner', done => {
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
objectOwnerCanonicalId, null, log); objectOwnerCanonicalId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -333,7 +333,7 @@ describe('bucket policy authorization', () => {
it('should deny access to non-object owner', it('should deny access to non-object owner',
done => { done => {
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -352,7 +352,7 @@ describe('bucket policy authorization', () => {
it('should allow access to non-object owner if principal is set to "*"', it('should allow access to non-object owner if principal is set to "*"',
done => { done => {
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -360,7 +360,7 @@ describe('bucket policy authorization', () => {
it('should allow access to public user if principal is set to "*"', it('should allow access to public user if principal is set to "*"',
done => { done => {
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
constants.publicId, null, log); constants.publicId, null, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -371,7 +371,7 @@ describe('bucket policy authorization', () => {
newPolicy.Statement[0][t.keyToChange] = t.objectValue; newPolicy.Statement[0][t.keyToChange] = t.objectValue;
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
t.objectId, t.objectAuthInfo, log); t.objectId, t.objectAuthInfo, false, log);
assert.equal(allowed, t.expected); assert.equal(allowed, t.expected);
done(); done();
}); });
@ -383,7 +383,7 @@ describe('bucket policy authorization', () => {
newPolicy.Statement[0].Action = ['s3:GetObject']; newPolicy.Statement[0].Action = ['s3:GetObject'];
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isObjAuthorized(bucket, object, 'objectHead', const allowed = isObjAuthorized(bucket, object, 'objectHead',
altAcctCanonicalId, altAcctAuthInfo, log); altAcctCanonicalId, altAcctAuthInfo, false, log);
assert.equal(allowed, true); assert.equal(allowed, true);
done(); done();
}); });
@ -393,7 +393,7 @@ describe('bucket policy authorization', () => {
newPolicy.Statement[0].Action = ['s3:PutObject']; newPolicy.Statement[0].Action = ['s3:PutObject'];
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isObjAuthorized(bucket, object, 'objectHead', const allowed = isObjAuthorized(bucket, object, 'objectHead',
altAcctCanonicalId, altAcctAuthInfo, log); altAcctCanonicalId, altAcctAuthInfo, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -408,7 +408,7 @@ describe('bucket policy authorization', () => {
}; };
bucket.setBucketPolicy(newPolicy); bucket.setBucketPolicy(newPolicy);
const allowed = isObjAuthorized(bucket, object, objAction, const allowed = isObjAuthorized(bucket, object, objAction,
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });
@ -416,7 +416,7 @@ describe('bucket policy authorization', () => {
it('should deny access to non-object owner with an unsupported action type', it('should deny access to non-object owner with an unsupported action type',
done => { done => {
const allowed = isObjAuthorized(bucket, object, 'unsupportedAction', const allowed = isObjAuthorized(bucket, object, 'unsupportedAction',
altAcctCanonicalId, null, log); altAcctCanonicalId, null, false, log);
assert.equal(allowed, false); assert.equal(allowed, false);
done(); done();
}); });

View File

@ -18,11 +18,10 @@ const testBucketPutRequest = {
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const canonicalIDforSample1 = const canonicalIDforSample1 = '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be';
'79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be'; const canonicalIDforSample2 = '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2bf';
const canonicalIDforSample2 =
'79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2bf';
const invalidIds = { const invalidIds = {
'too short': 'id="invalid_id"', 'too short': 'id="invalid_id"',
@ -42,11 +41,10 @@ describe('putBucketACL API', () => {
afterEach(() => cleanup()); afterEach(() => cleanup());
it('should parse a grantheader', () => { it('should parse a grantheader', () => {
const grantRead = const grantRead = `uri=${constants.logId}, `
`uri=${constants.logId}, ` + + 'emailAddress="test@testing.com", '
'emailAddress="test@testing.com", ' + + 'emailAddress="test2@testly.com", '
'emailAddress="test2@testly.com", ' + + 'id="sdfsdfsfwwiieohefs"';
'id="sdfsdfsfwwiieohefs"';
const grantReadHeader = aclUtils.parseGrant(grantRead, 'read'); const grantReadHeader = aclUtils.parseGrant(grantRead, 'read');
const firstIdentifier = grantReadHeader[0].identifier; const firstIdentifier = grantReadHeader[0].identifier;
assert.strictEqual(firstIdentifier, constants.logId); assert.strictEqual(firstIdentifier, constants.logId);
@ -58,7 +56,7 @@ describe('putBucketACL API', () => {
assert.strictEqual(fourthIdentifier, 'sdfsdfsfwwiieohefs'); assert.strictEqual(fourthIdentifier, 'sdfsdfsfwwiieohefs');
const fourthType = grantReadHeader[3].userIDType; const fourthType = grantReadHeader[3].userIDType;
assert.strictEqual(fourthType, 'id'); assert.strictEqual(fourthType, 'id');
const grantType = grantReadHeader[3].grantType; const { grantType } = grantReadHeader[3];
assert.strictEqual(grantType, 'read'); assert.strictEqual(grantType, 'read');
}); });
@ -72,6 +70,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -90,6 +89,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
@ -111,6 +111,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
const testACLRequest2 = { const testACLRequest2 = {
bucketName, bucketName,
@ -121,6 +122,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
@ -130,7 +132,7 @@ describe('putBucketACL API', () => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
metadata.getBucket(bucketName, log, (err, md) => { metadata.getBucket(bucketName, log, (err, md) => {
assert.strictEqual(md.getAcl().Canned, assert.strictEqual(md.getAcl().Canned,
'authenticated-read'); 'authenticated-read');
done(); done();
}); });
}); });
@ -138,8 +140,8 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should set a canned private ACL ' + it('should set a canned private ACL '
'followed by a log-delivery-write ACL', done => { + 'followed by a log-delivery-write ACL', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
@ -149,6 +151,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
const testACLRequest2 = { const testACLRequest2 = {
bucketName, bucketName,
@ -159,6 +162,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -169,7 +173,7 @@ describe('putBucketACL API', () => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
metadata.getBucket(bucketName, log, (err, md) => { metadata.getBucket(bucketName, log, (err, md) => {
assert.strictEqual(md.getAcl().Canned, assert.strictEqual(md.getAcl().Canned,
'log-delivery-write'); 'log-delivery-write');
done(); done();
}); });
}); });
@ -184,19 +188,20 @@ describe('putBucketACL API', () => {
headers: { headers: {
'host': `${bucketName}.s3.amazonaws.com`, 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-grant-full-control': 'x-amz-grant-full-control':
'emailaddress="sampleaccount1@sampling.com"' + 'emailaddress="sampleaccount1@sampling.com"'
',emailaddress="sampleaccount2@sampling.com"', + ',emailaddress="sampleaccount2@sampling.com"',
'x-amz-grant-read': `uri=${constants.logId}`, 'x-amz-grant-read': `uri=${constants.logId}`,
'x-amz-grant-write': `uri=${constants.publicId}`, 'x-amz-grant-write': `uri=${constants.publicId}`,
'x-amz-grant-read-acp': 'x-amz-grant-read-acp':
'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac' + 'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac'
'f8f8d5218e7cd47ef2be', + 'f8f8d5218e7cd47ef2be',
'x-amz-grant-write-acp': 'x-amz-grant-write-acp':
'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac' + 'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac'
'f8f8d5218e7cd47ef2bf', + 'f8f8d5218e7cd47ef2bf',
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
@ -223,21 +228,22 @@ describe('putBucketACL API', () => {
headers: { headers: {
'host': `${bucketName}.s3.amazonaws.com`, 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-grant-full-control': 'x-amz-grant-full-control':
'emailaddress="sampleaccount1@sampling.com"' + 'emailaddress="sampleaccount1@sampling.com"'
',emailaddress="sampleaccount2@sampling.com"', + ',emailaddress="sampleaccount2@sampling.com"',
'x-amz-grant-read': 'x-amz-grant-read':
'emailaddress="sampleaccount1@sampling.com"', 'emailaddress="sampleaccount1@sampling.com"',
'x-amz-grant-write': 'x-amz-grant-write':
'emailaddress="sampleaccount1@sampling.com"', 'emailaddress="sampleaccount1@sampling.com"',
'x-amz-grant-read-acp': 'x-amz-grant-read-acp':
'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac' + 'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac'
'f8f8d5218e7cd47ef2be', + 'f8f8d5218e7cd47ef2be',
'x-amz-grant-write-acp': 'x-amz-grant-write-acp':
'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac' + 'id=79a59df900b949e55d96a1e698fbacedfd6e09d98eac'
'f8f8d5218e7cd47ef2bf', + 'f8f8d5218e7cd47ef2bf',
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.strictEqual(err, undefined); assert.strictEqual(err, undefined);
@ -260,8 +266,8 @@ describe('putBucketACL API', () => {
}); });
Object.keys(invalidIds).forEach(idType => { Object.keys(invalidIds).forEach(idType => {
it('should return an error if grantee canonical ID provided in ACL ' + it('should return an error if grantee canonical ID provided in ACL '
`request invalid because ${idType}`, done => { + `request invalid because ${idType}`, done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
@ -271,6 +277,7 @@ describe('putBucketACL API', () => {
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
return bucketPutACL(authInfo, testACLRequest, log, err => { return bucketPutACL(authInfo, testACLRequest, log, err => {
assert.deepStrictEqual(err, errors.InvalidArgument); assert.deepStrictEqual(err, errors.InvalidArgument);
@ -279,19 +286,20 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should return an error if invalid email ' + it('should return an error if invalid email '
'provided in ACL header request', done => { + 'provided in ACL header request', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { headers: {
'host': `${bucketName}.s3.amazonaws.com`, 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-grant-full-control': 'x-amz-grant-full-control':
'emailaddress="sampleaccount1@sampling.com"' + 'emailaddress="sampleaccount1@sampling.com"'
',emailaddress="nonexistentEmail@sampling.com"', + ',emailaddress="nonexistentEmail@sampling.com"',
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -305,52 +313,53 @@ describe('putBucketACL API', () => {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Grantee>' + + '</Grantee>'
'<Permission>FULL_CONTROL</Permission>' + + '<Permission>FULL_CONTROL</Permission>'
'</Grant>' + + '</Grant>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="Group">' + + '<Grantee xsi:type="Group">'
`<URI>${constants.publicId}</URI>` + + `<URI>${constants.publicId}</URI>`
'</Grantee>' + + '</Grantee>'
'<Permission>READ</Permission>' + + '<Permission>READ</Permission>'
'</Grant>' + + '</Grant>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="Group">' + + '<Grantee xsi:type="Group">'
`<URI>${constants.logId}</URI>` + + `<URI>${constants.logId}</URI>`
'</Grantee>' + + '</Grantee>'
'<Permission>WRITE</Permission>' + + '<Permission>WRITE</Permission>'
'</Grant>' + + '</Grant>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="AmazonCustomerByEmail">' + + '<Grantee xsi:type="AmazonCustomerByEmail">'
'<EmailAddress>sampleaccount1@sampling.com' + + '<EmailAddress>sampleaccount1@sampling.com'
'</EmailAddress>' + + '</EmailAddress>'
'</Grantee>' + + '</Grantee>'
'<Permission>WRITE_ACP</Permission>' + + '<Permission>WRITE_ACP</Permission>'
'</Grant>' + + '</Grant>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>79a59df900b949e55d96a1e698fbacedfd' + + '<ID>79a59df900b949e55d96a1e698fbacedfd'
'6e09d98eacf8f8d5218e7cd47ef2bf</ID>' + + '6e09d98eacf8f8d5218e7cd47ef2bf</ID>'
'</Grantee>' + + '</Grantee>'
'<Permission>READ_ACP</Permission>' + + '<Permission>READ_ACP</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -362,7 +371,7 @@ describe('putBucketACL API', () => {
assert.strictEqual(md.getAcl().READ[0], constants.publicId); assert.strictEqual(md.getAcl().READ[0], constants.publicId);
assert.strictEqual(md.getAcl().WRITE[0], constants.logId); assert.strictEqual(md.getAcl().WRITE[0], constants.logId);
assert.strictEqual(md.getAcl().WRITE_ACP[0], assert.strictEqual(md.getAcl().WRITE_ACP[0],
canonicalIDforSample1); canonicalIDforSample1);
assert.strictEqual(md.getAcl().READ_ACP[0], assert.strictEqual(md.getAcl().READ_ACP[0],
canonicalIDforSample2); canonicalIDforSample2);
done(); done();
@ -375,17 +384,18 @@ describe('putBucketACL API', () => {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList></AccessControlList>' + + '<AccessControlList></AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -403,64 +413,66 @@ describe('putBucketACL API', () => {
}); });
it('should not be able to set ACLs without AccessControlList section', it('should not be able to set ACLs without AccessControlList section',
done => { done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
}; actionImplicitDenies: false,
};
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.deepStrictEqual(err, errors.MalformedACLError); assert.deepStrictEqual(err, errors.MalformedACLError);
done(); done();
});
}); });
});
it('should return an error if multiple AccessControlList section', done => { it('should return an error if multiple AccessControlList section', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Grantee>' + + '</Grantee>'
'<Permission>FULL_CONTROL</Permission>' + + '<Permission>FULL_CONTROL</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Grantee>' + + '</Grantee>'
'<Permission>READ</Permission>' + + '<Permission>READ</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -469,30 +481,31 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should return an error if invalid grantee user ID ' + it('should return an error if invalid grantee user ID '
'provided in ACL request body', done => { + 'provided in ACL request body', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>invalid_id</ID>' + + '<ID>invalid_id</ID>'
'</Grantee>' + + '</Grantee>'
'<Permission>READ_ACP</Permission>' + + '<Permission>READ_ACP</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
return bucketPutACL(authInfo, testACLRequest, log, err => { return bucketPutACL(authInfo, testACLRequest, log, err => {
@ -501,30 +514,31 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should return an error if invalid email ' + it('should return an error if invalid email '
'address provided in ACLs set out in request body', done => { + 'address provided in ACLs set out in request body', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="AmazonCustomerByEmail">' + + '<Grantee xsi:type="AmazonCustomerByEmail">'
'<EmailAddress>xyz@amazon.com</EmailAddress>' + + '<EmailAddress>xyz@amazon.com</EmailAddress>'
'</Grantee>' + + '</Grantee>'
'<Permission>WRITE_ACP</Permission>' + + '<Permission>WRITE_ACP</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
assert.deepStrictEqual(err, errors.UnresolvableGrantByEmailAddress); assert.deepStrictEqual(err, errors.UnresolvableGrantByEmailAddress);
@ -542,24 +556,25 @@ describe('putBucketACL API', () => {
* "Grant" which is part of the s3 xml scheme for ACLs * "Grant" which is part of the s3 xml scheme for ACLs
* so an error should be returned * so an error should be returned
*/ */
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<PowerGrant>' + + '<PowerGrant>'
'<Grantee xsi:type="AmazonCustomerByEmail">' + + '<Grantee xsi:type="AmazonCustomerByEmail">'
'<EmailAddress>xyz@amazon.com</EmailAddress>' + + '<EmailAddress>xyz@amazon.com</EmailAddress>'
'</Grantee>' + + '</Grantee>'
'<Permission>WRITE_ACP</Permission>' + + '<Permission>WRITE_ACP</Permission>'
'</PowerGrant>' + + '</PowerGrant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -579,32 +594,33 @@ describe('putBucketACL API', () => {
* "Grant" which is part of the s3 xml scheme for ACLs * "Grant" which is part of the s3 xml scheme for ACLs
* so an error should be returned * so an error should be returned
*/ */
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="CanonicalUser">' + + '<Grantee xsi:type="CanonicalUser">'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Grantee>' + + '</Grantee>'
'<Permission>FULL_CONTROL</Permission>' + + '<Permission>FULL_CONTROL</Permission>'
'</Grant>' + + '</Grant>'
'<PowerGrant>' + + '<PowerGrant>'
'<Grantee xsi:type="AmazonCustomerByEmail">' + + '<Grantee xsi:type="AmazonCustomerByEmail">'
'<EmailAddress>xyz@amazon.com</EmailAddress>' + + '<EmailAddress>xyz@amazon.com</EmailAddress>'
'</Grantee>' + + '</Grantee>'
'<Permission>WRITE_ACP</Permission>' + + '<Permission>WRITE_ACP</Permission>'
'</PowerGrant>' + + '</PowerGrant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -622,24 +638,25 @@ describe('putBucketACL API', () => {
// so an error should be returned // so an error should be returned
post: { post: {
'<AccessControlPolicy xmlns': '<AccessControlPolicy xmlns':
'"http://s3.amazonaws.com/doc/2006-03-01/">' + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'<Owner>' + + '<Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="AmazonCustomerByEmail">' + + '<Grantee xsi:type="AmazonCustomerByEmail">'
'<EmailAddress>xyz@amazon.com</EmailAddress>' + + '<EmailAddress>xyz@amazon.com</EmailAddress>'
'<Grantee>' + + '<Grantee>'
'<Permission>WRITE_ACP</Permission>' + + '<Permission>WRITE_ACP</Permission>'
'<Grant>' + + '<Grant>'
'<AccessControlList>' + + '<AccessControlList>'
'<AccessControlPolicy>', + '<AccessControlPolicy>',
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -648,32 +665,33 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should return an error if invalid group ' + it('should return an error if invalid group '
'uri provided in ACLs set out in request body', done => { + 'uri provided in ACLs set out in request body', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
// URI in grant below is not valid group URI for s3 // URI in grant below is not valid group URI for s3
post: '<AccessControlPolicy xmlns=' + post: '<AccessControlPolicy xmlns='
'"http://s3.amazonaws.com/doc/2006-03-01/">' + + '"http://s3.amazonaws.com/doc/2006-03-01/">'
'<Owner>' + + '<Owner>'
'<ID>79a59df900b949e55d96a1e698fbaced' + + '<ID>79a59df900b949e55d96a1e698fbaced'
'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>' + + 'fd6e09d98eacf8f8d5218e7cd47ef2be</ID>'
'<DisplayName>OwnerDisplayName</DisplayName>' + + '<DisplayName>OwnerDisplayName</DisplayName>'
'</Owner>' + + '</Owner>'
'<AccessControlList>' + + '<AccessControlList>'
'<Grant>' + + '<Grant>'
'<Grantee xsi:type="Group">' + + '<Grantee xsi:type="Group">'
'<URI>http://acs.amazonaws.com/groups/' + + '<URI>http://acs.amazonaws.com/groups/'
'global/NOTAVALIDGROUP</URI>' + + 'global/NOTAVALIDGROUP</URI>'
'</Grantee>' + + '</Grantee>'
'<Permission>READ</Permission>' + + '<Permission>READ</Permission>'
'</Grant>' + + '</Grant>'
'</AccessControlList>' + + '</AccessControlList>'
'</AccessControlPolicy>', + '</AccessControlPolicy>',
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {
@ -682,19 +700,20 @@ describe('putBucketACL API', () => {
}); });
}); });
it('should return an error if invalid group uri' + it('should return an error if invalid group uri'
'provided in ACL header request', done => { + 'provided in ACL header request', done => {
const testACLRequest = { const testACLRequest = {
bucketName, bucketName,
namespace, namespace,
headers: { headers: {
'host': `${bucketName}.s3.amazonaws.com`, 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-grant-full-control': 'x-amz-grant-full-control':
'uri="http://acs.amazonaws.com/groups/' + 'uri="http://acs.amazonaws.com/groups/'
'global/NOTAVALIDGROUP"', + 'global/NOTAVALIDGROUP"',
}, },
url: '/?acl', url: '/?acl',
query: { acl: '' }, query: { acl: '' },
actionImplicitDenies: false,
}; };
bucketPutACL(authInfo, testACLRequest, log, err => { bucketPutACL(authInfo, testACLRequest, log, err => {

View File

@ -3,13 +3,13 @@ const { errors } = require('arsenal');
const { bucketPut } = require('../../../lib/api/bucketPut'); const { bucketPut } = require('../../../lib/api/bucketPut');
const bucketPutCors = require('../../../lib/api/bucketPutCors'); const bucketPutCors = require('../../../lib/api/bucketPutCors');
const { _validator, parseCorsXml } const { _validator, parseCorsXml } = require('../../../lib/api/apiUtils/bucket/bucketCors');
= require('../../../lib/api/apiUtils/bucket/bucketCors'); const {
const { cleanup, cleanup,
DummyRequestLogger, DummyRequestLogger,
makeAuthInfo, makeAuthInfo,
CorsConfigTester } CorsConfigTester,
= require('../helpers'); } = require('../helpers');
const metadata = require('../../../lib/metadata/wrapper'); const metadata = require('../../../lib/metadata/wrapper');
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();
@ -19,6 +19,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function _testPutBucketCors(authInfo, request, log, errCode, cb) { function _testPutBucketCors(authInfo, request, log, errCode, cb) {
@ -30,13 +31,13 @@ function _testPutBucketCors(authInfo, request, log, errCode, cb) {
} }
function _generateSampleXml(value) { function _generateSampleXml(value) {
const xml = '<CORSConfiguration>' + const xml = '<CORSConfiguration>'
'<CORSRule>' + + '<CORSRule>'
'<AllowedMethod>PUT</AllowedMethod>' + + '<AllowedMethod>PUT</AllowedMethod>'
'<AllowedOrigin>www.example.com</AllowedOrigin>' + + '<AllowedOrigin>www.example.com</AllowedOrigin>'
`${value}` + + `${value}`
'</CORSRule>' + + '</CORSRule>'
'</CORSConfiguration>'; + '</CORSConfiguration>';
return xml; return xml;
} }
@ -125,8 +126,8 @@ describe('PUT bucket cors :: helper validation functions ', () => {
it('should return MalformedXML if more than one ID per rule', done => { it('should return MalformedXML if more than one ID per rule', done => {
const testValue = 'testid'; const testValue = 'testid';
const xml = _generateSampleXml(`<ID>${testValue}</ID>` + const xml = _generateSampleXml(`<ID>${testValue}</ID>`
`<ID>${testValue}</ID>`); + `<ID>${testValue}</ID>`);
parseCorsXml(xml, log, err => { parseCorsXml(xml, log, err => {
assert(err, 'Expected error but found none'); assert(err, 'Expected error but found none');
assert.deepStrictEqual(err, errors.MalformedXML); assert.deepStrictEqual(err, errors.MalformedXML);
@ -157,8 +158,8 @@ describe('PUT bucket cors :: helper validation functions ', () => {
describe('validateMaxAgeSeconds ', () => { describe('validateMaxAgeSeconds ', () => {
it('should validate successfully for valid value', done => { it('should validate successfully for valid value', done => {
const testValue = 60; const testValue = 60;
const xml = _generateSampleXml(`<MaxAgeSeconds>${testValue}` + const xml = _generateSampleXml(`<MaxAgeSeconds>${testValue}`
'</MaxAgeSeconds>'); + '</MaxAgeSeconds>');
parseCorsXml(xml, log, (err, result) => { parseCorsXml(xml, log, (err, result) => {
assert.strictEqual(err, null, `Found unexpected err ${err}`); assert.strictEqual(err, null, `Found unexpected err ${err}`);
assert.strictEqual(typeof result[0].maxAgeSeconds, 'number'); assert.strictEqual(typeof result[0].maxAgeSeconds, 'number');
@ -167,12 +168,13 @@ describe('PUT bucket cors :: helper validation functions ', () => {
}); });
}); });
it('should return MalformedXML if more than one MaxAgeSeconds ' + it('should return MalformedXML if more than one MaxAgeSeconds '
'per rule', done => { + 'per rule', done => {
const testValue = '60'; const testValue = '60';
const xml = _generateSampleXml( const xml = _generateSampleXml(
`<MaxAgeSeconds>${testValue}</MaxAgeSeconds>` + `<MaxAgeSeconds>${testValue}</MaxAgeSeconds>`
`<MaxAgeSeconds>${testValue}</MaxAgeSeconds>`); + `<MaxAgeSeconds>${testValue}</MaxAgeSeconds>`,
);
parseCorsXml(xml, log, err => { parseCorsXml(xml, log, err => {
assert(err, 'Expected error but found none'); assert(err, 'Expected error but found none');
assert.deepStrictEqual(err, errors.MalformedXML); assert.deepStrictEqual(err, errors.MalformedXML);
@ -182,8 +184,8 @@ describe('PUT bucket cors :: helper validation functions ', () => {
it('should validate & return undefined if empty value', done => { it('should validate & return undefined if empty value', done => {
const testValue = ''; const testValue = '';
const xml = _generateSampleXml(`<MaxAgeSeconds>${testValue}` + const xml = _generateSampleXml(`<MaxAgeSeconds>${testValue}`
'</MaxAgeSeconds>'); + '</MaxAgeSeconds>');
parseCorsXml(xml, log, (err, result) => { parseCorsXml(xml, log, (err, result) => {
assert.strictEqual(err, null, `Found unexpected err ${err}`); assert.strictEqual(err, null, `Found unexpected err ${err}`);
assert.strictEqual(result[0].MaxAgeSeconds, undefined); assert.strictEqual(result[0].MaxAgeSeconds, undefined);

View File

@ -14,6 +14,7 @@ const bucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
describe('bucketPutEncryption API', () => { describe('bucketPutEncryption API', () => {
@ -32,25 +33,27 @@ describe('bucketPutEncryption API', () => {
it('should reject a config with no Rule', done => { it('should reject a config with no Rule', done => {
bucketPutEncryption(authInfo, templateRequest(bucketName, bucketPutEncryption(authInfo, templateRequest(bucketName,
{ post: `<?xml version="1.0" encoding="UTF-8"?> {
post: `<?xml version="1.0" encoding="UTF-8"?>
<ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
</ServerSideEncryptionConfiguration>`, </ServerSideEncryptionConfiguration>`,
}), log, err => { }), log, err => {
assert.strictEqual(err.is.MalformedXML, true); assert.strictEqual(err.is.MalformedXML, true);
done(); done();
}); });
}); });
it('should reject a config with no ApplyServerSideEncryptionByDefault section', done => { it('should reject a config with no ApplyServerSideEncryptionByDefault section', done => {
bucketPutEncryption(authInfo, templateRequest(bucketName, bucketPutEncryption(authInfo, templateRequest(bucketName,
{ post: `<?xml version="1.0" encoding="UTF-8"?> {
post: `<?xml version="1.0" encoding="UTF-8"?>
<ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Rule></Rule> <Rule></Rule>
</ServerSideEncryptionConfiguration>`, </ServerSideEncryptionConfiguration>`,
}), log, err => { }), log, err => {
assert.strictEqual(err.is.MalformedXML, true); assert.strictEqual(err.is.MalformedXML, true);
done(); done();
}); });
}); });
it('should reject a config with no SSEAlgorithm', done => { it('should reject a config with no SSEAlgorithm', done => {
@ -155,33 +158,32 @@ describe('bucketPutEncryption API', () => {
}); });
}); });
it('should update SSEAlgorithm if existing SSEAlgorithm is AES256, ' + it('should update SSEAlgorithm if existing SSEAlgorithm is AES256, '
'new SSEAlgorithm is aws:kms and no KMSMasterKeyID is provided', + 'new SSEAlgorithm is aws:kms and no KMSMasterKeyID is provided',
done => { done => {
const post = templateSSEConfig({ algorithm: 'AES256' }); const post = templateSSEConfig({ algorithm: 'AES256' });
bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => { bucketPutEncryption(authInfo, templateRequest(bucketName, { post }), log, err => {
assert.ifError(err);
return getSSEConfig(bucketName, log, (err, sseInfo) => {
assert.ifError(err); assert.ifError(err);
return getSSEConfig(bucketName, log, (err, sseInfo) => { const { masterKeyId } = sseInfo;
assert.ifError(err); const newConf = templateSSEConfig({ algorithm: 'aws:kms' });
const { masterKeyId } = sseInfo; return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log,
const newConf = templateSSEConfig({ algorithm: 'aws:kms' }); err => {
return bucketPutEncryption(authInfo, templateRequest(bucketName, { post: newConf }), log, assert.ifError(err);
err => { return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => {
assert.ifError(err); assert.deepStrictEqual(updatedSSEInfo, {
return getSSEConfig(bucketName, log, (err, updatedSSEInfo) => { mandatory: true,
assert.deepStrictEqual(updatedSSEInfo, { algorithm: 'aws:kms',
mandatory: true, cryptoScheme: 1,
algorithm: 'aws:kms', masterKeyId,
cryptoScheme: 1,
masterKeyId,
});
done();
}); });
} done();
); });
}); });
}); });
}); });
});
it('should update SSEAlgorithm to aws:kms and set KMSMasterKeyID', done => { it('should update SSEAlgorithm to aws:kms and set KMSMasterKeyID', done => {
const post = templateSSEConfig({ algorithm: 'AES256' }); const post = templateSSEConfig({ algorithm: 'AES256' });

View File

@ -17,6 +17,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const expectedLifecycleConfig = { const expectedLifecycleConfig = {

View File

@ -15,6 +15,7 @@ const bucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const expectedNotifConfig = { const expectedNotifConfig = {
@ -52,6 +53,7 @@ function getNotifRequest(empty) {
host: `${bucketName}.s3.amazonaws.com`, host: `${bucketName}.s3.amazonaws.com`,
}, },
post: notifXml, post: notifXml,
actionImplicitDenies: false,
}; };
return putNotifConfigRequest; return putNotifConfigRequest;
} }

View File

@ -15,6 +15,7 @@ const bucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
const objectLockXml = '<ObjectLockConfiguration ' + const objectLockXml = '<ObjectLockConfiguration ' +
@ -30,6 +31,7 @@ const putObjLockRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
post: objectLockXml, post: objectLockXml,
actionImplicitDenies: false,
}; };
const expectedObjectLockConfig = { const expectedObjectLockConfig = {

View File

@ -15,6 +15,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
let expectedBucketPolicy = {}; let expectedBucketPolicy = {};
@ -25,6 +26,7 @@ function getPolicyRequest(policy) {
host: `${bucketName}.s3.amazonaws.com`, host: `${bucketName}.s3.amazonaws.com`,
}, },
post: JSON.stringify(policy), post: JSON.stringify(policy),
actionImplicitDenies: false,
}; };
} }
@ -76,7 +78,7 @@ describe('putBucketPolicy API', () => {
}); });
}); });
it('should return error if policy contains conditions', done => { it.skip('should return error if policy contains conditions', done => {
expectedBucketPolicy.Statement[0].Condition = expectedBucketPolicy.Statement[0].Condition =
{ StringEquals: { 's3:x-amz-acl': ['public-read'] } }; { StringEquals: { 's3:x-amz-acl': ['public-read'] } };
bucketPutPolicy(authInfo, getPolicyRequest(expectedBucketPolicy), log, bucketPutPolicy(authInfo, getPolicyRequest(expectedBucketPolicy), log,

View File

@ -19,6 +19,7 @@ const testBucketPutRequest = {
bucketName, bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}; };
function _getPutWebsiteRequest(xml) { function _getPutWebsiteRequest(xml) {
@ -29,6 +30,7 @@ function _getPutWebsiteRequest(xml) {
}, },
url: '/?website', url: '/?website',
query: { website: '' }, query: { website: '' },
actionImplicitDenies: false,
}; };
request.post = xml; request.post = xml;
return request; return request;

View File

@ -28,6 +28,7 @@ const testPutBucketRequest = new DummyRequest({
namespace, namespace,
headers: { host: `${bucketName}.s3.amazonaws.com` }, headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/', url: '/',
actionImplicitDenies: false,
}); });
const testDeleteRequest = new DummyRequest({ const testDeleteRequest = new DummyRequest({
bucketName, bucketName,
@ -35,6 +36,7 @@ const testDeleteRequest = new DummyRequest({
objectKey: objectName, objectKey: objectName,
headers: {}, headers: {},
url: `/${bucketName}/${objectName}`, url: `/${bucketName}/${objectName}`,
actionImplicitDenies: false,
}); });
function _createBucketPutVersioningReq(status) { function _createBucketPutVersioningReq(status) {
@ -45,6 +47,7 @@ function _createBucketPutVersioningReq(status) {
}, },
url: '/?versioning', url: '/?versioning',
query: { versioning: '' }, query: { versioning: '' },
actionImplicitDenies: false,
}; };
const xml = '<VersioningConfiguration ' + const xml = '<VersioningConfiguration ' +
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' + 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
@ -62,6 +65,7 @@ function _createMultiObjectDeleteRequest(numObjects) {
}, },
url: '/?delete', url: '/?delete',
query: { delete: '' }, query: { delete: '' },
actionImplicitDenies: false,
}; };
const xml = []; const xml = [];
xml.push('<?xml version="1.0" encoding="UTF-8"?>'); xml.push('<?xml version="1.0" encoding="UTF-8"?>');

Some files were not shown because too many files have changed in this diff Show More