Compare commits

...

1 Commits

Author SHA1 Message Date
alexandre-merle b549afe4b1 working draft [ci skip] 2016-07-24 08:34:38 -07:00
7 changed files with 239 additions and 123 deletions

View File

@ -6,9 +6,15 @@ function getCanonicalizedAmzHeaders(headers) {
Need to include 'x-amz-date' here even though AWS docs Need to include 'x-amz-date' here even though AWS docs
ambiguous on this. ambiguous on this.
*/ */
const amzHeaders = Object.keys(headers) const amzHeaders = [];
.filter(val => val.substr(0, 6) === 'x-amz-') const keys = Object.keys(headers);
.map(val => [val.trim(), headers[val].trim()]); const len = keys.length;
for (let i = 0; i < len; ++i) {
const key = keys[i];
if (key.startsWith('x-amz-')) {
amzHeaders.push([key.trim(), headers[key].trim()]);
}
}
/* /*
AWS docs state that duplicate headers should be combined AWS docs state that duplicate headers should be combined
in the same header with values concatenated with in the same header with values concatenated with
@ -27,18 +33,16 @@ function getCanonicalizedAmzHeaders(headers) {
return ''; return '';
} }
// Sort the amz headers by key (first item in tuple) // Sort the amz headers by key (first item in tuple)
amzHeaders.sort((a, b) => { amzHeaders.sort((a, b) => a[0] > b[0] ? 1 : -1);
if (a[0] > b[0]) {
return 1;
}
return -1;
});
// Build headerString // Build headerString
return amzHeaders.reduce((headerStr, current) => let headerStr = '';
`${headerStr}${current[0]}:${current[1]}\n`, const finLen = amzHeaders.length;
''); for (let i = 0; i < finLen; ++i) {
const current = amzHeaders[i];
headerStr += current[0] + ':' + current[1] + '\n';
}
return headerStr;
} }
module.exports = getCanonicalizedAmzHeaders; module.exports = getCanonicalizedAmzHeaders;

View File

@ -2,6 +2,76 @@
const url = require('url'); const url = require('url');
/*
If request includes a specified subresource,
add to the resourceString: (a) a '?', (b) the subresource,
and (c) its value (if any).
Separate multiple subresources with '&'.
Subresources must be in alphabetical order.
*/
// Specified subresources:
const subresources = [
'acl',
'lifecycle',
'location',
'logging',
'notification',
'partNumber',
'policy',
'requestPayment',
'torrent',
'uploadId',
'uploads',
'versionId',
'versioning',
'versions',
'website',
];
// Specified subresources:
const subresourcesMap = {
'acl': true,
'lifecycle': true,
'location': true,
'logging': true,
'notification': true,
'partNumber': true,
'policy': true,
'requestPayment': true,
'torrent': true,
'uploadId': true,
'uploads': true,
'versionId': true,
'versioning': true,
'versions': true,
'website': true,
};
/*
If the request includes parameters in the query string,
that override the headers, include
them in the resourceString
along with their values.
AWS is ambiguous about format. Used alphabetical order.
*/
const overridingParams = [
'response-cache-control',
'response-content-disposition',
'response-content-encoding',
'response-content-language',
'response-content-type',
'response-expires',
];
function makeValue(query, key) {
const val = query[key];
if (val !== '') {
return key + '=' + val;
}
return key;
}
function getCanonicalizedResource(request) { function getCanonicalizedResource(request) {
/* /*
This variable is used to determine whether to insert This variable is used to determine whether to insert
@ -13,74 +83,69 @@ function getCanonicalizedResource(request) {
let resourceString = request.gotBucketNameFromHost ? let resourceString = request.gotBucketNameFromHost ?
`/${request.bucketName}` : ''; `/${request.bucketName}` : '';
// Add the path to the resourceString // Add the path to the resourceString
resourceString += url.parse(request.url).pathname; const url = request.url;
let index = url.indexOf('?');
let pathname = url;
if (index === -1) {
index = url.indexOf('#');
}
if (index !== -1) {
pathname = url.substring(0, index);
}
/* resourceString += pathname;
If request includes a specified subresource,
add to the resourceString: (a) a '?', (b) the subresource,
and (c) its value (if any).
Separate multiple subresources with '&'.
Subresources must be in alphabetical order.
*/
// Specified subresources:
const subresources = [
'acl',
'lifecycle',
'location',
'logging',
'notification',
'partNumber',
'policy',
'requestPayment',
'torrent',
'uploadId',
'uploads',
'versionId',
'versioning',
'versions',
'website',
];
/*
If the request includes parameters in the query string,
that override the headers, include
them in the resourceString
along with their values.
AWS is ambiguous about format. Used alphabetical order.
*/
const overridingParams = [
'response-cache-control',
'response-content-disposition',
'response-content-encoding',
'response-content-language',
'response-content-type',
'response-expires',
];
// Check which specified subresources are present in query string, // Check which specified subresources are present in query string,
// build array with them // build array with them
const query = request.query; const query = request.query;
const presentSubresources = Object.keys(query).filter(val => const presentSubresources = [];
subresources.indexOf(val) !== -1); const queryKeys = Object.keys(query);
// Sort the array and add the subresources and their value (if any) const queryKeysLen = queryKeys.length;
// to the resourceString for (let i = 0; i < queryKeysLen; ++i) {
presentSubresources.sort(); const key = queryKeys[i];
resourceString = presentSubresources.reduce((prev, current) => { if (subresourcesMap[key]) {
const ch = (query[current] !== '' ? '=' : ''); // Sort the array and add the subresources and their value (if any)
const ret = `${prev}${queryChar}${current}${ch}${query[current]}`; // to the resourceString
queryChar = '&'; const subResourcesLen = presentSubresources.length;
return ret; if (subResourcesLen === 0) {
}, resourceString); presentSubresources.push(makeValue(query, key));
// Add the overriding parameters to our resourceString continue;
resourceString = overridingParams.reduce((prev, current) => { }
if (query[current]) { for (let j = 0; j < subResourcesLen; ++j) {
const ret = `${prev}${queryChar}${current}=${query[current]}`; if (key < presentSubresources[j]) {
queryChar = '&'; presentSubresources.splice(j, 0, makeValue(query, key));
return ret; break;
}
if (j === subResourcesLen - 1) {
presentSubresources.push(makeValue(query, key));
}
}
} }
return prev; }
}, resourceString); // const presentSubresources = Object.keys(query).filter(val =>
// subresources.indexOf(val) !== -1);
// presentSubresources.sort();
const subResourcesLen = presentSubresources.length;
if (subResourcesLen > 0) {
queryChar = '&';
resourceString += '?' + presentSubresources.join('&');
}
// resourceString = presentSubresources.reduce((prev, current) => {
// const ch = (query[current] !== '' ? '=' : '');
// const ret = `${prev}${queryChar}${current}${ch}${query[current]}`;
// queryChar = '&';
// return ret;
// }, resourceString);
// Add the overriding parameters to our resourceString
const overridingParamsLen = overridingParams.length;
for (let i = 0; i < overridingParamsLen; ++i) {
const current = overridingParams[i];
const value = query[current];
if (value) {
resourceString += `${queryChar}${current}=${value}`;
queryChar = '&';
}
}
/* /*
Per AWS, the delete query string parameter must be included when Per AWS, the delete query string parameter must be included when

View File

@ -20,20 +20,19 @@ const evaluators = {};
*/ */
function isResourceApplicable(requestContext, statementResource, log) { function isResourceApplicable(requestContext, statementResource, log) {
const resource = requestContext.getResource(); const resource = requestContext.getResource();
if (!Array.isArray(statementResource)) { const arrStatementRessource = Array.isArray(statementResource) ?
// eslint-disable-next-line no-param-reassign statementResource : [statementResource];
statementResource = [statementResource]; const arrLen = arrStatementRessource.length;
}
// ARN format: // ARN format:
// arn:partition:service:region:namespace:relative-id // arn:partition:service:region:namespace:relative-id
const requestResourceArr = resource.split(':'); const requestResourceArr = resource.split(':');
// Pull just the relative id because there is no restriction that it // Pull just the relative id because there is no restriction that it
// does not contain ":" // does not contain ":"
const requestRelativeId = requestResourceArr.slice(5).join(':'); const requestRelativeId = requestResourceArr.slice(5).join(':');
for (let i = 0; i < statementResource.length; i ++) { for (let i = 0; i < arrLen; i++) {
// Handle variables (must handle BEFORE wildcards) // Handle variables (must handle BEFORE wildcards)
const policyResource = const policyResource =
substituteVariables(statementResource[i], requestContext); substituteVariables(arrStatementRessource[i], requestContext);
// Handle wildcards // Handle wildcards
const arnSegmentsMatch = const arnSegmentsMatch =
checkArnMatch(policyResource, requestRelativeId, checkArnMatch(policyResource, requestRelativeId,
@ -43,7 +42,6 @@ function isResourceApplicable(requestContext, statementResource, log) {
{ requestResource: resource, policyResource }); { requestResource: resource, policyResource });
return true; return true;
} }
continue;
} }
log.trace('no policy resource is applicable to request', log.trace('no policy resource is applicable to request',
{ requestResource: resource }); { requestResource: resource });
@ -60,19 +58,17 @@ function isResourceApplicable(requestContext, statementResource, log) {
* @return {boolean} true if applicable, false if not * @return {boolean} true if applicable, false if not
*/ */
function isActionApplicable(requestAction, statementAction, log) { function isActionApplicable(requestAction, statementAction, log) {
if (!Array.isArray(statementAction)) { const arrStatementAction = Array.isArray(statementAction) ?
// eslint-disable-next-line no-param-reassign statementAction : [statementAction];
statementAction = [statementAction]; const length = arrStatementAction.length;
}
const length = statementAction.length;
for (let i = 0; i < length; i ++) { for (let i = 0; i < length; i ++) {
// No variables in actions so no need to handle // No variables in actions so no need to handle
const regExStrOfStatementAction = const regExStrOfStatementAction =
handleWildcards(statementAction[i]); handleWildcards(arrStatementAction[i]);
const actualRegEx = new RegExp(regExStrOfStatementAction); const actualRegEx = new RegExp(regExStrOfStatementAction);
if (actualRegEx.test(requestAction)) { if (actualRegEx.test(requestAction)) {
log.trace('policy action is applicable to request action', { log.trace('policy action is applicable to request action', {
requestAction, policyAction: statementAction[i], requestAction, policyAction: arrStatementAction[i],
}); });
return true; return true;
} }

View File

@ -32,6 +32,9 @@ function checkArnMatch(policyArn, requestRelativeId, requestArnArr,
// Check the other parts of the ARN to make sure they match. If not, // Check the other parts of the ARN to make sure they match. If not,
// return false. // return false.
for (let j = 0; j < 5; j ++) { for (let j = 0; j < 5; j ++) {
// if (regExofArn === '^.*?$') {
// continue;
// }
const segmentRegEx = new RegExp(regExofArn[j]); const segmentRegEx = new RegExp(regExofArn[j]);
const requestSegment = caseSensitive ? requestArnArr[j] : const requestSegment = caseSensitive ? requestArnArr[j] :
requestArnArr[j].toLowerCase(); requestArnArr[j].toLowerCase();

View File

@ -9,38 +9,86 @@ const wildcards = {};
// TODO: Note that there are special rules for * in Principal. // TODO: Note that there are special rules for * in Principal.
// Handle when working with bucket policies. // Handle when working with bucket policies.
const mustConvertForRegex = {
/** '\\': '\\\\',
* Converts string into a string that has all regEx characters escaped except '*': '.*?',
* for those needed to check for AWS wildcards. Converted string can then '?': '.',
* be used for a regEx comparison. '$': '\\$',
* @param {string} string - any input string '^': '\\^',
* @return {string} converted string '+': '\\+',
*/ '.': '\\.',
wildcards.handleWildcards = string => { '(': '\\(',
// Replace all '*' with '.*' (allow any combo of letters) ')': '\\)',
// and all '?' with '.{1}' (allow for any one character) '|': '\\|',
// If *, ? or $ are enclosed in ${}, keep literal *, ?, or $ '[': '\\[',
function characterMap(char) { ']': '\\]',
const map = { '{': '\\{',
'\\*': '.*?', '}': '\\}',
'\\?': '.{1}',
'\\$\\{\\*\\}': '\\*',
'\\$\\{\\?\\}': '\\?',
'\\$\\{\\$\\}': '\\$',
};
return map[char];
}
// Escape all regExp special characters
let regExStr = string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
// Replace the AWS special characters with regExp equivalents
regExStr = regExStr.replace(
// eslint-disable-next-line max-len
/(\\\*)|(\\\?)|(\\\$\\\{\\\*\\\})|(\\\$\\\{\\\?\\\})|(\\\$\\\{\\\$\\\})/g,
characterMap);
return `^${regExStr}$`;
}; };
wildcards.handleWildcards = str => {
const end = str.length;
let res = '';
let lastIndex = 0;
let i = 0;
for (; i < end; ++i) {
const c = str[i];
const c2 = str[i + 2];
if (c === '$' && str[i + 1] === '{' && str[i + 3] === '}'
&& (c2 === '*' || c2 === '?' || c2 === '$')) {
res += (i > lastIndex) ?
str.substring(lastIndex, i) + '\\' + c2 : // eslint-disable-line
'\\' + c2; // eslint-disable-line
i += 3;
lastIndex = i + 1;
continue;
}
const toSecure = mustConvertForRegex[c];
if (toSecure) {
res += (i > lastIndex) ? str.substring(lastIndex, i) + toSecure :
toSecure;
lastIndex = i + 1;
continue;
}
}
if (i > lastIndex) {
res += str.substring(lastIndex, i);
}
// eslint-disable-next-line prefer-template
return '^' + res + '$';
};
// /**
// * Converts string into a string that has all regEx characters escaped except
// * for those needed to check for AWS wildcards. Converted string can then
// * be used for a regEx comparison.
// * @param {string} string - any input string
// * @return {string} converted string
// */
// wildcards.handleWildcards = string => {
// // Replace all '*' with '.*' (allow any combo of letters)
// // and all '?' with '.{1}' (allow for any one character)
// // If *, ? or $ are enclosed in ${}, keep literal *, ?, or $
// function characterMap(char) {
// const map = {
// '\\*': '.*?',
// '\\?': '.{1}',
// '\\$\\{\\*\\}': '\\*',
// '\\$\\{\\?\\}': '\\?',
// '\\$\\{\\$\\}': '\\$',
// };
// return map[char];
// }
// // Escape all regExp special characters
// let regExStr = string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
// // Replace the AWS special characters with regExp equivalents
// regExStr = regExStr.replace(
// // eslint-disable-next-line max-len
// /(\\\*)|(\\\?)|(\\\$\\\{\\\*\\\})|(\\\$\\\{\\\?\\\})|(\\\$\\\{\\\$\\\})/g,
// characterMap);
// return `^${regExStr}$`;
// };
/** /**
* Converts each portion of an ARN into a converted regEx string * Converts each portion of an ARN into a converted regEx string
* to compare against each portion of the ARN from the request * to compare against each portion of the ARN from the request

View File

@ -1,6 +1,6 @@
{ {
"name": "arsenal", "name": "arsenal",
"version": "1.1.0", "version": "1.1.0-perf",
"description": "Common utilities for the S3 project components", "description": "Common utilities for the S3 project components",
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@ -26,7 +26,7 @@
"lolex": "^1.4.0", "lolex": "^1.4.0",
"mocha": "^2.3.3", "mocha": "^2.3.3",
"temp": "^0.8.3", "temp": "^0.8.3",
"werelogs": "scality/werelogs" "werelogs": "scality/werelogs#ft/performances"
}, },
"scripts": { "scripts": {
"lint": "eslint $(git ls-files '*.js')", "lint": "eslint $(git ls-files '*.js')",

View File

@ -977,7 +977,7 @@ describe('handleWildcards', () => {
'unlimited of any character and will match ? as any single ' + 'unlimited of any character and will match ? as any single ' +
'character', () => { 'character', () => {
const result = handleWildcards('lsdkfj?lk*'); const result = handleWildcards('lsdkfj?lk*');
assert.deepStrictEqual(result, '^lsdkfj.{1}lk.*?$'); assert.deepStrictEqual(result, '^lsdkfj.lk.*?$');
}); });
it('should convert a string to a regEx string that matches ${*} as ' + it('should convert a string to a regEx string that matches ${*} as ' +
@ -990,7 +990,7 @@ describe('handleWildcards', () => {
it('should escape other regular expression special characters', () => { it('should escape other regular expression special characters', () => {
const result = handleWildcards('*^.+?()|[\]{}'); const result = handleWildcards('*^.+?()|[\]{}');
assert.deepStrictEqual(result, assert.deepStrictEqual(result,
'^.*?\\^\\.\\+.{1}\\(\\)\\|\\[\\\]\\{\\}$'); '^.*?\\^\\.\\+.\\(\\)\\|\\[\\\]\\{\\}$');
}); });
}); });