Generalize CartoDB username extraction, allowing for multiuser setups

Closes #124
This commit is contained in:
Sandro Santilli 2013-12-18 11:57:46 +01:00
parent 46bc0eb369
commit 87d35aa155
11 changed files with 159 additions and 47 deletions

View File

@ -1,6 +1,8 @@
1.8.0 - 2013-MM-DD
------------------
* Add 'user_from_host' directive to generalize username extraction (#124)
1.7.1 - 2013-12-02
------------------

View File

@ -36,12 +36,16 @@ var express = require('express')
// global.settings.app_root + '/app/models/metadata')
, oAuth = require(global.settings.app_root + '/app/models/oauth')
, PSQL = require(global.settings.app_root + '/app/models/psql')
, CdbRequest = require(global.settings.app_root + '/app/models/cartodb_request')
, ApiKeyAuth = require(global.settings.app_root + '/app/models/apikey_auth')
, _ = require('underscore')
, LRU = require('lru-cache')
, formats = require(global.settings.app_root + '/app/models/formats')
;
var cdbReq = new CdbRequest(Meta);
var apiKeyAuth = new ApiKeyAuth(Meta, cdbReq);
// Set default configuration
global.settings.db_pubuser = global.settings.db_pubuser || "publicuser";
@ -173,6 +177,8 @@ function handleQuery(req, res) {
var formatter;
var cdbuser = cdbReq.userByReq(req);
// 1. Get database from redis via the username stored in the host header subdomain
// 2. Run the request through OAuth to get R/W user id if signed
// 3. Get the list of tables affected by the query
@ -181,7 +187,7 @@ function handleQuery(req, res) {
Step(
function getDatabaseName() {
if (_.isNull(database)) {
Meta.getDatabase(req, this);
Meta.getUserDBName(cdbuser, this);
} else {
// database hardcoded in query string (deprecated??): don't use redis
return database;
@ -201,7 +207,7 @@ function handleQuery(req, res) {
dbopts.dbname = database;
if(api_key) {
ApiKeyAuth.verifyRequest(req, this);
apiKeyAuth.verifyRequest(req, this);
} else {
oAuth.verifyRequest(req, this, requestProtocol);
}
@ -218,7 +224,7 @@ function handleQuery(req, res) {
dbopts.user = dbuser;
Meta.getDatabaseHost(req, this);
Meta.getUserDBHost(cdbuser, this);
},
function setDBHostGetPassword(err, data){
if (err) throw err;
@ -228,7 +234,7 @@ function handleQuery(req, res) {
// by-pass redis lookup for password if not authenticated
if ( ! authenticated ) return null;
Meta.getDatabasePassword(req, this);
Meta.getUserDBPass(cdbuser, this);
},
function queryExplain(err, data){
if (err) throw err;

View File

@ -2,45 +2,86 @@
* this module allows to auth user using an pregenerated api key
*/
var Meta = require("cartodb-redis")({
host: global.settings.redis_host,
port: global.settings.redis_port
})
, _ = require('underscore')
, Step = require('step');
var _ = require('underscore')
, Step = require('step');
module.exports = (function() {
function ApikeyAuth(cartodb_redis, cartodb_request) {
if ( ! cartodb_redis ) throw new Error("Cannot initialize ApikeyAuth with no cartodb_request");
if ( ! cartodb_request ) throw new Error("Cannot initialize ApikeyAuth with no cartodb-redis");
this.cdbRedis = cartodb_redis;
this.cdbRequest = cartodb_request;
}
var me = {}
module.exports = ApikeyAuth;
/**
* Get privacy for cartodb table
*
* @param req - standard req object. Importantly contains table and host information
* @param callback - err, user_id (null if no auth)
*/
me.verifyRequest = function(req, callback) {
var that = this;
var o = ApikeyAuth.prototype;
Step(
// check api key
function(){
Meta.checkAPIKey(req, this);
},
// get user id or fail
function (err, apikey_valid) {
if ( err ) throw err;
if (apikey_valid) {
Meta.getId(req, this);
} else {
// no auth
callback(null, null);
}
},
function (err, user_id){
callback(err, user_id);
o.userByReq = function(req) {
return this.cdbRequest.userByReq(req)
};
// Check if a request is authorized by api_key
//
// @param req express request object
// @param callback function(err, authorized)
//
o.authorizedByAPIKey = function(req, callback)
{
var user = this.userByReq(req);
var that = this;
Step(
function (){
that.cdbRedis.getUserMapKey(user, this);
},
function checkApiKey(err, val){
if (err) throw err;
var valid = 0;
if ( val ) {
if ( val == req.query.map_key ) valid = 1;
else if ( val == req.query.api_key ) valid = 1;
// check also in request body
else if ( req.body && req.body.map_key && val == req.body.map_key ) valid = 1;
else if ( req.body && req.body.api_key && val == req.body.api_key ) valid = 1;
}
return valid;
},
function finish(err, authorized) {
callback(err, authorized);
}
);
};
/**
* Get id of authorized user
*
* @param req - standard req object. Importantly contains table and host information
* @param callback - err, user_id (null if no auth)
*/
o.verifyRequest = function(req, callback) {
var user = this.userByReq(req);
var that = this;
Step(
// check api key
function(){
that.authorizedByAPIKey(req, this);
},
// get user id or fail
function (err, apikey_valid) {
if ( err ) throw err;
if (apikey_valid) {
that.cdbRedis.getUserId(user, this);
} else {
// no auth
callback(null, null);
}
);
};
return me;
})();
},
function (err, user_id){
callback(err, user_id);
}
);
};

View File

@ -0,0 +1,35 @@
/**
* this module provides cartodb-specific interpretation
* of request headers
*/
function CartodbRequest(cartodb_redis) {
this.cartodb_redis = cartodb_redis;
}
module.exports = CartodbRequest;
var o = CartodbRequest.prototype;
o.re_userFromHost = new RegExp(
global.settings.user_from_host ||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
);
o.userByReq = function(req) {
var host = req.headers.host;
var mat = host.match(this.re_userFromHost);
if ( ! mat ) {
console.error("ERROR: user pattern '" + this.re_userFromHost
+ "' does not match hostname '" + host + "'");
return;
}
// console.log("Matches: "); console.dir(mat);
if ( ! mat.length === 2 ) {
console.error("ERROR: pattern '" + this.re_userFromHost
+ "' gave unexpected matches against '" + host + "': " + mat);
return;
}
return mat[1];
};

View File

@ -1,5 +1,5 @@
// too bound to the request object, but ok for now
var RedisPool = require("../../node_modules/cartodb-redis/lib/redis_pool.js")({
var RedisPool = require("../../node_modules/cartodb-redis/node_modules/redis-mpool/")({
host: global.settings.redis_host,
port: global.settings.redis_port,
})

View File

@ -1,4 +1,7 @@
module.exports.base_url = '/api/:version';
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
module.exports.user_from_host = '^(.*)\\.localhost';
module.exports.node_port = 8080;
module.exports.node_host = '127.0.0.1';
// idle socket timeout, in miliseconds

View File

@ -1,4 +1,7 @@
module.exports.base_url = '/api/:version';
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
module.exports.user_from_host = '^(.*)\\.cartodb\\.com$';
module.exports.node_port = 8080;
module.exports.node_host = '127.0.0.1';
// idle socket timeout, in miliseconds

View File

@ -1,4 +1,7 @@
module.exports.base_url = '/api/:version';
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
module.exports.user_from_host = '^(.*)\\.cartodb\\.com$';
module.exports.node_port = 8080;
module.exports.node_host = '127.0.0.1';
// idle socket timeout, in miliseconds

View File

@ -1,4 +1,7 @@
module.exports.base_url = '/api/:version';
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
module.exports.user_from_host = '^([^.]*)\\.';
module.exports.node_port = 8080;
module.exports.node_host = '127.0.0.1';
// idle socket timeout, in miliseconds

24
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{
"name": "cartodb_sql_api",
"version": "1.7.1",
"version": "1.8.0",
"dependencies": {
"express": {
"version": "2.5.11",
@ -60,13 +60,29 @@
}
},
"cartodb-redis": {
"version": "0.1.0",
"version": "0.3.0",
"dependencies": {
"strftime": {
"version": "0.6.2"
},
"generic-pool": {
"version": "2.0.4"
"redis-mpool": {
"version": "0.0.3",
"dependencies": {
"generic-pool": {
"version": "2.0.4"
},
"redis": {
"version": "0.8.6"
},
"hiredis": {
"version": "0.1.16",
"dependencies": {
"bindings": {
"version": "1.1.1"
}
}
}
}
}
}
},

View File

@ -21,7 +21,7 @@
"underscore.string": "~1.1.6",
"pg": "~2.6.2",
"express": "~2.5.11",
"cartodb-redis": "~0.1.0",
"cartodb-redis": "~0.3.0",
"step": "0.0.x",
"topojson": "0.0.8",
"oauth-client": "0.2.0",