Add support for reading user-specific database_password from redis
This commits adds support for CartoDB-2.5.0 model. Closes #89. Change is backward compatible.
This commit is contained in:
parent
c7494c3c73
commit
baa95a62d1
6
NEWS.md
6
NEWS.md
@ -1,9 +1,9 @@
|
|||||||
1.5.0 -- 2013-MM-DD
|
1.5.0 -- 2013-MM-DD
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
* Add support for specifying database connection passwords
|
* Add support for configuring database connection passwords
|
||||||
* Add support for reading user-specific database_host from redis,
|
* Optionally read user-specific database_host and database_password
|
||||||
as per CartoDB-2.5.0 model (#88)
|
from redis as per CartoDB-2.5.0 model (#88, #89)
|
||||||
* Do not force ending dot in SQL-API hostname, for easier testing
|
* Do not force ending dot in SQL-API hostname, for easier testing
|
||||||
|
|
||||||
1.4.1 -- 2013-11-08
|
1.4.1 -- 2013-11-08
|
||||||
|
@ -11,7 +11,8 @@ var config = {
|
|||||||
,cache_enabled: false
|
,cache_enabled: false
|
||||||
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
||||||
,postgres_auth_user: 'development_cartodb_user_<%= user_id %>'
|
,postgres_auth_user: 'development_cartodb_user_<%= user_id %>'
|
||||||
,postgres_auth_pass: 'development_cartodb_user_<%= user_id %>_pass'
|
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||||
|
,postgres_auth_pass: '<%= user_password %>'
|
||||||
,postgres: {
|
,postgres: {
|
||||||
// Parameters to pass to datasource plugin of mapnik
|
// Parameters to pass to datasource plugin of mapnik
|
||||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||||
|
@ -11,7 +11,8 @@ var config = {
|
|||||||
,cache_enabled: true
|
,cache_enabled: true
|
||||||
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
||||||
,postgres_auth_user: 'cartodb_user_<%= user_id %>'
|
,postgres_auth_user: 'cartodb_user_<%= user_id %>'
|
||||||
,postgres_auth_pass: 'cartodb_user_<%= user_id %>_pass'
|
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||||
|
,postgres_auth_pass: '<%= user_password %>'
|
||||||
,postgres: {
|
,postgres: {
|
||||||
// Parameters to pass to datasource plugin of mapnik
|
// Parameters to pass to datasource plugin of mapnik
|
||||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||||
|
@ -11,7 +11,8 @@ var config = {
|
|||||||
,cache_enabled: true
|
,cache_enabled: true
|
||||||
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
|
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
|
||||||
,postgres_auth_user: 'cartodb_staging_user_<%= user_id %>'
|
,postgres_auth_user: 'cartodb_staging_user_<%= user_id %>'
|
||||||
,postgres_auth_pass: 'cartodb_staging_user_<%= user_id %>_pass'
|
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||||
|
,postgres_auth_pass: '<%= user_password %>'
|
||||||
,postgres: {
|
,postgres: {
|
||||||
// Parameters to pass to datasource plugin of mapnik
|
// Parameters to pass to datasource plugin of mapnik
|
||||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||||
|
@ -11,6 +11,7 @@ var config = {
|
|||||||
,cache_enabled: false
|
,cache_enabled: false
|
||||||
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
|
||||||
,postgres_auth_user: 'test_cartodb_user_<%= user_id %>'
|
,postgres_auth_user: 'test_cartodb_user_<%= user_id %>'
|
||||||
|
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||||
,postgres_auth_pass: 'test_cartodb_user_<%= user_id %>_pass'
|
,postgres_auth_pass: 'test_cartodb_user_<%= user_id %>_pass'
|
||||||
,postgres: {
|
,postgres: {
|
||||||
// Parameters to pass to datasource plugin of mapnik
|
// Parameters to pass to datasource plugin of mapnik
|
||||||
|
@ -123,6 +123,30 @@ module.exports = function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database password for this particular subdomain/username
|
||||||
|
*
|
||||||
|
* @param req - standard express req object. importantly contains host information
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
me.getDatabasePassword= function(req, callback) {
|
||||||
|
// strip subdomain from header host
|
||||||
|
var username = me.userFromHostname(req.headers.host);
|
||||||
|
var redisKey = _.template(this.user_key, {username: username});
|
||||||
|
|
||||||
|
this.retrieve(this.user_metadata_db, redisKey, 'database_password', function(err, data) {
|
||||||
|
if ( err ) callback(err, null);
|
||||||
|
else {
|
||||||
|
if ( data === null ) {
|
||||||
|
/* database_password was introduced in cartodb-2.5.0,
|
||||||
|
* for older versions we'll just use configured password */
|
||||||
|
//console.log("WARNING: missing " + username + "'s database_password in redis (try CARTODB/script/restore_redis)");
|
||||||
|
}
|
||||||
|
callback(err, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the user map key for this particular subdomain/username
|
* Check the user map key for this particular subdomain/username
|
||||||
*
|
*
|
||||||
@ -161,27 +185,49 @@ module.exports = function() {
|
|||||||
},
|
},
|
||||||
function checkIfInternal(err, check_result){
|
function checkIfInternal(err, check_result){
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
if (check_result === 1) {
|
|
||||||
|
// if unauthorized continue to check table privacy
|
||||||
|
if (check_result !== 1) return true;
|
||||||
|
|
||||||
// authorized by key, login as db owner
|
// authorized by key, login as db owner
|
||||||
that.getId(req, function(err, user_id) {
|
var user_params = {};
|
||||||
if (err) { callback(err); return; }
|
var auth_user = global.settings.postgres_auth_user;
|
||||||
var dbuser = _.template(global.settings.postgres_auth_user, {user_id: user_id});
|
var auth_pass = global.settings.postgres_auth_pass;
|
||||||
|
Step(
|
||||||
|
function getId() {
|
||||||
|
that.getId(req, this);
|
||||||
|
},
|
||||||
|
function(err, user_id) {
|
||||||
|
if (err) throw err;
|
||||||
|
user_params['user_id'] = user_id;
|
||||||
|
var dbuser = _.template(auth_user, user_params);
|
||||||
_.extend(req.params, {dbuser:dbuser});
|
_.extend(req.params, {dbuser:dbuser});
|
||||||
if ( global.settings.postgres_auth_pass ) {
|
|
||||||
var dbpass = _.template(global.settings.postgres_auth_pass, {user_id: user_id});
|
// skip looking up user_password if postgres_auth_pass
|
||||||
|
// doesn't contain the "user_password" label
|
||||||
|
if ( ! auth_pass.match(/\buser_password\b/) ) return null;
|
||||||
|
|
||||||
|
that.getDatabasePassword(req, this);
|
||||||
|
},
|
||||||
|
function(err, user_password) {
|
||||||
|
if (err) throw err;
|
||||||
|
user_params['user_password'] = user_password;
|
||||||
|
if ( auth_pass ) {
|
||||||
|
var dbpass = _.template(auth_pass, user_params);
|
||||||
_.extend(req.params, {dbpassword:dbpass});
|
_.extend(req.params, {dbpassword:dbpass});
|
||||||
}
|
}
|
||||||
callback(err, true);
|
return true;
|
||||||
});
|
},
|
||||||
} else {
|
function finish(err) {
|
||||||
return true; // continue to check if the table is public/private
|
callback(err, true); // authorized (or error)
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
,function (err, data){
|
,function getDatabase(err, data){
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
that.getDatabase(req, this);
|
that.getDatabase(req, this);
|
||||||
},
|
},
|
||||||
function(err, data){
|
function getPrivacy(err, data){
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
||||||
|
|
||||||
|
@ -807,6 +807,43 @@ suite('server', function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See https://github.com/CartoDB/Windshaft-cartodb/issues/XXX
|
||||||
|
test("get'ing a tile with a user-specific database password", function(done){
|
||||||
|
var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'});
|
||||||
|
var backupDBPass = global.settings.postgres_auth_pass;
|
||||||
|
global.settings.postgres_auth_pass = '<%= user_password %>';
|
||||||
|
Step (
|
||||||
|
function() {
|
||||||
|
var next = this;
|
||||||
|
assert.response(server, {
|
||||||
|
headers: {host: 'cartodb250user'},
|
||||||
|
url: '/tiles/test_table/15/16046/12354.png?'
|
||||||
|
+ 'cache_buster=4.20&api_key=4321&' + style,
|
||||||
|
method: 'GET',
|
||||||
|
encoding: 'binary'
|
||||||
|
},{}, function(res){
|
||||||
|
next(null, res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function checkRes(err, res) {
|
||||||
|
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||||
|
var ct = res.headers['content-type'];
|
||||||
|
assert.equal(ct, 'image/png');
|
||||||
|
assert.imageEqualsFile(res.body,
|
||||||
|
'./test/fixtures/test_table_15_16046_12354_styled_black.png',
|
||||||
|
2, this);
|
||||||
|
},
|
||||||
|
function checkImage(err, similarity) {
|
||||||
|
if (err) throw err;
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
global.settings.postgres_auth_pass = backupDBPass;
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("get'ing a tile with url specified 2.1.0 style should return an expected tile", function(done){
|
test("get'ing a tile with url specified 2.1.0 style should return an expected tile", function(done){
|
||||||
var style = querystring.stringify({style: test_style_black_210, style_version: '2.1.0'});
|
var style = querystring.stringify({style: test_style_black_210, style_version: '2.1.0'});
|
||||||
assert.response(server, {
|
assert.response(server, {
|
||||||
|
@ -73,6 +73,7 @@ echo "HSET rails:users:cartodb250user id ${TESTUSERID}" | redis-cli -p ${REDIS_P
|
|||||||
echo 'HSET rails:users:cartodb250user database_name "'${TEST_DB}'"' | redis-cli -p ${REDIS_PORT} -n 5
|
echo 'HSET rails:users:cartodb250user database_name "'${TEST_DB}'"' | redis-cli -p ${REDIS_PORT} -n 5
|
||||||
echo 'HSET rails:users:cartodb250user database_host "localhost"' | redis-cli -p ${REDIS_PORT} -n 5
|
echo 'HSET rails:users:cartodb250user database_host "localhost"' | redis-cli -p ${REDIS_PORT} -n 5
|
||||||
echo 'HSET rails:users:cartodb250user database_password "'${TESTPASS}'"' | redis-cli -p ${REDIS_PORT} -n 5
|
echo 'HSET rails:users:cartodb250user database_password "'${TESTPASS}'"' | redis-cli -p ${REDIS_PORT} -n 5
|
||||||
|
echo "HSET rails:users:cartodb250user map_key 4321" | redis-cli -p ${REDIS_PORT} -n 5
|
||||||
|
|
||||||
echo 'HSET rails:'"${TEST_DB}"':my_table infowindow "this, that, the other"' | redis-cli -p ${REDIS_PORT} -n 0
|
echo 'HSET rails:'"${TEST_DB}"':my_table infowindow "this, that, the other"' | redis-cli -p ${REDIS_PORT} -n 0
|
||||||
echo 'HSET rails:'"${TEST_DB}"':test_table_private_1 privacy "0"' | redis-cli -p ${REDIS_PORT} -n 0
|
echo 'HSET rails:'"${TEST_DB}"':test_table_private_1 privacy "0"' | redis-cli -p ${REDIS_PORT} -n 0
|
||||||
|
Loading…
Reference in New Issue
Block a user