Merge remote-tracking branch 'origin/master' into mvt-path-conf

This commit is contained in:
David Manzanares 2017-10-10 17:53:28 +02:00
commit d30f710534
49 changed files with 903 additions and 687 deletions

View File

@ -16,7 +16,7 @@ config.status--test:
./configure --environment=test
config/environments/test.js: config.status--test
./config.status--test
./config.status--test
TEST_SUITE := $(shell find test/{acceptance,integration,unit} -name "*.js")
TEST_SUITE_UNIT := $(shell find test/unit -name "*.js")

View File

@ -3,6 +3,12 @@
## 4.0.1
Released 2017-mm-dd
- Split and move `req2params` method to multiple middlewares.
- Use express error handler middleware to respond in case of something went wrong.
- Use `res.locals` object to share info between middlewares and leave `req.params` as an object containing properties mapped to the named route params.
- Move `LZMA` decompression to its own middleware.
## 4.0.0
Released 2017-10-04

View File

@ -19,22 +19,22 @@ function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) {
module.exports = AuthApi;
// Check if a request is authorized by a signer
// Check if the user is authorized by a signer
//
// @param req express request object
// @param res express response object
// @param callback function(err, signed_by) signed_by will be
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
AuthApi.prototype.authorizedBySigner = function(req, callback) {
if ( ! req.params.token || ! req.params.signer ) {
AuthApi.prototype.authorizedBySigner = function(res, callback) {
if ( ! res.locals.token || ! res.locals.signer ) {
return callback(null, false); // no signer requested
}
var self = this;
var layergroup_id = req.params.token;
var auth_token = req.params.auth_token;
var layergroup_id = res.locals.token;
var auth_token = res.locals.auth_token;
this.mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
@ -84,11 +84,12 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
* Check access authorization
*
* @param req - standard req object. Importantly contains table and host information
* @param res - standard res object. Contains the auth parameters in locals
* @param callback function(err, allowed) is access allowed not?
*/
AuthApi.prototype.authorize = function(req, callback) {
AuthApi.prototype.authorize = function(req, res, callback) {
var self = this;
var user = req.context.user;
var user = res.locals.user;
step(
function () {
@ -101,11 +102,11 @@ AuthApi.prototype.authorize = function(req, callback) {
// if not authorized by api_key, continue
if (!authorized) {
// not authorized by api_key, check if authorized by signer
return self.authorizedBySigner(req, this);
return self.authorizedBySigner(res, this);
}
// authorized by api key, login as the given username and stop
self.pgConnection.setDBAuth(user, req.params, function(err) {
self.pgConnection.setDBAuth(user, res.locals, function(err) {
callback(err, true); // authorized (or error)
});
},
@ -120,7 +121,7 @@ AuthApi.prototype.authorize = function(req, callback) {
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
//
if ( ! req.params.signer ) {
if ( ! res.locals.signer ) {
return callback(null, true); // authorized so far
}
@ -128,7 +129,7 @@ AuthApi.prototype.authorize = function(req, callback) {
return callback(null, false);
}
self.pgConnection.setDBAuth(user, req.params, function(err) {
self.pgConnection.setDBAuth(user, res.locals, function(err) {
req.profiler.done('setDBAuth');
callback(err, true); // authorized (or error)
});

View File

@ -24,7 +24,7 @@ module.exports = DataviewBackend;
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
var dataviewName = params.dataviewName;
var dataviewName = params.dataviewName;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
@ -113,9 +113,7 @@ function getOverrideParams(params, ownFilter) {
return overrideParams;
}
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
var dataviewName = params.dataviewName;
DataviewBackend.prototype.search = function (mapConfigProvider, user, dataviewName, params, callback) {
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);

View File

@ -10,8 +10,10 @@ var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
function AnalysesController(authApi, pgConnection) {
BaseController.call(this, authApi, pgConnection);
function AnalysesController(prepareContext) {
BaseController.call(this);
this.prepareContext = prepareContext;
}
util.inherits(AnalysesController, BaseController);
@ -19,7 +21,13 @@ util.inherits(AnalysesController, BaseController);
module.exports = AnalysesController;
AnalysesController.prototype.register = function(app) {
app.get(app.base_url_mapconfig + '/analyses/catalog', cors(), userMiddleware, this.catalog.bind(this));
app.get(
app.base_url_mapconfig + '/analyses/catalog',
cors(),
userMiddleware,
this.prepareContext,
this.catalog.bind(this)
);
};
AnalysesController.prototype.sendResponse = function(req, res, resource) {
@ -27,17 +35,13 @@ AnalysesController.prototype.sendResponse = function(req, res, resource) {
this.send(req, res, resource, 200);
};
AnalysesController.prototype.catalog = function(req, res) {
AnalysesController.prototype.catalog = function (req, res, next) {
var self = this;
var username = req.context.user;
var username = res.locals.user;
step(
function reqParams() {
self.req2params(req, this);
},
function catalogQuery(err) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(req.params));
function catalogQuery() {
var pg = new PSQL(dbParamsFromReqParams(res.locals));
getMetadata(username, pg, this);
},
function prepareResponse(err, results) {
@ -80,7 +84,8 @@ AnalysesController.prototype.catalog = function(req, res) {
err = new Error('Unauthorized');
err.http_status = 401;
}
self.sendError(req, res, err);
next(req, res, err);
} else {
self.sendResponse(req, res, { catalog: catalogWithTables });
}

View File

@ -1,130 +1,12 @@
var assert = require('assert');
var _ = require('underscore');
var step = require('step');
var debug = require('debug')('windshaft:cartodb');
// Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [
'config',
'map_key',
'api_key',
'auth_token',
'callback',
'zoom',
'lon',
'lat',
// analysis
'filters' // json
];
function BaseController(authApi, pgConnection) {
this.authApi = authApi;
this.pgConnection = pgConnection;
function BaseController() {
}
module.exports = BaseController;
// jshint maxcomplexity:8
/**
* Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis
* @param req - standard express request obj. Should have host & table
* @param callback
*/
BaseController.prototype.req2params = function(req, callback){
var self = this;
var allowedQueryParams = REQUEST_QUERY_PARAMS_WHITELIST;
if (Array.isArray(req.context.allowedQueryParams)) {
allowedQueryParams = allowedQueryParams.concat(req.context.allowedQueryParams);
}
req.query = _.pick(req.query, allowedQueryParams);
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
var user = req.context.user;
if ( req.params.token ) {
// Token might match the following patterns:
// - {user}@{tpl_id}@{token}:{cache_buster}
var tksplit = req.params.token.split(':');
req.params.token = tksplit[0];
if ( tksplit.length > 1 ) {
req.params.cache_buster= tksplit[1];
}
tksplit = req.params.token.split('@');
if ( tksplit.length > 1 ) {
req.params.signer = tksplit.shift();
if ( ! req.params.signer ) {
req.params.signer = user;
}
else if ( req.params.signer !== user ) {
var err = new Error(
'Cannot use map signature of user "' + req.params.signer + '" on db of user "' + user + '"'
);
err.http_status = 403;
req.profiler.done('req2params');
callback(err);
return;
}
if ( tksplit.length > 1 ) {
/*var template_hash = */tksplit.shift(); // unused
}
req.params.token = tksplit.shift();
}
}
// bring all query values onto req.params object
_.extend(req.params, req.query);
req.profiler.done('req2params.setup');
step(
function getPrivacy(){
self.authApi.authorize(req, this);
},
function validateAuthorization(err, authorized) {
req.profiler.done('authorize');
assert.ifError(err);
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
throw err;
}
return null;
},
function getDatabase(err){
assert.ifError(err);
self.pgConnection.setDBConn(user, req.params, this);
},
function finishSetup(err) {
if ( err ) {
req.profiler.done('req2params');
return callback(err, req);
}
// Add default database connection parameters
// if none given
_.defaults(req.params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
req.profiler.done('req2params');
callback(null, req);
}
);
};
// jshint maxcomplexity:6
// jshint maxcomplexity:9
BaseController.prototype.send = function(req, res, body, status, headers) {
if (req.params.dbhost) {
res.set('X-Served-By-DB-Host', req.params.dbhost);
}
res.set('X-Tiler-Profiler', req.profiler.toJSONString());
if (headers) {
@ -152,150 +34,3 @@ BaseController.prototype.send = function(req, res, body, status, headers) {
}
};
// jshint maxcomplexity:6
BaseController.prototype.sendError = function(req, res, err, label) {
var allErrors = Array.isArray(err) ? err : [err];
allErrors = populateTimeoutErrors(allErrors);
label = label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
if (err.message === 'Tile does not exist' && req.params.format === 'mvt') {
statusCode = 204;
}
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {
statusCode = 200;
}
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
this.send(req, res, errorResponseBody, statusCode);
};
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
.replace(/Connection string: '[^']*'\n\s/im, '')
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
var ERROR_INFO_TO_EXPOSE = {
message: true,
layer: true,
type: true,
analysis: true,
subtype: true
};
function shouldBeExposed (prop) {
return !!ERROR_INFO_TO_EXPOSE[prop];
}
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return stripConnectionInfo(message);
}
function errorMessageWithContext(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
var error = {
type: err.type || 'unknown',
message: stripConnectionInfo(message),
};
for (var prop in err) {
// type & message are properties from Error's prototype and will be skipped
if (err.hasOwnProperty(prop) && shouldBeExposed(prop)) {
error[prop] = err[prop];
}
}
return error;
}
module.exports.errorMessage = errorMessage;
function findStatusCode(err) {
var statusCode;
if ( err.http_status ) {
statusCode = err.http_status;
} else {
statusCode = statusFromErrorMessage('' + err);
}
return statusCode;
}
module.exports.findStatusCode = findStatusCode;
function statusFromErrorMessage(errMsg) {
// Find an appropriate statusCode based on message
// jshint maxcomplexity:7
var statusCode = 400;
if ( -1 !== errMsg.indexOf('permission denied') ) {
statusCode = 403;
}
else if ( -1 !== errMsg.indexOf('authentication failed') ) {
statusCode = 403;
}
else if (errMsg.match(/Postgis Plugin.*[\s|\n].*column.*does not exist/)) {
statusCode = 400;
}
else if ( -1 !== errMsg.indexOf('does not exist') ) {
if ( -1 !== errMsg.indexOf(' role ') ) {
statusCode = 403; // role 'xxx' does not exist
} else if ( errMsg.match(/function .* does not exist/) ) {
statusCode = 400; // invalid SQL (SQL function does not exist)
} else {
statusCode = 404;
}
}
return statusCode;
}
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function populateTimeoutErrors (errors) {
return errors.map(function (error) {
if (isRenderTimeoutError(error)) {
error.subtype = 'render';
}
if (isDatasourceTimeoutError(error)) {
error.subtype = 'datasource';
}
if (isTimeoutError(error)) {
error.message = 'You are over platform\'s limits. Please contact us to know more details';
error.type = 'limit';
error.http_status = 429;
}
return error;
});
}

View File

@ -28,9 +28,9 @@ var QueryTables = require('cartodb-query-tables');
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
function LayergroupController(prepareContext, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
BaseController.call(this);
this.pgConnection = pgConnection;
this.mapStore = mapStore;
@ -43,39 +43,65 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
this.prepareContext = prepareContext;
}
util.inherits(LayergroupController, BaseController);
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y@:scale_factor?x.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format',
cors(),
userMiddleware,
this.prepareContext,
this.tile.bind(this)
);
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y.:format',
cors(),
userMiddleware,
this.prepareContext,
this.tile.bind(this)
);
app.get(app.base_url_mapconfig +
'/:token/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.layer.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
validateLayerRouteMiddleware,
this.prepareContext,
this.layer.bind(this)
);
app.get(app.base_url_mapconfig +
'/:token/:layer/attributes/:fid', cors(), userMiddleware,
this.attributes.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:layer/attributes/:fid',
cors(),
userMiddleware,
this.prepareContext,
this.attributes.bind(this)
);
app.get(app.base_url_mapconfig +
'/static/center/:token/:z/:lat/:lng/:width/:height.:format',
cors(), userMiddleware, allowQueryParams(['layer']),
this.center.bind(this));
app.get(
app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer']),
this.prepareContext,
this.center.bind(this)
);
app.get(app.base_url_mapconfig +
'/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
cors(), userMiddleware, allowQueryParams(['layer']),
this.bbox.bind(this));
app.get(
app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer']),
this.prepareContext,
this.bbox.bind(this)
);
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
@ -98,6 +124,7 @@ LayergroupController.prototype.register = function(app) {
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
);
@ -106,6 +133,7 @@ LayergroupController.prototype.register = function(app) {
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
);
@ -114,6 +142,7 @@ LayergroupController.prototype.register = function(app) {
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
);
@ -122,30 +151,32 @@ LayergroupController.prototype.register = function(app) {
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
);
app.get(app.base_url_mapconfig +
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
this.analysisNodeStatus.bind(this));
app.get(
app.base_url_mapconfig + '/:token/analysis/node/:nodeId',
cors(),
userMiddleware,
this.prepareContext,
this.analysisNodeStatus.bind(this)
);
};
LayergroupController.prototype.analysisNodeStatus = function(req, res) {
LayergroupController.prototype.analysisNodeStatus = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveNodeStatus(err) {
assert.ifError(err);
self.analysisStatusBackend.getNodeStatus(req.params, this);
function retrieveNodeStatus() {
self.analysisStatusBackend.getNodeStatus(res.locals, this);
},
function finish(err, nodeStatus, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET NODE STATUS');
err.label = 'GET NODE STATUS';
next(err);
} else {
self.sendResponse(req, res, nodeStatus, 200, {
'Cache-Control': 'public,max-age=5',
@ -156,54 +187,50 @@ LayergroupController.prototype.analysisNodeStatus = function(req, res) {
);
};
LayergroupController.prototype.dataview = function(req, res) {
LayergroupController.prototype.dataview = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveDataview(err) {
assert.ifError(err);
function retrieveDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.getDataview(
mapConfigProvider,
res.locals.user,
res.locals,
this
);
self.dataviewBackend.getDataview(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, dataview, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW');
err.label = 'GET DATAVIEW';
next(err);
} else {
self.sendResponse(req, res, dataview, 200);
}
}
);
};
LayergroupController.prototype.dataviewSearch = function(req, res) {
LayergroupController.prototype.dataviewSearch = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function searchDataview(err) {
assert.ifError(err);
function searchDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.search(mapConfigProvider, req.context.user, req.params, this);
self.dataviewBackend.search(mapConfigProvider, res.locals.user, req.params.dataviewName, res.locals, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW SEARCH');
err.label = 'GET DATAVIEW SEARCH';
next(err);
} else {
self.sendResponse(req, res, searchResult, 200);
}
@ -212,28 +239,24 @@ LayergroupController.prototype.dataviewSearch = function(req, res) {
};
LayergroupController.prototype.attributes = function(req, res) {
LayergroupController.prototype.attributes = function(req, res, next) {
var self = this;
req.profiler.start('windshaft.maplayer_attribute');
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveFeatureAttributes(err) {
assert.ifError(err);
function retrieveFeatureAttributes() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, req.params, false, this);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, res.locals, false, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET ATTRIBUTES');
err.label = 'GET ATTRIBUTES';
next(err);
} else {
self.sendResponse(req, res, tile, 200);
}
@ -243,37 +266,30 @@ LayergroupController.prototype.attributes = function(req, res) {
};
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
LayergroupController.prototype.tile = function(req, res) {
LayergroupController.prototype.tile = function(req, res, next) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res);
this.tileOrLayer(req, res, next);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
LayergroupController.prototype.layer = function(req, res, next) {
if (req.params.token === 'static') {
return next();
}
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res);
this.tileOrLayer(req, res, next);
};
LayergroupController.prototype.tileOrLayer = function (req, res) {
LayergroupController.prototype.tileOrLayer = function (req, res, next) {
var self = this;
step(
function mapController$prepareParams() {
self.req2params(req, this);
},
function mapController$getTileOrGrid(err) {
assert.ifError(err);
function mapController$getTileOrGrid() {
self.tileBackend.getTile(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
req.params, this
);
},
function mapController$finalize(err, tile, headers, stats) {
req.profiler.add(stats);
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
self.finalizeGetTileOrGrid(err, req, res, tile, headers, next);
}
);
};
@ -284,7 +300,7 @@ function getStatusCode(tile, format){
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers, next) {
var supportedFormats = {
grid_json: true,
json_torque: true,
@ -313,7 +329,9 @@ LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, t
}
err.message = errMsg;
this.sendError(req, res, err, 'TILE RENDER');
err.label = 'TILE RENDER';
next(err);
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
@ -323,42 +341,38 @@ LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, t
}
};
LayergroupController.prototype.bbox = function(req, res) {
LayergroupController.prototype.bbox = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
});
}, null, next);
};
LayergroupController.prototype.center = function(req, res) {
LayergroupController.prototype.center = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
});
}, next);
};
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center, next) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.layer = 'all';
req.params.format = 'png';
req.params.format = req.params.format || 'png';
res.locals.layer = res.locals.layer || 'all';
var self = this;
step(
function reqParams() {
self.req2params(req, this);
},
function getImage(err) {
assert.ifError(err);
function getImage() {
if (center) {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom /* bounds */, this);
}
},
@ -367,7 +381,8 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'STATIC_MAP');
err.label = 'STATIC_MAP';
next(err);
} else {
res.set('Content-Type', headers['Content-Type'] || 'image/' + format);
self.sendResponse(req, res, image, 200);
@ -385,18 +400,18 @@ LayergroupController.prototype.sendResponse = function(req, res, body, status, h
// Set Last-Modified header
var lastUpdated;
if (req.params.cache_buster) {
if (res.locals.cache_buster) {
// Assuming cache_buster is a timestamp
lastUpdated = new Date(parseInt(req.params.cache_buster));
lastUpdated = new Date(parseInt(res.locals.cache_buster));
} else {
lastUpdated = new Date();
}
res.set('Last-Modified', lastUpdated.toUTCString());
var dbName = req.params.dbname;
var dbName = res.locals.dbname;
step(
function getAffectedTables() {
self.getAffectedTables(req.context.user, dbName, req.params.token, this);
self.getAffectedTables(res.locals.user, dbName, res.locals.token, this);
},
function sendResponse(err, affectedTables) {
req.profiler.done('affectedTables');
@ -472,3 +487,12 @@ LayergroupController.prototype.getAffectedTables = function(user, dbName, layerg
callback
);
};
function validateLayerRouteMiddleware(req, res, next) {
if (req.params.token === 'static') {
return next('route');
}
next();
}

View File

@ -20,7 +20,6 @@ var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
/**
* @param {AuthApi} authApi
* @param {PgConnection} pgConnection
@ -34,11 +33,11 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/cr
* @param {StatsBackend} statsBackend
* @constructor
*/
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
function MapController(prepareContext, pgConnection, templateMaps, mapBackend, metadataBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, mapConfigAdapter,
statsBackend) {
BaseController.call(this, authApi, pgConnection);
BaseController.call(this);
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
@ -52,6 +51,7 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
this.resourceLocator = new ResourceLocator(global.environment);
this.statsBackend = statsBackend;
this.prepareContext = prepareContext;
}
util.inherits(MapController, BaseController);
@ -60,38 +60,60 @@ module.exports = MapController;
MapController.prototype.register = function(app) {
app.get(app.base_url_mapconfig, cors(), userMiddleware, this.createGet.bind(this));
app.post(app.base_url_mapconfig, cors(), userMiddleware, this.createPost.bind(this));
app.get(app.base_url_templated + '/:template_id/jsonp', cors(), userMiddleware, this.jsonp.bind(this));
app.post(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.instantiate.bind(this));
app.get(
app.base_url_mapconfig,
cors(),
userMiddleware,
this.prepareContext,
this.createGet.bind(this)
);
app.post(
app.base_url_mapconfig,
cors(),
userMiddleware,
this.prepareContext,
this.createPost.bind(this)
);
app.get(
app.base_url_templated + '/:template_id/jsonp',
cors(),
userMiddleware,
this.prepareContext,
this.jsonp.bind(this)
);
app.post(
app.base_url_templated + '/:template_id',
cors(),
userMiddleware,
this.prepareContext,
this.instantiate.bind(this)
);
app.options(app.base_url_mapconfig, cors('Content-Type'));
};
MapController.prototype.createGet = function(req, res){
MapController.prototype.createGet = function(req, res, next){
req.profiler.start('windshaft.createmap_get');
this.create(req, res, function createGet$prepareConfig(err, req) {
assert.ifError(err);
if ( ! req.params.config ) {
this.create(req, res, function createGet$prepareConfig(req, config) {
if ( ! config ) {
throw new Error('layergroup GET needs a "config" parameter');
}
return JSON.parse(req.params.config);
});
return JSON.parse(config);
}, next);
};
MapController.prototype.createPost = function(req, res) {
MapController.prototype.createPost = function(req, res, next) {
req.profiler.start('windshaft.createmap_post');
this.create(req, res, function createPost$prepareConfig(err, req) {
assert.ifError(err);
this.create(req, res, function createPost$prepareConfig(req) {
if (!req.is('application/json')) {
throw new Error('layergroup POST data must be of type application/json');
}
return req.body;
});
}, next);
};
MapController.prototype.instantiate = function(req, res) {
MapController.prototype.instantiate = function(req, res, next) {
req.profiler.start('windshaft-cartodb.instance_template_post');
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
@ -99,10 +121,10 @@ MapController.prototype.instantiate = function(req, res) {
return callback(new Error('Template POST data must be of type application/json'));
}
return callback(null, req.body);
});
}, next);
};
MapController.prototype.jsonp = function(req, res) {
MapController.prototype.jsonp = function(req, res, next) {
req.profiler.start('windshaft-cartodb.instance_template_get');
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
@ -121,10 +143,10 @@ MapController.prototype.jsonp = function(req, res) {
}
return callback(err, templateParams);
});
}, next);
};
MapController.prototype.create = function(req, res, prepareConfigFn) {
MapController.prototype.create = function(req, res, prepareConfigFn, next) {
var self = this;
var mapConfig;
@ -132,35 +154,36 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
var context = {};
step(
function setupParams(){
self.req2params(req, this);
function prepareConfig () {
const requestMapConfig = prepareConfigFn(req, res.locals.config);
return requestMapConfig;
},
prepareConfigFn,
function prepareAdapterMapConfig(err, requestMapConfig) {
assert.ifError(err);
context.analysisConfiguration = {
user: req.context.user,
user: res.locals.user,
db: {
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
user: req.params.dbuser,
pass: req.params.dbpassword
host: res.locals.dbhost,
port: res.locals.dbport,
dbname: res.locals.dbname,
user: res.locals.dbuser,
pass: res.locals.dbpassword
},
batch: {
username: req.context.user,
apiKey: req.params.api_key
username: res.locals.user,
apiKey: res.locals.api_key
}
};
self.mapConfigAdapter.getMapConfig(req.context.user, requestMapConfig, req.params, context, this);
self.mapConfigAdapter.getMapConfig(res.locals.user, requestMapConfig, res.locals, context, this);
},
function createLayergroup(err, requestMapConfig) {
assert.ifError(err);
var datasource = context.datasource || Datasource.EmptyDatasource();
mapConfig = new MapConfig(requestMapConfig, datasource);
self.mapBackend.createLayergroup(
mapConfig, req.params,
new CreateLayergroupMapConfigProvider(mapConfig, req.context.user, self.userLimitsApi, req.params),
mapConfig,
res.locals,
new CreateLayergroupMapConfigProvider(mapConfig, res.locals.user, self.userLimitsApi, res.locals),
this
);
},
@ -188,11 +211,12 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
err = error;
}
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
err.label = 'ANONYMOUS LAYERGROUP';
next(err);
} else {
var analysesResults = context.analysesResults || [];
self.addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
self.addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
self.addDataviewsAndWidgetsUrls(res.locals.user, layergroup, mapConfig.obj());
self.addAnalysesMetadata(res.locals.user, layergroup, analysesResults, true);
addContextMetadata(layergroup, mapConfig.obj(), context);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.send(req, res, layergroup, 200);
@ -212,17 +236,14 @@ function addContextMetadata(layergroup, mapConfig, context) {
}
}
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn, next) {
var self = this;
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
var mapConfigProvider;
var mapConfig;
step(
function setupParams(){
self.req2params(req, this);
},
function getTemplateParams() {
prepareParamsFn(this);
},
@ -237,8 +258,8 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
cdbuser,
req.params.template_id,
templateParams,
req.query.auth_token,
req.params
res.locals.auth_token,
res.locals
);
mapConfigProvider.getMapConfig(this);
},
@ -259,7 +280,8 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
self.sendError(req, res, err, 'NAMED MAP LAYERGROUP');
err.label = 'NAMED MAP LAYERGROUP';
next(err);
} else {
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
@ -282,7 +304,7 @@ MapController.prototype.afterLayergroupCreate =
function(req, res, mapconfig, layergroup, analysesResults, callback) {
var self = this;
var username = req.context.user;
var username = res.locals.user;
var tasksleft = 2; // redis key and affectedTables
var errors = [];
@ -323,7 +345,7 @@ function(req, res, mapconfig, layergroup, analysesResults, callback) {
}
});
var dbName = req.params.dbname;
var dbName = res.locals.dbname;
var layergroupId = layergroup.layergroupid;
var dbConnection;

View File

@ -10,9 +10,9 @@ var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var allowQueryParams = require('../middleware/allow-query-params');
function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend,
function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, previewBackend,
surrogateKeysCache, tablesExtentApi, metadataBackend) {
BaseController.call(this, authApi, pgConnection);
BaseController.call(this);
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
@ -20,6 +20,7 @@ function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileB
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
this.metadataBackend = metadataBackend;
this.prepareContext = prepareContext;
}
util.inherits(NamedMapsController, BaseController);
@ -27,18 +28,26 @@ util.inherits(NamedMapsController, BaseController);
module.exports = NamedMapsController;
NamedMapsController.prototype.register = function(app) {
app.get(app.base_url_templated +
'/:template_id/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.tile.bind(this));
app.get(
app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
this.prepareContext,
this.tile.bind(this)
);
app.get(app.base_url_mapconfig +
'/static/named/:template_id/:width/:height.:format', cors(), userMiddleware,
app.get(
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
this.staticMap.bind(this));
this.prepareContext,
this.staticMap.bind(this)
);
};
NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName()));
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(res.locals.user, namedMapProvider.getTemplateName()));
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
@ -75,24 +84,20 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header
);
};
NamedMapsController.prototype.tile = function(req, res) {
NamedMapsController.prototype.tile = function(req, res, next) {
var self = this;
var cdbUser = req.context.user;
var cdbUser = res.locals.user;
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
function getNamedMapProvider() {
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
res.locals,
this
);
},
@ -104,7 +109,8 @@ NamedMapsController.prototype.tile = function(req, res) {
function handleImage(err, tile, headers, stats) {
req.profiler.add(stats);
if (err) {
self.sendError(req, res, err, 'NAMED_MAP_TILE');
err.label = 'NAMED_MAP_TILE';
next(err);
} else {
self.sendResponse(req, res, tile, headers, namedMapProvider);
}
@ -112,28 +118,24 @@ NamedMapsController.prototype.tile = function(req, res) {
);
};
NamedMapsController.prototype.staticMap = function(req, res) {
NamedMapsController.prototype.staticMap = function(req, res, next) {
var self = this;
var cdbUser = req.context.user;
var cdbUser = res.locals.user;
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.format = 'png';
req.params.layer = 'all';
res.locals.format = req.params.format || 'png';
res.locals.layer = res.locals.layer || 'all';
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
function getNamedMapProvider() {
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
res.locals,
this
);
},
@ -141,12 +143,12 @@ NamedMapsController.prototype.staticMap = function(req, res) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, namedMapProvider, this);
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, res.locals, namedMapProvider, this);
},
function prepareImageOptions(err) {
assert.ifError(err);
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
self.getStaticImageOptions(cdbUser, res.locals, namedMapProvider, this);
},
function getImage(err, imageOpts) {
assert.ifError(err);
@ -180,7 +182,8 @@ NamedMapsController.prototype.staticMap = function(req, res) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'STATIC_VIZ_MAP');
err.label = 'STATIC_VIZ_MAP';
next(err);
} else {
self.sendResponse(req, res, image, headers, namedMapProvider);
}
@ -188,7 +191,13 @@ NamedMapsController.prototype.staticMap = function(req, res) {
);
};
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (user, req, namedMapProvider, callback) {
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (
user,
req,
params,
namedMapProvider,
callback
) {
var self = this;
namedMapProvider.getTemplate(function (err, template) {
if (err) {
@ -213,7 +222,7 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (us
}
// overwrites 'all' default filter
req.params.layer = layerVisibilityFilter.join(',');
params.layer = layerVisibilityFilter.join(',');
// recreates the provider
self.namedMapProviderCache.get(
@ -221,7 +230,7 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (us
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
params,
callback
);
});

View File

@ -15,8 +15,8 @@ var userMiddleware = require('../middleware/user');
* @param {TemplateMaps} templateMaps
* @constructor
*/
function NamedMapsAdminController(authApi, pgConnection, templateMaps) {
BaseController.call(this, authApi, pgConnection);
function NamedMapsAdminController(authApi, templateMaps) {
BaseController.call(this);
this.authApi = authApi;
this.templateMaps = templateMaps;
@ -26,19 +26,52 @@ util.inherits(NamedMapsAdminController, BaseController);
module.exports = NamedMapsAdminController;
NamedMapsAdminController.prototype.register = function(app) {
app.post(app.base_url_templated, cors(), userMiddleware, this.create.bind(this));
app.put(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.update.bind(this));
app.get(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.retrieve.bind(this));
app.delete(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.destroy.bind(this));
app.get(app.base_url_templated, cors(), userMiddleware, this.list.bind(this));
app.options(app.base_url_templated + '/:template_id', cors('Content-Type'));
NamedMapsAdminController.prototype.register = function (app) {
app.post(
app.base_url_templated + '/',
cors(),
userMiddleware,
this.create.bind(this)
);
app.put(
app.base_url_templated + '/:template_id',
cors(),
userMiddleware,
this.update.bind(this)
);
app.get(
app.base_url_templated + '/:template_id',
cors(),
userMiddleware,
this.retrieve.bind(this)
);
app.delete(
app.base_url_templated + '/:template_id',
cors(),
userMiddleware,
this.destroy.bind(this)
);
app.get(
app.base_url_templated + '/',
cors(),
userMiddleware,
this.list.bind(this)
);
app.options(
app.base_url_templated + '/:template_id',
cors('Content-Type')
);
};
NamedMapsAdminController.prototype.create = function(req, res) {
NamedMapsAdminController.prototype.create = function(req, res, next) {
var self = this;
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
step(
function checkPerms(){
@ -55,14 +88,14 @@ NamedMapsAdminController.prototype.create = function(req, res) {
assert.ifError(err);
return { template_id: tpl_id };
},
finishFn(self, req, res, 'POST TEMPLATE')
finishFn(self, req, res, 'POST TEMPLATE', null, next)
);
};
NamedMapsAdminController.prototype.update = function(req, res) {
NamedMapsAdminController.prototype.update = function(req, res, next) {
var self = this;
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
var template;
var tpl_id;
@ -84,16 +117,16 @@ NamedMapsAdminController.prototype.update = function(req, res) {
return { template_id: tpl_id };
},
finishFn(self, req, res, 'PUT TEMPLATE')
finishFn(self, req, res, 'PUT TEMPLATE', null, next)
);
};
NamedMapsAdminController.prototype.retrieve = function(req, res) {
NamedMapsAdminController.prototype.retrieve = function(req, res, next) {
var self = this;
req.profiler.start('windshaft-cartodb.get_template');
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
var tpl_id;
step(
function checkPerms(){
@ -118,16 +151,16 @@ NamedMapsAdminController.prototype.retrieve = function(req, res) {
delete tpl_val.auth_id;
return { template: tpl_val };
},
finishFn(self, req, res, 'GET TEMPLATE')
finishFn(self, req, res, 'GET TEMPLATE', null, next)
);
};
NamedMapsAdminController.prototype.destroy = function(req, res) {
NamedMapsAdminController.prototype.destroy = function(req, res, next) {
var self = this;
req.profiler.start('windshaft-cartodb.delete_template');
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
var tpl_id;
step(
function checkPerms(){
@ -144,15 +177,15 @@ NamedMapsAdminController.prototype.destroy = function(req, res) {
assert.ifError(err);
return '';
},
finishFn(self, req, res, 'DELETE TEMPLATE', 204)
finishFn(self, req, res, 'DELETE TEMPLATE', 204, next)
);
};
NamedMapsAdminController.prototype.list = function(req, res) {
NamedMapsAdminController.prototype.list = function(req, res, next) {
var self = this;
req.profiler.start('windshaft-cartodb.get_template_list');
var cdbuser = req.context.user;
var cdbuser = res.locals.user;
step(
function checkPerms(){
@ -168,14 +201,15 @@ NamedMapsAdminController.prototype.list = function(req, res) {
assert.ifError(err);
return { template_ids: tpl_ids };
},
finishFn(self, req, res, 'GET TEMPLATE LIST')
finishFn(self, req, res, 'GET TEMPLATE LIST', null, next)
);
};
function finishFn(controller, req, res, description, status) {
function finishFn(controller, req, res, description, status, next) {
return function finish(err, response){
if (err) {
controller.sendError(req, res, err, description);
err.label = description;
next(err);
} else {
controller.send(req, res, response, status || 200);
}

View File

@ -3,7 +3,7 @@ module.exports = function allowQueryParams(params) {
throw new Error('allowQueryParams must receive an Array of params');
}
return function allowQueryParamsMiddleware(req, res, next) {
req.context.allowedQueryParams = params;
res.locals.allowedQueryParams = params;
next();
};
};

View File

@ -0,0 +1,20 @@
module.exports = function authorizeMiddleware (authApi) {
return function (req, res, next) {
req.profiler.done('req2params.setup');
authApi.authorize(req, res, (err, authorized) => {
req.profiler.done('authorize');
if (err) {
return next(err);
}
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
return next(err);
}
return next();
});
};
};

View File

@ -0,0 +1,32 @@
const _ = require('underscore');
// Whitelist query parameters and attach format
const REQUEST_QUERY_PARAMS_WHITELIST = [
'config',
'map_key',
'api_key',
'auth_token',
'callback',
'zoom',
'lon',
'lat',
// analysis
'filters' // json
];
module.exports = function cleanUpQueryParamsMiddleware () {
return function cleanUpQueryParams (req, res, next) {
var allowedQueryParams = REQUEST_QUERY_PARAMS_WHITELIST;
if (Array.isArray(res.locals.allowedQueryParams)) {
allowedQueryParams = allowedQueryParams.concat(res.locals.allowedQueryParams);
}
req.query = _.pick(req.query, allowedQueryParams);
// bring all query values onto res.locals object
_.extend(res.locals, req.query);
next();
};
};

View File

@ -0,0 +1,31 @@
const _ = require('underscore');
module.exports = function dbConnSetupMiddleware(pgConnection) {
return function dbConnSetup(req, res, next) {
const user = res.locals.user;
pgConnection.setDBConn(user, res.locals, (err) => {
if (err) {
if (err.message && -1 !== err.message.indexOf('name not found')) {
err.http_status = 404;
}
req.profiler.done('req2params');
return next(err);
}
// Add default database connection parameters
// if none given
_.defaults(res.locals, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
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);
});
};
};

View File

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

View File

@ -0,0 +1,32 @@
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();
};

View File

@ -0,0 +1,8 @@
const _ = require('underscore');
module.exports = function localsMiddleware(req, res, next) {
_.extend(res.locals, req.params);
next();
};

View File

@ -0,0 +1,172 @@
const _ = require('underscore');
const debug = require('debug')('windshaft:cartodb:error-middleware');
module.exports = function errorMiddleware (/* options */) {
return function error (err, req, res, next) {
// jshint unused:false
// jshint maxcomplexity:9
var allErrors = Array.isArray(err) ? err : [err];
allErrors = populateTimeoutErrors(allErrors);
const label = err.label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
if (err.message === 'Tile does not exist' && res.locals.format === 'mvt') {
statusCode = 204;
}
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {
statusCode = 200;
}
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
res.set('X-Tiler-Profiler', req.profiler.toJSONString());
res.status(statusCode);
if (req.query && req.query.callback) {
res.jsonp(errorResponseBody);
} else {
res.json(errorResponseBody);
}
try {
// May throw due to dns, see
// See http://github.com/CartoDB/Windshaft/issues/166
req.profiler.sendStats();
} catch (err) {
debug("error sending profiling stats: " + err);
}
};
};
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function populateTimeoutErrors (errors) {
return errors.map(function (error) {
if (isRenderTimeoutError(error)) {
error.subtype = 'render';
}
if (isDatasourceTimeoutError(error)) {
error.subtype = 'datasource';
}
if (isTimeoutError(error)) {
error.message = 'You are over platform\'s limits. Please contact us to know more details';
error.type = 'limit';
error.http_status = 429;
}
return error;
});
}
function findStatusCode(err) {
var statusCode;
if ( err.http_status ) {
statusCode = err.http_status;
} else {
statusCode = statusFromErrorMessage('' + err);
}
return statusCode;
}
module.exports.findStatusCode = findStatusCode;
function statusFromErrorMessage(errMsg) {
// Find an appropriate statusCode based on message
// jshint maxcomplexity:7
var statusCode = 400;
if ( -1 !== errMsg.indexOf('permission denied') ) {
statusCode = 403;
}
else if ( -1 !== errMsg.indexOf('authentication failed') ) {
statusCode = 403;
}
else if (errMsg.match(/Postgis Plugin.*[\s|\n].*column.*does not exist/)) {
statusCode = 400;
}
else if ( -1 !== errMsg.indexOf('does not exist') ) {
if ( -1 !== errMsg.indexOf(' role ') ) {
statusCode = 403; // role 'xxx' does not exist
} else if ( errMsg.match(/function .* does not exist/) ) {
statusCode = 400; // invalid SQL (SQL function does not exist)
} else {
statusCode = 404;
}
}
return statusCode;
}
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return stripConnectionInfo(message);
}
module.exports.errorMessage = errorMessage;
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
.replace(/Connection string: '[^']*'\n\s/im, '')
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
var ERROR_INFO_TO_EXPOSE = {
message: true,
layer: true,
type: true,
analysis: true,
subtype: true
};
function shouldBeExposed (prop) {
return !!ERROR_INFO_TO_EXPOSE[prop];
}
function errorMessageWithContext(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
var error = {
type: err.type || 'unknown',
message: stripConnectionInfo(message),
};
for (var prop in err) {
// type & message are properties from Error's prototype and will be skipped
if (err.hasOwnProperty(prop) && shouldBeExposed(prop)) {
error[prop] = err[prop];
}
}
return error;
}

View File

@ -1,8 +1,8 @@
'use strict';
var LZMA = require('lzma').LZMA;
const LZMA = require('lzma').LZMA;
var lzmaWorker = new LZMA();
const lzmaWorker = new LZMA();
module.exports = function lzmaMiddleware(req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {

View File

@ -2,6 +2,13 @@ var CdbRequest = require('../models/cdb_request');
var cdbRequest = new CdbRequest();
module.exports = function userMiddleware(req, res, next) {
req.context.user = cdbRequest.userByReq(req);
res.locals.user = cdbRequest.userByReq(req);
// avoid a req.params.user equals to undefined
// overwrites res.locals.user
if(!req.params.user) {
delete req.params.user;
}
next();
};

View File

@ -4,8 +4,6 @@ var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
var lzmaMiddleware = require('./middleware/lzma');
var controller = require('./controllers');
var SurrogateKeysCache = require('./cache/surrogate_keys_cache');
@ -46,6 +44,11 @@ var MapConfigAdapter = require('./models/mapconfig/adapter');
var StatsBackend = require('./backends/stats');
const lzmaMiddleware = require('./middleware/lzma');
const errorMiddleware = require('./middleware/error-middleware');
const prepareContextMiddleware = require('./middleware/context');
module.exports = function(serverOptions) {
// Make stats client globally accessible
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
@ -209,12 +212,16 @@ module.exports = function(serverOptions) {
var versions = getAndValidateVersions(serverOptions);
const prepareContext = typeof serverOptions.req2params === 'function' ?
serverOptions.req2params :
prepareContextMiddleware(authApi, pgConnection);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
new controller.Layergroup(
authApi,
prepareContext,
pgConnection,
mapStore,
tileBackend,
@ -227,7 +234,7 @@ module.exports = function(serverOptions) {
).register(app);
new controller.Map(
authApi,
prepareContext,
pgConnection,
templateMaps,
mapBackend,
@ -240,8 +247,7 @@ module.exports = function(serverOptions) {
).register(app);
new controller.NamedMaps(
authApi,
pgConnection,
prepareContext,
namedMapProviderCache,
tileBackend,
previewBackend,
@ -250,9 +256,9 @@ module.exports = function(serverOptions) {
metadataBackend
).register(app);
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
new controller.NamedMapsAdmin(authApi, templateMaps).register(app);
new controller.Analyses(authApi, pgConnection).register(app);
new controller.Analyses(prepareContext).register(app);
new controller.ServerInfo(versions).register(app);
@ -260,6 +266,8 @@ module.exports = function(serverOptions) {
* END Routing
******************************************************************************************************************/
app.use(errorMiddleware());
return app;
};
@ -354,7 +362,6 @@ function bootstrap(opts) {
app.use(bodyParser.json());
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
req.context = req.context || {};
req.profiler = new Profiler({
statsd_client: global.statsClient,
profile: opts.useProfiler

View File

@ -7,7 +7,7 @@ var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var TestClient = require('../../support/test-client');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('named-maps analysis', function() {

View File

@ -8,7 +8,7 @@ var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('get requests with cache headers', function() {

View File

@ -1,6 +1,6 @@
var assert = require('../support/assert');
var step = require('step');
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var testHelper = require(__dirname + '/../support/test_helper');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');

View File

@ -9,7 +9,7 @@ var mapnik = require('windshaft').mapnik;
var semver = require('semver');
var helper = require(__dirname + '/../support/test_helper');
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures';

View File

@ -4,7 +4,7 @@ var assert = require('../support/assert');
var _ = require('underscore');
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var QueryTables = require('cartodb-query-tables');

View File

@ -5,7 +5,7 @@ var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js');

View File

@ -5,7 +5,7 @@ var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var RedisPool = require('redis-mpool');

View File

@ -5,7 +5,7 @@ var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var RedisPool = require('redis-mpool');

View File

@ -6,7 +6,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var PortedServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('attributes', function() {

View File

@ -7,7 +7,7 @@ var step = require('step');
var mapnik = require('windshaft').mapnik;
var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
var BaseController = require('../../../lib/cartodb/controllers/base');
describe('multilayer', function() {

View File

@ -5,7 +5,7 @@ var _ = require('underscore');
var cartodbServer = require('../../../lib/cartodb/server');
var getLayerTypeFn = require('windshaft').model.MapConfig.prototype.getType;
var PortedServerOptions = require('./support/ported_server_options');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
var BaseController = require('../../../lib/cartodb/controllers/base');

View File

@ -6,7 +6,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('raster', function() {

View File

@ -6,7 +6,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('retina support', function() {

View File

@ -7,7 +7,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 85;

View File

@ -1,7 +1,7 @@
var _ = require('underscore');
var serverOptions = require('../../../../lib/cartodb/server_options');
var LayergroupToken = require('../../../support/layergroup-token');
var mapnik = require('windshaft').mapnik;
var LayergroupToken = require('../../../../lib/cartodb/models/layergroup-token');
var OverviewsQueryRewriter = require('../../../../lib/cartodb/utils/overviews_query_rewriter');
var overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
@ -48,7 +48,7 @@ module.exports = _.extend({}, serverOptions, {
log_format: null, // do not log anything
afterLayergroupCreateCalls: 0,
useProfiler: true,
req2params: function(req, callback){
req2params: function(req, res, callback){
if ( req.query.testUnexpectedError ) {
return callback('test unexpected error');
@ -56,13 +56,14 @@ module.exports = _.extend({}, serverOptions, {
// this is in case you want to test sql parameters eg ...png?sql=select * from my_table limit 10
req.params = _.extend({}, req.params);
if (req.params.token) {
req.params.token = LayergroupToken.parse(req.params.token).token;
}
_.extend(req.params, req.query);
req.params.user = 'localhost';
req.context = {user: 'localhost'};
res.locals.user = 'localhost';
req.params.dbhost = global.environment.postgres.host;
req.params.dbport = req.params.dbport || global.environment.postgres.port;
@ -73,6 +74,9 @@ module.exports = _.extend({}, serverOptions, {
}
req.params.dbname = 'test_windshaft_cartodb_user_1_db';
// add all params to res.locals
res.locals = _.extend({}, req.params);
// increment number of calls counter
global.req2params_calls = global.req2params_calls ? global.req2params_calls + 1 : 1;

View File

@ -1,5 +1,5 @@
var testHelper = require('../../../support/test_helper');
var LayergroupToken = require('../../../support/layergroup-token');
var LayergroupToken = require('../../../../lib/cartodb/models/layergroup-token');
var step = require('step');
var assert = require('../../../support/assert');

View File

@ -7,7 +7,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('torque', function() {

View File

@ -5,7 +5,7 @@ var cartodbServer = require('../../../lib/cartodb/server');
var ServerOptions = require('./support/ported_server_options');
var BaseController = require('../../../lib/cartodb/controllers/base');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('torque boundary points', function() {

View File

@ -23,7 +23,7 @@ var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
var LayergroupToken = require('../support/layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
describe('template_api', function() {
server.layergroupAffectedTablesCache.cache.reset();

View File

@ -1,6 +1,6 @@
var assert = require('../../support/assert');
var step = require('step');
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
var testHelper = require('../../support/test_helper');
var CartodbWindshaft = require('../../../lib/cartodb/server');
var serverOptions = require('../../../lib/cartodb/server_options');

View File

@ -10,7 +10,7 @@ var CartodbWindshaft = require('../../../lib/cartodb/server');
var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var LayergroupToken = require('../../support/layergroup-token');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
describe('named-maps widgets', function() {

View File

@ -7,7 +7,7 @@ var PSQL = require('cartodb-psql');
var _ = require('underscore');
var mapnik = require('windshaft').mapnik;
var LayergroupToken = require('./layergroup-token');
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
var assert = require('./assert');
var helper = require('./test_helper');
@ -116,6 +116,15 @@ module.exports.SQL = {
ONE_POINT: 'select 1 as cartodb_id, \'SRID=3857;POINT(0 0)\'::geometry the_geom_webmercator'
};
function resErr2errRes(callback) {
return (res, err) => {
if (err) {
return callback(err);
}
return callback(err, res);
};
}
TestClient.prototype.getWidget = function(widgetName, params, callback) {
var self = this;
@ -716,9 +725,9 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
}
assert.response(self.server, request, expectedResponse, this);
assert.response(self.server, request, expectedResponse, resErr2errRes(this));
},
function finish(res, err) {
function finish(err, res) {
if (err) {
return callback(err);
}
@ -869,9 +878,9 @@ TestClient.prototype.getStaticCenter = function (params, callback) {
}
}, params.response);
assert.response(self.server, request, expectedResponse, this);
assert.response(self.server, request, expectedResponse, resErr2errRes(this));
},
function(res, err) {
function(err, res) {
if (err) {
return callback(err);
}
@ -968,9 +977,9 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
}
};
assert.response(self.server, request, expectedResponse, this);
assert.response(self.server, request, expectedResponse, resErr2errRes(this));
},
function finish(res, err) {
function finish(err, res) {
if (err) {
return callback(err);
}
@ -1063,9 +1072,9 @@ TestClient.prototype.getAttributes = function(params, callback) {
}
};
assert.response(self.server, request, expectedResponse, this);
assert.response(self.server, request, expectedResponse, resErr2errRes(this));
},
function finish(res, err) {
function finish(err, res) {
if (err) {
return callback(err);
}

View File

@ -1,21 +1,21 @@
require('../../support/test_helper.js');
var assert = require('assert');
var BaseController = require('../../../lib/cartodb/controllers/base');
var errorMiddleware = require('../../../lib/cartodb/middleware/error-middleware');
describe('BaseController', function() {
describe('error-middleware', function() {
it('different formats for postgis plugin error returns 400 as status code', function() {
var expectedStatusCode = 400;
assert.equal(
BaseController.findStatusCode("Postgis Plugin: ERROR: column \"missing\" does not exist\n"),
errorMiddleware.findStatusCode("Postgis Plugin: ERROR: column \"missing\" does not exist\n"),
expectedStatusCode,
"Error status code for single line does not match"
);
assert.equal(
BaseController.findStatusCode("Postgis Plugin: PSQL error:\nERROR: column \"missing\" does not exist\n"),
errorMiddleware.findStatusCode("Postgis Plugin: PSQL error:\nERROR: column \"missing\" does not exist\n"),
expectedStatusCode,
"Error status code for multiline/PSQL does not match"
);

View File

@ -2,7 +2,7 @@ require('../../support/test_helper');
var assert = require('assert');
var BaseController = require('../../../lib/cartodb/controllers/base');
var errorMiddleware = require('../../../lib/cartodb/middleware/error-middleware');
describe('error messages clean up', function() {
@ -15,7 +15,7 @@ describe('error messages clean up', function() {
" encountered during parsing of layer 'layer0' in Layer"
].join('\n');
var outMessage = BaseController.errorMessage(inMessage);
var outMessage = errorMiddleware.errorMessage(inMessage);
assert.ok(outMessage.match('connect'), outMessage);
assert.ok(!outMessage.match(/666/), outMessage);

View File

@ -41,7 +41,9 @@ describe('tile stats', function() {
jsonp: function() {},
send: function() {}
};
layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, resMock, null, null);
var next = function () {};
layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, resMock, null, null, next);
assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
@ -74,7 +76,8 @@ describe('tile stats', function() {
var layergroupController = new LayergroupController();
layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, resMock, null, null);
var next = function () {};
layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, resMock, null, null, next);
assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');

View File

@ -0,0 +1,171 @@
var assert = require('assert');
var _ = require('underscore');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../../lib/cartodb/backends/pg_connection');
var AuthApi = require('../../../lib/cartodb/api/auth_api');
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 localsMiddleware = require('../../../lib/cartodb/middleware/context/locals');
var windshaft = require('windshaft');
describe('prepare-context', function() {
var test_user = _.template(global.environment.postgres_auth_user, {user_id:1});
var test_pubuser = global.environment.postgres.user;
var test_database = test_user + '_db';
let cleanUpQueryParams;
let dbConnSetup;
let authorize;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var mapStore = new windshaft.storage.MapStore();
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var templateMaps = new TemplateMaps(redisPool);
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
authorize = authorizeMiddleware(authApi);
dbConnSetup = dbConnSetupMiddleware(pgConnection);
});
it('can be found in server_options', function(){
assert.ok(_.isFunction(authorize));
assert.ok(_.isFunction(dbConnSetup));
assert.ok(_.isFunction(cleanUpQueryParams));
});
function prepareRequest(req) {
req.profiler = {
done: function() {}
};
return req;
}
function prepareResponse(res) {
if(!res.locals) {
res.locals = {};
}
res.locals.user = 'localhost';
return res;
}
it('res.locals are created', function(done) {
let req = {};
let res = {};
localsMiddleware(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 = {};
cleanUpQueryParams(prepareRequest(req), prepareResponse(res), function(err) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
assert.ok(!res.locals.hasOwnProperty('interactivity'), 'response locals do not have interactivity');
done();
});
});
it('sets dbname from redis metadata', function(done){
var req = {headers: { host:'localhost' }, query: {} };
var res = { set: function () {} };
dbConnSetup(prepareRequest(req), prepareResponse(res), function(err) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
assert.ok(!res.locals.hasOwnProperty('interactivity'), 'response locals do not have interactivity');
assert.equal(res.locals.dbname, test_database);
assert.ok(res.locals.dbuser === test_pubuser, 'could inject dbuser ('+res.locals.dbuser+')');
done();
});
});
it('sets also dbuser for authenticated requests', function(done){
var req = { headers: { host: 'localhost' }, query: { map_key: '1234' }};
var res = { set: function () {} };
// FIXME: review authorize-pgconnsetup workflow, It might we are doing authorization twice.
authorize(prepareRequest(req), prepareResponse(res), function (err) {
if (err) { done(err); return; }
dbConnSetup(req, res, function(err) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
assert.ok(!res.locals.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(res.locals.dbname, test_database);
assert.equal(res.locals.dbuser, test_user);
req = {
headers: {
host:'localhost'
},
query: {
map_key: '1235'
}
};
res = { set: function () {} };
dbConnSetup(prepareRequest(req), prepareResponse(res), function() {
// wrong key resets params to no user
assert.ok(res.locals.dbuser === test_pubuser, 'could inject dbuser ('+res.locals.dbuser+')');
done();
});
});
});
});
it('it should remove invalid params', function(done) {
var config = {
version: '1.3.0'
};
var req = {
headers: {
host:'localhost'
},
query: {
non_included: 'toberemoved',
api_key: 'test',
style: 'override',
config: config
}
};
var res = {};
cleanUpQueryParams(prepareRequest(req), prepareResponse(res), function (err) {
if ( err ) {
return done(err);
}
var query = res.locals;
assert.deepEqual(config, query.config);
assert.equal('test', query.api_key);
assert.equal(undefined, query.non_included);
done();
});
});
});

View File

@ -1,129 +0,0 @@
var assert = require('assert');
var _ = require('underscore');
require('../../support/test_helper');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../../lib/cartodb/backends/pg_connection');
var AuthApi = require('../../../lib/cartodb/api/auth_api');
var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
var BaseController = require('../../../lib/cartodb/controllers/base');
var windshaft = require('windshaft');
describe('req2params', function() {
var test_user = _.template(global.environment.postgres_auth_user, {user_id:1});
var test_pubuser = global.environment.postgres.user;
var test_database = test_user + '_db';
var baseController;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var mapStore = new windshaft.storage.MapStore();
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var templateMaps = new TemplateMaps(redisPool);
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
baseController = new BaseController(authApi, pgConnection);
});
it('can be found in server_options', function(){
assert.ok(_.isFunction(baseController.req2params));
});
function prepareRequest(req) {
req.profiler = {
done: function() {}
};
req.context = { user: 'localhost' };
return req;
}
it('cleans up request', function(done){
var req = {headers: { host:'localhost' }, query: {dbuser:'hacker',dbname:'secret'}};
baseController.req2params(prepareRequest(req), function(err, req) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database, 'could forge dbname: '+ req.params.dbname);
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
done();
});
});
it('sets dbname from redis metadata', function(done){
var req = {headers: { host:'localhost' }, query: {} };
baseController.req2params(prepareRequest(req), function(err, req) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database);
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
done();
});
});
it('sets also dbuser for authenticated requests', function(done){
var req = {headers: { host:'localhost' }, query: {map_key: '1234'} };
baseController.req2params(prepareRequest(req), function(err, req) {
if ( err ) { done(err); return; }
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database);
assert.equal(req.params.dbuser, test_user);
req = {
headers: {
host:'localhost'
},
query: {
map_key: '1235'
}
};
baseController.req2params(prepareRequest(req), function(err, req) {
// wrong key resets params to no user
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
done();
});
});
});
it('it should remove invalid params', function(done) {
var config = {
version: '1.3.0'
};
var req = {
headers: {
host:'localhost'
},
query: {
non_included: 'toberemoved',
api_key: 'test',
style: 'override',
config: config
}
};
baseController.req2params(prepareRequest(req), function(err, req) {
if (err) {
return done(err);
}
var query = req.params;
assert.deepEqual(config, query.config);
assert.equal('test', query.api_key);
assert.equal(undefined, query.non_included);
assert.equal(undefined, query.style);
done();
});
});
});