Compare commits

...

4 Commits

Author SHA1 Message Date
Bennett Buchanan 1ab37bb05c [squash] Check both auth methods 2017-06-26 17:10:10 -07:00
Bennett Buchanan dc6b9c712f [squash] Use AuthController 2017-06-26 16:49:46 -07:00
Bennett Buchanan 822ae24ed2 [squash] Use ObjectController 2017-06-26 16:33:28 -07:00
Bennett Buchanan 8b5dcf2d7c REFACTOR: Use MVC approach to refactor routes 2017-06-26 16:21:27 -07:00
7 changed files with 328 additions and 175 deletions

View File

@ -0,0 +1,74 @@
const { responseNoBody, parseContentMD5, responseXMLBody } =
require('../routesUtils');
const errors = require('../../errors');
class AuthController {
static bucketPut(req, res, log) {
// content-length for object is handled separately below
const contentLength = req.headers['content-length'];
if ((contentLength && (isNaN(contentLength) || contentLength < 0)) ||
contentLength === '') {
log.debug('invalid content-length header');
return responseNoBody(errors.BadRequest, null, res, null, log);
}
return undefined;
}
/* eslint-disable no-param-reassign */
static objectPutAction(req, res, log) {
// PUT object, PUT object ACL, PUT object multipart or
// PUT object copy
// if content-md5 is not present in the headers, try to
// parse content-md5 from meta headers
if (req.headers['content-md5'] === '') {
log.debug('empty content-md5 header', {
method: 'routePUT',
});
return responseNoBody(errors.InvalidDigest, null, res, 200, log);
}
if (req.headers['content-md5']) {
req.contentMD5 = req.headers['content-md5'];
} else {
req.contentMD5 = parseContentMD5(req.headers);
}
if (req.contentMD5 && req.contentMD5.length !== 32) {
req.contentMD5 = Buffer.from(req.contentMD5, 'base64')
.toString('hex');
if (req.contentMD5 && req.contentMD5.length !== 32) {
log.debug('invalid md5 digest', { contentMD5: req.contentMD5 });
return responseNoBody(errors.InvalidDigest, null, res, 200,
log);
}
}
const encryptionHeaders = [
'x-amz-server-side-encryption',
'x-amz-server-side-encryption-customer-algorithm',
'x-amz-server-side-encryption-aws-kms-key-id',
'x-amz-server-side-encryption-context',
'x-amz-server-side-encryption-customer-key',
'x-amz-server-side-encryption-customer-key-md5',
];
// object level encryption
if (encryptionHeaders.some(i => req.headers[i] !== undefined)) {
return responseXMLBody(errors.NotImplemented, null, res, log);
}
return undefined;
}
static objectPut(req, res, log) {
if (req.headers['content-length'] === undefined &&
req.headers['x-amz-decoded-content-length'] === undefined) {
return responseNoBody(errors.MissingContentLength, null, res, 411,
log);
}
if (Number.isNaN(req.parsedContentLength) ||
req.parsedContentLength < 0) {
return responseNoBody(errors.BadRequest, null, res, 400, log);
}
log.end().addDefaultFields({ contentLength: req.parsedContentLength });
return undefined;
}
}
module.exports = AuthController;

View File

@ -0,0 +1,50 @@
const { statsReport500, responseNoBody } = require('../routesUtils');
class BucketController {
static bucketPutACL(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, corsHeaders, res, 200, log);
});
}
static bucketPutVersioning(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, corsHeaders, res, 200, log);
});
}
static bucketPutWebsite(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, corsHeaders, res, 200, log);
});
}
static bucketPutCors(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, corsHeaders, res, 200, log);
});
}
static bucketPutReplication(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, corsHeaders, res, 200, log);
});
}
static bucketPut(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, corsHeaders) => {
statsReport500(err, statsClient);
const location = { Location: `/${req.bucketName}` };
const resHeaders = corsHeaders ?
Object.assign({}, location, corsHeaders) : location;
return responseNoBody(err, resHeaders, res, 200, log);
});
}
}
module.exports = BucketController;

View File

