Compare commits

..

No commits in common. "23f7e5af6ae467bf80ebc52825f2781e774c746f" and "c16d4e6954eeb55a87849218a6ce67c795faf16c" have entirely different histories.

12 changed files with 597 additions and 685 deletions

View File

@ -271,4 +271,4 @@ class Delimiter extends Extension {
} }
} }
module.exports = { Delimiter }; module.exports = { Delimiter, getCommonPrefix };

View File

@ -33,6 +33,8 @@ class DelimiterMaster extends Delimiter {
this.prvKey = undefined; this.prvKey = undefined;
this.prvPHDKey = undefined; this.prvPHDKey = undefined;
this.inReplayPrefix = false; this.inReplayPrefix = false;
this.prefixKeySeen = false;
this.prefixEndsWithDelim = this.prefix && this.prefix.endsWith(this.delimiter);
Object.assign(this, { Object.assign(this, {
[BucketVersioningKeyFormat.v0]: { [BucketVersioningKeyFormat.v0]: {
@ -62,6 +64,10 @@ class DelimiterMaster extends Delimiter {
let key = obj.key; let key = obj.key;
const value = obj.value; const value = obj.value;
if (key === this.prefix) {
this.prefixKeySeen = true;
}
if (key.startsWith(DbPrefixes.Replay)) { if (key.startsWith(DbPrefixes.Replay)) {
this.inReplayPrefix = true; this.inReplayPrefix = true;
return FILTER_SKIP; return FILTER_SKIP;
@ -95,9 +101,8 @@ class DelimiterMaster extends Delimiter {
* NextMarker to the common prefix instead of the whole key * NextMarker to the common prefix instead of the whole key
* value. (TODO: remove this test once ZENKO-1048 is fixed) * value. (TODO: remove this test once ZENKO-1048 is fixed)
* */ * */
if (key === this.prvKey || key === this[this.nextContinueMarker] || if (key === this.prvKey || key === this[this.nextContinueMarker]
(this.delimiter && || (this.delimiter && key.startsWith(this[this.nextContinueMarker]))) {
key.startsWith(this[this.nextContinueMarker]))) {
/* master version already filtered */ /* master version already filtered */
return FILTER_SKIP; return FILTER_SKIP;
} }
@ -127,6 +132,14 @@ class DelimiterMaster extends Delimiter {
return FILTER_ACCEPT; return FILTER_ACCEPT;
} }
this.prvKey = key; this.prvKey = key;
if (this.prefixEndsWithDelim) {
/* When the prefix ends with a delimiter, update nextContinueMarker
* to be able to skip ranges of the form prefix/subprefix/ as an optimization.
* The marker may also end up being prefix/, in which case .skipping will determine
* if a skip over the full range is allowed or a smaller skipping range of prefix/{VID_SEP}
* must be used. */
this[this.nextContinueMarker] = key.slice(0, key.lastIndexOf(this.delimiter) + 1);
}
return FILTER_SKIP; return FILTER_SKIP;
} }
@ -162,6 +175,27 @@ class DelimiterMaster extends Delimiter {
return super.filter(obj); return super.filter(obj);
} }
/**
* Determine if a nextContinueMarker ending with a delimiter
* can be skipped.
* @returns {bool}
*/
allowDelimiterRangeSkip() {
if (!this.prefixKeySeen) {
// A prefix key is a master key equal to the prefix. If it has
// not been encountered, can skip.
return true;
}
const marker = this[this.nextContinueMarker];
// prefix = prefix, key = prefix/. Can skip since key will be part of commonPrefixes.
if (marker.length > this.prefix.length && marker.startsWith(this.prefix)) {
return true;
}
const lastIdx = marker.lastIndexOf(this.delimiter); // prefix/foo is a masterKey following a prefix key.
return marker.slice(0, lastIdx + 1) !== this.prefix; // cannot skip the full range prefix/ range.
}
skippingBase() { skippingBase() {
if (this[this.nextContinueMarker]) { if (this[this.nextContinueMarker]) {
// next marker or next continuation token: // next marker or next continuation token:
@ -169,7 +203,7 @@ class DelimiterMaster extends Delimiter {
// - foo : skipping foo. // - foo : skipping foo.
const index = this[this.nextContinueMarker]. const index = this[this.nextContinueMarker].
lastIndexOf(this.delimiter); lastIndexOf(this.delimiter);
if (index === this[this.nextContinueMarker].length - 1) { if (index === this[this.nextContinueMarker].length - 1 && this.allowDelimiterRangeSkip()) {
return this[this.nextContinueMarker]; return this[this.nextContinueMarker];
} }
return this[this.nextContinueMarker] + VID_SEP; return this[this.nextContinueMarker] + VID_SEP;

View File

@ -166,6 +166,7 @@ export default class RequestContext {
_policyArn: string; _policyArn: string;
_action?: string; _action?: string;
_needQuota: boolean; _needQuota: boolean;
_postXml?: string;
_requestObjTags: string | null; _requestObjTags: string | null;
_existingObjTag: string | null; _existingObjTag: string | null;
_needTagEval: boolean; _needTagEval: boolean;
@ -189,9 +190,7 @@ export default class RequestContext {
securityToken: string, securityToken: string,
policyArn: string, policyArn: string,
action?: string, action?: string,
requestObjTags?: string, postXml?: string,
existingObjTag?: string,
needTagEval?: false,
) { ) {
this._headers = headers; this._headers = headers;
this._query = query; this._query = query;
@ -221,9 +220,10 @@ export default class RequestContext {
this._policyArn = policyArn; this._policyArn = policyArn;
this._action = action; this._action = action;
this._needQuota = _actionNeedQuotaCheck[apiMethod] === true; this._needQuota = _actionNeedQuotaCheck[apiMethod] === true;
this._requestObjTags = requestObjTags || null; this._postXml = postXml;
this._existingObjTag = existingObjTag || null; this._requestObjTags = null;
this._needTagEval = needTagEval || false; this._existingObjTag = null;
this._needTagEval = false;
return this; return this;
} }
@ -236,7 +236,7 @@ export default class RequestContext {
apiMethod: this._apiMethod, apiMethod: this._apiMethod,
headers: this._headers, headers: this._headers,
query: this._query, query: this._query,
requesterInfo: this._requesterInfo, requersterInfo: this._requesterInfo,
requesterIp: this._requesterIp, requesterIp: this._requesterIp,
sslEnabled: this._sslEnabled, sslEnabled: this._sslEnabled,
awsService: this._awsService, awsService: this._awsService,
@ -252,6 +252,7 @@ export default class RequestContext {
securityToken: this._securityToken, securityToken: this._securityToken,
policyArn: this._policyArn, policyArn: this._policyArn,
action: this._action, action: this._action,
postXml: this._postXml,
requestObjTags: this._requestObjTags, requestObjTags: this._requestObjTags,
existingObjTag: this._existingObjTag, existingObjTag: this._existingObjTag,
needTagEval: this._needTagEval, needTagEval: this._needTagEval,
@ -275,27 +276,12 @@ export default class RequestContext {
if (resource) { if (resource) {
obj.specificResource = resource; obj.specificResource = resource;
} }
return new RequestContext( return new RequestContext(obj.headers, obj.query, obj.generalResource,
obj.headers, obj.specificResource, obj.requesterIp, obj.sslEnabled,
obj.query, obj.apiMethod, obj.awsService, obj.locationConstraint,
obj.generalResource, obj.requesterInfo, obj.signatureVersion,
obj.specificResource, obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
obj.requesterIp, obj.action, obj.postXml);
obj.sslEnabled,
obj.apiMethod,
obj.awsService,
obj.locationConstraint,
obj.requesterInfo,
obj.signatureVersion,
obj.authType,
obj.signatureAge,
obj.securityToken,
obj.policyArn,
obj.action,
obj.requestObjTags,
obj.existingObjTag,
obj.needTagEval,
);
} }
/** /**
@ -639,6 +625,26 @@ export default class RequestContext {
return this._needQuota; return this._needQuota;
} }
/**
* Set request post
*
* @param postXml - request post
* @return itself
*/
setPostXml(postXml: string) {
this._postXml = postXml;
return this;
}
/**
* Get request post
*
* @return request post
*/
getPostXml() {
return this._postXml;
}
/** /**
* Set request object tags * Set request object tags
* *

View File

@ -13,11 +13,7 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
const operatorsWithNegation = ['StringNotEquals', const operatorsWithNegation = ['StringNotEquals',
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals', 'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
'ArnNotLike', 'NumericNotEquals']; 'ArnNotLike', 'NumericNotEquals'];
const tagConditions = new Set([ const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey', 's3:RequestObjectTagKeys']);
's3:ExistingObjectTag',
's3:RequestObjectTagKey',
's3:RequestObjectTagKeys',
]);
/** /**
@ -28,11 +24,11 @@ const tagConditions = new Set([
* @param log - logger * @param log - logger
* @return true if applicable, false if not * @return true if applicable, false if not
*/ */
export function isResourceApplicable( export const isResourceApplicable = (
requestContext: RequestContext, requestContext: RequestContext,
statementResource: string | string[], statementResource: string | string[],
log: Logger, log: Logger,
): boolean { ): boolean => {
const resource = requestContext.getResource(); const resource = requestContext.getResource();
if (!Array.isArray(statementResource)) { if (!Array.isArray(statementResource)) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -63,7 +59,7 @@ export function isResourceApplicable(
{ requestResource: resource }); { requestResource: resource });
// If no match found, no resource is applicable // If no match found, no resource is applicable
return false; return false;
} };
/** /**
* Check whether action in policy statement applies to request * Check whether action in policy statement applies to request
@ -73,11 +69,11 @@ export function isResourceApplicable(
* @param log - logger * @param log - logger
* @return true if applicable, false if not * @return true if applicable, false if not
*/ */
export function isActionApplicable( export const isActionApplicable = (
requestAction: string, requestAction: string,
statementAction: string | string[], statementAction: string | string[],
log: Logger, log: Logger,
): boolean { ): boolean => {
if (!Array.isArray(statementAction)) { if (!Array.isArray(statementAction)) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
statementAction = [statementAction]; statementAction = [statementAction];
@ -99,33 +95,32 @@ export function isActionApplicable(
{ requestAction }); { requestAction });
// If no match found, return false // If no match found, return false
return false; return false;
} };
/** /**
* Check whether request meets policy conditions * Check whether request meets policy conditions
* @param {RequestContext} requestContext - info about request * @param requestContext - info about request
* @param {object} statementCondition - Condition statement from policy * @param statementCondition - Condition statement from policy
* @param {Logger} log - logger * @param log - logger
* @return {boolean|null} a condition evaluation result, one of: * @return contains whether conditions are allowed and whether they
* - true: condition is met * contain any tag condition keys
* - false: condition is not met
* - null: condition evaluation requires additional info to be
* provided (namely, for tag conditions, request tags and/or object
* tags have to be provided to evaluate the condition)
*/ */
export function meetConditions( export const meetConditions = (
requestContext: RequestContext, requestContext: RequestContext,
statementCondition: any, statementCondition: any,
log: Logger, log: Logger,
): boolean | null { ) => {
let hasTagConditions = false;
// The Condition portion of a policy is an object with different // The Condition portion of a policy is an object with different
// operators as keys // operators as keys
for (const operator of Object.keys(statementCondition)) { const conditionEval = {};
const operators = Object.keys(statementCondition);
const length = operators.length;
for (let i = 0; i < length; i++) {
const operator = operators[i];
const hasPrefix = operator.includes(':'); const hasPrefix = operator.includes(':');
const hasIfExistsCondition = operator.endsWith('IfExists'); const hasIfExistsCondition = operator.endsWith('IfExists');
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or // If has "IfExists" added to operator name, or operator has "ForAnyValue" or
// "ForAllValues" prefix, find operator name without "IfExists" or prefix // "For All Values" prefix, find operator name without "IfExists" or prefix
let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) : let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
operator; operator;
let prefix: string | undefined; let prefix: string | undefined;
@ -140,6 +135,10 @@ export function meetConditions(
// Note: this should be the actual operator name, not the bareOperator // Note: this should be the actual operator name, not the bareOperator
const conditionsWithSameOperator = statementCondition[operator]; const conditionsWithSameOperator = statementCondition[operator];
const conditionKeys = Object.keys(conditionsWithSameOperator); const conditionKeys = Object.keys(conditionsWithSameOperator);
if (conditionKeys.some(key => tagConditions.has(key)) && !requestContext.getNeedTagEval()) {
// @ts-expect-error
conditionEval.tagConditions = true;
}
const conditionKeysLength = conditionKeys.length; const conditionKeysLength = conditionKeys.length;
for (let j = 0; j < conditionKeysLength; j++) { for (let j = 0; j < conditionKeysLength; j++) {
const key = conditionKeys[j]; const key = conditionKeys[j];
@ -156,10 +155,6 @@ export function meetConditions(
// tag key is included in condition key and needs to be // tag key is included in condition key and needs to be
// moved to value for evaluation, otherwise key/value are unchanged // moved to value for evaluation, otherwise key/value are unchanged
const [transformedKey, transformedValue] = transformTagKeyValue(key, value); const [transformedKey, transformedValue] = transformTagKeyValue(key, value);
if (tagConditions.has(transformedKey) && !requestContext.getNeedTagEval()) {
hasTagConditions = true;
continue;
}
// Pull key using requestContext // Pull key using requestContext
// TODO: If applicable to S3, handle policy set operations // TODO: If applicable to S3, handle policy set operations
// where a keyBasedOnRequestContext returns multiple values and // where a keyBasedOnRequestContext returns multiple values and
@ -185,10 +180,11 @@ export function meetConditions(
log.trace('condition not satisfied due to ' + log.trace('condition not satisfied due to ' +
'missing info', { operator, 'missing info', { operator,
conditionKey: transformedKey, policyValue: transformedValue }); conditionKey: transformedKey, policyValue: transformedValue });
return false; return { allow: false };
} }
// If condition operator prefix is included, the key should be an array // If condition operator prefix is included, the key should be an array
if (prefix && !Array.isArray(keyBasedOnRequestContext)) { if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
// @ts-expect-error
keyBasedOnRequestContext = [keyBasedOnRequestContext]; keyBasedOnRequestContext = [keyBasedOnRequestContext];
} }
// Transalate operator into function using bareOperator // Transalate operator into function using bareOperator
@ -200,16 +196,14 @@ export function meetConditions(
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) { if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
log.trace('did not satisfy condition', { operator: bareOperator, log.trace('did not satisfy condition', { operator: bareOperator,
keyBasedOnRequestContext, policyValue: transformedValue }); keyBasedOnRequestContext, policyValue: transformedValue });
return false; return { allow: false };
} }
} }
} }
// one or more conditions required tag info to be evaluated // @ts-expect-error
if (hasTagConditions) { conditionEval.allow = true;
return null; return conditionEval;
} };
return true;
}
/** /**
* Evaluate whether a request is permitted under a policy. * Evaluate whether a request is permitted under a policy.
@ -222,15 +216,13 @@ export function meetConditions(
* @return Allow if permitted, Deny if not permitted or Neutral * @return Allow if permitted, Deny if not permitted or Neutral
* if not applicable * if not applicable
*/ */
export function evaluatePolicy( export const evaluatePolicy = (
requestContext: RequestContext, requestContext: RequestContext,
policy: any, policy: any,
log: Logger, log: Logger,
): string { ): string => {
// TODO: For bucket policies need to add Principal evaluation // TODO: For bucket policies need to add Principal evaluation
let allow = false; let verdict = 'Neutral';
let allowWithTagCondition = false;
let denyWithTagCondition = false;
if (!Array.isArray(policy.Statement)) { if (!Array.isArray(policy.Statement)) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -267,18 +259,10 @@ export function evaluatePolicy(
} }
const conditionEval = currentStatement.Condition ? const conditionEval = currentStatement.Condition ?
meetConditions(requestContext, currentStatement.Condition, log) : meetConditions(requestContext, currentStatement.Condition, log) :
true; null;
// If do not meet conditions move on to next statement // If do not meet conditions move on to next statement
if (conditionEval === false) { // @ts-expect-error
continue; if (conditionEval && !conditionEval.allow) {
}
// If condition needs tag info to be evaluated, mark and move on to next statement
if (conditionEval === null) {
if (currentStatement.Effect === 'Deny') {
denyWithTagCondition = true;
} else {
allowWithTagCondition = true;
}
continue; continue;
} }
if (currentStatement.Effect === 'Deny') { if (currentStatement.Effect === 'Deny') {
@ -287,27 +271,17 @@ export function evaluatePolicy(
return 'Deny'; return 'Deny';
} }
log.trace('Allow statement applies'); log.trace('Allow statement applies');
// statement is applicable, conditions are met and Effect is // If statement is applicable, conditions are met and Effect is
// to Allow // to Allow, set verdict to Allow
allow = true;
}
let verdict;
if (denyWithTagCondition) {
// priority is on checking tags to potentially deny
verdict = 'DenyWithTagCondition';
} else if (allow) {
// at least one statement is an allow
verdict = 'Allow'; verdict = 'Allow';
} else if (allowWithTagCondition) { // @ts-expect-error
// all allow statements need tag checks if (conditionEval && conditionEval.tagConditions) {
verdict = 'AllowWithTagCondition'; verdict = 'NeedTagConditionEval';
} else { }
// no statement matched to allow or deny
verdict = 'Neutral';
} }
log.trace('result of evaluating single policy', { verdict }); log.trace('result of evaluating single policy', { verdict });
return verdict; return verdict;
} };
/** /**
* Evaluate whether a request is permitted under a policy. * Evaluate whether a request is permitted under a policy.
@ -320,43 +294,24 @@ export function evaluatePolicy(
* @return Allow if permitted, Deny if not permitted. * @return Allow if permitted, Deny if not permitted.
* Default is to Deny. Deny overrides an Allow * Default is to Deny. Deny overrides an Allow
*/ */
export function evaluateAllPolicies( export const evaluateAllPolicies = (
requestContext: RequestContext, requestContext: RequestContext,
allPolicies: any[], allPolicies: any[],
log: Logger, log: Logger,
): string { ): string => {
log.trace('evaluating all policies'); log.trace('evaluating all policies');
let allow = false; let verdict = 'Deny';
let allowWithTagCondition = false;
let denyWithTagCondition = false;
for (let i = 0; i < allPolicies.length; i++) { for (let i = 0; i < allPolicies.length; i++) {
const singlePolicyVerdict = evaluatePolicy(requestContext, allPolicies[i], log); const singlePolicyVerdict =
evaluatePolicy(requestContext, allPolicies[i], log);
// If there is any Deny, just return Deny // If there is any Deny, just return Deny
if (singlePolicyVerdict === 'Deny') { if (singlePolicyVerdict === 'Deny') {
return 'Deny'; return 'Deny';
} }
if (singlePolicyVerdict === 'Allow') { if (singlePolicyVerdict === 'Allow') {
allow = true;
} else if (singlePolicyVerdict === 'AllowWithTagCondition') {
allowWithTagCondition = true;
} else if (singlePolicyVerdict === 'DenyWithTagCondition') {
denyWithTagCondition = true;
} // else 'Neutral'
}
let verdict;
if (allow) {
if (denyWithTagCondition) {
verdict = 'NeedTagConditionEval';
} else {
verdict = 'Allow'; verdict = 'Allow';
} }
} else {
if (allowWithTagCondition) {
verdict = 'NeedTagConditionEval';
} else {
verdict = 'Deny';
}
} }
log.trace('result of evaluating all policies', { verdict }); log.trace('result of evaluating all pollicies', { verdict });
return verdict; return verdict;
} };

View File

@ -23,22 +23,15 @@ export default class Principal {
* @param statement - Statement policy field * @param statement - Statement policy field
* @return True if meet conditions * @return True if meet conditions
*/ */
static _evaluateStatement( static _evaluateCondition(
params: Params, params: Params,
statement: Statement, statement: Statement,
): 'Neutral' | 'Allow' | 'Deny' { // TODO Fix return type
const reverse = !!statement.NotPrincipal; ): any {
if (reverse) {
// In case of anonymous NotPrincipal, this will neutral everyone
return 'Neutral';
}
if (statement.Condition) { if (statement.Condition) {
const conditionEval = meetConditions(params.rc, statement.Condition, params.log); return meetConditions(params.rc, statement.Condition, params.log);
if (conditionEval === false || conditionEval === null) {
return 'Neutral';
}
} }
return statement.Effect; return true;
} }
/** /**
@ -55,12 +48,19 @@ export default class Principal {
statement: Statement, statement: Statement,
valids: Valid, valids: Valid,
): 'Neutral' | 'Allow' | 'Deny' { ): 'Neutral' | 'Allow' | 'Deny' {
const principal = (statement.Principal || statement.NotPrincipal)!;
const reverse = !!statement.NotPrincipal; const reverse = !!statement.NotPrincipal;
if (typeof principal === 'string') { const principal = (statement.Principal || statement.NotPrincipal)!;
if (principal === '*') { if (typeof principal === 'string' && principal === '*') {
return Principal._evaluateStatement(params, statement); if (reverse) {
// In case of anonymous NotPrincipal, this will neutral everyone
return 'Neutral';
} }
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
} else if (typeof principal === 'string') {
return 'Deny'; return 'Deny';
} }
let ref = []; let ref = [];
@ -82,8 +82,28 @@ export default class Principal {
} }
toCheck = Array.isArray(toCheck) ? toCheck : [toCheck]; toCheck = Array.isArray(toCheck) ? toCheck : [toCheck];
ref = Array.isArray(ref) ? ref : [ref]; ref = Array.isArray(ref) ? ref : [ref];
if (toCheck.includes('*') || ref.some(r => toCheck.includes(r))) { if (toCheck.indexOf('*') !== -1) {
return Principal._evaluateStatement(params, statement); if (reverse) {
return 'Neutral';
}
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
}
const len = ref.length;
for (let i = 0; i < len; ++i) {
if (toCheck.indexOf(ref[i]) !== -1) {
if (reverse) {
return 'Neutral';
}
const conditionEval = Principal._evaluateCondition(params, statement);
if (!conditionEval || conditionEval.allow === false) {
return 'Neutral';
}
return statement.Effect;
}
} }
if (reverse) { if (reverse) {
return statement.Effect; return statement.Effect;

View File

@ -4,14 +4,14 @@ const sharedActionMap = {
bucketDeleteEncryption: 's3:PutEncryptionConfiguration', bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
bucketDeletePolicy: 's3:DeleteBucketPolicy', bucketDeletePolicy: 's3:DeleteBucketPolicy',
bucketDeleteWebsite: 's3:DeleteBucketWebsite', bucketDeleteWebsite: 's3:DeleteBucketWebsite',
bucketDeleteTagging: 's3:PutBucketTagging', bucketDeleteTagging: 's3:DeleteBucketTagging',
bucketGet: 's3:ListBucket', bucketGet: 's3:ListBucket',
bucketGetACL: 's3:GetBucketAcl', bucketGetACL: 's3:GetBucketAcl',
bucketGetCors: 's3:GetBucketCORS', bucketGetCors: 's3:GetBucketCORS',
bucketGetEncryption: 's3:GetEncryptionConfiguration', bucketGetEncryption: 's3:GetEncryptionConfiguration',
bucketGetLifecycle: 's3:GetLifecycleConfiguration', bucketGetLifecycle: 's3:GetLifecycleConfiguration',
bucketGetLocation: 's3:GetBucketLocation', bucketGetLocation: 's3:GetBucketLocation',
bucketGetNotification: 's3:GetBucketNotification', bucketGetNotification: 's3:GetBucketNotificationConfiguration',
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration', bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
bucketGetPolicy: 's3:GetBucketPolicy', bucketGetPolicy: 's3:GetBucketPolicy',
bucketGetReplication: 's3:GetReplicationConfiguration', bucketGetReplication: 's3:GetReplicationConfiguration',
@ -23,7 +23,7 @@ const sharedActionMap = {
bucketPutCors: 's3:PutBucketCORS', bucketPutCors: 's3:PutBucketCORS',
bucketPutEncryption: 's3:PutEncryptionConfiguration', bucketPutEncryption: 's3:PutEncryptionConfiguration',
bucketPutLifecycle: 's3:PutLifecycleConfiguration', bucketPutLifecycle: 's3:PutLifecycleConfiguration',
bucketPutNotification: 's3:PutBucketNotification', bucketPutNotification: 's3:PutBucketNotificationConfiguration',
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration', bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
bucketPutPolicy: 's3:PutBucketPolicy', bucketPutPolicy: 's3:PutBucketPolicy',
bucketPutReplication: 's3:PutReplicationConfiguration', bucketPutReplication: 's3:PutReplicationConfiguration',
@ -55,8 +55,8 @@ const actionMapRQ = {
// see http://docs.aws.amazon.com/AmazonS3/latest/API/ // see http://docs.aws.amazon.com/AmazonS3/latest/API/
// RESTBucketDELETEcors.html // RESTBucketDELETEcors.html
bucketDeleteCors: 's3:PutBucketCORS', bucketDeleteCors: 's3:PutBucketCORS',
bucketDeleteReplication: 's3:PutReplicationConfiguration', bucketDeleteReplication: 's3:DeleteReplicationConfiguration',
bucketDeleteLifecycle: 's3:PutLifecycleConfiguration', bucketDeleteLifecycle: 's3:DeleteLifecycleConfiguration',
completeMultipartUpload: 's3:PutObject', completeMultipartUpload: 's3:PutObject',
initiateMultipartUpload: 's3:PutObject', initiateMultipartUpload: 's3:PutObject',
objectDeleteVersion: 's3:DeleteObjectVersion', objectDeleteVersion: 's3:DeleteObjectVersion',
@ -72,7 +72,6 @@ const actionMapRQ = {
objectReplicate: 's3:ReplicateObject', objectReplicate: 's3:ReplicateObject',
objectPutRetentionVersion: 's3:PutObjectVersionRetention', objectPutRetentionVersion: 's3:PutObjectVersionRetention',
objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold', objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold',
listObjectVersions: 's3:ListBucketVersions',
...sharedActionMap, ...sharedActionMap,
}; };
@ -105,7 +104,7 @@ const actionMonitoringMapS3 = {
bucketGetCors: 'GetBucketCors', bucketGetCors: 'GetBucketCors',
bucketGetLifecycle: 'GetBucketLifecycleConfiguration', bucketGetLifecycle: 'GetBucketLifecycleConfiguration',
bucketGetLocation: 'GetBucketLocation', bucketGetLocation: 'GetBucketLocation',
bucketGetNotification: 'GetBucketNotification', bucketGetNotification: 'GetBucketNotificationConfiguration',
bucketGetObjectLock: 'GetObjectLockConfiguration', bucketGetObjectLock: 'GetObjectLockConfiguration',
bucketGetPolicy: 'GetBucketPolicy', bucketGetPolicy: 'GetBucketPolicy',
bucketGetReplication: 'GetBucketReplication', bucketGetReplication: 'GetBucketReplication',
@ -118,7 +117,7 @@ const actionMonitoringMapS3 = {
bucketPutACL: 'PutBucketAcl', bucketPutACL: 'PutBucketAcl',
bucketPutCors: 'PutBucketCors', bucketPutCors: 'PutBucketCors',
bucketPutLifecycle: 'PutBucketLifecycleConfiguration', bucketPutLifecycle: 'PutBucketLifecycleConfiguration',
bucketPutNotification: 'PutBucketNotification', bucketPutNotification: 'PutBucketNotificationConfiguration',
bucketPutObjectLock: 'PutObjectLockConfiguration', bucketPutObjectLock: 'PutObjectLockConfiguration',
bucketPutPolicy: 'PutBucketPolicy', bucketPutPolicy: 'PutBucketPolicy',
bucketPutReplication: 'PutBucketReplication', bucketPutReplication: 'PutBucketReplication',

View File

@ -11,30 +11,31 @@ import ipaddr from 'ipaddr.js';
* @param requestContext - info sent with request * @param requestContext - info sent with request
* @return condition key value * @return condition key value
*/ */
export function findConditionKey( export const findConditionKey = (
key: string, key: string,
requestContext: RequestContext, requestContext: RequestContext,
): any { ): string => {
// TODO: Consider combining with findVariable function if no benefit // TODO: Consider combining with findVariable function if no benefit
// to keeping separate // to keeping separate
const headers = requestContext.getHeaders(); const headers = requestContext.getHeaders();
const query = requestContext.getQuery(); const query = requestContext.getQuery();
const requesterInfo = requestContext.getRequesterInfo(); const requesterInfo = requestContext.getRequesterInfo();
const map = new Map();
// Possible AWS Condition keys (http://docs.aws.amazon.com/IAM/latest/ // Possible AWS Condition keys (http://docs.aws.amazon.com/IAM/latest/
// UserGuide/reference_policies_elements.html#AvailableKeys) // UserGuide/reference_policies_elements.html#AvailableKeys)
switch (key) {
// aws:CurrentTime Used for date/time conditions // aws:CurrentTime Used for date/time conditions
// (see Date Condition Operators). // (see Date Condition Operators).
case 'aws:CurrentTime': return new Date().toISOString(); map.set('aws:CurrentTime', new Date().toISOString());
// aws:EpochTime Used for date/time conditions // aws:EpochTime Used for date/time conditions
// (see Date Condition Operators). // (see Date Condition Operators).
case 'aws:EpochTime': return Date.now().toString(); map.set('aws:EpochTime', Date.now().toString());
// aws:TokenIssueTime Date/time that temporary security // aws:TokenIssueTime Date/time that temporary security
// credentials were issued (see Date Condition Operators). // credentials were issued (see Date Condition Operators).
// Only present in requests that are signed using temporary security // Only present in requests that are signed using temporary security
// credentials. // credentials.
case 'aws:TokenIssueTime': return requestContext.getTokenIssueTime(); map.set('aws:TokenIssueTime', requestContext.getTokenIssueTime());
// aws:MultiFactorAuthPresent Used to check whether MFA was used // aws:MultiFactorAuthPresent Used to check whether MFA was used
// (see Boolean Condition Operators). // (see Boolean Condition Operators).
// Note: This key is only present if MFA was used. So, the following // Note: This key is only present if MFA was used. So, the following
@ -44,132 +45,131 @@ export function findConditionKey(
// Instead use: // Instead use:
// "Condition" : // "Condition" :
// { "Null" : { "aws:MultiFactorAuthPresent" : true } } // { "Null" : { "aws:MultiFactorAuthPresent" : true } }
case 'aws:MultiFactorAuthPresent': return requestContext.getMultiFactorAuthPresent(); map.set('aws:MultiFactorAuthPresent',
requestContext.getMultiFactorAuthPresent());
// aws:MultiFactorAuthAge Used to check how many seconds since // aws:MultiFactorAuthAge Used to check how many seconds since
// MFA credentials were issued. If MFA was not used, // MFA credentials were issued. If MFA was not used,
// this key is not present // this key is not present
case 'aws:MultiFactorAuthAge': return requestContext.getMultiFactorAuthAge(); map.set('aws:MultiFactorAuthAge', requestContext.getMultiFactorAuthAge());
// aws:principaltype states whether the principal is an account, // aws:principaltype states whether the principal is an account,
// user, federated, or assumed role // user, federated, or assumed role
// Note: Docs for conditions have "PrincipalType" but simulator // Note: Docs for conditions have "PrincipalType" but simulator
// and docs for variables have lowercase // and docs for variables have lowercase
case 'aws:principaltype': return requesterInfo.principaltype; map.set('aws:principaltype', requesterInfo.principaltype);
// aws:Referer Used to check who referred the client browser to // aws:Referer Used to check who referred the client browser to
// the address the request is being sent to. Only supported by some // the address the request is being sent to. Only supported by some
// services, such as S3. Value comes from the referer header in the // services, such as S3. Value comes from the referer header in the
// HTTPS request made to AWS. // HTTPS request made to AWS.
case 'aws:referer': return headers.referer; map.set('aws:referer', headers.referer);
// aws:SecureTransport Used to check whether the request was sent // aws:SecureTransport Used to check whether the request was sent
// using SSL (see Boolean Condition Operators). // using SSL (see Boolean Condition Operators).
case 'aws:SecureTransport': return requestContext.getSslEnabled() ? 'true' : 'false'; map.set('aws:SecureTransport',
requestContext.getSslEnabled() ? 'true' : 'false');
// aws:SourceArn Used check the source of the request, // aws:SourceArn Used check the source of the request,
// using the ARN of the source. N/A here. // using the ARN of the source. N/A here.
case 'aws:SourceArn': return undefined; map.set('aws:SourceArn', undefined);
// aws:SourceIp Used to check the requester's IP address // aws:SourceIp Used to check the requester's IP address
// (see IP Address Condition Operators) // (see IP Address Condition Operators)
case 'aws:SourceIp': return requestContext.getRequesterIp(); map.set('aws:SourceIp', requestContext.getRequesterIp());
// aws:SourceVpc Used to restrict access to a specific // aws:SourceVpc Used to restrict access to a specific
// AWS Virtual Private Cloud. N/A here. // AWS Virtual Private Cloud. N/A here.
case 'aws:SourceVpc': return undefined; map.set('aws:SourceVpc', undefined);
// aws:SourceVpce Used to limit access to a specific VPC endpoint // aws:SourceVpce Used to limit access to a specific VPC endpoint
// N/A here // N/A here
case 'aws:SourceVpce': return undefined; map.set('aws:SourceVpce', undefined);
// aws:UserAgent Used to check the requester's client app. // aws:UserAgent Used to check the requester's client app.
// (see String Condition Operators) // (see String Condition Operators)
case 'aws:UserAgent': return headers['user-agent']; map.set('aws:UserAgent', headers['user-agent']);
// aws:userid Used to check the requester's unique user ID. // aws:userid Used to check the requester's unique user ID.
// (see String Condition Operators) // (see String Condition Operators)
case 'aws:userid': return requesterInfo.userid; map.set('aws:userid', requesterInfo.userid);
// aws:username Used to check the requester's friendly user name. // aws:username Used to check the requester's friendly user name.
// (see String Condition Operators) // (see String Condition Operators)
case 'aws:username': return requesterInfo.username; map.set('aws:username', requesterInfo.username);
// Possible condition keys for S3: // Possible condition keys for S3:
// s3:x-amz-acl is acl request for bucket or object put request // s3:x-amz-acl is acl request for bucket or object put request
case 's3:x-amz-acl': return headers['x-amz-acl']; map.set('s3:x-amz-acl', headers['x-amz-acl']);
// s3:x-amz-grant-PERMISSION (where permission can be: // s3:x-amz-grant-PERMISSION (where permission can be:
// read, write, read-acp, write-acp or full-control) // read, write, read-acp, write-acp or full-control)
// Value is the value of that header (ex. id of grantee) // Value is the value of that header (ex. id of grantee)
case 's3:x-amz-grant-read': return headers['x-amz-grant-read']; map.set('s3:x-amz-grant-read', headers['x-amz-grant-read']);
case 's3:x-amz-grant-write': return headers['x-amz-grant-write']; map.set('s3:x-amz-grant-write', headers['x-amz-grant-write']);
case 's3:x-amz-grant-read-acp': return headers['x-amz-grant-read-acp']; map.set('s3:x-amz-grant-read-acp', headers['x-amz-grant-read-acp']);
case 's3:x-amz-grant-write-acp': return headers['x-amz-grant-write-acp']; map.set('s3:x-amz-grant-write-acp', headers['x-amz-grant-write-acp']);
case 's3:x-amz-grant-full-control': return headers['x-amz-grant-full-control']; map.set('s3:x-amz-grant-full-control', headers['x-amz-grant-full-control']);
// s3:x-amz-copy-source is x-amz-copy-source header if applicable on // s3:x-amz-copy-source is x-amz-copy-source header if applicable on
// a put object // a put object
case 's3:x-amz-copy-source': return headers['x-amz-copy-source']; map.set('s3:x-amz-copy-source', headers['x-amz-copy-source']);
// s3:x-amz-metadata-directive is x-amz-metadata-directive header if // s3:x-amz-metadata-directive is x-amz-metadata-directive header if
// applicable on a put object copy. Determines whether metadata will // applicable on a put object copy. Determines whether metadata will
// be copied from original object or replaced. Values or "COPY" or // be copied from original object or replaced. Values or "COPY" or
// "REPLACE". Default is "COPY" // "REPLACE". Default is "COPY"
case 's3:x-amz-metadata-directive': return headers['metadata-directive']; map.set('s3:x-amz-metadata-directive', headers['metadata-directive']);
// s3:x-amz-server-side-encryption -- Used to require that object put // s3:x-amz-server-side-encryption -- Used to require that object put
// use server side encryption. Value is the encryption algo such as // use server side encryption. Value is the encryption algo such as
// "AES256" // "AES256"
case 's3:x-amz-server-side-encryption': return headers['x-amz-server-side-encryption']; map.set('s3:x-amz-server-side-encryption',
headers['x-amz-server-side-encryption']);
// s3:x-amz-storage-class -- x-amz-storage-class header value // s3:x-amz-storage-class -- x-amz-storage-class header value
// (STANDARD, etc.) // (STANDARD, etc.)
case 's3:x-amz-storage-class': return headers['x-amz-storage-class']; map.set('s3:x-amz-storage-class', headers['x-amz-storage-class']);
// s3:VersionId -- version id of object // s3:VersionId -- version id of object
case 's3:VersionId': return query.versionId; map.set('s3:VersionId', query.versionId);
// s3:LocationConstraint -- Used to restrict creation of bucket // s3:LocationConstraint -- Used to restrict creation of bucket
// in certain region. Only applicable for CreateBucket // in certain region. Only applicable for CreateBucket
case 's3:LocationConstraint': return requestContext.getLocationConstraint(); map.set('s3:LocationConstraint', requestContext.getLocationConstraint());
// s3:delimiter is delimiter for listing request // s3:delimiter is delimiter for listing request
case 's3:delimiter': return query.delimiter; map.set('s3:delimiter', query.delimiter);
// s3:max-keys is max-keys for listing request // s3:max-keys is max-keys for listing request
case 's3:max-keys': return query['max-keys']; map.set('s3:max-keys', query['max-keys']);
// s3:prefix is prefix for listing request // s3:prefix is prefix for listing request
case 's3:prefix': return query.prefix; map.set('s3:prefix', query.prefix);
// s3 auth v4 additional condition keys // s3 auth v4 additional condition keys
// (See http://docs.aws.amazon.com/AmazonS3/latest/API/ // (See http://docs.aws.amazon.com/AmazonS3/latest/API/
// bucket-policy-s3-sigv4-conditions.html) // bucket-policy-s3-sigv4-conditions.html)
// s3:signatureversion -- Either "AWS" for v2 or // s3:signatureversion -- Either "AWS" for v2 or
// "AWS4-HMAC-SHA256" for v4 // "AWS4-HMAC-SHA256" for v4
case 's3:signatureversion': return requestContext.getSignatureVersion(); map.set('s3:signatureversion', requestContext.getSignatureVersion());
// s3:authType -- Method of authentication: either "REST-HEADER", // s3:authType -- Method of authentication: either "REST-HEADER",
// "REST-QUERY-STRING" or "POST" // "REST-QUERY-STRING" or "POST"
case 's3:authType': return requestContext.getAuthType(); map.set('s3:authType', requestContext.getAuthType());
// s3:signatureAge is the length of time, in milliseconds, // s3:signatureAge is the length of time, in milliseconds,
// that a signature is valid in an authenticated request. So, // that a signature is valid in an authenticated request. So,
// can use this to limit the age to less than 7 days // can use this to limit the age to less than 7 days
case 's3:signatureAge': return requestContext.getSignatureAge(); map.set('s3:signatureAge', requestContext.getSignatureAge());
// s3:x-amz-content-sha256 - Valid value is "UNSIGNED-PAYLOAD" // s3:x-amz-content-sha256 - Valid value is "UNSIGNED-PAYLOAD"
// so can use this in a deny policy to deny any requests that do not // so can use this in a deny policy to deny any requests that do not
// have a signed payload // have a signed payload
case 's3:x-amz-content-sha256': return headers['x-amz-content-sha256']; map.set('s3:x-amz-content-sha256', headers['x-amz-content-sha256']);
// s3:ObjLocationConstraint is the location constraint set for an // s3:ObjLocationConstraint is the location constraint set for an
// object on a PUT request using the "x-amz-meta-scal-location-constraint" // object on a PUT request using the "x-amz-meta-scal-location-constraint"
// header // header
case 's3:ObjLocationConstraint': return headers['x-amz-meta-scal-location-constraint']; map.set('s3:ObjLocationConstraint',
case 'sts:ExternalId': return requestContext.getRequesterExternalId(); headers['x-amz-meta-scal-location-constraint']);
case 'iam:PolicyArn': return requestContext.getPolicyArn(); map.set('sts:ExternalId', requestContext.getRequesterExternalId());
map.set('iam:PolicyArn', requestContext.getPolicyArn());
// s3:ExistingObjectTag - Used to check that existing object tag has // s3:ExistingObjectTag - Used to check that existing object tag has
// specific tag key and value. Extraction of correct tag key is done in CloudServer. // specific tag key and value. Extraction of correct tag key is done in CloudServer.
// On first pass of policy evaluation, CloudServer information will not be included, // On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped // so evaluation should be skipped
case 's3:ExistingObjectTag': map.set('s3:ExistingObjectTag', requestContext.getNeedTagEval() ? requestContext.getExistingObjTag() : undefined);
return requestContext.getNeedTagEval()
? requestContext.getExistingObjTag() : undefined;
// s3:RequestObjectTag - Used to limit putting object tags to specific // s3:RequestObjectTag - Used to limit putting object tags to specific
// tag key and value. N/A here. // tag key and value. N/A here.
// Requires information from CloudServer // Requires information from CloudServer
// On first pass of policy evaluation, CloudServer information will not be included, // On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped // so evaluation should be skipped
case 's3:RequestObjectTagKey': map.set('s3:RequestObjectTagKey', requestContext.getNeedTagEval() ? requestContext.getRequestObjTags() : undefined);
return requestContext.getNeedTagEval()
? requestContext.getRequestObjTags() : undefined;
// s3:RequestObjectTagKeys - Used to limit putting object tags specific tag keys. // s3:RequestObjectTagKeys - Used to limit putting object tags specific tag keys.
// Requires information from CloudServer. // Requires information from CloudServer.
// On first pass of policy evaluation, CloudServer information will not be included, // On first pass of policy evaluation, CloudServer information will not be included,
// so evaluation should be skipped // so evaluation should be skipped
case 's3:RequestObjectTagKeys': map.set('s3:RequestObjectTagKeys',
return requestContext.getNeedTagEval() && requestContext.getRequestObjTags() requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
? getTagKeys(requestContext.getRequestObjTags()!) ? getTagKeys(requestContext.getRequestObjTags()!)
: undefined; : undefined,
default: );
return undefined; return map.get(key);
} };
}
// Wildcards are allowed in certain string comparison and arn comparisons // Wildcards are allowed in certain string comparison and arn comparisons
@ -229,7 +229,7 @@ function convertToEpochTime(time: string | string[]) {
* reference_policies_elements.html) * reference_policies_elements.html)
* @return true if condition passes and false if not * @return true if condition passes and false if not
*/ */
export function convertConditionOperator(operator: string): boolean { export const convertConditionOperator = (operator: string): boolean => {
// Policy Validator checks that the condition operator // Policy Validator checks that the condition operator
// is only one of these strings so should not have undefined // is only one of these strings so should not have undefined
// or security issue with object assignment // or security issue with object assignment
@ -444,4 +444,4 @@ export function convertConditionOperator(operator: string): boolean {
}, },
}; };
return operatorMap[operator]; return operatorMap[operator];
} };

View File

@ -3,7 +3,7 @@
"engines": { "engines": {
"node": ">=16" "node": ">=16"
}, },
"version": "7.10.29-4", "version": "7.10.29-1",
"description": "Common utilities for the S3 project components", "description": "Common utilities for the S3 project components",
"main": "build/index.js", "main": "build/index.js",
"repository": { "repository": {
@ -62,6 +62,7 @@
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/xml2js": "^0.4.11", "@types/xml2js": "^0.4.11",
"chance": "^1.1.8",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"eslint-config-airbnb": "6.2.0", "eslint-config-airbnb": "6.2.0",
"eslint-config-scality": "scality/Guidelines#7.10.2", "eslint-config-scality": "scality/Guidelines#7.10.2",

View File

@ -1,7 +1,7 @@
'use strict'; // eslint-disable-line strict 'use strict'; // eslint-disable-line strict
const assert = require('assert'); const assert = require('assert');
const chance = require('chance').Chance(); // eslint-disable-line
const DelimiterMaster = const DelimiterMaster =
require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster; require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster;
const { const {
@ -16,8 +16,6 @@ const Version = require('../../../../lib/versioning/Version').Version;
const { generateVersionId } = require('../../../../lib/versioning/VersionID'); const { generateVersionId } = require('../../../../lib/versioning/VersionID');
const { DbPrefixes } = VSConst; const { DbPrefixes } = VSConst;
const zpad = require('../../helpers').zpad; const zpad = require('../../helpers').zpad;
const VID_SEP = VSConst.VersionId.Separator; const VID_SEP = VSConst.VersionId.Separator;
const EmptyResult = { const EmptyResult = {
CommonPrefixes: [], CommonPrefixes: [],
@ -488,6 +486,336 @@ function getListingKey(key, vFormat) {
// ...it should return to skipping by prefix as usual // ...it should return to skipping by prefix as usual
assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`); assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`);
}); });
it('should not skip over whole prefix when a key equals the prefix and ends with delimiter', () => {
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
const delimiter = new DelimiterMaster({
prefix,
delimiter: '/',
}, fakeLogger, vFormat);
for (const testEntry of [
{
key: prefix,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}${VID_SEP}v1`,
value: '{}',
expectedRes: FILTER_SKIP, // versions get skipped after master
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}deleted`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP, // delete markers get skipped
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}deleted${VID_SEP}v1`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}deleted${VID_SEP}v2`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}notdeleted`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}notdeleted${VID_SEP}`,
},
{
key: `${prefix}notdeleted${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}notdeleted${VID_SEP}`,
},
{
key: `${prefix}subprefix1/key-1`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}subprefix1/`,
},
{
key: `${prefix}subprefix1/key-1${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}subprefix1/`,
},
{
key: `${prefix}subprefix1/key-2`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}subprefix1/`,
},
{
key: `${prefix}subprefix1/key-2${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}subprefix1/`,
},
]) {
const entry = {
key: testEntry.key,
};
if (testEntry.isDeleteMarker) {
entry.value = '{"isDeleteMarker":true}';
} else {
entry.value = '{}';
}
const res = delimiter.filter(entry);
const skipping = delimiter.skipping();
assert.strictEqual(res, testEntry.expectedRes);
assert.strictEqual(skipping, testEntry.expectedSkipping);
}
}
});
it('should skip over whole prefix when a key equals the prefix and does not end with delimiter', () => {
for (const prefix of ['prefix', 'prefix/subprefix']) {
const delimiter = new DelimiterMaster({
prefix,
delimiter: '/',
}, fakeLogger, vFormat);
for (const testEntry of [
{
key: prefix,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}/`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}/`,
},
{
key: `${prefix}/${VID_SEP}v1`,
value: '{}',
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}/`, // common prefix already seen
},
{
key: `${prefix}/deleted`,
isDeleteMarker: true, // skipped delete marker
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}/`, // already added to common prefix
},
{
key: `${prefix}/notdeleted`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}/`, // already added to common prefix
},
{
key: `${prefix}ed`,
isDeleteMarker: false,
expectedRes: FILTER_ACCEPT, // new master key seen
expectedSkipping: `${prefix}ed${VID_SEP}`,
},
{
key: `${prefix}ed/`,
expectedRes: FILTER_ACCEPT, // new master key ending with prefix
expectedSkipping: `${prefix}ed/`,
},
{
key: `${prefix}ed/subprefix1/key-1`,
expectedRes: FILTER_SKIP, // already have prefixed/ common prefix
expectedSkipping: `${prefix}ed/`,
},
{
key: `${prefix}ed/subprefix1/key-1${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}ed/`,
},
{
key: `${prefix}ed/subprefix1/key-2`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}ed/`,
},
{
key: `${prefix}ed/subprefix1/key-2${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}ed/`,
},
]) {
const entry = {
key: testEntry.key,
};
if (testEntry.isDeleteMarker) {
entry.value = '{"isDeleteMarker":true}';
} else {
entry.value = '{}';
}
const res = delimiter.filter(entry);
const skipping = delimiter.skipping();
assert.strictEqual(res, testEntry.expectedRes);
assert.strictEqual(skipping, testEntry.expectedSkipping);
}
}
});
it('should not skip over whole prefix when key equals the prefix and prefix key has delete marker ' +
'and prefix ends with delimiter', () => {
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
const delimiter = new DelimiterMaster({
prefix,
delimiter: '/',
}, fakeLogger, vFormat);
for (const testEntry of [
{
key: prefix,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}subprefix-1`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}subprefix-1${VID_SEP}`,
},
{
key: `${prefix}subprefix-1/foo`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}subprefix-1/`,
},
{
key: `${prefix}subprefix-1/bar`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}subprefix-1/`, // already added to common prefix
},
]) {
const entry = {
key: testEntry.key,
};
if (testEntry.isDeleteMarker) {
entry.value = '{"isDeleteMarker":true}';
} else {
entry.value = '{}';
}
const res = delimiter.filter(entry);
const skipping = delimiter.skipping();
assert.strictEqual(res, testEntry.expectedRes);
assert.strictEqual(skipping, testEntry.expectedSkipping);
}
}
});
it('should skip over whole prefix when key equals the prefix, prefix key has delete marker ' +
'and prefix does not end with delimiter', () => {
for (const prefix of ['prefix', 'prefix/subprefix']) {
const delimiter = new DelimiterMaster({
prefix,
delimiter: '/',
}, fakeLogger, vFormat);
for (const testEntry of [
{
key: prefix,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}${VID_SEP}v1`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}/`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}/subprefix-1`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}/`,
},
{
key: `${prefix}/subprefix-1/foo`,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}/`,
},
{
key: `${prefix}aa`,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}aa${VID_SEP}`, // already added to common prefix
},
]) {
const entry = {
key: testEntry.key,
};
if (testEntry.isDeleteMarker) {
entry.value = '{"isDeleteMarker":true}';
} else {
entry.value = '{}';
}
const res = delimiter.filter(entry);
const skipping = delimiter.skipping();
assert.strictEqual(res, testEntry.expectedRes);
assert.strictEqual(skipping, testEntry.expectedSkipping);
}
}
});
it('should be able to skip subprefixes within deleteMarker keys when a prefix key ' +
'ending with delimiter is seen', () => {
for (const prefix of ['prefix/', 'prefix/subprefix/']) {
const delimiter = new DelimiterMaster({
prefix,
delimiter: '/',
}, fakeLogger, vFormat);
for (const testEntry of [
{
key: prefix,
expectedRes: FILTER_ACCEPT,
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}${VID_SEP}v1`,
// isDeleteMarker: true,
expectedRes: FILTER_SKIP, // versions get skipped after master
expectedSkipping: `${prefix}${VID_SEP}`,
},
{
key: `${prefix}foo/`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}foo/`,
},
{
key: `${prefix}foo/1`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}foo/`,
},
{
key: `${prefix}foo/2`,
isDeleteMarker: true,
expectedRes: FILTER_SKIP,
expectedSkipping: `${prefix}foo/`,
},
]) {
const entry = {
key: testEntry.key,
};
if (testEntry.isDeleteMarker) {
entry.value = '{"isDeleteMarker":true}';
} else {
entry.value = '{}';
}
const res = delimiter.filter(entry);
const skipping = delimiter.skipping();
assert.strictEqual(res, testEntry.expectedRes);
assert.strictEqual(skipping, testEntry.expectedSkipping);
}
}
});
} }
}); });
}); });

View File

@ -1162,6 +1162,30 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral'); check(requestContext, rcModifiers, policy, 'Neutral');
}); });
it('should allow with StringEquals operator and ExistingObjectTag ' +
'key if meet condition', () => {
policy.Statement.Condition = {
StringEquals: { 's3:ExistingObjectTag/tagKey': 'tagValue' },
};
const rcModifiers = {
_existingObjTag: 'tagKey=tagValue',
_needTagEval: true,
};
check(requestContext, rcModifiers, policy, 'Allow');
});
it('should allow StringEquals operator and RequestObjectTag ' +
'key if meet condition', () => {
policy.Statement.Condition = {
StringEquals: { 's3:RequestObjectTagKey/tagKey': 'tagValue' },
};
const rcModifiers = {
_requestObjTags: 'tagKey=tagValue',
_needTagEval: true,
};
check(requestContext, rcModifiers, policy, 'Allow');
});
it('should allow with ForAnyValue prefix if meet condition', () => { it('should allow with ForAnyValue prefix if meet condition', () => {
policy.Statement.Condition = { policy.Statement.Condition = {
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] }, 'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
@ -1184,7 +1208,7 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Allow'); check(requestContext, rcModifiers, policy, 'Allow');
}); });
it('should be neutral with ForAnyValue prefix if do not meet condition', () => { it('should not allow with ForAnyValue prefix if do not meet condition', () => {
policy.Statement.Condition = { policy.Statement.Condition = {
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] }, 'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
}; };
@ -1195,12 +1219,12 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral'); check(requestContext, rcModifiers, policy, 'Neutral');
}); });
it('should be neutral with ForAllValues prefix if do not meet condition', () => { it('should not allow with ForAllValues prefix if do not meet condition', () => {
policy.Statement.Condition = { policy.Statement.Condition = {
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] }, 'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
}; };
const rcModifiers = { const rcModifiers = {
_requestObjTags: 'tagOne=keyOne&tagThree=keyThree', _requestObjTags: 'tagThree=keyThree&tagFour=keyFour',
_needTagEval: true, _needTagEval: true,
}; };
check(requestContext, rcModifiers, policy, 'Neutral'); check(requestContext, rcModifiers, policy, 'Neutral');
@ -1217,203 +1241,6 @@ describe('policyEvaluator', () => {
check(requestContext, rcModifiers, policy, 'Neutral'); check(requestContext, rcModifiers, policy, 'Neutral');
}); });
}); });
describe('with multiple statements', () => {
beforeEach(() => {
requestContext = new RequestContext({}, {}, 'bucket',
undefined, undefined, undefined, 'objectPut', 's3');
requestContext.setRequesterInfo({});
});
const TestMatrix = [
{
statementsToEvaluate: [],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Neutral',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Allow', meetConditions: true },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: false },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: null },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'Allow',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: false },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'AllowWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: true },
{ effect: 'Allow', meetConditions: null },
],
expectedPolicyEvaluation: 'Deny',
},
{
statementsToEvaluate: [
{ effect: 'Deny', meetConditions: false },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: true },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
{
statementsToEvaluate: [
{ effect: 'Allow', meetConditions: null },
{ effect: 'Deny', meetConditions: null },
],
expectedPolicyEvaluation: 'DenyWithTagCondition',
},
];
TestMatrix.forEach(testCase => {
const policyDesc = testCase.statementsToEvaluate
.map(statement => `${statement.effect}(met:${statement.meetConditions})`)
.join(', ');
it(`policy with statements evaluating individually to [${policyDesc}] ` +
`should return ${testCase.expectedPolicyEvaluation}`, () => {
const policy = {
Version: '2012-10-17',
Statement: testCase.statementsToEvaluate.map(statement => {
let condition;
if (statement.meetConditions === true) {
condition = {
StringEquals: { 'aws:UserAgent': 'CyberSquaw' },
};
} else if (statement.meetConditions === false) {
condition = {
StringEquals: { 'aws:UserAgent': 'OtherAgent' },
};
} else if (statement.meetConditions === null) {
condition = {
StringEquals: { 's3:ExistingObjectTag/tagKey': 'tagValue' },
};
}
return {
Effect: statement.effect,
Action: 's3:PutObject',
Resource: 'arn:aws:s3:::bucket',
Condition: condition,
};
}),
};
requestContext.setHeaders({
'user-agent': 'CyberSquaw',
});
requestContext.setNeedTagEval(false);
const result = evaluatePolicy(requestContext, policy, log);
assert.strictEqual(result, testCase.expectedPolicyEvaluation);
});
});
});
}); });
describe('evaluate multiple policies', () => { describe('evaluate multiple policies', () => {
@ -1439,152 +1266,17 @@ describe('policyEvaluator', () => {
assert.strictEqual(result, 'Deny'); assert.strictEqual(result, 'Deny');
}); });
it('should deny access if request resource is not in any policy', () => { it('should deny access if request resource is not in any policy',
requestContext = new RequestContext({}, {}, () => {
'notbucket', undefined,
undefined, undefined, 'objectGet', 's3');
requestContext.setRequesterInfo({});
const result = evaluateAllPolicies(requestContext, [
samples['Multi-Statement Policy'],
samples['Variable Bucket Policy'],
], log);
assert.strictEqual(result, 'Deny');
});
const TestMatrixPolicies = {
Allow: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: '*',
},
},
Neutral: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: 'arn:aws:s3:::other-bucket',
},
},
Deny: {
Version: '2012-10-17',
Statement: {
Effect: 'Deny',
Action: 's3:*',
Resource: '*',
},
},
AllowWithTagCondition: {
Version: '2012-10-17',
Statement: {
Effect: 'Allow',
Action: 's3:*',
Resource: '*',
Condition: {
StringEquals: {
's3:ExistingObjectTag/tagKey': 'tagValue',
},
},
},
},
DenyWithTagCondition: {
Version: '2012-10-17',
Statement: {
Effect: 'Deny',
Action: 's3:*',
Resource: '*',
Condition: {
StringEquals: {
's3:ExistingObjectTag/tagKey': 'tagValue',
},
},
},
},
};
const TestMatrix = [
{
policiesToEvaluate: [],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Allow', 'Neutral'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral', 'Allow'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['Neutral', 'Neutral'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['AllowWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['Allow', 'AllowWithTagCondition'],
expectedPolicyEvaluation: 'Allow',
},
{
policiesToEvaluate: ['DenyWithTagCondition'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['Allow', 'DenyWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['AllowWithTagCondition', 'DenyWithTagCondition'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
{
policiesToEvaluate: ['AllowWithTagCondition', 'DenyWithTagCondition', 'Deny'],
expectedPolicyEvaluation: 'Deny',
},
{
policiesToEvaluate: ['DenyWithTagCondition', 'AllowWithTagCondition', 'Allow'],
expectedPolicyEvaluation: 'NeedTagConditionEval',
},
];
TestMatrix.forEach(testCase => {
it(`policies evaluating individually to [${testCase.policiesToEvaluate.join(', ')}] `
+ `should return ${testCase.expectedPolicyEvaluation}`, () => {
requestContext = new RequestContext({}, {}, requestContext = new RequestContext({}, {},
'my_favorite_bucket', undefined, 'notbucket', undefined,
undefined, undefined, 'objectGet', 's3'); undefined, undefined, 'objectGet', 's3');
requestContext.setRequesterInfo({}); requestContext.setRequesterInfo({});
const result = evaluateAllPolicies( const result = evaluateAllPolicies(requestContext,
requestContext, [samples['Multi-Statement Policy'],
testCase.policiesToEvaluate.map(policyName => TestMatrixPolicies[policyName]), samples['Variable Bucket Policy']], log);
log); assert.strictEqual(result, 'Deny');
assert.strictEqual(result, testCase.expectedPolicyEvaluation);
}); });
});
}); });
}); });

View File

@ -1,128 +0,0 @@
const assert = require('assert');
const RequestContext = require('../../../lib/policyEvaluator/RequestContext').default;
describe('RequestContext', () => {
const constructorParams = [
{ 'some-header': 'some-value' }, // headers
{ q1: 'v1', q2: 'v2' }, // query
'general-resource', // generalResource
'specific-resource', // specificResource
'127.0.0.1', // requesterIp
true, // sslEnabled
'GET', // apiMethod
's3', // awsService
'us-east-1', // locationConstraint
{ // requesterInfo
arn: 'arn:aws:iam::user/johndoe',
accountId: 'JOHNACCOUNT',
username: 'John Doe',
principalType: 'user',
},
'v4', // signatureVersion
'REST-HEADER', // authType
123456, // signatureAge
'security-token', // securityToken
'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess', // policyArn
'objectGet', // action
'reqTagOne=valueOne&reqTagTwo=valueTwo', // requestObjTags
'existingTagOne=valueOne&existingTagTwo=valueTwo', // existingObjTag
true, // needTagEval
];
const rc = new RequestContext(...constructorParams);
const GetterTests = [
{ name: 'getAction', expectedValue: 'objectGet' },
{ name: 'getResource', expectedValue: 'arn:aws:s3:::general-resource/specific-resource' },
{ name: 'getHeaders', expectedValue: { 'some-header': 'some-value' } },
{ name: 'getQuery', expectedValue: { q1: 'v1', q2: 'v2' } },
{
name: 'getRequesterInfo',
expectedValue: {
accountId: 'JOHNACCOUNT',
arn: 'arn:aws:iam::user/johndoe',
username: 'John Doe',
principalType: 'user',
},
},
{ name: 'getRequesterIp', expectedValueToString: '127.0.0.1' },
{ name: 'getRequesterAccountId', expectedValue: undefined },
{ name: 'getRequesterEndArn', expectedValue: 'arn:aws:iam::user/johndoe' },
{ name: 'getRequesterExternalId', expectedValue: undefined },
{ name: 'getRequesterPrincipalArn', expectedValue: 'arn:aws:iam::user/johndoe' },
{ name: 'getRequesterType', expectedValue: 'user' },
{ name: 'getSslEnabled', expectedValue: true },
{ name: 'getSignatureVersion', expectedValue: 'v4' },
{ name: 'getAuthType', expectedValue: 'REST-HEADER' },
{ name: 'getSignatureAge', expectedValue: 123456 },
{ name: 'getLocationConstraint', expectedValue: 'us-east-1' },
{ name: 'getAwsService', expectedValue: 's3' },
{ name: 'getTokenIssueTime', expectedValue: null },
{ name: 'getMultiFactorAuthPresent', expectedValue: null },
{ name: 'getMultiFactorAuthAge', expectedValue: null },
{ name: 'getSecurityToken', expectedValue: 'security-token' },
{ name: 'getPolicyArn', expectedValue: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' },
{ name: 'isQuotaCheckNeeded', expectedValue: false },
{ name: 'getRequestObjTags', expectedValue: 'reqTagOne=valueOne&reqTagTwo=valueTwo' },
{ name: 'getExistingObjTag', expectedValue: 'existingTagOne=valueOne&existingTagTwo=valueTwo' },
{ name: 'getNeedTagEval', expectedValue: true },
];
GetterTests.forEach(testCase => {
it(`getter:${testCase.name}`, () => {
const getterResult = rc[testCase.name]();
if (testCase.expectedValueToString) {
assert.strictEqual(getterResult.toString(), testCase.expectedValueToString);
} else {
assert.deepStrictEqual(getterResult, testCase.expectedValue);
}
});
});
const SerializedFields = {
action: 'objectGet',
apiMethod: 'GET',
authType: 'REST-HEADER',
awsService: 's3',
existingObjTag: 'existingTagOne=valueOne&existingTagTwo=valueTwo',
generalResource: 'general-resource',
headers: {
'some-header': 'some-value',
},
locationConstraint: 'us-east-1',
multiFactorAuthAge: null,
multiFactorAuthPresent: null,
needTagEval: true,
policyArn: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess',
query: {
q1: 'v1',
q2: 'v2',
},
requesterInfo: {
accountId: 'JOHNACCOUNT',
arn: 'arn:aws:iam::user/johndoe',
principalType: 'user',
username: 'John Doe',
},
requestObjTags: 'reqTagOne=valueOne&reqTagTwo=valueTwo',
requesterIp: '127.0.0.1',
securityToken: 'security-token',
signatureAge: 123456,
signatureVersion: 'v4',
specificResource: 'specific-resource',
sslEnabled: true,
tokenIssueTime: null,
};
it('serialize()', () => {
assert.deepStrictEqual(JSON.parse(rc.serialize()), SerializedFields);
});
it('deSerialize()', () => {
// check that everything that was serialized is deserialized
// properly into a new RequestContext object by making sure
// the serialized version of the latter corresponds to the
// input
const serialized = JSON.stringify(SerializedFields);
const deserializedRC = RequestContext.deSerialize(serialized);
const newSerialized = JSON.parse(deserializedRC.serialize());
assert.deepStrictEqual(newSerialized, SerializedFields);
});
});

View File

@ -2144,6 +2144,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
chance@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.8.tgz#5d6c2b78c9170bf6eb9df7acdda04363085be909"
integrity sha512-v7fi5Hj2VbR6dJEGRWLmJBA83LJMS47pkAbmROFxHWd9qmE1esHRZW8Clf1Fhzr3rjxnNZVCjOEv/ivFxeIMtg==
char-regex@^1.0.2: char-regex@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"