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,
|
||||
"description": "The specified bucket does not have a bucket policy."
|
||||
},
|
||||
"ObjectLockConfigurationNotFoundError": {
|
||||
"code": 404,
|
||||
"description": "Object Lock configuration does not exist for this bucket."
|
||||
},
|
||||
"OperationAborted": {
|
||||
"code": 409,
|
||||
"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:
|
||||
require('./lib/models/LifecycleConfiguration'),
|
||||
BucketPolicy: require('./lib/models/BucketPolicy'),
|
||||
ObjectLockConfiguration:
|
||||
require('./lib/models/ObjectLockConfiguration'),
|
||||
},
|
||||
metrics: {
|
||||
StatsClient: require('./lib/metrics/StatsClient'),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const assert = require('assert');
|
||||
|
||||
const errors = require('../errors');
|
||||
|
||||
/**
|
||||
* Format of xml request:
|
||||
*
|
||||
|
@ -36,6 +38,128 @@ class ObjectLockConfiguration {
|
|||
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
|
||||
* value types
|
||||
|
@ -53,6 +177,31 @@ class ObjectLockConfiguration {
|
|||
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;
|
||||
|
|
|
@ -78,6 +78,13 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
return routesUtils.responseXMLBody(err, xml, response,
|
||||
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 {
|
||||
// GET bucket
|
||||
api.callApiMethod('bucketGet', request, response, log,
|
||||
|
@ -88,7 +95,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
/* eslint-disable no-lonely-if */
|
||||
if (request.query.acl !== undefined) {
|
||||
// GET object ACL
|
||||
api.callApiMethod('objectGetACL', request, response, log,
|
||||
|
@ -128,7 +134,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
range, log);
|
||||
});
|
||||
}
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,13 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||
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 {
|
||||
// PUT bucket
|
||||
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