@ -0,0 +1,57 @@
const { statsReport500, responseNoBody, responseXMLBody } =
require('../routesUtils');
class ObjectController {
static objectPutCopyPart(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log,
(err, xml, additionalHeaders) => {
statsReport500(err, statsClient);
return responseXMLBody(err, xml, res, log, additionalHeaders);
});
}
static objectPutPart(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log,
(err, calculatedHash, corsHeaders) => {
if (err) {
return responseNoBody(err, corsHeaders, res, 200, log);
}
const resMetaHeaders = corsHeaders || {};
// ETag's hex should always be enclosed in quotes
resMetaHeaders.ETag = `"${calculatedHash}"`;
statsReport500(err, statsClient);
return responseNoBody(err, resMetaHeaders, res, 200, log);
});
}
static objectPutACL(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, resHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, resHeaders, res, 200, log);
});
}
static objectPutTagging(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, resHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, resHeaders, res, 200, log);
});
}
static objectCopy(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log,
(err, xml, additionalHeaders) => {
statsReport500(err, statsClient);
responseXMLBody(err, xml, res, log, additionalHeaders);
});
}
static objectPut(action, req, res, api, log, statsClient) {
return api.callApiMethod(action, req, res, log, (err, resHeaders) => {
statsReport500(err, statsClient);
return responseNoBody(err, resHeaders, res, 200, log);
});
}
}
module.exports = ObjectController;

View File

@ -9,6 +9,8 @@ const routePOST = require('./routes/routePOST');
const routeOPTIONS = require('./routes/routeOPTIONS');
const routesUtils = require('./routesUtils');
const routeWebsite = require('./routes/routeWebsite');
const routesObjects = require('./routes/routesObjects.json');
const Router = require('./routes/Router');
const routeMap = {
GET: routeGET,
@ -212,6 +214,10 @@ function routes(req, res, params, logger) {
return routeWebsite(req, res, api, log, statsClient, dataRetrievalFn);
}
if (reqMethod === 'PUT') {
return new Router(routesObjects).exec(req, res, api, log, statsClient);
}
return method(req, res, api, log, statsClient, dataRetrievalFn);
}

View File

@ -0,0 +1,37 @@
const BucketController = require('../controller/BucketController');
const ObjectController = require('../controller/ObjectController');
const AuthController = require('../controller/AuthController');
class Router {
constructor(routes) {
this._routes = routes;
this._controllers = { BucketController, ObjectController };
return this;
}
_matchRoute(route, req) {
const query = route.query || [];
const headers = route.headers || [];
return req.method === route.method &&
Boolean(req.bucketName) === Boolean(route.bucket) &&
Boolean(req.objectKey) === Boolean(route.object) &&
query.every(q => q in req.query) &&
headers.every(h => h in req.headers);
}
exec(req, res, api, log, statsClient) {
const route = this._routes.find(r => this._matchRoute(r, req));
const { controller, action, auth } = route;
for (let i = 0; i < auth.length; i++) {
const authMethod = auth[i];
const authResponse = AuthController[authMethod](req, res, log);
if (authResponse !== undefined) {
return authResponse;
}
}
const routeMethod = this._controllers[controller][action];
return routeMethod(action, req, res, api, log, statsClient);
}
}
module.exports = Router;

View File

