diff --git a/lib/cartodb/api/auth_api.js b/lib/cartodb/api/auth_api.js new file mode 100644 index 00000000..5b20cbf4 --- /dev/null +++ b/lib/cartodb/api/auth_api.js @@ -0,0 +1,141 @@ +var assert = require('assert'); +var step = require('step'); + +/** + * + * @param {PgConnection} pgConnection + * @param metadataBackend + * @param {MapStore} mapStore + * @param {TemplateMaps} templateMaps + * @constructor + * @type {AuthApi} + */ +function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) { + this.pgConnection = pgConnection; + this.metadataBackend = metadataBackend; + this.mapStore = mapStore; + this.templateMaps = templateMaps; +} + +module.exports = AuthApi; + +// Check if a request is authorized by a signer +// +// @param req express request 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 ) { + return callback(null, false); // no signer requested + } + + var self = this; + + var layergroup_id = req.params.token; + var auth_token = req.params.auth_token; + + this.mapStore.load(layergroup_id, function(err, mapConfig) { + if (err) { + return callback(err); + } + + var authorized = self.templateMaps.isAuthorized(mapConfig.obj().template, auth_token); + + return callback(null, authorized); + }); +}; + +// Check if a request is authorized by api_key +// +// @param user +// @param req express request object +// @param callback function(err, authorized) +// NOTE: authorized is expected to be 0 or 1 (integer) +// +AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) { + var givenKey = req.query.api_key || req.query.map_key; + if ( ! givenKey && req.body ) { + // check also in request body + givenKey = req.body.api_key || req.body.map_key; + } + if ( ! givenKey ) { + return callback(null, 0); // no api key, no authorization... + } + + var self = this; + + step( + function () { + self.metadataBackend.getUserMapKey(user, this); + }, + function checkApiKey(err, val){ + assert.ifError(err); + return val && givenKey == val; + }, + function finish(err, authorized) { + callback(err, authorized); + } + ); +}; + +/** + * Check access authorization + * + * @param req - standard req object. Importantly contains table and host information + * @param callback function(err, allowed) is access allowed not? + */ +AuthApi.prototype.authorize = function(req, callback) { + var self = this; + var user = req.context.user; + + step( + function () { + self.authorizedByAPIKey(user, req, this); + }, + function checkApiKey(err, authorized){ + if (req.profiler) { + req.profiler.done('authorizedByAPIKey'); + } + assert.ifError(err); + + // if not authorized by api_key, continue + if (!authorized) { + // not authorized by api_key, check if authorized by signer + return self.authorizedBySigner(req, this); + } + + // authorized by api key, login as the given username and stop + self.pgConnection.setDBAuth(user, req.params, function(err) { + callback(err, true); // authorized (or error) + }); + }, + function checkSignAuthorized(err, authorized) { + if (err) { + return callback(err); + } + + if ( ! authorized ) { + // request not authorized by signer. + + // if no signer name was given, let dbparams and + // PostgreSQL do the rest. + // + if ( ! req.params.signer ) { + return callback(null, true); // authorized so far + } + + // if signer name was given, return no authorization + return callback(null, false); + } + + self.pgConnection.setDBAuth(user, req.params, function(err) { + if (req.profiler) { + req.profiler.done('setDBAuth'); + } + callback(err, true); // authorized (or error) + }); + } + ); +}; diff --git a/lib/cartodb/controllers/named_maps_admin.js b/lib/cartodb/controllers/named_maps_admin.js index db92f3ae..e0b9c035 100644 --- a/lib/cartodb/controllers/named_maps_admin.js +++ b/lib/cartodb/controllers/named_maps_admin.js @@ -5,9 +5,16 @@ var templateName = require('../backends/template_maps').templateName; var cors = require('../middleware/cors'); -function NamedMapsAdminController(app, templateMaps) { +/** + * @param app + * @param {TemplateMaps} templateMaps + * @param {AuthApi} authApi + * @constructor + */ +function NamedMapsAdminController(app, templateMaps, authApi) { this.app = app; this.templateMaps = templateMaps; + this.authApi = authApi; } module.exports = NamedMapsAdminController; @@ -28,7 +35,7 @@ NamedMapsAdminController.prototype.create = function(req, res) { step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function addTemplate(err, authenticated) { assert.ifError(err); @@ -53,7 +60,7 @@ NamedMapsAdminController.prototype.update = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function updateTemplate(err, authenticated) { assert.ifError(err); @@ -84,7 +91,7 @@ NamedMapsAdminController.prototype.retrieve = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function getTemplate(err, authenticated) { assert.ifError(err); @@ -120,7 +127,7 @@ NamedMapsAdminController.prototype.destroy = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function deleteTemplate(err, authenticated) { assert.ifError(err); @@ -147,7 +154,7 @@ NamedMapsAdminController.prototype.list = function(req, res) { step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function listTemplates(err, authenticated) { assert.ifError(err); diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index ffd71219..fdc028ce 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -19,6 +19,7 @@ var mapnik = windshaft.mapnik; var TemplateMaps = require('./backends/template_maps.js'); var QueryTablesApi = require('./api/query_tables_api'); var UserLimitsApi = require('./api/user_limits_api'); +var AuthApi = require('./api/auth_api'); var PgQueryRunner = require('./backends/pg_query_runner'); var PgConnection = require('./backends/pg_connection'); @@ -167,6 +168,8 @@ module.exports = function(serverOptions) { var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend); var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend); + var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps); + app.findStatusCode = function(err) { var statusCode; if ( err.http_status ) { @@ -220,7 +223,7 @@ module.exports = function(serverOptions) { userLimitsApi ).register(app); - new controller.NamedMapsAdmin(app, templateMaps).register(app); + new controller.NamedMapsAdmin(app, templateMaps, authApi).register(app); new controller.ServerInfo().register(app); @@ -453,7 +456,7 @@ module.exports = function(serverOptions) { step( function getPrivacy(){ - app.authorize(req, this); + authApi.authorize(req, this); }, function gatekeep(err, authorized){ if (req.profiler) { @@ -626,122 +629,6 @@ module.exports = function(serverOptions) { }); }; - // Check if a request is authorized by a signer - // - // @param req express request 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. - // - app.authorizedBySigner = function(req, callback) { - if ( ! req.params.token || ! req.params.signer ) { - return callback(null, false); // no signer requested - } - - var layergroup_id = req.params.token; - var auth_token = req.params.auth_token; - - mapStore.load(layergroup_id, function(err, mapConfig) { - if (err) { - return callback(err); - } - - var authorized = templateMaps.isAuthorized(mapConfig.obj().template, auth_token); - - return callback(null, authorized); - }); - }; - - // Check if a request is authorized by api_key - // - // @param user - // @param req express request object - // @param callback function(err, authorized) - // NOTE: authorized is expected to be 0 or 1 (integer) - // - app.authorizedByAPIKey = function(user, req, callback) { - var givenKey = req.query.api_key || req.query.map_key; - if ( ! givenKey && req.body ) { - // check also in request body - givenKey = req.body.api_key || req.body.map_key; - } - if ( ! givenKey ) { - return callback(null, 0); // no api key, no authorization... - } - step( - function () { - metadataBackend.getUserMapKey(user, this); - }, - function checkApiKey(err, val){ - assert.ifError(err); - return val && givenKey == val; - }, - function finish(err, authorized) { - callback(err, authorized); - } - ); - }; - - /** - * Check access authorization - * - * @param req - standard req object. Importantly contains table and host information - * @param callback function(err, allowed) is access allowed not? - */ - app.authorize = function(req, callback) { - var self = this; - var user = req.context.user; - - step( - function () { - self.authorizedByAPIKey(user, req, this); - }, - function checkApiKey(err, authorized){ - if (req.profiler) { - req.profiler.done('authorizedByAPIKey'); - } - assert.ifError(err); - - // if not authorized by api_key, continue - if (!authorized) { - // not authorized by api_key, check if authorized by signer - return self.authorizedBySigner(req, this); - } - - // authorized by api key, login as the given username and stop - pgConnection.setDBAuth(user, req.params, function(err) { - callback(err, true); // authorized (or error) - }); - }, - function checkSignAuthorized(err, authorized) { - if (err) { - return callback(err); - } - - if ( ! authorized ) { - // request not authorized by signer. - - // if no signer name was given, let dbparams and - // PostgreSQL do the rest. - // - if ( ! req.params.signer ) { - return callback(null, true); // authorized so far - } - - // if signer name was given, return no authorization - return callback(null, false); - } - - pgConnection.setDBAuth(user, req.params, function(err) { - if (req.profiler) { - req.profiler.done('setDBAuth'); - } - callback(err, true); // authorized (or error) - }); - } - ); - }; - return app; };