Compare commits

...

14 Commits

Author SHA1 Message Date
bert-e b16a796433 Merge branch 'feature/CLDSRV-102/port_hotfix_7_10_1' into q/7.10.1.7 2022-03-01 04:43:22 +00:00
bert-e 2c1189f76b
bugfix: CLDSRV-145 remove trailing commas 2022-02-28 14:47:35 -08:00
Ronnie b3b733970c
bugfix: CLDSRV-144: Also check 127 and grammar suggestion
Co-authored-by: Jonathan Gramain <jonathan.gramain@scality.com>
(cherry picked from commit e01d62a5ad)
2022-02-28 14:26:17 -08:00
Ronnie Smith c98195a835
Merge remote-tracking branch 'origin/bugfix/CLDSRV-144/do-not-allow-non-printable-chars' into w/7.10/bugfix/CLDSRV-144/do-not-allow-non-printable-chars
(cherry picked from commit 9149cb4abb)
2022-02-28 14:26:06 -08:00
Ronnie Smith a118ac89c3
bugfix: CLDSRV-144 Do not allow non-printable characters in object names
(cherry picked from commit d9d3ec682d)
2022-02-28 14:25:01 -08:00
Taylor McKinnon 7a2e1389fe ft(CLDSRV-102): Add Aborted MPU PUT
(cherry picked from commit 9eba583eb0)
2022-02-24 13:24:21 -08:00
Jonathan Gramain 25c5cca38c CLDSRV-53 CLDSRV-60 [hotfix] bump arsenal hash 2021-12-08 16:04:01 -08:00
Jonathan Gramain 474413afc0 CLDSRV-60 address review: improve test self-doc
In versioning tests, add a 'description' field in test cases instead
of comments to make the description part of the test name

(cherry picked from commit 6d741d1312)
2021-12-08 16:01:27 -08:00
Jonathan Gramain 5f13af45f3 improvement: CLDSRV-60 cleanup replay IDs of null versions
Replace the previous way of passing replayId option to metadata DELETE
by the result of the preprocessingVersioningDelete function, since it
handles more cases like when a bucket with versioning suspended has to
delete the previous null version, in which case, the null version's
replay IDs have to be cleaned up.

(cherry picked from commit 2d44334a1f)
2021-12-08 16:01:27 -08:00
Jonathan Gramain abfccc41eb improvement: CLDSRV-60 process upload IDs in versioning helpers
In versioning helpers, make sure we pass on the nullUploadId/replayId
to the metadataStoreObject() or deleteObject() function, so that they
can update ObjectMD and pass the appropriate replay options to metadata

(cherry picked from commit a6e1c2ec83)
2021-12-08 16:01:27 -08:00
Jonathan Gramain 57dbc39755 CLDSRV-61 add JSDoc
Add JSDoc for processVersioningState() and getMasterState() versioning
helper functions

(cherry picked from commit 8f913101a6)
2021-12-08 16:01:27 -08:00
Jonathan Gramain 0f46d861ef improvement: CLDSRV-61 test preprocessingVersioningDelete
Add unit tests for preprocessingVersioningDelete helper

(cherry picked from commit 1ccd36f9bf)
2021-12-08 16:01:27 -08:00
Jonathan Gramain e4dd8b6d68 improvement: CLDSRV-61 refactor and test processVersioningState
- add unit tests for processVersioningState() helper

- remove the callback argument, instead, return the list of parameters
  as an object, it simplifies and enhances testability

(cherry picked from commit 22f7f253a1)
2021-12-08 16:01:27 -08:00
Jonathan Gramain 088fb9484d CLDSRV-53 pass replayId when completing/deleting an MPU
Pass the 'replayId' option to metadata when:

- completing an MPU, to allow metadata to check or write a replay key

- deleting an MPU, to allow metadata to delete the replay key

(cherry picked from commit 5fc53b3b95)
2021-12-08 16:01:26 -08:00
17 changed files with 812 additions and 75 deletions

View File

