Generalize CartoDB username extraction, allowing for multiuser setups
Closes #124
This commit is contained in:
parent
46bc0eb369
commit
87d35aa155
2
NEWS.md
2
NEWS.md
@ -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
|
||||
------------------
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
35
app/models/cartodb_request.js
Normal file
35
app/models/cartodb_request.js
Normal 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];
|
||||
};
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
24
npm-shrinkwrap.json
generated
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user