Compare commits

...

39 Commits

Author SHA1 Message Date
Will Toozs f817122415
REVISIT THIS COMMIT
The code in this commit causes an error in the 'implicit deny iam policy with "Allow" bucket policy for User ARN' test for PutObject, giving an AccessDenied because the implicitDent is true. It seems worth re-evaluating the ACL expectations at this point.
2023-09-25 11:40:17 +02:00
Will Toozs 3773bcf61c
add evaluateBucketPolicyWithIAM omissions 2023-09-21 11:41:30 +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
128 changed files with 2306 additions and 1503 deletions

View File

@ -153,6 +153,8 @@ const constants = {
'objectDeleteTagging',
'objectGetTagging',
'objectPutTagging',
'objectPutLegalHold',
'objectPutRetention',
],
// response header to be sent when there are invalid
// 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
if (apiMethod === 'websiteGet' || apiMethod === 'websiteHead' ||
apiMethod === 'corsPreflight') {
request.actionImplicitDenies = false;
return this[apiMethod](request, log, callback);
}
@ -129,15 +130,25 @@ const api = {
const requestContexts = prepareRequestContexts(apiMethod, request,
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) {
let returnTagCount = true;
const isImplicitDeny = {};
let isOnlyImplicitDeny = true;
if (apiMethod === 'objectGet') {
// 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');
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
if (!authResults[1].isAllowed) {
log.trace('get tagging authorization denial ' +
@ -146,13 +157,25 @@ const api = {
}
} else {
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');
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([
@ -230,7 +253,16 @@ const api = {
if (checkedResults instanceof Error) {
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') {
request._response = response;

View File

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

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

View File

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

View File

@ -3,6 +3,7 @@ const moment = require('moment');
const { config } = require('../../../Config');
const vault = require('../../../auth/vault');
const { evaluateBucketPolicyWithIAM } = require('../authorization/permissionChecks');
/**
* Calculates retain until date for the locked object version
@ -302,7 +303,11 @@ function checkUserGovernanceBypass(request, authInfo, bucketMD, objectKey, log,
if (err) {
return cb(err);
}
if (authorizationResults[0].isAllowed !== true) {
// Deny immediately if there is any explicit deny
const explicitDenyExists = authorizationResults.some(
authzResult => authzResult.isAllowed === false && authzResult.isImplicit === false);
if (explicitDenyExists) {
log.trace('authorization check failed for user',
{
'method': 'checkUserPolicyGovernanceBypass',
@ -310,7 +315,25 @@ function checkUserGovernanceBypass(request, authInfo, bucketMD, objectKey, log,
});
return cb(errors.AccessDenied);
}
return cb(null);
// Convert authorization results into an easier to handle format
const iamAuthzResults = authorizationResults.reduce((acc, curr, idx) => {
// eslint-disable-next-line no-param-reassign
acc[requestContextParams[idx].apiMethod] = curr.isImplicit;
return acc;
}, {});
// Evaluate against the bucket policies
const areAllActionsAllowed = evaluateBucketPolicyWithIAM(
bucketMD,
Object.keys(iamAuthzResults),
authInfo.getCanonicalID(),
authInfo,
iamAuthzResults,
log,
request);
return cb(areAllActionsAllowed ? null : errors.AccessDenied);
});
}

View File

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

View File

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

View File

@ -26,7 +26,7 @@ function bucketDeleteEncryption(authInfo, request, log, callback) {
};
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) => {
const sseConfig = bucket.getServerSideEncryption();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
request.method, bucket);
if (err) {

View File

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

View File

@ -27,7 +27,7 @@ function bucketGetEncryption(authInfo, request, log, callback) {
};
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) => {
// If sseInfo is present but the `mandatory` flag is not set

View File

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

View File

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

View File

@ -41,7 +41,7 @@ function bucketGetNotification(authInfo, request, log, callback) {
request,
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {

View File

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

View File

@ -21,7 +21,7 @@ function bucketGetPolicy(authInfo, request, log, callback) {
request,
};
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
return metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
if (err) {
log.debug('error processing request', {

View File

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

View File

@ -57,7 +57,7 @@ function bucketGetVersioning(authInfo, request, log, callback) {
request,
};
metadataValidateBucket(metadataValParams, log, (err, bucket) => {
metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
if (err) {

View File

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

View File

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

View File

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

View File

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

View File

@ -18,17 +18,17 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
*/
function bucketPutEncryption(authInfo, request, log, callback) {
const bucketName = request.bucketName;
const { bucketName } = request;
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketPutEncryption',
requestType: request.apiMethods || 'bucketPutEncryption',
request,
};
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) => {
log.trace('parsing encryption config', { method: 'bucketPutEncryption' });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -87,13 +87,13 @@ function bucketPutVersioning(authInfo, request, log, callback) {
const metadataValParams = {
authInfo,
bucketName,
requestType: 'bucketPutVersioning',
requestType: request.apiMethods || 'bucketPutVersioning',
request,
};
return waterfall([
next => _parseXML(request, log, next),
next => metadataValidateBucket(metadataValParams, log,
next => metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => next(err, bucket)), // ignore extra null object,
(bucket, next) => parseString(request.post, (err, result) => {
// 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) {
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', {
requestType,
method: 'bucketPutWebsite',

View File

@ -120,7 +120,7 @@ function completeMultipartUpload(authInfo, request, log, callback) {
// at the destinationBucket level are same as objectPut
requestType: 'objectPut',
};
metadataValidateBucketAndObj(metadataValParams, log, next);
metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log, next);
},
function validateMultipart(destBucket, objMD, next) {
if (objMD) {
@ -190,9 +190,14 @@ function completeMultipartUpload(authInfo, request, log, callback) {
const mdInfo = { storedParts, mpuOverviewKey, splitter };
const mpuInfo =
{ 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,
null, null, null, locationConstraintCheck, log,
(err, completeObjData) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) {
return next(err, destBucket);
}

View File

@ -253,7 +253,7 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
}
async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(error, destinationBucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, destinationBucket);

View File

@ -104,7 +104,7 @@ function listMultipartUploads(authInfo, request, log, callback) {
function waterfall1(next) {
// Check final destination bucket for authorization rather
// than multipart upload bucket
metadataValidateBucket(metadataValParams, log,
metadataValidateBucket(metadataValParams, request.actionImplicitDenies, log,
(err, bucket) => next(err, bucket));
},
function getMPUBucket(bucket, next) {

View File

@ -112,7 +112,7 @@ function listParts(authInfo, request, log, callback) {
async.waterfall([
function checkDestBucketVal(next) {
metadataValidateBucketAndObj(metadataValParams, log,
metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, destinationBucket) => {
if (err) {
return next(err, destinationBucket, null);
@ -150,8 +150,13 @@ function listParts(authInfo, request, log, callback) {
mpuOverviewObj,
destBucket,
};
const originalIdentityImpDenies = request.actionImplicitDenies;
// eslint-disable-next-line no-param-reassign
delete request.actionImplicitDenies;
return data.listParts(mpuInfo, request, locationConstraintCheck,
log, (err, backendPartList) => {
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = originalIdentityImpDenies;
if (err) {
return next(err, destBucket);
}

View File

@ -11,7 +11,7 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const services = require('../services');
const vault = require('../auth/vault');
const { isBucketAuthorized } =
const { isBucketAuthorized, evaluateBucketPolicyWithIAM } =
require('./apiUtils/authorization/permissionChecks');
const { preprocessingVersioningDelete }
= require('./apiUtils/object/versioning');
@ -385,15 +385,37 @@ function multiObjectDelete(authInfo, request, log, callback) {
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
const inPlay = [];
const errorResults = [];
// 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()) {
return next(null, quietSetting, errorResults, objects);
return next(null, quietSetting, errorResults, objects, bucketMD);
}
// TODO: once arsenal's extractParams is separated from doAuth
@ -429,7 +451,6 @@ function multiObjectDelete(authInfo, request, log, callback) {
};
return vault.checkPolicies(requestContextParams, authInfo.getArn(),
log, (err, authorizationResults) => {
// there were no policies so received a blanket AccessDenied
if (err && err.is.AccessDenied) {
objects.forEach(entry => {
errorResults.push({
@ -437,7 +458,7 @@ function multiObjectDelete(authInfo, request, log, callback) {
error: errors.AccessDenied });
});
// send empty array for inPlay
return next(null, quietSetting, errorResults, []);
return next(null, quietSetting, errorResults, [], bucketMD);
}
if (err) {
log.trace('error checking policies', {
@ -455,6 +476,16 @@ function multiObjectDelete(authInfo, request, log, callback) {
});
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++) {
const result = authorizationResults[i];
// result is { isAllowed: true,
@ -470,7 +501,27 @@ function multiObjectDelete(authInfo, request, log, callback) {
key: result.arn.slice(slashIndex + 1),
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);
} else {
errorResults.push({
@ -479,50 +530,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,
bucket, next) {
return getObjMetadataAndDelete(authInfo, canonicalID, request,

View File

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

View File

@ -56,8 +56,8 @@ function objectDelete(authInfo, request, log, cb) {
const canonicalID = authInfo.getCanonicalID();
return async.waterfall([
function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(valParams, log,
(err, bucketMD, objMD) => {
return metadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log,
(err, bucketMD, objMD) => {
if (err) {
return next(err, bucketMD);
}

View File

@ -46,7 +46,7 @@ function objectDeleteTagging(authInfo, request, log, callback) {
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',

View File

@ -48,7 +48,7 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
request,
};
return metadataValidateBucketAndObj(mdValParams, log,
return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log,
(err, bucket, objMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);

View File

@ -71,7 +71,7 @@ function objectGetACL(authInfo, request, log, callback) {
return async.waterfall([
function validateBucketAndObj(next) {
return metadataValidateBucketAndObj(metadataValParams, log,
return metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',

View File

@ -43,7 +43,7 @@ function objectGetLegalHold(authInfo, request, log, callback) {
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',

View File

@ -43,7 +43,7 @@ function objectGetRetention(authInfo, request, log, callback) {
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',

View File

@ -43,7 +43,7 @@ function objectGetTagging(authInfo, request, log, callback) {
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',

View File

@ -48,7 +48,7 @@ function objectHead(authInfo, request, log, callback) {
request,
};
return metadataValidateBucketAndObj(mdValParams, log,
return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log,
(err, bucket, objMD) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,45 +41,57 @@ function objectPutRetention(authInfo, request, log, callback) {
authInfo,
bucketName,
objectKey,
requestType: 'objectPutRetention',
versionId: reqVersionId,
requestType: request.apiMethods || 'objectPutRetention',
request,
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, 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' });
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) => {
next => {
log.trace('parsing retention information');
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) => {
const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);
if (hasGovernanceBypass && authInfo.isRequesterAnIAMUser()) {

View File

@ -1,8 +1,7 @@
const async = require('async');
const { errors, s3middleware } = require('arsenal');
const { decodeVersionId, getVersionIdResHeader } =
require('./apiUtils/object/versioning');
const { decodeVersionId, getVersionIdResHeader } = require('./apiUtils/object/versioning');
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities');
@ -10,6 +9,7 @@ const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const metadata = require('../metadata/wrapper');
const { data } = require('../data/wrapper');
const { parseTagXml } = s3middleware.tagging;
const REPLICATION_ACTION = 'PUT_TAGGING';
@ -24,8 +24,8 @@ const REPLICATION_ACTION = 'PUT_TAGGING';
function objectPutTagging(authInfo, request, log, callback) {
log.debug('processing request', { method: 'objectPutTagging' });
const bucketName = request.bucketName;
const objectKey = request.objectKey;
const { bucketName } = request;
const { objectKey } = request;
const decodedVidResult = decodeVersionId(request.query);
if (decodedVidResult instanceof Error) {
@ -41,13 +41,13 @@ function objectPutTagging(authInfo, request, log, callback) {
authInfo,
bucketName,
objectKey,
requestType: 'objectPutTagging',
versionId: reqVersionId,
requestType: request.apiMethods || 'objectPutTagging',
request,
};
return async.waterfall([
next => metadataValidateBucketAndObj(metadataValParams, log,
next => metadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
(err, bucket, objectMD) => {
if (err) {
log.trace('request authorization failed',
@ -70,8 +70,7 @@ function objectPutTagging(authInfo, request, log, callback) {
}),
(bucket, objectMD, next) => {
log.trace('parsing tag(s)');
parseTagXml(request.post, log, (err, tags) =>
next(err, bucket, tags, objectMD));
parseTagXml(request.post, log, (err, tags) => next(err, bucket, tags, objectMD));
},
(bucket, tags, objectMD, next) => {
// eslint-disable-next-line no-param-reassign
@ -88,13 +87,11 @@ function objectPutTagging(authInfo, request, log, callback) {
// eslint-disable-next-line no-param-reassign
objectMD.originOp = 's3:ObjectTagging:Put';
metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params,
log, err =>
next(err, bucket, objectMD));
log, err => next(err, bucket, objectMD));
},
(bucket, objectMD, next) =>
// if external backend handles tagging
data.objectTagging('Put', objectKey, bucket, objectMD,
log, err => next(err, bucket, objectMD)),
// if external backend handles tagging
(bucket, objectMD, next) => data.objectTagging('Put', objectKey, bucket, objectMD,
log, err => next(err, bucket, objectMD)),
], (err, bucket, objectMD) => {
const additionalResHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
@ -110,8 +107,7 @@ function objectPutTagging(authInfo, request, log, callback) {
location: objectMD ? objectMD.dataStoreName : undefined,
});
const verCfg = bucket.getVersioningConfiguration();
additionalResHeaders['x-amz-version-id'] =
getVersionIdResHeader(verCfg, objectMD);
additionalResHeaders['x-amz-version-id'] = getVersionIdResHeader(verCfg, objectMD);
}
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
* websiteGet)
* @param {object} corsHeaders - CORS-related response headers
* @param {object} request - normalized request object
* @param {object} log - Werelogs instance
* @param {function} callback - callback to function in route
* @return {undefined}
*/
function _errorActions(err, errorDocument, routingRules,
bucket, objectKey, corsHeaders, log, callback) {
bucket, objectKey, corsHeaders, request, log, callback) {
const bucketName = bucket.getName();
const errRoutingRule = findRoutingRule(routingRules,
objectKey, err.code);
@ -47,7 +48,7 @@ function _errorActions(err, errorDocument, routingRules,
// return the default error message if the object is private
// rather than sending a stored error file
if (!isObjAuthorized(bucket, errObjMD, 'objectGet',
constants.publicId, null, log)) {
constants.publicId, null, request.actionImplicitDenies, log)) {
log.trace('errorObj not authorized', { error: err });
return callback(err, true, null, corsHeaders);
}
@ -144,7 +145,7 @@ function websiteGet(request, log, callback) {
{ error: err });
let returnErr = err;
const bucketAuthorized = isBucketAuthorized(bucket,
'bucketGet', constants.publicId, null, log, request);
'bucketGet', constants.publicId, null, request.actionImplicitDenies, log, request);
// if index object does not exist and bucket is private AWS
// returns 403 - AccessDenied error.
if (err.is.NoSuchKey && !bucketAuthorized) {
@ -152,16 +153,16 @@ function websiteGet(request, log, callback) {
}
return _errorActions(returnErr,
websiteConfig.getErrorDocument(), routingRules,
bucket, reqObjectKey, corsHeaders, log,
bucket, reqObjectKey, corsHeaders, request, log,
callback);
}
if (!isObjAuthorized(bucket, objMD, 'objectGet',
constants.publicId, null, log, request)) {
constants.publicId, null, request.actionImplicitDenies, log, request)) {
const err = errors.AccessDenied;
log.trace('request not authorized', { error: err });
return _errorActions(err, websiteConfig.getErrorDocument(),
routingRules, bucket,
reqObjectKey, corsHeaders, log, callback);
reqObjectKey, corsHeaders, request, log, callback);
}
const headerValResult = validateHeaders(request.headers,
@ -171,7 +172,7 @@ function websiteGet(request, log, callback) {
log.trace('header validation error', { error: err });
return _errorActions(err, websiteConfig.getErrorDocument(),
routingRules, bucket, reqObjectKey,
corsHeaders, log, callback);
corsHeaders, request, log, callback);
}
// check if object to serve has website redirect header
// Note: AWS prioritizes website configuration rules over

View File

@ -104,7 +104,7 @@ function websiteHead(request, log, callback) {
{ error: err });
let returnErr = err;
const bucketAuthorized = isBucketAuthorized(bucket,
'bucketGet', constants.publicId, null, log, request);
'bucketGet', constants.publicId, null, request.actionImplicitDenies, log, request);
// if index object does not exist and bucket is private AWS
// returns 403 - AccessDenied error.
if (err.is.NoSuchKey && !bucketAuthorized) {
@ -114,7 +114,7 @@ function websiteHead(request, log, callback) {
reqObjectKey, corsHeaders, log, callback);
}
if (!isObjAuthorized(bucket, objMD, 'objectGet',
constants.publicId, null, log, request)) {
constants.publicId, null, request.actionImplicitDenies, log, request)) {
const err = errors.AccessDenied;
log.trace('request not authorized', { error: err });
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
* master version objMD. The null version object md must be retrieved in a
* separate step using the master object md: see getNullVersion().
* @param {string} requestType - type of request
* @param {string} bucketName - name of bucket
* @param {string} objectKey - name of object key
* @param {string} [versionId] - version of object to retrieve
@ -50,7 +49,7 @@ function getNullVersion(objMD, bucketName, objectKey, log, cb) {
* @param {function} cb - callback
* @return {undefined} - and call callback with err, bucket md and object md
*/
function metadataGetBucketAndObject(requestType, bucketName, objectKey,
function metadataGetBucketAndObject(bucketName, objectKey,
versionId, log, cb) {
const options = {
// if attempting to get 'null' version, must retrieve null version id
@ -73,13 +72,6 @@ function metadataGetBucketAndObject(requestType, bucketName, objectKey,
});
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');
return cb(null, bucket, obj);
});
@ -118,6 +110,35 @@ function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
});
}
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
* and check if user is authorized to access them.
* @param {object} params - function parameters
@ -127,41 +148,45 @@ function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
* @param {string} [params.versionId] - version id if getting specific version
* @param {string} params.requestType - type of request
* @param {object} params.request - http request object
* @param {boolean} actionImplicitDenies - identity authorization results
* @param {RequestLogger} log - request logger
* @param {function} callback - callback
* @return {undefined} - and call callback with params err, bucket md
*/
function metadataValidateBucketAndObj(params, log, callback) {
const { authInfo, bucketName, objectKey, versionId, requestType, preciseRequestType, request } = params;
const canonicalID = authInfo.getCanonicalID();
function metadataValidateBucketAndObj(params, actionImplicitDenies, log, callback) {
const { authInfo, bucketName, objectKey, versionId, request } = params;
let requestType = params.requestType;
if (!Array.isArray(requestType)) {
requestType = [requestType];
}
async.waterfall([
function getBucketAndObjectMD(next) {
return metadataGetBucketAndObject(requestType, bucketName,
objectKey, versionId, log, next);
},
function checkBucketAuth(bucket, objMD, next) {
// 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);
next => metadataGetBucketAndObject(bucketName,
objectKey, versionId, log, (err, bucket, objMD) => {
if (err) {
// if some implicit actionImplicitDenies, return AccessDenied
// before leaking any state information
if (actionImplicitDenies && Object.values(actionImplicitDenies).some(v => v === true)) {
return next(errors.AccessDenied);
}
return next(err);
}
return next(null, bucket, objMD);
}),
(bucket, objMD, next) => {
const validationError = validateBucket(bucket, params, actionImplicitDenies, log);
if (validationError) {
return next(validationError, bucket);
}
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID,
authInfo, log, request)) {
log.debug('access denied for user on bucket', { requestType });
return next(errors.AccessDenied, bucket);
}
return next(null, bucket, objMD);
},
function handleNullVersionGet(bucket, objMD, next) {
if (objMD && versionId === 'null') {
return getNullVersion(objMD, bucketName, objectKey, log,
(err, nullVer) => next(err, bucket, nullVer));
}
return next(null, bucket, objMD);
},
function checkObjectAuth(bucket, objMD, next) {
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, log, request)) {
(bucket, objMD, next) => {
const canonicalID = authInfo.getCanonicalID();
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID, authInfo, actionImplicitDenies,
log, request)) {
log.debug('access denied for user on object', { requestType });
return next(errors.AccessDenied, bucket);
}
@ -209,34 +234,25 @@ function metadataGetBucket(requestType, bucketName, log, cb) {
* @param {string} params.bucketName - name of bucket
* @param {string} params.requestType - type of request
* @param {string} params.request - http request object
* @param {boolean} actionImplicitDenies - identity authorization results
* @param {RequestLogger} log - request logger
* @param {function} callback - callback
* @return {undefined} - and call callback with params err, bucket md
*/
function metadataValidateBucket(params, log, callback) {
const { authInfo, bucketName, requestType, preciseRequestType, request } = params;
const canonicalID = authInfo.getCanonicalID();
function metadataValidateBucket(params, actionImplicitDenies, log, callback) {
const { bucketName, requestType } = params;
return metadataGetBucket(requestType, bucketName, log, (err, bucket) => {
if (err) {
return callback(err);
}
// 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 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);
const validationError = validateBucket(bucket, params, actionImplicitDenies, log);
return callback(validationError, bucket);
});
}
module.exports = {
metadataGetObject,
validateBucket,
metadataValidateBucketAndObj,
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
// eslint-disable-next-line no-param-reassign
request.apiMethod = 'routeBackbeat';
// eslint-disable-next-line no-param-reassign
request.actionImplicitDenies = false;
log.debug('routing request', {
method: 'routeBackbeat',
@ -1273,7 +1275,7 @@ function routeBackbeat(clientIP, request, response, log) {
requestType: 'ReplicateObject',
request,
};
return metadataValidateBucketAndObj(mdValParams, log, next);
return metadataValidateBucketAndObj(mdValParams, request.actionImplicitDenies, log, next);
},
(bucketInfo, objMd, next) => {
if (useMultipleBackend) {

View File

@ -20,7 +20,7 @@
"homepage": "https://github.com/scality/S3#readme",
"dependencies": {
"@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",
"aws-sdk": "2.905.0",
"azure-storage": "^2.1.0",

View File

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

Binary file not shown.

View File

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

View File

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

View File

@ -3,21 +3,19 @@ const async = require('async');
const { parseString } = require('xml2js');
const AWS = require('aws-sdk');
const { cleanup, DummyRequestLogger, makeAuthInfo }
= require('../unit/helpers');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../unit/helpers');
const { ds } = require('arsenal').storage.data.inMemory.datastore;
const { bucketPut } = require('../../lib/api/bucketPut');
const initiateMultipartUpload
= require('../../lib/api/initiateMultipartUpload');
const initiateMultipartUpload = require('../../lib/api/initiateMultipartUpload');
const objectPut = require('../../lib/api/objectPut');
const objectPutCopyPart = require('../../lib/api/objectPutCopyPart');
const DummyRequest = require('../unit/DummyRequest');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const constants = require('../../constants');
const s3 = new AWS.S3();
const splitter = constants.splitter;
const { splitter } = constants;
const log = new DummyRequestLogger();
const canonicalID = 'accessKey1';
const authInfo = makeAuthInfo(canonicalID);
@ -56,14 +54,14 @@ function getAwsParamsBucketMismatch(destObjName, uploadId) {
}
function copyPutPart(bucketLoc, mpuLoc, srcObjLoc, requestHost, cb,
errorPutCopyPart) {
errorPutCopyPart) {
const keys = getSourceAndDestKeys();
const { sourceObjName, destObjName } = keys;
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>' +
'<CreateBucketConfiguration ' +
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
`<LocationConstraint>${bucketLoc}</LocationConstraint>` +
'</CreateBucketConfiguration>' : '';
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>'
+ '<CreateBucketConfiguration '
+ 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
+ `<LocationConstraint>${bucketLoc}</LocationConstraint>`
+ '</CreateBucketConfiguration>' : '';
const bucketPutReq = new DummyRequest({
bucketName,
namespace,
@ -80,10 +78,13 @@ errorPutCopyPart) {
objectKey: destObjName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${destObjName}?uploads`,
actionImplicitDenies: false,
};
if (mpuLoc) {
initiateReq.headers = { 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}` };
initiateReq.headers = {
'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}`,
};
}
if (requestHost) {
initiateReq.parsedHost = requestHost;
@ -94,10 +95,13 @@ errorPutCopyPart) {
objectKey: sourceObjName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
if (srcObjLoc) {
sourceObjPutParams.headers = { 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${srcObjLoc}` };
sourceObjPutParams.headers = {
'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${srcObjLoc}`,
};
}
const sourceObjPutReq = new DummyRequest(sourceObjPutParams, body);
if (requestHost) {
@ -112,8 +116,7 @@ errorPutCopyPart) {
});
},
next => {
objectPut(authInfo, sourceObjPutReq, undefined, log, err =>
next(err));
objectPut(authInfo, sourceObjPutReq, undefined, log, err => next(err));
},
next => {
initiateMultipartUpload(authInfo, initiateReq, log, next);
@ -130,8 +133,8 @@ errorPutCopyPart) {
// Need to build request in here since do not have
// uploadId until here
assert.ifError(err, 'Error putting source object or initiate MPU');
const testUploadId = json.InitiateMultipartUploadResult.
UploadId[0];
const testUploadId = json.InitiateMultipartUploadResult
.UploadId[0];
const copyPartParams = {
bucketName,
namespace,
@ -172,137 +175,137 @@ function assertPartList(partList, uploadId) {
}
describeSkipIfE2E('ObjectCopyPutPart API with multiple backends',
function testSuite() {
this.timeout(60000);
function testSuite() {
this.timeout(60000);
beforeEach(() => {
cleanup();
});
beforeEach(() => {
cleanup();
});
it('should copy part to mem based on mpu location', done => {
copyPutPart(fileLocation, memLocation, null, 'localhost', () => {
it('should copy part to mem based on mpu location', done => {
copyPutPart(fileLocation, memLocation, null, 'localhost', () => {
// object info is stored in ds beginning at index one,
// so an array length of two means only one object
// was stored in mem
assert.strictEqual(ds.length, 2);
assert.deepStrictEqual(ds[1].value, body);
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();
});
assert.strictEqual(ds.length, 2);
assert.deepStrictEqual(ds[1].value, body);
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 file based on mpu location', done => {
copyPutPart(memLocation, fileLocation, null, 'localhost', () => {
assert.strictEqual(ds.length, 2);
done();
});
});
});
it('should copy part to mem based on bucket location', done => {
copyPutPart(memLocation, null, null, 'localhost', () => {
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 => {
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
// and copied objects should be in mem
assert.strictEqual(ds.length, 3);
assert.deepStrictEqual(ds[2].value, body);
done();
assert.strictEqual(ds.length, 3);
assert.deepStrictEqual(ds[2].value, body);
done();
});
});
});
it('should copy part to file based on bucket location', done => {
copyPutPart(fileLocation, null, null, 'localhost', () => {
it('should copy part to file based on bucket location', done => {
copyPutPart(fileLocation, null, null, 'localhost', () => {
// ds should be empty because both source and
// coped objects should be in file
assert.deepStrictEqual(ds, []);
done();
assert.deepStrictEqual(ds, []);
done();
});
});
});
it('should copy part to AWS based on bucket location', done => {
copyPutPart(awsLocation, null, null, 'localhost', (keys, uploadId) => {
assert.deepStrictEqual(ds, []);
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 AWS based on bucket location', done => {
copyPutPart(awsLocation, null, null, 'localhost', (keys, uploadId) => {
assert.deepStrictEqual(ds, []);
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 an object on AWS location that has bucketMatch ' +
'equals false to a mpu with a different AWS location', done => {
copyPutPart(null, awsLocation, awsLocationMismatch, 'localhost',
(keys, uploadId) => {
assert.deepStrictEqual(ds, []);
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 an object on AWS location that has bucketMatch '
+ 'equals false to a mpu with a different AWS location', done => {
copyPutPart(null, awsLocation, awsLocationMismatch, 'localhost',
(keys, uploadId) => {
assert.deepStrictEqual(ds, []);
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 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 { parseString } = require('xml2js');
const AWS = require('aws-sdk');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const { config } = require('../../lib/Config');
const { cleanup, DummyRequestLogger, makeAuthInfo }
= require('../unit/helpers');
const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../unit/helpers');
const { ds } = require('arsenal').storage.data.inMemory.datastore;
const { bucketPut } = require('../../lib/api/bucketPut');
const initiateMultipartUpload
= require('../../lib/api/initiateMultipartUpload');
const initiateMultipartUpload = require('../../lib/api/initiateMultipartUpload');
const objectPutPart = require('../../lib/api/objectPutPart');
const DummyRequest = require('../unit/DummyRequest');
const { metadata } = require('arsenal').storage.metadata.inMemory.metadata;
const mdWrapper = require('../../lib/metadata/wrapper');
const constants = require('../../constants');
const { getRealAwsConfig } =
require('../functional/aws-node-sdk/test/support/awsConfig');
const { getRealAwsConfig } = require('../functional/aws-node-sdk/test/support/awsConfig');
const memLocation = 'scality-internal-mem';
const fileLocation = 'scality-internal-file';
@ -25,7 +22,7 @@ const awsLocationMismatch = 'awsbackendmismatch';
const awsConfig = getRealAwsConfig(awsLocation);
const s3 = new AWS.S3(awsConfig);
const splitter = constants.splitter;
const { splitter } = constants;
const log = new DummyRequestLogger();
const canonicalID = 'accessKey1';
const authInfo = makeAuthInfo(canonicalID);
@ -47,13 +44,13 @@ function _getOverviewKey(objectKey, uploadId) {
}
function putPart(bucketLoc, mpuLoc, requestHost, cb,
errorDescription) {
errorDescription) {
const objectName = `objectName-${Date.now()}`;
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>' +
'<CreateBucketConfiguration ' +
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
`<LocationConstraint>${bucketLoc}</LocationConstraint>` +
'</CreateBucketConfiguration>' : '';
const post = bucketLoc ? '<?xml version="1.0" encoding="UTF-8"?>'
+ '<CreateBucketConfiguration '
+ 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
+ `<LocationConstraint>${bucketLoc}</LocationConstraint>`
+ '</CreateBucketConfiguration>' : '';
const bucketPutReq = {
bucketName,
namespace,
@ -70,10 +67,13 @@ errorDescription) {
objectKey: objectName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectName}?uploads`,
actionImplicitDenies: false,
};
if (mpuLoc) {
initiateReq.headers = { 'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}` };
initiateReq.headers = {
'host': `${bucketName}.s3.amazonaws.com`,
'x-amz-meta-scal-location-constraint': `${mpuLoc}`,
};
}
if (requestHost) {
initiateReq.parsedHost = requestHost;
@ -123,9 +123,9 @@ errorDescription) {
const partReq = new DummyRequest(partReqParams, body1);
return objectPutPart(authInfo, partReq, undefined, log, err => {
assert.strictEqual(err, null);
if (bucketLoc !== awsLocation && mpuLoc !== awsLocation &&
bucketLoc !== awsLocationMismatch &&
mpuLoc !== awsLocationMismatch) {
if (bucketLoc !== awsLocation && mpuLoc !== awsLocation
&& bucketLoc !== awsLocationMismatch
&& mpuLoc !== awsLocationMismatch) {
const keysInMPUkeyMap = [];
metadata.keyMaps.get(mpuBucket).forEach((val, key) => {
keysInMPUkeyMap.push(key);
@ -138,7 +138,7 @@ errorDescription) {
});
const partKey = sortedKeyMap[1];
const partETag = metadata.keyMaps.get(mpuBucket)
.get(partKey)['content-md5'];
.get(partKey)['content-md5'];
assert.strictEqual(keysInMPUkeyMap.length, 2);
assert.strictEqual(partETag, calculatedHash1);
}
@ -148,8 +148,8 @@ errorDescription) {
}
function listAndAbort(uploadId, calculatedHash2, objectName, done) {
const awsBucket = config.locationConstraints[awsLocation].
details.bucketName;
const awsBucket = config.locationConstraints[awsLocation]
.details.bucketName;
const params = {
Bucket: awsBucket,
Key: objectName,
@ -162,167 +162,168 @@ function listAndAbort(uploadId, calculatedHash2, objectName, done) {
assert.strictEqual(`"${calculatedHash2}"`, data.Parts[0].ETag);
}
s3.abortMultipartUpload(params, err => {
assert.equal(err, null, `Error aborting MPU: ${err}. ` +
`You must abort MPU with upload ID ${uploadId} manually.`);
assert.equal(err, null, `Error aborting MPU: ${err}. `
+ `You must abort MPU with upload ID ${uploadId} manually.`);
done();
});
});
}
describeSkipIfE2E('objectPutPart API with multiple backends',
function testSuite() {
this.timeout(5000);
function testSuite() {
this.timeout(5000);
beforeEach(() => {
cleanup();
});
beforeEach(() => {
cleanup();
});
it('should upload a part to file based on mpu location', done => {
putPart(memLocation, fileLocation, 'localhost', () => {
it('should upload a part to file based on mpu location', done => {
putPart(memLocation, fileLocation, 'localhost', () => {
// if ds is empty, the object is not in mem, which means it
// must be in file because those are the only possibilities
// for unit tests
assert.deepStrictEqual(ds, []);
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);
assert.deepStrictEqual(ds, []);
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 mem based on mpu location', 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();
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 '
+ '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.actionImplicitDenies = false;
if (Array.isArray(msg)) {
msg.forEach(part => {
this.push(part);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ const testBucketPutRequest = {
bucketName,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: '/',
actionImplicitDenies: false,
};
let expectedBucketPolicy = {};
@ -25,6 +26,7 @@ function getPolicyRequest(policy) {
host: `${bucketName}.s3.amazonaws.com`,
},
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 =
{ StringEquals: { 's3:x-amz-acl': ['public-read'] } };
bucketPutPolicy(authInfo, getPolicyRequest(expectedBucketPolicy), log,

View File

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

View File

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

View File

@ -58,11 +58,13 @@ const baseTestRequest = {
post: '',
headers: { host: `${bucketName}.s3.amazonaws.com` },
query: {},
actionImplicitDenies: false,
};
const serviceGetRequest = {
parsedHost: 's3.amazonaws.com',
headers: { host: 's3.amazonaws.com' },
url: '/',
actionImplicitDenies: false,
};
const userBucketOwner = 'admin';
@ -103,7 +105,6 @@ function confirmDeleted(done) {
});
}
describe('deleted flag bucket handling', () => {
beforeEach(done => {
cleanup();

View File

@ -32,6 +32,7 @@ describe('listMultipartUploads API', () => {
namespace,
headers: {},
url: `/${bucketName}`,
actionImplicitDenies: false,
};
const testInitiateMPURequest1 = {
bucketName,
@ -39,6 +40,7 @@ describe('listMultipartUploads API', () => {
objectKey: objectName1,
headers: {},
url: `/${bucketName}/${objectName1}?uploads`,
actionImplicitDenies: false,
};
const testInitiateMPURequest2 = {
bucketName,
@ -46,6 +48,7 @@ describe('listMultipartUploads API', () => {
objectKey: objectName2,
headers: {},
url: `/${bucketName}/${objectName2}?uploads`,
actionImplicitDenies: false,
};
const testInitiateMPURequest3 = {
bucketName,
@ -53,6 +56,7 @@ describe('listMultipartUploads API', () => {
objectKey: objectName3,
headers: {},
url: `/${bucketName}/${objectName3}?uploads`,
actionImplicitDenies: false,
};
it('should return the name of the common prefix ' +
@ -65,6 +69,7 @@ describe('listMultipartUploads API', () => {
headers: { host: '/' },
url: `/${bucketName}?uploads&delimiter=/&prefix=sub`,
query: { delimiter, prefix },
actionImplicitDenies: false,
};
async.waterfall([
@ -94,6 +99,7 @@ describe('listMultipartUploads API', () => {
headers: { host: '/' },
url: `/${bucketName}?uploads`,
query: {},
actionImplicitDenies: false,
};
@ -127,6 +133,7 @@ describe('listMultipartUploads API', () => {
headers: { host: '/' },
url: `/${bucketName}?uploads`,
query: { 'max-uploads': '1' },
actionImplicitDenies: false,
};
async.waterfall([
@ -163,6 +170,7 @@ describe('listMultipartUploads API', () => {
headers: { host: '/' },
url: `/${bucketName}?uploads`,
query: { 'encoding-type': 'url' },
actionImplicitDenies: false,
};
async.waterfall([
@ -195,6 +203,7 @@ describe('listMultipartUploads API', () => {
headers: { host: '/' },
url: `/${bucketName}?uploads`,
query: { 'key-marker': objectName1 },
actionImplicitDenies: false,
};
async.waterfall([

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