@ -85,6 +85,15 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
} }
return next(null, mpuBucket, destBucket, false); return next(null, mpuBucket, destBucket, false);
}, },
function sendAbortPut(mpuBucket, destBucket, skipDataDelete, next) {
services.sendAbortMPUPut(bucketName, objectKey, uploadId, log,
err => {
if (err) {
return next(err, destBucket);
}
return next(null, mpuBucket, destBucket, skipDataDelete);
});
},
function getPartLocations(mpuBucket, destBucket, skipDataDelete, function getPartLocations(mpuBucket, destBucket, skipDataDelete,
next) { next) {
services.getMPUparts(mpuBucket.getName(), uploadId, log, services.getMPUparts(mpuBucket.getName(), uploadId, log,

View File

@ -253,6 +253,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
metadataStoreParams.versioning = options.versioning; metadataStoreParams.versioning = options.versioning;
metadataStoreParams.isNull = options.isNull; metadataStoreParams.isNull = options.isNull;
metadataStoreParams.nullVersionId = options.nullVersionId; metadataStoreParams.nullVersionId = options.nullVersionId;
metadataStoreParams.nullUploadId = options.nullUploadId;
return _storeInMDandDeleteData(bucketName, infoArr, return _storeInMDandDeleteData(bucketName, infoArr,
cipherBundle, metadataStoreParams, cipherBundle, metadataStoreParams,
options.dataToDelete, requestLogger, requestMethod, next); options.dataToDelete, requestLogger, requestMethod, next);

View File

@ -129,7 +129,23 @@ function _deleteNullVersionMD(bucketName, objKey, options, mst, log, cb) {
}); });
} }
function processVersioningState(mst, vstat, cb) { /**
* Process state from the master version of an object and the bucket
* versioning configuration, return a set of options objects
*
* @param {object} mst - state of master version, as returned by
* getMasterState()
* @param {string} vstat - bucket versioning status: 'Enabled' or 'Suspended'
*
* @return {object} result object with the following attributes:
* - {object} options: versioning-related options to pass to the
services.metadataStoreObject() call
* - {object} [storeOptions]: options for metadata to create a new
null version key, if needed
* - {object} [delOptions]: options for metadata to delete the null
version key, if needed
*/
function processVersioningState(mst, vstat) {
const options = {}; const options = {};
const storeOptions = {}; const storeOptions = {};
const delOptions = {}; const delOptions = {};
@ -143,9 +159,12 @@ function processVersioningState(mst, vstat, cb) {
// if null version exists, clean it up prior to put // if null version exists, clean it up prior to put
if (mst.isNull) { if (mst.isNull) {
delOptions.versionId = mst.versionId; delOptions.versionId = mst.versionId;
return cb(null, options, null, delOptions); if (mst.uploadId) {
delOptions.replayId = mst.uploadId;
} }
return cb(null, options); return { options, delOptions };
}
return { options };
} }
// versioning is enabled, create a new version // versioning is enabled, create a new version
options.versioning = true; options.versioning = true;
@ -155,9 +174,14 @@ function processVersioningState(mst, vstat, cb) {
storeOptions.versionId = versionId; storeOptions.versionId = versionId;
storeOptions.isNull = true; storeOptions.isNull = true;
options.nullVersionId = versionId; options.nullVersionId = versionId;
return cb(null, options, storeOptions); // non-versioned (non-null) MPU objects don't have a
// replay ID, so don't reference their uploadId
if (mst.isNull && mst.uploadId) {
options.nullUploadId = mst.uploadId;
} }
return cb(null, options); return { options, storeOptions };
}
return { options };
} }
// master is versioned and is not a null version // master is versioned and is not a null version
const nullVersionId = mst.nullVersionId; const nullVersionId = mst.nullVersionId;
@ -166,17 +190,36 @@ function processVersioningState(mst, vstat, cb) {
options.versionId = ''; options.versionId = '';
options.isNull = true; options.isNull = true;
if (nullVersionId === undefined) { if (nullVersionId === undefined) {
return cb(null, options); return { options };
} }
delOptions.versionId = nullVersionId; delOptions.versionId = nullVersionId;
return cb(null, options, null, delOptions); if (mst.nullUploadId) {
delOptions.replayId = mst.nullUploadId;
}
return { options, delOptions };
} }
// versioning is enabled, put the new version // versioning is enabled, put the new version
options.versioning = true; options.versioning = true;
options.nullVersionId = nullVersionId; options.nullVersionId = nullVersionId;
return cb(null, options); if (mst.nullUploadId) {
options.nullUploadId = mst.nullUploadId;
}
return { options };
} }
/**
* Build the state of the master version from its object metadata
*
* @param {object} objMD - object metadata parsed from JSON
*
* @return {object} state of master version, with the following attributes:
* - {boolean} exists - true if the object exists (i.e. if `objMD` is truish)
* - {string} versionId - version ID of the master key
* - {boolean} isNull - whether the master version is a null version
* - {string} nullVersionId - if not a null version, reference to the
* null version ID
* - {array} objLocation - array of data locations
*/
function getMasterState(objMD) { function getMasterState(objMD) {
if (!objMD) { if (!objMD) {
return {}; return {};
@ -184,8 +227,10 @@ function getMasterState(objMD) {
const mst = { const mst = {
exists: true, exists: true,
versionId: objMD.versionId, versionId: objMD.versionId,
uploadId: objMD.uploadId,
isNull: objMD.isNull, isNull: objMD.isNull,
nullVersionId: objMD.nullVersionId, nullVersionId: objMD.nullVersionId,
nullUploadId: objMD.nullUploadId,
}; };
if (objMD.location) { if (objMD.location) {
mst.objLocation = Array.isArray(objMD.location) ? mst.objLocation = Array.isArray(objMD.location) ?
@ -213,35 +258,29 @@ function getMasterState(objMD) {
*/ */
function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD, function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
log, callback) { log, callback) {
const options = {};
const mst = getMasterState(objMD); const mst = getMasterState(objMD);
const vCfg = bucketMD.getVersioningConfiguration(); const vCfg = bucketMD.getVersioningConfiguration();
// bucket is not versioning configured // bucket is not versioning configured
if (!vCfg) { if (!vCfg) {
options.dataToDelete = mst.objLocation; const options = { dataToDelete: mst.objLocation };
return process.nextTick(callback, null, options); return process.nextTick(callback, null, options);
} }
// bucket is versioning configured // bucket is versioning configured
return async.waterfall([ const { options, storeOptions, delOptions } =
function processState(next) { processVersioningState(mst, vCfg.Status);
processVersioningState(mst, vCfg.Status, return async.series([
(err, options, storeOptions, delOptions) => { function storeVersion(next) {
process.nextTick(next, err, options, storeOptions,
delOptions);
});
},
function storeVersion(options, storeOptions, delOptions, next) {
if (!storeOptions) { if (!storeOptions) {
return process.nextTick(next, null, options, delOptions); return process.nextTick(next);
} }
const versionMD = Object.assign({}, objMD, storeOptions); const versionMD = Object.assign({}, objMD, storeOptions);
const params = { versionId: storeOptions.versionId }; const params = { versionId: storeOptions.versionId };
return _storeNullVersionMD(bucketName, objectKey, versionMD, return _storeNullVersionMD(bucketName, objectKey, versionMD,
params, log, err => next(err, options, delOptions)); params, log, next);
}, },
function deleteNullVersion(options, delOptions, next) { function deleteNullVersion(next) {
if (!delOptions) { if (!delOptions) {
return process.nextTick(next, null, options); return process.nextTick(next);
} }
return _deleteNullVersionMD(bucketName, objectKey, delOptions, mst, return _deleteNullVersionMD(bucketName, objectKey, delOptions, mst,
log, (err, nullDataToDelete) => { log, (err, nullDataToDelete) => {
@ -259,10 +298,10 @@ function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
return next(errors.InternalError); return next(errors.InternalError);
} }
Object.assign(options, { dataToDelete: nullDataToDelete }); Object.assign(options, { dataToDelete: nullDataToDelete });
return next(null, options); return next();
}); });
}, },
], (err, options) => callback(err, options)); ], err => callback(err, options));
} }
/** preprocessingVersioningDelete - return versioning information for S3 to /** preprocessingVersioningDelete - return versioning information for S3 to
@ -291,6 +330,9 @@ function preprocessingVersioningDelete(bucketName, bucketMD, objectMD,
// deleting a specific version // deleting a specific version
options.deleteData = true; options.deleteData = true;
options.versionId = reqVersionId; options.versionId = reqVersionId;
if (objectMD.uploadId) {
options.replayId = objectMD.uploadId;
}
return callback(null, options); return callback(null, options);
} }
if (reqVersionId) { if (reqVersionId) {
@ -298,18 +340,26 @@ function preprocessingVersioningDelete(bucketName, bucketMD, objectMD,
if (objectMD.versionId === undefined) { if (objectMD.versionId === undefined) {
// object is not versioned, deleting it // object is not versioned, deleting it
options.deleteData = true; options.deleteData = true;
// non-versioned (non-null) MPU objects don't have a
// replay ID, so don't reference their uploadId
return callback(null, options); return callback(null, options);
} }
if (objectMD.isNull) { if (objectMD.isNull) {
// master is the null version // master is the null version
options.deleteData = true; options.deleteData = true;
options.versionId = objectMD.versionId; options.versionId = objectMD.versionId;
if (objectMD.uploadId) {
options.replayId = objectMD.uploadId;
}
return callback(null, options); return callback(null, options);
} }
if (objectMD.nullVersionId) { if (objectMD.nullVersionId) {
// null version exists, deleting it // null version exists, deleting it
options.deleteData = true; options.deleteData = true;
options.versionId = objectMD.nullVersionId; options.versionId = objectMD.nullVersionId;
if (objectMD.nullUploadId) {
options.replayId = objectMD.nullUploadId;
}
return callback(null, options); return callback(null, options);
} }
// null version does not exist, no deletion // null version does not exist, no deletion
@ -324,6 +374,8 @@ module.exports = {
decodeVersionId, decodeVersionId,
getVersionIdResHeader, getVersionIdResHeader,
checkQueryVersionId, checkQueryVersionId,
processVersioningState,
getMasterState,
versioningPreprocessing, versioningPreprocessing,
preprocessingVersioningDelete, preprocessingVersioningDelete,
}; };

View File

@ -336,6 +336,7 @@ function completeMultipartUpload(authInfo, request, log, callback) {
metaStoreParams.versioning = options.versioning; metaStoreParams.versioning = options.versioning;
metaStoreParams.isNull = options.isNull; metaStoreParams.isNull = options.isNull;
metaStoreParams.nullVersionId = options.nullVersionId; metaStoreParams.nullVersionId = options.nullVersionId;
metaStoreParams.nullUploadId = options.nullUploadId;
return next(null, destBucket, dataLocations, return next(null, destBucket, dataLocations,
metaStoreParams, mpuBucket, keysToDelete, aggregateETag, metaStoreParams, mpuBucket, keysToDelete, aggregateETag,
objMD, extraPartLocations, pseudoCipherBundle, objMD, extraPartLocations, pseudoCipherBundle,

View File

@ -5,6 +5,7 @@ const getMetaHeaders = s3middleware.userMetadata.getMetaHeaders;
const convertToXml = s3middleware.convertToXml; const convertToXml = s3middleware.convertToXml;
const { pushMetric } = require('../utapi/utilities'); const { pushMetric } = require('../utapi/utilities');
const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const collectCorsHeaders = require('../utilities/collectCorsHeaders');
const { hasNonPrintables } = require('../utilities/stringChecks');
const { cleanUpBucket } = require('./apiUtils/bucket/bucketCreation'); const { cleanUpBucket } = require('./apiUtils/bucket/bucketCreation');
const constants = require('../../constants'); const constants = require('../../constants');
const services = require('../services'); const services = require('../services');
@ -49,6 +50,13 @@ function initiateMultipartUpload(authInfo, request, log, callback) {
log.debug('processing request', { method: 'initiateMultipartUpload' }); log.debug('processing request', { method: 'initiateMultipartUpload' });
const bucketName = request.bucketName; const bucketName = request.bucketName;
const objectKey = request.objectKey; const objectKey = request.objectKey;
if (hasNonPrintables(objectKey)) {
return callback(errors.InvalidInput.customizeDescription(
'object keys cannot contain non-printable characters'
));
}
// Note that we are using the string set forth in constants.js // Note that we are using the string set forth in constants.js
// to split components in the storage // to split components in the storage
// of each MPU. AWS does not restrict characters in object keys so // of each MPU. AWS does not restrict characters in object keys so

View File

@ -435,6 +435,8 @@ function objectCopy(authInfo, request, sourceBucket,
storeMetadataParams.isNull = options.isNull; storeMetadataParams.isNull = options.isNull;
// eslint-disable-next-line // eslint-disable-next-line
storeMetadataParams.nullVersionId = options.nullVersionId; storeMetadataParams.nullVersionId = options.nullVersionId;
// eslint-disable-next-line
storeMetadataParams.nullUploadId = options.nullUploadId;
const dataToDelete = options.dataToDelete; const dataToDelete = options.dataToDelete;
return next(null, storeMetadataParams, destDataGetInfoArr, return next(null, storeMetadataParams, destDataGetInfoArr,
destObjMD, serverSideEncryption, destBucketMD, destObjMD, serverSideEncryption, destBucketMD,

View File

@ -10,6 +10,7 @@ const { checkQueryVersionId } = require('./apiUtils/object/versioning');
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils'); const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
const { pushMetric } = require('../utapi/utilities'); const { pushMetric } = require('../utapi/utilities');
const { validateHeaders } = require('./apiUtils/object/objectLockHelpers'); const { validateHeaders } = require('./apiUtils/object/objectLockHelpers');
const { hasNonPrintables } = require('../utilities/stringChecks');
const kms = require('../kms/wrapper'); const kms = require('../kms/wrapper');
const { config } = require('../Config'); const { config } = require('../Config');
@ -58,6 +59,13 @@ function objectPut(authInfo, request, streamingV4Params, log, callback) {
const requestType = 'objectPut'; const requestType = 'objectPut';
const valParams = { authInfo, bucketName, objectKey, requestType, request }; const valParams = { authInfo, bucketName, objectKey, requestType, request };
const canonicalID = authInfo.getCanonicalID(); const canonicalID = authInfo.getCanonicalID();
if (hasNonPrintables(objectKey)) {
return callback(errors.InvalidInput.customizeDescription(
'object keys can not contain non-printable characters'
));
}
log.trace('owner canonicalID to send to data', { canonicalID }); log.trace('owner canonicalID to send to data', { canonicalID });
return metadataValidateBucketAndObj(valParams, log, return metadataValidateBucketAndObj(valParams, log,

View File

@ -190,6 +190,10 @@ class BucketFileInterface {
if (err) { if (err) {
return cb(err); return cb(err);
} }
// Ignore the PUT done by AbortMPU
if (params && params.isAbort) {
return cb();
}
db.withRequestLogger(log) db.withRequestLogger(log)
.put(objName, JSON.stringify(objVal), params, (err, data) => { .put(objName, JSON.stringify(objVal), params, (err, data) => {
if (err) { if (err) {

View File

@ -79,7 +79,11 @@ const metastore = {
putObject: (bucketName, objName, objVal, params, log, cb) => { putObject: (bucketName, objName, objVal, params, log, cb) => {
process.nextTick(() => { process.nextTick(() => {
metastore.getBucketAttributes(bucketName, log, err => { // Ignore the PUT done by AbortMPU
if (params && params.isAbort) {
return cb(null);
}
return metastore.getBucketAttributes(bucketName, log, err => {
if (err) { if (err) {
return cb(err); return cb(err);
} }

View File

@ -96,7 +96,8 @@ const services = {
const { objectKey, authInfo, size, contentMD5, metaHeaders, const { objectKey, authInfo, size, contentMD5, metaHeaders,
contentType, cacheControl, contentDisposition, contentEncoding, contentType, cacheControl, contentDisposition, contentEncoding,
expires, multipart, headers, overrideMetadata, log, expires, multipart, headers, overrideMetadata, log,
lastModifiedDate, versioning, versionId, tagging, taggingCopy, lastModifiedDate, versioning, versionId, uploadId,
tagging, taggingCopy,
replicationInfo, defaultRetention, dataStoreName, replicationInfo, defaultRetention, dataStoreName,
retentionMode, retentionDate, legalHold, originOp } = params; retentionMode, retentionDate, legalHold, originOp } = params;
log.trace('storing object in metadata'); log.trace('storing object in metadata');
@ -157,11 +158,16 @@ const services = {
if (versionId || versionId === '') { if (versionId || versionId === '') {
options.versionId = versionId; options.versionId = versionId;
} }
if (uploadId) {
md.setUploadId(uploadId);
options.replayId = uploadId;
}
// information to store about the version and the null version id // information to store about the version and the null version id
// in the object metadata // in the object metadata
const { isNull, nullVersionId, isDeleteMarker } = params; const { isNull, nullVersionId, nullUploadId, isDeleteMarker } = params;
md.setIsNull(isNull) md.setIsNull(isNull)
.setNullVersionId(nullVersionId) .setNullVersionId(nullVersionId)
.setNullUploadId(nullUploadId)
.setIsDeleteMarker(isDeleteMarker); .setIsDeleteMarker(isDeleteMarker);
if (versionId && versionId !== 'null') { if (versionId && versionId !== 'null') {
md.setVersionId(versionId); md.setVersionId(versionId);
@ -735,6 +741,23 @@ const services = {
metadata.deleteObjectMD(mpuBucketName, key, {}, log, callback); metadata.deleteObjectMD(mpuBucketName, key, {}, log, callback);
}, err => cb(err)); }, err => cb(err));
}, },
/**
*
* @param {string} bucketName - MPU destination bucket name
* @param {string} objectKey - MPU destination key
* @param {string} uploadId - MPU uploadId
* @param {object} log - werelogs logger
* @param {Function} cb - callback called with possible error
* @returns {undefined} -
*/
sendAbortMPUPut(bucketName, objectKey, uploadId, log, cb) {
const storeParams = {
isAbort: true,
replayId: uploadId,
};
metadata.putObjectMD(bucketName, objectKey, {}, storeParams, log, cb);
},
}; };
module.exports = services; module.exports = services;

View File

@ -0,0 +1,20 @@
/**
* hasNonPrintables returns whether or not the given value
* includes characters that are non-printable.
*
* @param {string} value - value to check for non-printables
* @returns {Boolean} whether or not the value has non-printables
*/
function hasNonPrintables(value) {
for (const char of value) {
const codePoint = char.codePointAt(0);
if (codePoint < 32 || codePoint === 127) {
return true;
}
}
return false;
}
module.exports = {
hasNonPrintables,
};

View File

@ -20,7 +20,7 @@
"homepage": "https://github.com/scality/S3#readme", "homepage": "https://github.com/scality/S3#readme",
"dependencies": { "dependencies": {
"@hapi/joi": "^17.1.0", "@hapi/joi": "^17.1.0",
"arsenal": "github:scality/Arsenal#7.10.2", "arsenal": "github:scality/Arsenal#5772c37",
"async": "~2.5.0", "async": "~2.5.0",
"aws-sdk": "2.905.0", "aws-sdk": "2.905.0",
"azure-storage": "^2.1.0", "azure-storage": "^2.1.0",
@ -34,7 +34,6 @@
"level-mem": "^5.0.1", "level-mem": "^5.0.1",
"moment": "^2.26.0", "moment": "^2.26.0",
"npm-run-all": "~4.1.5", "npm-run-all": "~4.1.5",
"sinon": "^9.0.2",
"sproxydclient": "scality/sproxydclient#8.0.2", "sproxydclient": "scality/sproxydclient#8.0.2",
"utapi": "scality/utapi#7.10.2", "utapi": "scality/utapi#7.10.2",
"utf8": "~2.1.1", "utf8": "~2.1.1",
@ -56,6 +55,7 @@
"mocha-junit-reporter": "^1.23.1", "mocha-junit-reporter": "^1.23.1",
"mocha-multi-reporters": "^1.1.7", "mocha-multi-reporters": "^1.1.7",
"node-mocks-http": "1.5.2", "node-mocks-http": "1.5.2",
"sinon": "^13.0.1",
"tv4": "^1.2.7" "tv4": "^1.2.7"
}, },
"scripts": { "scripts": {

View File

@ -0,0 +1,437 @@
const assert = require('assert');
const { errors, versioning } = require('arsenal');
const { config } = require('../../../../lib/Config');
const INF_VID = versioning.VersionID.getInfVid(config.replicationGroupId);
const { processVersioningState, getMasterState,
preprocessingVersioningDelete } =
require('../../../../lib/api/apiUtils/object/versioning');
describe('versioning helpers', () => {
describe('getMasterState+processVersioningState', () => {
[
{
description: 'no prior version exists',
objMD: null,
versioningEnabledExpectedRes: {
options: {
versioning: true,
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
},
},
{
description: 'prior non-null object version exists',
objMD: {
versionId: 'v1',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
},
},
{
description: 'prior MPU object non-null version exists',
objMD: {
versionId: 'v1',
uploadId: 'fooUploadId',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
},
},
{
description: 'prior null object version exists',
objMD: {
versionId: 'vnull',
isNull: true,
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: 'vnull',
},
// instruct to first copy the null version onto a
// newly created version key preserving the version ID
storeOptions: {
isNull: true,
versionId: 'vnull',
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
delOptions: {
versionId: 'vnull',
},
},
},
{
description: 'prior MPU object null version exists',
objMD: {
versionId: 'vnull',
isNull: true,
uploadId: 'fooUploadId',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: 'vnull',
nullUploadId: 'fooUploadId',
},
// instruct to first copy the null version onto a
// newly created version key preserving the version ID
storeOptions: {
isNull: true,
versionId: 'vnull',
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
delOptions: {
versionId: 'vnull',
replayId: 'fooUploadId',
},
},
},
{
description:
'prior object exists, put before versioning was first enabled',
objMD: {},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: INF_VID,
},
// instruct to first copy the null version onto a
// newly created version key as the oldest version
storeOptions: {
isNull: true,
versionId: INF_VID,
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
},
},
{
description: 'prior MPU object exists, put before versioning ' +
'was first enabled',
objMD: {
uploadId: 'fooUploadId',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: INF_VID,
},
// instruct to first copy the null version onto a
// newly created version key as the oldest version
storeOptions: {
isNull: true,
versionId: INF_VID,
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
},
},
{
description:
'prior non-null object version exists with ref to null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: 'vnull',
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
delOptions: {
versionId: 'vnull',
},
},
},
{
description: 'prior MPU object non-null version exists with ' +
'ref to null version',
objMD: {
versionId: 'v1',
uploadId: 'fooUploadId',
nullVersionId: 'vnull',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: 'vnull',
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
delOptions: {
versionId: 'vnull',
},
},
},
{
description: 'prior object non-null version exists with ' +
'ref to MPU null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
nullUploadId: 'nullFooUploadId',
},
versioningEnabledExpectedRes: {
options: {
versioning: true,
nullVersionId: 'vnull',
nullUploadId: 'nullFooUploadId',
},
},
versioningSuspendedExpectedRes: {
options: {
isNull: true,
versionId: '',
},
delOptions: {
versionId: 'vnull',
replayId: 'nullFooUploadId',
},
},
},
].forEach(testCase =>
['Enabled', 'Suspended'].forEach(versioningStatus => it(
`${testCase.description}, versioning Status=${versioningStatus}`,
() => {
const mst = getMasterState(testCase.objMD);
// stringify and parse to get rid of the "undefined"
// properties, artifacts of how the function builds the
// result
const res = JSON.parse(
JSON.stringify(
processVersioningState(mst, versioningStatus)
)
);
const expectedRes =
testCase[`versioning${versioningStatus}ExpectedRes`];
assert.deepStrictEqual(res, expectedRes);
})));
});
describe('preprocessingVersioningDelete', () => {
[
{
description: 'no reqVersionId: no delete action',
objMD: {
versionId: 'v1',
},
expectedRes: {},
},
{
description: 'delete non-null object version',
objMD: {
versionId: 'v1',
},
reqVersionId: 'v1',
expectedRes: {
deleteData: true,
versionId: 'v1',
},
},
{
description: 'delete MPU object non-null version',
objMD: {
versionId: 'v1',
uploadId: 'fooUploadId',
},
reqVersionId: 'v1',
expectedRes: {
deleteData: true,
versionId: 'v1',
replayId: 'fooUploadId',
},
},
{
description: 'delete null object version',
objMD: {
versionId: 'vnull',
isNull: true,
},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
versionId: 'vnull',
},
},
{
description: 'delete MPU object null version',
objMD: {
versionId: 'vnull',
isNull: true,
uploadId: 'fooUploadId',
},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
versionId: 'vnull',
replayId: 'fooUploadId',
},
},
{
description:
'delete object put before versioning was first enabled',
objMD: {},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
},
},
{
description:
'delete MPU object put before versioning was first enabled',
objMD: {
uploadId: 'fooUploadId',
},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
},
},
{
description:
'delete non-null object version with ref to null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
},
reqVersionId: 'v1',
expectedRes: {
deleteData: true,
versionId: 'v1',
},
},
{
description:
'delete MPU object non-null version with ref to null version',
objMD: {
versionId: 'v1',
uploadId: 'fooUploadId',
nullVersionId: 'vnull',
},
reqVersionId: 'v1',
expectedRes: {
deleteData: true,
versionId: 'v1',
replayId: 'fooUploadId',
},
},
{
description:
'delete non-null object version with ref to MPU null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
nullUploadId: 'nullFooUploadId',
},
reqVersionId: 'v1',
expectedRes: {
deleteData: true,
versionId: 'v1',
},
},
{
description:
'delete null object version from ref to null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
versionId: 'vnull',
},
},
{
description:
'delete MPU object null version from ref to null version',
objMD: {
versionId: 'v1',
nullVersionId: 'vnull',
nullUploadId: 'nullFooUploadId',
},
reqVersionId: 'null',
expectedRes: {
deleteData: true,
versionId: 'vnull',
replayId: 'nullFooUploadId',
},
},
{
description: 'delete null version that does not exist',
objMD: {
versionId: 'v1',
},
reqVersionId: 'null',
expectedError: errors.NoSuchKey,
},
].forEach(testCase => it(testCase.description, done => {
const mockBucketMD = {
getVersioningConfiguration: () => ({ Status: 'Enabled' }),
};
preprocessingVersioningDelete(
'foobucket', mockBucketMD, testCase.objMD,
testCase.reqVersionId, null, (err, options) => {
if (testCase.expectedError) {
assert.strictEqual(err, testCase.expectedError);
} else {
assert.ifError(err);
assert.deepStrictEqual(options, testCase.expectedRes);
}
done();
});
}));
});
});

