From 824b7c084ee417f4ce6713bafa2d1c512c4ee8cf Mon Sep 17 00:00:00 2001 From: javi Date: Fri, 30 Jan 2015 11:37:29 +0100 Subject: [PATCH 1/4] added /u/:user routing --- app/controllers/app.js | 34 ++++++++++++++++------ config/environments/development.js.example | 6 ++++ test/acceptance/app.test.js | 11 +++++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/controllers/app.js b/app/controllers/app.js index 013ec6de..51fa900d 100755 --- a/app/controllers/app.js +++ b/app/controllers/app.js @@ -164,13 +164,24 @@ app.set("trust proxy", true); // basic routing app.options('*', function(req,res) { setCrossDomain(res); res.end(); }); -app.all(global.settings.base_url+'/sql', function(req, res) { handleQuery(req, res) } ); -app.all(global.settings.base_url+'/sql.:f', function(req, res) { handleQuery(req, res) } ); -app.get(global.settings.base_url+'/cachestatus', function(req, res) { handleCacheStatus(req, res) } ); -app.get(global.settings.base_url+'/health', function(req, res) { handleHealthCheck(req, res) } ); -app.get(global.settings.base_url+'/version', function(req, res) { - res.send(getVersion()); -}); + +app.all(global.settings.base_url + '/sql', handleQuery); +app.all(global.settings.base_url + '/sql.:f', handleQuery); +app.get(global.settings.base_url + '/cachestatus', handleCacheStatus); +app.get(global.settings.base_url + '/health', handleHealthCheck); +app.get(global.settings.base_url + '/version', handleVersion); + +if (global.settings.user_url) { + var user_url = global.settings.user_url; + if (user_url.indexOf(':user') === -1) { + throw new Error("user_url setting must contain :user") + } + app.all(user_url + global.settings.base_url + '/sql', handleQuery); + app.all(user_url + global.settings.base_url + '/sql.:f', handleQuery); + app.get(user_url + global.settings.base_url + '/cachestatus', handleCacheStatus); + app.get(user_url + global.settings.base_url + '/health', handleHealthCheck); + app.get(user_url + global.settings.base_url + '/version', handleVersion); +} var sqlQueryMayWriteRegex = new RegExp("\\b(alter|insert|update|delete|create|drop|reindex|truncate)\\b", "i"); /** @@ -192,6 +203,10 @@ function sanitize_filename(filename) { } // request handlers +function handleVersion(req, res) { + res.send(getVersion()); +} + function handleQuery(req, res) { // extract input @@ -207,6 +222,8 @@ function handleQuery(req, res) { var requestedFilename = params.filename; var filename = requestedFilename; var requestedSkipfields = params.skipfields; + // if the request contains the user use it, if not guess from the host + var cdbUsername = req.params.user || cdbReq.userByReq(req); var skipfields; var dp = params.dp; // decimal point digits (defaults to 6) var gn = "the_geom"; // TODO: read from configuration file @@ -280,8 +297,7 @@ function handleQuery(req, res) { var formatter; - var cdbUsername = cdbReq.userByReq(req), - authApi = new AuthApi(req, params), + var authApi = new AuthApi(req, params), dbParams; if ( req.profiler ) req.profiler.done('init'); diff --git a/config/environments/development.js.example b/config/environments/development.js.example index 8aa86073..65c2fdfd 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -1,4 +1,10 @@ module.exports.base_url = '/api/:version'; +// if user_url is defined the api will respond to requests like: +// user_url + base_url +// for example +// /u/:user/api/sql?q=.... +// needs to have :user parameter, if not it will fail on server startup +module.exports.user_url = '/u/:user'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. diff --git a/test/acceptance/app.test.js b/test/acceptance/app.test.js index fdf5824d..b6381c0d 100644 --- a/test/acceptance/app.test.js +++ b/test/acceptance/app.test.js @@ -147,6 +147,17 @@ test('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just }); }); +test('GET /u/vizzuality/api/v1/sql with SQL parameter on SELECT only', function(done){ + assert.response(app, { + url: '/u/vizzuality/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4', + method: 'GET' + },{ }, function(res) { + assert.equal(res.statusCode, 200, res.body); + done(); + }); +}); + + // See https://github.com/CartoDB/CartoDB-SQL-API/issues/121 test('SELECT from user-specific database', function(done){ var backupDBHost = global.settings.db_host; From 81edbfb826c1bbd1b5a991d4d9c5cce78413570e Mon Sep 17 00:00:00 2001 From: javi Date: Fri, 30 Jan 2015 11:56:22 +0100 Subject: [PATCH 2/4] updated test confi --- config/environments/test.js.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environments/test.js.example b/config/environments/test.js.example index 4ed76558..6b2c254a 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -1,4 +1,5 @@ module.exports.base_url = '/api/:version'; +module.exports.user_url = '/u/:user'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. From e5ab4272eb738f942f8c2354a6ba1d2e72475f1f Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 2 Feb 2015 12:09:34 +0100 Subject: [PATCH 3/4] Use a multiple params route to be able to extract the username from the path or default to host header. --- app/controllers/app.js | 14 +--------- app/models/cartodb_request.js | 30 +++++++++++++--------- config/environments/development.js.example | 10 +++----- config/environments/production.js.example | 4 ++- config/environments/staging.js.example | 4 ++- config/environments/test.js.example | 5 ++-- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/app/controllers/app.js b/app/controllers/app.js index 51fa900d..fb43cb92 100755 --- a/app/controllers/app.js +++ b/app/controllers/app.js @@ -171,18 +171,6 @@ app.get(global.settings.base_url + '/cachestatus', handleCacheStatus); app.get(global.settings.base_url + '/health', handleHealthCheck); app.get(global.settings.base_url + '/version', handleVersion); -if (global.settings.user_url) { - var user_url = global.settings.user_url; - if (user_url.indexOf(':user') === -1) { - throw new Error("user_url setting must contain :user") - } - app.all(user_url + global.settings.base_url + '/sql', handleQuery); - app.all(user_url + global.settings.base_url + '/sql.:f', handleQuery); - app.get(user_url + global.settings.base_url + '/cachestatus', handleCacheStatus); - app.get(user_url + global.settings.base_url + '/health', handleHealthCheck); - app.get(user_url + global.settings.base_url + '/version', handleVersion); -} - var sqlQueryMayWriteRegex = new RegExp("\\b(alter|insert|update|delete|create|drop|reindex|truncate)\\b", "i"); /** * This is a fuzzy check, the return could be true even if the query doesn't really write anything. But you can be @@ -223,7 +211,7 @@ function handleQuery(req, res) { var filename = requestedFilename; var requestedSkipfields = params.skipfields; // if the request contains the user use it, if not guess from the host - var cdbUsername = req.params.user || cdbReq.userByReq(req); + var cdbUsername = cdbReq.userByReq(req); var skipfields; var dp = params.dp; // decimal point digits (defaults to 6) var gn = "the_geom"; // TODO: read from configuration file diff --git a/app/models/cartodb_request.js b/app/models/cartodb_request.js index 6cc2de97..4a73978d 100644 --- a/app/models/cartodb_request.js +++ b/app/models/cartodb_request.js @@ -9,20 +9,26 @@ function CartodbRequest() { module.exports = CartodbRequest; CartodbRequest.prototype.userByReq = function(req) { - var host = req.headers.host; - var mat = host.match(re_userFromHost); - if ( ! mat ) { - console.error("ERROR: user pattern '" + re_userFromHost + "' does not match hostname '" + host + "'"); - return; - } - // console.log("Matches: "); console.dir(mat); - if ( ! mat.length === 2 ) { - console.error("ERROR: pattern '" + re_userFromHost + "' gave unexpected matches against '" + host + "': " + mat); - return; - } - return mat[1]; + if (req.params.user) { + return req.params.user; + } + return userByHostName(req.headers.host); }; var re_userFromHost = new RegExp( global.settings.user_from_host || '^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com" ); + +function userByHostName(host) { + var mat = host.match(re_userFromHost); + if (!mat) { + console.error("ERROR: user pattern '" + re_userFromHost + "' does not match hostname '" + host + "'"); + return; + } + + if (mat.length !== 2) { + console.error("ERROR: pattern '" + re_userFromHost + "' gave unexpected matches against '" + host + "': " + mat); + return; + } + return mat[1]; +} diff --git a/config/environments/development.js.example b/config/environments/development.js.example index 65c2fdfd..183eb8d6 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -1,10 +1,6 @@ -module.exports.base_url = '/api/:version'; -// if user_url is defined the api will respond to requests like: -// user_url + base_url -// for example -// /u/:user/api/sql?q=.... -// needs to have :user parameter, if not it will fail on server startup -module.exports.user_url = '/u/:user'; +// In case the base_url has a :user param the username will be the one specified in the URL, +// otherwise it will fallback to extract the username from the host header. +module.exports.base_url = '(?:/api/:version|/u/:user/api/:version)'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. diff --git a/config/environments/production.js.example b/config/environments/production.js.example index bcb85834..729e242b 100644 --- a/config/environments/production.js.example +++ b/config/environments/production.js.example @@ -1,4 +1,6 @@ -module.exports.base_url = '/api/:version'; +// In case the base_url has a :user param the username will be the one specified in the URL, +// otherwise it will fallback to extract the username from the host header. +module.exports.base_url = '(?:/api/:version|/u/:user/api/:version)'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. diff --git a/config/environments/staging.js.example b/config/environments/staging.js.example index 21bcd031..889df0fa 100644 --- a/config/environments/staging.js.example +++ b/config/environments/staging.js.example @@ -1,4 +1,6 @@ -module.exports.base_url = '/api/:version'; +// In case the base_url has a :user param the username will be the one specified in the URL, +// otherwise it will fallback to extract the username from the host header. +module.exports.base_url = '(?:/api/:version|/u/:user/api/:version)'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. diff --git a/config/environments/test.js.example b/config/environments/test.js.example index 6b2c254a..ae2c6afc 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -1,5 +1,6 @@ -module.exports.base_url = '/api/:version'; -module.exports.user_url = '/u/:user'; +// In case the base_url has a :user param the username will be the one specified in the URL, +// otherwise it will fallback to extract the username from the host header. +module.exports.base_url = '(?:/api/:version|/u/:user/api/:version)'; // If useProfiler is true every response will be served with an // X-SQLAPI-Profile header containing elapsed timing for various // steps taken for producing the response. From 302a856d3741838ef9cb90a7a293df6b5c2d315c Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 2 Feb 2015 12:15:53 +0100 Subject: [PATCH 4/4] Move note about username extraction to cartodb_request --- app/controllers/app.js | 1 - app/models/cartodb_request.js | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/app.js b/app/controllers/app.js index fb43cb92..50bda42b 100755 --- a/app/controllers/app.js +++ b/app/controllers/app.js @@ -210,7 +210,6 @@ function handleQuery(req, res) { var requestedFilename = params.filename; var filename = requestedFilename; var requestedSkipfields = params.skipfields; - // if the request contains the user use it, if not guess from the host var cdbUsername = cdbReq.userByReq(req); var skipfields; var dp = params.dp; // decimal point digits (defaults to 6) diff --git a/app/models/cartodb_request.js b/app/models/cartodb_request.js index 4a73978d..fb4765ee 100644 --- a/app/models/cartodb_request.js +++ b/app/models/cartodb_request.js @@ -8,6 +8,9 @@ function CartodbRequest() { module.exports = CartodbRequest; +/** + * If the request contains the user use it, if not guess from the host + */ CartodbRequest.prototype.userByReq = function(req) { if (req.params.user) { return req.params.user;