Merge pull request #882 from CartoDB/middleware-refactor

Middleware refactor
This commit is contained in:
Daniel 2018-03-07 15:19:04 +01:00 committed by GitHub
commit 8509796743
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 165 additions and 150 deletions

View File

@ -62,7 +62,7 @@ function isValidApiKey(apikey) {
//
AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
const apikeyToken = res.locals.api_key;
const apikeyUsername = res.locals.apikeyUsername;
const basicAuthUsername = res.locals.basicAuthUsername;
if ( ! apikeyToken ) {
return callback(null, false); // no api key, no authorization...
@ -91,7 +91,7 @@ AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
return callback(error);
}
if (!usernameMatches(apikeyUsername, res.locals.user)) {
if (!usernameMatches(basicAuthUsername, res.locals.user)) {
const error = new Error('Forbidden');
error.type = 'auth';
error.subtype = 'api-key-username-mismatch';
@ -149,8 +149,8 @@ function isNameNotFoundError (err) {
return err.message && -1 !== err.message.indexOf('name not found');
}
function usernameMatches (apikeyUsername, requestUsername) {
return !(apikeyUsername && (apikeyUsername !== requestUsername));
function usernameMatches (basicAuthUsername, requestUsername) {
return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
}
/**

View File

@ -12,7 +12,7 @@ AnalysesController.prototype.register = function (app) {
app.get(
`${app.base_url_mapconfig}/analyses/catalog`,
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.createPGClient(),
this.getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),

View File

@ -49,7 +49,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format',
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.tile.bind(this),
vectorError()
@ -58,7 +58,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y.:format',
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.tile.bind(this),
vectorError()
@ -67,7 +67,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
userMiddleware(),
validateLayerRouteMiddleware,
this.prepareContext,
this.layer.bind(this),
@ -77,7 +77,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:layer/attributes/:fid',
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.attributes.bind(this)
);
@ -85,7 +85,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer']),
this.prepareContext,
this.center.bind(this)
@ -94,7 +94,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer']),
this.prepareContext,
this.bbox.bind(this)
@ -121,7 +121,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
@ -130,7 +130,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
@ -139,7 +139,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName/search',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
@ -148,7 +148,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName/search',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
@ -157,7 +157,7 @@ LayergroupController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/:token/analysis/node/:nodeId',
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.analysisNodeStatus.bind(this)
);

View File

@ -69,7 +69,7 @@ MapController.prototype.composeCreateMapMiddleware = function (useTemplate = fal
return [
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['aggregation']),
this.prepareContext,
this.initProfiler(isTemplateInstantiation),

View File

@ -48,7 +48,7 @@ NamedMapsController.prototype.register = function(app) {
app.get(
app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
userMiddleware(),
this.prepareContext,
this.getNamedMapProvider(tileOptions),
this.getAffectedTables(),
@ -70,7 +70,7 @@ NamedMapsController.prototype.register = function(app) {
app.get(
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
cors(),
userMiddleware,
userMiddleware(),
allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
this.prepareContext,
this.getNamedMapProvider(staticOptions),

View File

@ -2,12 +2,7 @@ const { templateName } = require('../backends/template_maps');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const localsMiddleware = require('../middleware/context/locals');
const apikeyCredentialsMiddleware = require('../middleware/context/apikey-credentials');
const apikeyMiddleware = [
localsMiddleware,
apikeyCredentialsMiddleware(),
];
const credentialsMiddleware = require('../middleware/context/credentials');
/**
* @param {AuthApi} authApi
@ -28,8 +23,9 @@ NamedMapsAdminController.prototype.register = function (app) {
app.post(
`${base_url_templated}/`,
cors(),
userMiddleware,
apikeyMiddleware,
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
this.checkContentType('POST', 'POST TEMPLATE'),
this.authorizedByAPIKey('create', 'POST TEMPLATE'),
this.create()
@ -38,8 +34,9 @@ NamedMapsAdminController.prototype.register = function (app) {
app.put(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
apikeyMiddleware,
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
this.checkContentType('PUT', 'PUT TEMPLATE'),
this.authorizedByAPIKey('update', 'PUT TEMPLATE'),
this.update()
@ -48,8 +45,9 @@ NamedMapsAdminController.prototype.register = function (app) {
app.get(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
apikeyMiddleware,
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
this.authorizedByAPIKey('get', 'GET TEMPLATE'),
this.retrieve()
);
@ -57,8 +55,9 @@ NamedMapsAdminController.prototype.register = function (app) {
app.delete(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
apikeyMiddleware,
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
this.authorizedByAPIKey('delete', 'DELETE TEMPLATE'),
this.destroy()
);
@ -66,8 +65,9 @@ NamedMapsAdminController.prototype.register = function (app) {
app.get(
`${base_url_templated}/`,
cors(),
userMiddleware,
apikeyMiddleware,
userMiddleware(),
localsMiddleware(),
credentialsMiddleware(),
this.authorizedByAPIKey('list', 'GET TEMPLATE LIST'),
this.list()
);

View File

@ -1,8 +1,9 @@
module.exports = function allowQueryParams(params) {
module.exports = function allowQueryParams (params) {
if (!Array.isArray(params)) {
throw new Error('allowQueryParams must receive an Array of params');
}
return function allowQueryParamsMiddleware(req, res, next) {
return function allowQueryParamsMiddleware (req, res, next) {
res.locals.allowedQueryParams = params;
next();
};

View File

@ -1,9 +1,8 @@
module.exports = function authorizeMiddleware (authApi) {
return function (req, res, next) {
req.profiler.done('req2params.setup');
module.exports = function authorize (authApi) {
return function authorizeMiddleware (req, res, next) {
authApi.authorize(req, res, (err, authorized) => {
req.profiler.done('authorize');
if (err) {
return next(err);
}

View File

@ -1,18 +1,16 @@
'use strict';
const basicAuth = require('basic-auth');
module.exports = function apikeyToken () {
return function apikeyTokenMiddleware(req, res, next) {
module.exports = function credentials () {
return function credentialsMiddleware(req, res, next) {
const apikeyCredentials = getApikeyCredentialsFromRequest(req);
res.locals.api_key = apikeyCredentials.token;
res.locals.apikeyUsername = apikeyCredentials.username;
res.locals.basicAuthUsername = apikeyCredentials.username;
return next();
};
};
//--------------------------------------------------------------------------------
const basicAuth = require('basic-auth');
function getApikeyCredentialsFromRequest(req) {
let apikeyCredentials = {
token: null,

View File

@ -1,14 +1,17 @@
const _ = require('underscore');
module.exports = function dbConnSetupMiddleware(pgConnection) {
return function dbConnSetup(req, res, next) {
const user = res.locals.user;
module.exports = function dbConnSetup (pgConnection) {
return function dbConnSetupMiddleware (req, res, next) {
const { user } = res.locals;
pgConnection.setDBConn(user, res.locals, (err) => {
req.profiler.done('dbConnSetup');
if (err) {
if (err.message && -1 !== err.message.indexOf('name not found')) {
err.http_status = 404;
}
req.profiler.done('req2params');
return next(err);
}
@ -18,12 +21,10 @@ module.exports = function dbConnSetupMiddleware(pgConnection) {
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
res.set('X-Served-By-DB-Host', res.locals.dbhost);
req.profiler.done('req2params');
next(null);
next();
});
};
};

View File

@ -1,16 +1,16 @@
const locals = require('./locals');
const cleanUpQueryParams = require('./clean-up-query-params');
const layergroupToken = require('./layergroup-token');
const apikeyCredentials = require('./apikey-credentials');
const credentials = require('./credentials');
const authorize = require('./authorize');
const dbConnSetup = require('./db-conn-setup');
module.exports = function prepareContextMiddleware(authApi, pgConnection) {
return [
locals,
locals(),
cleanUpQueryParams(),
layergroupToken,
apikeyCredentials(),
layergroupToken(),
credentials(),
authorize(authApi),
dbConnSetup(pgConnection)
];

View File

@ -1,32 +1,33 @@
var LayergroupToken = require('../../models/layergroup-token');
module.exports = function layergroupTokenMiddleware(req, res, next) {
if (!res.locals.token) {
return next();
}
var user = res.locals.user;
var layergroupToken = LayergroupToken.parse(res.locals.token);
res.locals.token = layergroupToken.token;
res.locals.cache_buster = layergroupToken.cacheBuster;
if (layergroupToken.signer) {
res.locals.signer = layergroupToken.signer;
if (!res.locals.signer) {
res.locals.signer = user;
} else if (res.locals.signer !== user) {
var err = new Error(`Cannot use map signature of user "${res.locals.signer}" on db of user "${user}"`);
err.type = 'auth';
err.http_status = 403;
if (req.query && req.query.callback) {
err.http_status = 200;
}
req.profiler.done('req2params');
return next(err);
}
}
return next();
const LayergroupToken = require('../../models/layergroup-token');
const authErrorMessageTemplate = function (signer, user) {
return `Cannot use map signature of user "${signer}" on db of user "${user}"`;
};
module.exports = function layergroupToken () {
return function layergroupTokenMiddleware (req, res, next) {
if (!res.locals.token) {
return next();
}
const user = res.locals.user;
const layergroupToken = LayergroupToken.parse(res.locals.token);
res.locals.token = layergroupToken.token;
res.locals.cache_buster = layergroupToken.cacheBuster;
if (layergroupToken.signer) {
res.locals.signer = layergroupToken.signer;
if (res.locals.signer !== user) {
const err = new Error(authErrorMessageTemplate(res.locals.signer, user));
err.type = 'auth';
err.http_status = (req.query && req.query.callback) ? 200: 403;
return next(err);
}
}
return next();
};
};

View File

@ -1,6 +1,7 @@
module.exports = function localsMiddleware(req, res, next) {
// save req.params in res.locals
res.locals = Object.assign(req.params || {}, res.locals);
module.exports = function locals () {
return function localsMiddleware (req, res, next) {
res.locals = Object.assign(req.params || {}, res.locals);
next();
next();
};
};

View File

@ -1,11 +1,14 @@
module.exports = function cors(extraHeaders) {
return function(req, res, next) {
var baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
module.exports = function cors (extraHeaders) {
return function corsMiddleware (req, res, next) {
let baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
if(extraHeaders) {
baseHeaders += ", " + extraHeaders;
}
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Headers", baseHeaders);
next();
};
};

View File

@ -1,30 +1,33 @@
'use strict';
const LZMA = require('lzma').LZMA;
const lzmaWorker = new LZMA();
module.exports = function lzma () {
const lzmaWorker = new LZMA();
module.exports = function lzmaMiddleware(req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {
return next();
}
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(lzma, function(result) {
try {
delete req.query.lzma;
Object.assign(req.query, JSON.parse(result));
next();
} catch (err) {
next(new Error('Error parsing lzma as JSON: ' + err));
return function lzmaMiddleware (req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {
return next();
}
});
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(lzma, function(result) {
try {
delete req.query.lzma;
Object.assign(req.query, JSON.parse(result));
req.profiler.done('lzma');
next();
} catch (err) {
next(new Error('Error parsing lzma as JSON: ' + err));
}
});
};
};

View File

@ -2,10 +2,10 @@ const Profiler = require('../stats/profiler_proxy');
const debug = require('debug')('windshaft:cartodb:stats');
const onHeaders = require('on-headers');
module.exports = function statsMiddleware(options) {
module.exports = function stats (options) {
const { enabled = true, statsClient } = options;
return function stats(req, res, next) {
return function statsMiddleware (req, res, next) {
req.profiler = new Profiler({
statsd_client: statsClient,
profile: enabled

View File

@ -1,8 +1,11 @@
var CdbRequest = require('../models/cdb_request');
var cdbRequest = new CdbRequest();
const CdbRequest = require('../models/cdb_request');
module.exports = function userMiddleware(req, res, next) {
res.locals.user = cdbRequest.userByReq(req);
module.exports = function user () {
const cdbRequest = new CdbRequest();
next();
return function userMiddleware(req, res, next) {
res.locals.user = cdbRequest.userByReq(req);
next();
};
};

View File

@ -1,5 +1,4 @@
const fs = require('fs');
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
module.exports = function vectorError() {

View File

@ -377,7 +377,7 @@ function bootstrap(opts) {
statsClient: global.statsClient
}));
app.use(lzmaMiddleware);
app.use(lzmaMiddleware());
// temporary measure until we upgrade to newer version expressjs so we can check err.status
app.use(function(err, req, res, next) {

View File

@ -12,6 +12,7 @@ describe('lzma-middleware', function() {
}
};
testHelper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
const lzma = lzmaMiddleware();
var req = {
headers: {
host:'localhost'
@ -19,9 +20,13 @@ describe('lzma-middleware', function() {
query: {
api_key: 'test',
lzma: data
},
profiler: {
done: function () {}
}
};
lzmaMiddleware(req, {}, function(err) {
lzma(req, {}, function(err) {
if ( err ) {
return done(err);
}

View File

@ -10,7 +10,7 @@ var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/middleware/context/clean-up-query-params');
const authorizeMiddleware = require('../../../lib/cartodb/middleware/context/authorize');
const dbConnSetupMiddleware = require('../../../lib/cartodb/middleware/context/db-conn-setup');
const apikeyCredentialsMiddleware = require('../../../lib/cartodb/middleware/context/apikey-credentials');
const credentialsMiddleware = require('../../../lib/cartodb/middleware/context/credentials');
const localsMiddleware = require('../../../lib/cartodb/middleware/context/locals');
var windshaft = require('windshaft');
@ -24,7 +24,7 @@ describe('prepare-context', function() {
let cleanUpQueryParams;
let dbConnSetup;
let authorize;
let setApikeyCredentials;
let setCredentials;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
@ -37,7 +37,7 @@ describe('prepare-context', function() {
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
authorize = authorizeMiddleware(authApi);
dbConnSetup = dbConnSetupMiddleware(pgConnection);
setApikeyCredentials = apikeyCredentialsMiddleware();
setCredentials = credentialsMiddleware();
});
@ -65,16 +65,17 @@ describe('prepare-context', function() {
}
it('res.locals are created', function(done) {
const locals = localsMiddleware();
let req = {};
let res = {};
localsMiddleware(prepareRequest(req), prepareResponse(res), function(err) {
locals(prepareRequest(req), prepareResponse(res), function(err) {
if ( err ) { done(err); return; }
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
done();
});
});
it('cleans up request', function(done){
var req = {headers: { host:'localhost' }, query: {dbuser:'hacker',dbname:'secret'}};
var res = {};
@ -106,18 +107,18 @@ describe('prepare-context', function() {
});
it('sets also dbuser for authenticated requests', function(done){
var req = {
headers: {
host: 'localhost'
},
var req = {
headers: {
host: 'localhost'
},
query: {
api_key: '1234'
}
};
var res = {
var res = {
set: function () {},
locals: {
api_key: '1234'
api_key: '1234'
}
};
@ -169,7 +170,7 @@ describe('prepare-context', function() {
}
};
var res = {};
cleanUpQueryParams(prepareRequest(req), prepareResponse(res), function (err) {
if ( err ) {
return done(err);
@ -194,12 +195,12 @@ describe('prepare-context', function() {
}
};
var res = {};
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}
var query = res.locals;
assert.equal('1234', query.api_key);
done();
});
@ -215,7 +216,7 @@ describe('prepare-context', function() {
}
};
var res = {};
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}
@ -234,7 +235,7 @@ describe('prepare-context', function() {
}
};
var res = {};
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
if (err) {
return done(err);
}