View File

@ -1,11 +1,13 @@
const assert = require('assert'); const assert = require('assert');
const async = require('async'); const async = require('async');
const { parseString } = require('xml2js'); const { parseString } = require('xml2js');
const sinon = require('sinon');
const { errors } = require('arsenal'); const { errors } = require('arsenal');
const { cleanup, DummyRequestLogger } = require('../helpers'); const { cleanup, DummyRequestLogger } = require('../helpers');
const { config } = require('../../../lib/Config'); const { config } = require('../../../lib/Config');
const services = require('../../../lib/services');
const DummyRequest = require('../DummyRequest'); const DummyRequest = require('../DummyRequest');
const { bucketPut } = require('../../../lib/api/bucketPut'); const { bucketPut } = require('../../../lib/api/bucketPut');
const initiateMultipartUpload const initiateMultipartUpload
@ -40,6 +42,7 @@ function _createAndAbortMpu(usEastSetting, fakeUploadID, locationConstraint,
callback) { callback) {
config.locationConstraints['us-east-1'].legacyAwsBehavior = config.locationConstraints['us-east-1'].legacyAwsBehavior =
usEastSetting; usEastSetting;
let uploadId;
const post = '<?xml version="1.0" encoding="UTF-8"?>' + const post = '<?xml version="1.0" encoding="UTF-8"?>' +
'<CreateBucketConfiguration ' + '<CreateBucketConfiguration ' +
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' + 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
@ -54,7 +57,7 @@ function _createAndAbortMpu(usEastSetting, fakeUploadID, locationConstraint,
(json, next) => { (json, next) => {
// use uploadId parsed from initiateMpu request to construct // use uploadId parsed from initiateMpu request to construct
// uploadPart and deleteMpu requests // uploadPart and deleteMpu requests
const uploadId = uploadId =
json.InitiateMultipartUploadResult.UploadId[0]; json.InitiateMultipartUploadResult.UploadId[0];
const partBody = Buffer.from('I am a part\n', 'utf8'); const partBody = Buffer.from('I am a part\n', 'utf8');
const partRequest = new DummyRequest({ const partRequest = new DummyRequest({
@ -89,7 +92,7 @@ function _createAndAbortMpu(usEastSetting, fakeUploadID, locationConstraint,
}), }),
(deleteMpuRequest, next) => (deleteMpuRequest, next) =>
multipartDelete(authInfo, deleteMpuRequest, log, next), multipartDelete(authInfo, deleteMpuRequest, log, next),
], callback); ], err => callback(err, uploadId));
} }
describe('Multipart Delete API', () => { describe('Multipart Delete API', () => {
@ -136,4 +139,15 @@ describe('Multipart Delete API', () => {
done(); done();
}); });
}); });
it('should send a PUT to bucketd with `isAbort` and `replayId`', done => {
const spy = sinon.spy(services, 'sendAbortMPUPut');
_createAndAbortMpu(true, false, eastLocation, (err, uploadId) => {
assert.ifError(err);
assert.strictEqual(spy.calledOnce, true);
assert.strictEqual(
spy.calledOnceWith(bucketName, objectKey, uploadId), true);
done();
});
});
}); });

