Compare commits
4 Commits
developmen
...
feature/S3
Author | SHA1 | Date |
---|---|---|
Ilke | 997a5d5e6e | |
Ilke | 8e45e4c4f7 | |
Ilke | 9279a64b36 | |
Dora Korpar | c5e21e0719 |
|
@ -284,6 +284,10 @@
|
||||||
"code": 404,
|
"code": 404,
|
||||||
"description": "The specified bucket does not have a bucket policy."
|
"description": "The specified bucket does not have a bucket policy."
|
||||||
},
|
},
|
||||||
|
"ObjectLockConfigurationNotFoundError": {
|
||||||
|
"code": 404,
|
||||||
|
"description": "Object Lock configuration does not exist for this bucket."
|
||||||
|
},
|
||||||
"OperationAborted": {
|
"OperationAborted": {
|
||||||
"code": 409,
|
"code": 409,
|
||||||
"description": "A conflicting conditional operation is currently in progress against this resource. Try again."
|
"description": "A conflicting conditional operation is currently in progress against this resource. Try again."
|
||||||
|
|
2
index.js
2
index.js
|
@ -112,6 +112,8 @@ module.exports = {
|
||||||
LifecycleConfiguration:
|
LifecycleConfiguration:
|
||||||
require('./lib/models/LifecycleConfiguration'),
|
require('./lib/models/LifecycleConfiguration'),
|
||||||
BucketPolicy: require('./lib/models/BucketPolicy'),
|
BucketPolicy: require('./lib/models/BucketPolicy'),
|
||||||
|
ObjectLockConfiguration:
|
||||||
|
require('./lib/models/ObjectLockConfiguration'),
|
||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
StatsClient: require('./lib/metrics/StatsClient'),
|
StatsClient: require('./lib/metrics/StatsClient'),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const errors = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format of xml request:
|
* Format of xml request:
|
||||||
*
|
*
|
||||||
|
@ -36,6 +38,128 @@ class ObjectLockConfiguration {
|
||||||
this._config = {};
|
this._config = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the object lock configuration
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
getValidatedObjectLockConfiguration() {
|
||||||
|
const validConfig = this._parseObjectLockConfig();
|
||||||
|
if (validConfig.error) {
|
||||||
|
this._config.error = validConfig.error;
|
||||||
|
}
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that mode is valid
|
||||||
|
* @param {array} mode - array containing mode value
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseMode(mode) {
|
||||||
|
const validMode = {};
|
||||||
|
const expectedModes = ['GOVERNANCE', 'COMPLIANCE'];
|
||||||
|
if (!mode || !mode[0] || mode[0] === '') {
|
||||||
|
validMode.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Mode');
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
if (!expectedModes.includes(mode[0])) {
|
||||||
|
validMode.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Mode request xml must be one of "GOVERNANCE", "COMPLIANCE"');
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
validMode.mode = mode[0];
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that time limit is valid
|
||||||
|
* @param {object} dr - DefaultRetention object containing days or years
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseTime(dr) {
|
||||||
|
const validTime = {};
|
||||||
|
if (dr.Days && dr.Years) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains both Days and Years');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
const timeType = dr.Days ? 'Days' : 'Years';
|
||||||
|
if (!dr[timeType] || !dr[timeType][0] || dr[timeType][0] === '') {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Days or Years');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
const timeValue = Number.parseInt(dr[timeType][0], 10);
|
||||||
|
if (Number.isNaN(timeValue)) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain valid retention period');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
validTime.timeType = timeType.toLowerCase();
|
||||||
|
validTime.timeValue = timeValue;
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that object lock configuration is valid
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseObjectLockConfig() {
|
||||||
|
const validConfig = {};
|
||||||
|
if (!this._parsedXml || this._parsedXml === '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml is undefined or empty');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const objectLockConfig = this._parsedXml.ObjectLockConfiguration;
|
||||||
|
if (!objectLockConfig && objectLockConfig !== '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not include ObjectLockConfiguration');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const objectLockEnabled = objectLockConfig.ObjectLockEnabled;
|
||||||
|
if (!objectLockEnabled || objectLockEnabled[0] !== 'Enabled') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not include valid ObjectLockEnabled');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const ruleArray = objectLockConfig.Rule;
|
||||||
|
if (ruleArray.length > 1) {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one rule');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drArray = ruleArray[0].DefaultRetention;
|
||||||
|
if (!drArray || !drArray[0] || drArray[0] === '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Rule request xml does not contain DefaultRetention');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
if (!drArray[0].Mode || (!drArray[0].Days && !drArray[0].Years)) {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'DefaultRetention request xml does not contain Mode or ' +
|
||||||
|
'retention period (Days or Years)');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const validMode = this._parseMode(drArray[0].Mode);
|
||||||
|
if (validMode.error) {
|
||||||
|
validConfig.error = validMode.error;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const validTime = this._parseTime(drArray[0]);
|
||||||
|
if (validTime.error) {
|
||||||
|
validConfig.error = validTime.error;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config.rule = {};
|
||||||
|
this._config.rule.mode = validMode.mode;
|
||||||
|
this._config.rule[validTime.timeType] = validTime.timeValue;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the bucket metadata lifecycle configuration structure and
|
* Validate the bucket metadata lifecycle configuration structure and
|
||||||
* value types
|
* value types
|
||||||
|
@ -53,6 +177,31 @@ class ObjectLockConfiguration {
|
||||||
assert.strictEqual(typeof rule.years, 'number');
|
assert.strictEqual(typeof rule.years, 'number');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the XML representation of the configuration object
|
||||||
|
* @param {object} config - The bucket object lock configuration
|
||||||
|
* @return {string} - The XML representation of the configuration
|
||||||
|
*/
|
||||||
|
static getConfigXML(config) {
|
||||||
|
const { days, years, mode } = config.rule;
|
||||||
|
const retentionDays = days !== undefined ? `<Days>${days}</Days>` : '';
|
||||||
|
const retentionYears = years !== undefined ?
|
||||||
|
`<Years>${years}</Years>` : '';
|
||||||
|
const retentionModeXml = `<Mode>${mode}</Mode>`;
|
||||||
|
const retentionTimeXml = retentionDays || retentionYears;
|
||||||
|
return '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||||
|
'<ObjectLockConfiguration' +
|
||||||
|
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
|
||||||
|
'<Rule>' +
|
||||||
|
'<DefaultRetention>' +
|
||||||
|
`${retentionModeXml}` +
|
||||||
|
`${retentionTimeXml}` +
|
||||||
|
'</DefaultRetention>' +
|
||||||
|
'</Rule>' +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ObjectLockConfiguration;
|
module.exports = ObjectLockConfiguration;
|
||||||
|
|
|
@ -78,6 +78,13 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
return routesUtils.responseXMLBody(err, xml, response,
|
return routesUtils.responseXMLBody(err, xml, response,
|
||||||
log, corsHeaders);
|
log, corsHeaders);
|
||||||
});
|
});
|
||||||
|
} else if (request.query['object-lock'] !== undefined) {
|
||||||
|
api.callApiMethod('bucketGetObjectLock', request, response, log,
|
||||||
|
(err, xml, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseXMLBody(err, xml, response,
|
||||||
|
log, corsHeaders);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// GET bucket
|
// GET bucket
|
||||||
api.callApiMethod('bucketGet', request, response, log,
|
api.callApiMethod('bucketGet', request, response, log,
|
||||||
|
@ -88,7 +95,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* eslint-disable no-lonely-if */
|
|
||||||
if (request.query.acl !== undefined) {
|
if (request.query.acl !== undefined) {
|
||||||
// GET object ACL
|
// GET object ACL
|
||||||
api.callApiMethod('objectGetACL', request, response, log,
|
api.callApiMethod('objectGetACL', request, response, log,
|
||||||
|
@ -128,7 +134,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
range, log);
|
range, log);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,13 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||||
log);
|
log);
|
||||||
});
|
});
|
||||||
|
} else if (request.query['object-lock'] !== undefined) {
|
||||||
|
api.callApiMethod('bucketPutObjectLock', request, response, log,
|
||||||
|
(err, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||||
|
log);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// PUT bucket
|
// PUT bucket
|
||||||
return api.callApiMethod('bucketPut', request, response, log,
|
return api.callApiMethod('bucketPut', request, response, log,
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const { parseString } = require('xml2js');
|
||||||
|
|
||||||
|
const ObjectLockConfiguration =
|
||||||
|
require('../../../lib/models/ObjectLockConfiguration.js');
|
||||||
|
|
||||||
|
function checkError(parsedXml, errMessage, cb) {
|
||||||
|
const config = new ObjectLockConfiguration(parsedXml).
|
||||||
|
getValidatedObjectLockConfiguration();
|
||||||
|
assert.strictEqual(config.error.MalformedXML, true);
|
||||||
|
assert.strictEqual(config.error.description, errMessage);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRule(testParams) {
|
||||||
|
if (testParams.key === 'Rule') {
|
||||||
|
return `<Rule>${testParams.value}</Rule>`;
|
||||||
|
}
|
||||||
|
if (testParams.key === 'DefaultRetention') {
|
||||||
|
return `<Rule><DefaultRetention>${testParams.value} ` +
|
||||||
|
'</DefaultRetention></Rule>';
|
||||||
|
}
|
||||||
|
const mode = testParams.key === 'Mode' ?
|
||||||
|
`<Mode>${testParams.value}</Mode>` : '<Mode>GOVERNANCE</Mode>';
|
||||||
|
|
||||||
|
let time = '<Days>1</Days>';
|
||||||
|
if (testParams.key === 'Days') {
|
||||||
|
time = `<Days>${testParams.value}</Days>`;
|
||||||
|
}
|
||||||
|
if (testParams.key === 'Years') {
|
||||||
|
time = `<Years>${testParams.value}</Years>`;
|
||||||
|
}
|
||||||
|
return `<Rule><DefaultRetention>${mode}${time}</DefaultRetention></Rule>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateXml(testParams) {
|
||||||
|
const Enabled = testParams.key === 'ObjectLockEnabled' ?
|
||||||
|
`<ObjectLockEnabled>${testParams.value}</ObjectLockEnabled>` :
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>';
|
||||||
|
const Rule = generateRule(testParams);
|
||||||
|
const ObjectLock = testParams.key === 'ObjectLockConfiguration' ? '' :
|
||||||
|
`<ObjectLockConfiguration>${Enabled}${Rule}` +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
return ObjectLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateParsedXml(testParams, cb) {
|
||||||
|
const xml = generateXml(testParams);
|
||||||
|
parseString(xml, (err, parsedXml) => {
|
||||||
|
assert.equal(err, null, 'Error parsing xml');
|
||||||
|
cb(parsedXml);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const failTests = [
|
||||||
|
{
|
||||||
|
name: 'fail with empty configuration',
|
||||||
|
params: { key: 'ObjectLockConfiguration' },
|
||||||
|
errorMessage: 'request xml is undefined or empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty ObjectLockEnabled',
|
||||||
|
params: { key: 'ObjectLockEnabled', value: '' },
|
||||||
|
errorMessage: 'request xml does not include valid ObjectLockEnabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with invalid value for ObjectLockEnabled',
|
||||||
|
params: { key: 'ObjectLockEnabled', value: 'Disabled' },
|
||||||
|
errorMessage: 'request xml does not include valid ObjectLockEnabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty rule',
|
||||||
|
params: { key: 'Rule', value: '' },
|
||||||
|
errorMessage: 'Rule request xml does not contain DefaultRetention',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty DefaultRetention',
|
||||||
|
params: { key: 'DefaultRetention', value: '' },
|
||||||
|
errorMessage: 'DefaultRetention request xml does not contain Mode or ' +
|
||||||
|
'retention period (Days or Years)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty mode',
|
||||||
|
params: { key: 'Mode', value: '' },
|
||||||
|
errorMessage: 'request xml does not contain Mode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with invalid mode',
|
||||||
|
params: { key: 'Mode', value: 'COMPLOVERNANCE' },
|
||||||
|
errorMessage: 'Mode request xml must be one of "GOVERNANCE", ' +
|
||||||
|
'"COMPLIANCE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with lowercase mode',
|
||||||
|
params: { key: 'Mode', value: 'governance' },
|
||||||
|
errorMessage: 'Mode request xml must be one of "GOVERNANCE", ' +
|
||||||
|
'"COMPLIANCE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty retention period',
|
||||||
|
params: { key: 'Days', value: '' },
|
||||||
|
errorMessage: 'request xml does not contain Days or Years',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with NaN retention period',
|
||||||
|
params: { key: 'Days', value: 'one' },
|
||||||
|
errorMessage: 'request xml does not contain valid retention period',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const passTests = [
|
||||||
|
{
|
||||||
|
name: 'pass with GOVERNANCE retention mode and valid Days ' +
|
||||||
|
'retention period',
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pass with COMPLIANCE retention mode',
|
||||||
|
params: { key: 'Mode', value: 'COMPLIANCE' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pass with valid Years retention period',
|
||||||
|
params: { key: 'Years', value: 1 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('ObjectLockConfiguration class getValidatedObjectLockConfiguration',
|
||||||
|
() => {
|
||||||
|
it('should return MalformedXML error if request xml is empty', done => {
|
||||||
|
const errMessage = 'request xml is undefined or empty';
|
||||||
|
checkError('', errMessage, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
failTests.forEach(test => {
|
||||||
|
it(`should ${test.name}`, done => {
|
||||||
|
generateParsedXml(test.params, xml => {
|
||||||
|
checkError(xml, test.errorMessage, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
passTests.forEach(test => {
|
||||||
|
it(`should ${test.name}`, done => {
|
||||||
|
generateParsedXml(test.params, xml => {
|
||||||
|
const config = new ObjectLockConfiguration(xml).
|
||||||
|
getValidatedObjectLockConfiguration();
|
||||||
|
assert.ifError(config.error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue