diff --git a/lib/cartodb/controllers/base.js b/lib/cartodb/controllers/base.js new file mode 100644 index 00000000..3d2fad13 --- /dev/null +++ b/lib/cartodb/controllers/base.js @@ -0,0 +1,150 @@ +var assert = require('assert'); + +var _ = require('underscore'); +var step = require('step'); + +var LZMA = require('lzma').LZMA; +var lzmaWorker = new LZMA(); + +// Whitelist query parameters and attach format +var REQUEST_QUERY_PARAMS_WHITELIST = [ + 'config', + 'map_key', + 'api_key', + 'auth_token', + 'callback' +]; + +function BaseController(authApi, pgConnection) { + this.authApi = authApi; + this.pgConnection = pgConnection; +} + +module.exports = BaseController; + +// jshint maxcomplexity:9 +/** + * 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; + + if ( req.query.lzma ) { + + // 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) { + if (req.profiler) { + req.profiler.done('lzma'); + } + try { + delete req.query.lzma; + _.extend(req.query, JSON.parse(result)); + self.req2params(req, callback); + } catch (err) { + req.profiler.done('req2params'); + callback(new Error('Error parsing lzma as JSON: ' + err)); + } + } + ); + return; + } + + req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST); + 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} + //console.log("Request parameters include token " + req.params.token); + 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(); + //console.log("Request for token " + req.params.token + " with signature from " + req.params.signer); + } + } + + // bring all query values onto req.params object + _.extend(req.params, req.query); + + if (req.profiler) { + req.profiler.done('req2params.setup'); + } + + step( + function getPrivacy(){ + self.authApi.authorize(req, this); + }, + function validateAuthorization(err, authorized) { + if (req.profiler) { + 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 diff --git a/lib/cartodb/controllers/layergroup.js b/lib/cartodb/controllers/layergroup.js index 57a7628f..ed95dabe 100644 --- a/lib/cartodb/controllers/layergroup.js +++ b/lib/cartodb/controllers/layergroup.js @@ -1,6 +1,9 @@ var assert = require('assert'); var step = require('step'); +var util = require('util'); +var BaseController = require('./base'); + var cors = require('../middleware/cors'); var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider'); @@ -8,6 +11,8 @@ var TablesCacheEntry = require('../cache/model/database_tables_entry'); /** * @param app + * @param {AuthApi} authApi + * @param {PgConnection} pgConnection * @param {MapStore} mapStore * @param {TileBackend} tileBackend * @param {PreviewBackend} previewBackend @@ -18,8 +23,10 @@ var TablesCacheEntry = require('../cache/model/database_tables_entry'); * @param {LayergroupAffectedTables} layergroupAffectedTables * @constructor */ -function LayergroupController(app, mapStore, tileBackend, previewBackend, attributesBackend, surrogateKeysCache, - userLimitsApi, queryTablesApi, layergroupAffectedTables) { +function LayergroupController(app, authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend, + surrogateKeysCache, userLimitsApi, queryTablesApi, layergroupAffectedTables) { + BaseController.call(this, authApi, pgConnection); + this.app = app; this.mapStore = mapStore; this.tileBackend = tileBackend; @@ -31,6 +38,8 @@ function LayergroupController(app, mapStore, tileBackend, previewBackend, attrib this.layergroupAffectedTables = layergroupAffectedTables; } +util.inherits(LayergroupController, BaseController); + module.exports = LayergroupController; @@ -52,7 +61,7 @@ LayergroupController.prototype.attributes = function(req, res) { step( function setupParams() { - self.app.req2params(req, this); + self.req2params(req, this); }, function retrieveFeatureAttributes(err) { assert.ifError(err); @@ -95,7 +104,7 @@ LayergroupController.prototype.tileOrLayer = function (req, res) { step( function mapController$prepareParams() { - self.app.req2params(req, this); + self.req2params(req, this); }, function mapController$getTileOrGrid(err) { assert.ifError(err); @@ -181,7 +190,7 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo step( function() { - self.app.req2params(req, this); + self.req2params(req, this); }, function(err) { assert.ifError(err); diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index 73182387..45b2b1ce 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -3,6 +3,9 @@ var assert = require('assert'); var step = require('step'); var windshaft = require('windshaft'); +var util = require('util'); +var BaseController = require('./base'); + var cors = require('../middleware/cors'); var MapConfig = windshaft.model.MapConfig; @@ -17,6 +20,7 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_laye /** * @param app + * @param {AuthApi} authApi * @param {PgConnection} pgConnection * @param {TemplateMaps} templateMaps * @param {MapBackend} mapBackend @@ -27,8 +31,11 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_laye * @param {LayergroupAffectedTables} layergroupAffectedTables * @constructor */ -function MapController(app, pgConnection, templateMaps, mapBackend, metadataBackend, queryTablesApi, +function MapController(app, authApi, pgConnection, templateMaps, mapBackend, metadataBackend, queryTablesApi, surrogateKeysCache, userLimitsApi, layergroupAffectedTables) { + + BaseController.call(this, authApi, pgConnection); + this.app = app; this.pgConnection = pgConnection; this.templateMaps = templateMaps; @@ -42,6 +49,8 @@ function MapController(app, pgConnection, templateMaps, mapBackend, metadataBack this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); } +util.inherits(MapController, BaseController); + module.exports = MapController; @@ -121,7 +130,7 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { step( function setupParams(){ - self.app.req2params(req, this); + self.req2params(req, this); }, prepareConfigFn, function beforeLayergroupCreate(err, requestMapConfig) { @@ -174,7 +183,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn step( function setupParams(){ - self.app.req2params(req, this); + self.req2params(req, this); }, function getTemplateParams() { prepareParamsFn(this); diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index c123c461..6e897c35 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -2,12 +2,18 @@ var step = require('step'); var assert = require('assert'); var _ = require('underscore'); var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); + +var util = require('util'); +var BaseController = require('./base'); + var cors = require('../middleware/cors'); var TablesCacheEntry = require('../cache/model/database_tables_entry'); -function NamedMapsController(app, namedMapProviderCache, tileBackend, previewBackend, surrogateKeysCache, +function NamedMapsController(app, authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend, surrogateKeysCache, tablesExtentApi) { + BaseController.call(this, authApi, pgConnection); + this.app = app; this.namedMapProviderCache = namedMapProviderCache; this.tileBackend = tileBackend; @@ -16,6 +22,8 @@ function NamedMapsController(app, namedMapProviderCache, tileBackend, previewBac this.tablesExtentApi = tablesExtentApi; } +util.inherits(NamedMapsController, BaseController); + module.exports = NamedMapsController; NamedMapsController.prototype.register = function(app) { @@ -73,7 +81,7 @@ NamedMapsController.prototype.tile = function(req, res) { var namedMapProvider; step( function reqParams() { - self.app.req2params(req, this); + self.req2params(req, this); }, function getTile() { namedMapProvider = self.namedMapProviderCache.get( @@ -110,7 +118,7 @@ NamedMapsController.prototype.staticMap = function(req, res) { var namedMapProvider; step( function reqParams() { - self.app.req2params(req, this); + self.req2params(req, this); }, function getTemplate(err) { assert.ifError(err); diff --git a/lib/cartodb/controllers/named_maps_admin.js b/lib/cartodb/controllers/named_maps_admin.js index 53265cd5..c57e3c9f 100644 --- a/lib/cartodb/controllers/named_maps_admin.js +++ b/lib/cartodb/controllers/named_maps_admin.js @@ -1,15 +1,22 @@ var step = require('step'); var assert = require('assert'); var templateName = require('../backends/template_maps').templateName; + +var util = require('util'); +var BaseController = require('./base'); + var cors = require('../middleware/cors'); /** * @param {TemplateMaps} templateMaps * @param {AuthApi} authApi + * @param {PgConnection} pgConnection * @constructor */ -function NamedMapsAdminController(templateMaps, authApi) { +function NamedMapsAdminController(templateMaps, authApi, pgConnection) { + BaseController.call(this, authApi, pgConnection); + this.templateMaps = templateMaps; this.authApi = authApi; } diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index d2006ead..d3158632 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -178,6 +178,8 @@ module.exports = function(serverOptions) { new controller.Layergroup( app, + authApi, + pgConnection, mapStore, tileBackend, previewBackend, @@ -190,6 +192,7 @@ module.exports = function(serverOptions) { new controller.Map( app, + authApi, pgConnection, templateMaps, mapBackend, @@ -202,6 +205,8 @@ module.exports = function(serverOptions) { new controller.NamedMaps( app, + authApi, + pgConnection, namedMapProviderCache, tileBackend, previewBackend, @@ -209,7 +214,7 @@ module.exports = function(serverOptions) { tablesExtentApi ).register(app); - new controller.NamedMapsAdmin(templateMaps, authApi).register(app); + new controller.NamedMapsAdmin(templateMaps, authApi, pgConnection).register(app); new controller.ServerInfo().register(app); @@ -217,131 +222,6 @@ module.exports = function(serverOptions) { * END Routing ******************************************************************************************************************/ - // jshint maxcomplexity:10 - /** - * 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 - */ - app.req2params = function(req, callback){ - - if ( req.query.lzma ) { - - // 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) { - if (req.profiler) { - req.profiler.done('lzma'); - } - try { - delete req.query.lzma; - _.extend(req.query, JSON.parse(result)); - app.req2params(req, callback); - } catch (err) { - req.profiler.done('req2params'); - callback(new Error('Error parsing lzma as JSON: ' + err)); - } - } - ); - return; - } - - req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST); - 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} - //console.log("Request parameters include token " + req.params.token); - 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(); - //console.log("Request for token " + req.params.token + " with signature from " + req.params.signer); - } - } - - // bring all query values onto req.params object - _.extend(req.params, req.query); - - if (req.profiler) { - req.profiler.done('req2params.setup'); - } - - step( - function getPrivacy(){ - authApi.authorize(req, this); - }, - function validateAuthorization(err, authorized) { - if (req.profiler) { - 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); - 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 - return app; };