View File

@ -557,6 +557,7 @@ describe('Multipart Upload API', () => {
assert(MD); assert(MD);
assert.strictEqual(MD['x-amz-meta-stuff'], assert.strictEqual(MD['x-amz-meta-stuff'],
'I am some user metadata'); 'I am some user metadata');
assert.strictEqual(MD.uploadId, testUploadId);
done(); done();
}); });
}); });
@ -1375,7 +1376,7 @@ describe('Multipart Upload API', () => {
}); });
it('should return no error if attempt to abort/delete ' + it('should return no error if attempt to abort/delete ' +
'a multipart upload that does not exist and not using' + 'a multipart upload that does not exist and not using ' +
'legacyAWSBehavior', done => { 'legacyAWSBehavior', done => {
async.waterfall([ async.waterfall([
next => bucketPut(authInfo, bucketPutRequest, log, next), next => bucketPut(authInfo, bucketPutRequest, log, next),
@ -1629,7 +1630,7 @@ describe('Multipart Upload API', () => {
}); });
}); });
it('should throw an error on put of an object part with an invalid' + it('should throw an error on put of an object part with an invalid ' +
'uploadId', done => { 'uploadId', done => {
const testUploadId = 'invalidUploadID'; const testUploadId = 'invalidUploadID';
const partRequest = new DummyRequest({ const partRequest = new DummyRequest({
@ -1793,12 +1794,14 @@ describe('complete mpu with versioning', () => {
versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Enabled'); versioningTestUtils.createBucketPutVersioningReq(bucketName, 'Enabled');
const suspendVersioningRequest = versioningTestUtils const suspendVersioningRequest = versioningTestUtils
.createBucketPutVersioningReq(bucketName, 'Suspended'); .createBucketPutVersioningReq(bucketName, 'Suspended');
const testPutObjectRequests = objData.slice(0, 2).map(data => let testPutObjectRequests;
versioningTestUtils.createPutObjectRequest(bucketName, objectKey,
data));
beforeEach(done => { beforeEach(done => {
cleanup(); cleanup();
testPutObjectRequests = objData
.slice(0, 2)
.map(data => versioningTestUtils.createPutObjectRequest(
bucketName, objectKey, data));
bucketPut(authInfo, bucketPutRequest, log, done); bucketPut(authInfo, bucketPutRequest, log, done);
}); });
@ -1808,7 +1811,58 @@ describe('complete mpu with versioning', () => {
}); });
it('should delete null version when creating new null version, ' + it('should delete null version when creating new null version, ' +
'even when null version is not the latest version', done => { 'when null version is the latest version', done => {
async.waterfall([
next => bucketPutVersioning(authInfo,
suspendVersioningRequest, log, err => next(err)),
next => initiateMultipartUpload(
authInfo, initiateRequest, log, next),
(result, corsHeaders, next) => parseString(result, next),
(json, next) => {
const partBody = objData[2];
const testUploadId =
json.InitiateMultipartUploadResult.UploadId[0];
const partRequest = _createPutPartRequest(testUploadId, 1,
partBody);
objectPutPart(authInfo, partRequest, undefined, log,
(err, eTag) => next(err, eTag, testUploadId));
},
(eTag, testUploadId, next) => {
const origPutObject = metadataBackend.putObject;
metadataBackend.putObject =
(bucketName, objName, objVal, params, log, cb) => {
assert.strictEqual(params.replayId, testUploadId);
metadataBackend.putObject = origPutObject;
metadataBackend.putObject(
bucketName, objName, objVal, params, log, cb);
};
const parts = [{ partNumber: 1, eTag }];
const completeRequest = _createCompleteMpuRequest(testUploadId,
parts);
completeMultipartUpload(authInfo, completeRequest, log,
err => next(err, testUploadId));
},
(testUploadId, next) => {
const origDeleteObject = metadataBackend.deleteObject;
metadataBackend.deleteObject =
(bucketName, objName, params, log, cb) => {
assert.strictEqual(params.replayId, testUploadId);
metadataBackend.deleteObject = origDeleteObject;
metadataBackend.deleteObject(
bucketName, objName, params, log, cb);
};
// overwrite null version with a non-MPU object
objectPut(authInfo, testPutObjectRequests[1],
undefined, log, err => next(err));
},
], err => {
assert.ifError(err, `Unexpected err: ${err}`);
done();
});
});
it('should delete null version when creating new null version, ' +
'when null version is not the latest version', done => {
async.waterfall([ async.waterfall([
// putting null version: put obj before versioning configured // putting null version: put obj before versioning configured
next => objectPut(authInfo, testPutObjectRequests[0], next => objectPut(authInfo, testPutObjectRequests[0],
@ -1836,18 +1890,39 @@ describe('complete mpu with versioning', () => {
(err, eTag) => next(err, eTag, testUploadId)); (err, eTag) => next(err, eTag, testUploadId));
}, },
(eTag, testUploadId, next) => { (eTag, testUploadId, next) => {
const origPutObject = metadataBackend.putObject;
metadataBackend.putObject =
(bucketName, objName, objVal, params, log, cb) => {
assert.strictEqual(params.replayId, testUploadId);
metadataBackend.putObject = origPutObject;
metadataBackend.putObject(
bucketName, objName, objVal, params, log, cb);
};
const parts = [{ partNumber: 1, eTag }]; const parts = [{ partNumber: 1, eTag }];
const completeRequest = _createCompleteMpuRequest(testUploadId, const completeRequest = _createCompleteMpuRequest(testUploadId,
parts); parts);
completeMultipartUpload(authInfo, completeRequest, log, next); completeMultipartUpload(authInfo, completeRequest, log,
err => next(err, testUploadId));
},
(testUploadId, next) => {
versioningTestUtils.assertDataStoreValues(
ds, [undefined, objData[1], objData[2]]);
const origDeleteObject = metadataBackend.deleteObject;
metadataBackend.deleteObject =
(bucketName, objName, params, log, cb) => {
assert.strictEqual(params.replayId, testUploadId);
metadataBackend.deleteObject = origDeleteObject;
metadataBackend.deleteObject(
bucketName, objName, params, log, cb);
};
// overwrite null version with a non-MPU object
objectPut(authInfo, testPutObjectRequests[1],
undefined, log, err => next(err));
}, },
], err => { ], err => {
assert.ifError(err, `Unexpected err: ${err}`); assert.ifError(err, `Unexpected err: ${err}`);
process.nextTick(() => { done();
versioningTestUtils.assertDataStoreValues(ds, [undefined,
objData[1], objData[2]]);
done(err);
});
}); });
}); });

View File

@ -1,5 +1,8 @@
const assert = require('assert'); const assert = require('assert');
const async = require('async');
const crypto = require('crypto');
const { errors } = require('arsenal'); const { errors } = require('arsenal');
const xml2js = require('xml2js');
const { bucketPut } = require('../../../lib/api/bucketPut'); const { bucketPut } = require('../../../lib/api/bucketPut');
const bucketPutACL = require('../../../lib/api/bucketPutACL'); const bucketPutACL = require('../../../lib/api/bucketPutACL');
@ -8,6 +11,11 @@ const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers');
const objectPut = require('../../../lib/api/objectPut'); const objectPut = require('../../../lib/api/objectPut');
const objectDelete = require('../../../lib/api/objectDelete'); const objectDelete = require('../../../lib/api/objectDelete');
const objectGet = require('../../../lib/api/objectGet'); const objectGet = require('../../../lib/api/objectGet');
const initiateMultipartUpload
= require('../../../lib/api/initiateMultipartUpload');
const objectPutPart = require('../../../lib/api/objectPutPart');
const completeMultipartUpload
= require('../../../lib/api/completeMultipartUpload');
const DummyRequest = require('../DummyRequest'); const DummyRequest = require('../DummyRequest');
const log = new DummyRequestLogger(); const log = new DummyRequestLogger();
@ -69,6 +77,14 @@ describe('objectDelete API', () => {
url: `/${bucketName}/${objectKey}`, url: `/${bucketName}/${objectKey}`,
}); });
const initiateMPURequest = {
bucketName,
namespace,
objectKey,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectKey}?uploads`,
};
it('should delete an object', done => { it('should delete an object', done => {
bucketPut(authInfo, testBucketPutRequest, log, () => { bucketPut(authInfo, testBucketPutRequest, log, () => {
objectPut(authInfo, testPutObjectRequest, objectPut(authInfo, testPutObjectRequest,
@ -112,6 +128,57 @@ describe('objectDelete API', () => {
}); });
}); });
it('should delete a multipart upload', done => {
const partBody = Buffer.from('I am a part\n', 'utf8');
let testUploadId;
let calculatedHash;
async.waterfall([
next => bucketPut(authInfo, testBucketPutRequest, log, next),
(corsHeaders, next) => initiateMultipartUpload(authInfo,
initiateMPURequest, log, next),
(result, corsHeaders, next) => xml2js.parseString(result, next),
(json, next) => {
testUploadId = json.InitiateMultipartUploadResult.UploadId[0];
const md5Hash = crypto.createHash('md5').update(partBody);
calculatedHash = md5Hash.digest('hex');
const partRequest = new DummyRequest({
bucketName,
namespace,
objectKey,
headers: { host: `${bucketName}.s3.amazonaws.com` },
url: `/${objectKey}?partNumber=1&uploadId=${testUploadId}`,
query: {
partNumber: '1',
uploadId: testUploadId,
},
calculatedHash,
}, partBody);
objectPutPart(authInfo, partRequest, undefined, log, next);
},
(hexDigest, corsHeaders, next) => {
const completeBody = '<CompleteMultipartUpload>' +
'<Part>' +
'<PartNumber>1</PartNumber>' +
`<ETag>"${calculatedHash}"</ETag>` +
'</Part>' +
'</CompleteMultipartUpload>';
const completeRequest = {
bucketName,
namespace,
objectKey,
parsedHost: 's3.amazonaws.com',
url: `/${objectKey}?uploadId=${testUploadId}`,
headers: { host: `${bucketName}.s3.amazonaws.com` },
query: { uploadId: testUploadId },
post: completeBody,
};
completeMultipartUpload(authInfo, completeRequest, log, next);
},
(result, resHeaders, next) =>
objectDelete(authInfo, testDeleteRequest, log, next),
], done);
});
it('should prevent anonymous user deleteObject API access', done => { it('should prevent anonymous user deleteObject API access', done => {
const publicAuthInfo = makeAuthInfo(constants.publicId); const publicAuthInfo = makeAuthInfo(constants.publicId);
bucketPut(authInfo, testBucketPutRequest, log, () => { bucketPut(authInfo, testBucketPutRequest, log, () => {

View File

@ -105,24 +105,31 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1"
integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==
"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0":
version "1.8.2" version "1.8.2"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b"
integrity sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw== integrity sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==
dependencies: dependencies:
type-detect "4.0.8" type-detect "4.0.8"
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": "@sinonjs/commons@^1.8.3":
version "6.0.1" version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==
dependencies:
type-detect "4.0.8"
"@sinonjs/fake-timers@>=5", "@sinonjs/fake-timers@^9.0.0":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.0.tgz#8c92c56f195e0bed4c893ba59c8e3d55831ca0df"
integrity sha512-M8vapsv9qQupMdzrVzkn5rb9jG7aUTEPAZdMtME2PuBaefksFZVE2C1g4LBRTkF/k3nRDNbDc5tp5NFC1PEYxA==
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@sinonjs/commons" "^1.7.0"
"@sinonjs/samsam@^5.3.1": "@sinonjs/samsam@^6.1.1":
version "5.3.1" version "6.1.1"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.1.1.tgz#627f7f4cbdb56e6419fa2c1a3e4751ce4f6a00b1"
integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== integrity sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==
dependencies: dependencies:
"@sinonjs/commons" "^1.6.0" "@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2" lodash.get "^4.4.2"
@ -629,9 +636,9 @@ arraybuffer.slice@~0.0.7:
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
"arsenal@github:scality/Arsenal#7.10.2": "arsenal@github:scality/Arsenal#5772c37":
version "7.10.2" version "7.10.4"
resolved "https://codeload.github.com/scality/Arsenal/tar.gz/07a110ff86a67884d9f992e4570fddd529435729" resolved "https://codeload.github.com/scality/Arsenal/tar.gz/5772c37b00505ba99eb339c161f8299c2e60449a"
dependencies: dependencies:
"@hapi/joi" "^15.1.0" "@hapi/joi" "^15.1.0"
JSONStream "^1.0.0" JSONStream "^1.0.0"
@ -1825,11 +1832,16 @@ diff@1.4.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8=
diff@^4.0.1, diff@^4.0.2: diff@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diff@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
diskusage@1.1.3, diskusage@^1.1.1, diskusage@^1.1.3: diskusage@1.1.3, diskusage@^1.1.1, diskusage@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b" resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b"
@ -4538,13 +4550,13 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nise@^4.0.4: nise@^5.1.1:
version "4.1.0" version "5.1.1"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.1.tgz#ac4237e0d785ecfcb83e20f389185975da5c31f3"
integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== integrity sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@sinonjs/commons" "^1.8.3"
"@sinonjs/fake-timers" "^6.0.0" "@sinonjs/fake-timers" ">=5"
"@sinonjs/text-encoding" "^0.7.1" "@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2" just-extend "^4.0.2"
path-to-regexp "^1.7.0" path-to-regexp "^1.7.0"
@ -5657,17 +5669,17 @@ simple-swizzle@^0.2.2:
dependencies: dependencies:
is-arrayish "^0.3.1" is-arrayish "^0.3.1"
sinon@^9.0.2: sinon@^13.0.1:
version "9.2.4" version "13.0.1"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" resolved "https://registry.yarnpkg.com/sinon/-/sinon-13.0.1.tgz#2a568beca2084c48985dd98e276e065c81738e3c"
integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== integrity sha512-8yx2wIvkBjIq/MGY1D9h1LMraYW+z1X0mb648KZnKSdvLasvDu7maa0dFaNYdTDczFgbjNw2tOmWdTk9saVfwQ==
dependencies: dependencies:
"@sinonjs/commons" "^1.8.1" "@sinonjs/commons" "^1.8.3"
"@sinonjs/fake-timers" "^6.0.1" "@sinonjs/fake-timers" "^9.0.0"
"@sinonjs/samsam" "^5.3.1" "@sinonjs/samsam" "^6.1.1"
diff "^4.0.2" diff "^5.0.0"
nise "^4.0.4" nise "^5.1.1"
supports-color "^7.1.0" supports-color "^7.2.0"
slice-ansi@0.0.4: slice-ansi@0.0.4:
version "0.0.4" version "0.0.4"
@ -6397,7 +6409,7 @@ supports-color@^5.3.0:
dependencies: dependencies:
has-flag "^3.0.0" has-flag "^3.0.0"
supports-color@^7.1.0: supports-color@^7.1.0, supports-color@^7.2.0:
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==