@ -1,181 +1,7 @@
const errors = require('../../errors');
const routesUtils = require('../routesUtils');
const encryptionHeaders = [
'x-amz-server-side-encryption',
'x-amz-server-side-encryption-customer-algorithm',
'x-amz-server-side-encryption-aws-kms-key-id',
'x-amz-server-side-encryption-context',
'x-amz-server-side-encryption-customer-key',
'x-amz-server-side-encryption-customer-key-md5',
];
/* eslint-disable no-param-reassign */
function routePUT(request, response, api, log, statsClient) {
function routePUT(request, response, api, log) {
log.debug('routing request', { method: 'routePUT' });
if (request.objectKey === undefined) {
// PUT bucket - PUT bucket ACL
// content-length for object is handled separately below
const contentLength = request.headers['content-length'];
if ((contentLength && (isNaN(contentLength) || contentLength < 0)) ||
contentLength === '') {
log.debug('invalid content-length header');
return routesUtils.responseNoBody(
errors.BadRequest, null, response, null, log);
}
// PUT bucket ACL
if (request.query.acl !== undefined) {
api.callApiMethod('bucketPutACL', request, response, log,
(err, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
});
} else if (request.query.versioning !== undefined) {
api.callApiMethod('bucketPutVersioning', request, response, log,
(err, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
routesUtils.responseNoBody(err, corsHeaders, response, 200,
log);
});
} else if (request.query.website !== undefined) {
api.callApiMethod('bucketPutWebsite', request, response, log,
(err, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
});
} else if (request.query.cors !== undefined) {
api.callApiMethod('bucketPutCors', request, response, log,
(err, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
});
} else if (request.query.replication !== undefined) {
api.callApiMethod('bucketPutReplication', 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,
(err, corsHeaders) => {
routesUtils.statsReport500(err, statsClient);
const location = { Location: `/${request.bucketName}` };
const resHeaders = corsHeaders ?
Object.assign({}, location, corsHeaders) : location;
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
}
} else {
// PUT object, PUT object ACL, PUT object multipart or
// PUT object copy
// if content-md5 is not present in the headers, try to
// parse content-md5 from meta headers
if (request.headers['content-md5'] === '') {
log.debug('empty content-md5 header', {
method: 'routePUT',
});
return routesUtils
.responseNoBody(errors.InvalidDigest, null, response, 200, log);
}
if (request.headers['content-md5']) {
request.contentMD5 = request.headers['content-md5'];
} else {
request.contentMD5 = routesUtils.parseContentMD5(request.headers);
}
if (request.contentMD5 && request.contentMD5.length !== 32) {
request.contentMD5 = Buffer.from(request.contentMD5, 'base64')
.toString('hex');
if (request.contentMD5 && request.contentMD5.length !== 32) {
log.debug('invalid md5 digest', {
contentMD5: request.contentMD5,
});
return routesUtils
.responseNoBody(errors.InvalidDigest, null, response, 200,
log);
}
}
// object level encryption
if (encryptionHeaders.some(i => request.headers[i] !== undefined)) {
return routesUtils.responseXMLBody(errors.NotImplemented, null,
response, log);
}
if (request.query.partNumber) {
if (request.headers['x-amz-copy-source']) {
api.callApiMethod('objectPutCopyPart', request, response, log,
(err, xml, additionalHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseXMLBody(err, xml, response, log,
additionalHeaders);
});
} else {
api.callApiMethod('objectPutPart', request, response, log,
(err, calculatedHash, corsHeaders) => {
if (err) {
return routesUtils.responseNoBody(err, corsHeaders,
response, 200, log);
}
// ETag's hex should always be enclosed in quotes
const resMetaHeaders = corsHeaders || {};
resMetaHeaders.ETag = `"${calculatedHash}"`;
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resMetaHeaders,
response, 200, log);
});
}
} else if (request.query.acl !== undefined) {
api.callApiMethod('objectPutACL', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (request.query.tagging !== undefined) {
api.callApiMethod('objectPutTagging', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
} else if (request.headers['x-amz-copy-source']) {
return api.callApiMethod('objectCopy', request, response, log,
(err, xml, additionalHeaders) => {
routesUtils.statsReport500(err, statsClient);
routesUtils.responseXMLBody(err, xml, response, log,
additionalHeaders);
});
} else {
if (request.headers['content-length'] === undefined &&
request.headers['x-amz-decoded-content-length'] === undefined) {
return routesUtils.responseNoBody(errors.MissingContentLength,
null, response, 411, log);
}
if (Number.isNaN(request.parsedContentLength) ||
request.parsedContentLength < 0) {
return routesUtils.responseNoBody(errors.BadRequest,
null, response, 400, log);
}
log.end().addDefaultFields({
contentLength: request.parsedContentLength,
});
api.callApiMethod('objectPut', request, response, log,
(err, resHeaders) => {
routesUtils.statsReport500(err, statsClient);
return routesUtils.responseNoBody(err, resHeaders,
response, 200, log);
});
}
}
return undefined;
}
/* eslint-enable no-param-reassign */

View File

@ -0,0 +1,103 @@
[
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPutACL",
"query": [ "acl" ],
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPutVersioning",
"query": [ "versioning" ],
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPutWebsite",
"query": [ "website" ],
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPutCors",
"query": [ "cors" ],
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPutReplication",
"query": [ "replication" ],
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"method": "PUT",
"controller": "BucketController",
"action": "bucketPut",
"auth": [ "bucketPut" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectPutCopyPart",
"headers": [ "x-amz-copy-source" ],
"query": [ "partNumber" ],
"auth": [ "objectPutAction" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectPutPart",
"query": [ "partNumber" ],
"auth": [ "objectPutAction" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectPutACL",
"query": [ "acl" ],
"auth": [ "objectPutAction" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectPutTagging",
"query": [ "tagging" ],
"auth": [ "objectPutAction" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectCopy",
"headers": [ "x-amz-copy-source" ],
"auth": [ "objectPutAction" ]
},
{
"bucket": true,
"object": true,
"method": "PUT",
"controller": "ObjectController",
"action": "objectPut",
"auth": [ "objectPutAction", "objectPut" ]
}
]