2015-02-06 00:05:50 +08:00
|
|
|
var _ = require('underscore');
|
2015-03-16 07:21:55 +08:00
|
|
|
var step = require('step');
|
2015-02-06 00:05:50 +08:00
|
|
|
var QueryTablesApi = require('./api/query_tables_api');
|
2015-02-09 21:46:52 +08:00
|
|
|
var PgConnection = require('./backends/pg_connection');
|
2015-02-06 00:05:50 +08:00
|
|
|
var LZMA = require('lzma').LZMA;
|
2015-01-30 22:30:13 +08:00
|
|
|
var TemplateMaps = require('./template_maps.js');
|
2015-01-30 22:31:49 +08:00
|
|
|
var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter');
|
2015-03-24 00:35:09 +08:00
|
|
|
var CdbRequest = require('./models/cdb_request');
|
2015-03-24 00:56:18 +08:00
|
|
|
var assert = require('assert');
|
2011-09-05 07:00:41 +08:00
|
|
|
|
2015-01-15 01:11:13 +08:00
|
|
|
// Whitelist query parameters and attach format
|
|
|
|
var REQUEST_QUERY_PARAMS_WHITELIST = [
|
2015-03-27 00:19:16 +08:00
|
|
|
'config',
|
2015-01-15 01:11:13 +08:00
|
|
|
'map_key',
|
|
|
|
'api_key',
|
|
|
|
'auth_token',
|
2015-03-27 00:19:16 +08:00
|
|
|
'callback'
|
2015-01-15 01:11:13 +08:00
|
|
|
];
|
|
|
|
|
2014-09-24 17:42:36 +08:00
|
|
|
module.exports = function(redisPool) {
|
2015-03-16 06:44:45 +08:00
|
|
|
redisPool = redisPool ||
|
|
|
|
require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
|
2015-01-30 22:28:55 +08:00
|
|
|
|
|
|
|
var cartoData = require('cartodb-redis')({ pool: redisPool }),
|
2014-09-24 17:42:36 +08:00
|
|
|
lzmaWorker = new LZMA(),
|
2015-02-09 21:46:52 +08:00
|
|
|
pgConnection = new PgConnection(cartoData),
|
2015-03-24 00:35:09 +08:00
|
|
|
queryTablesApi = new QueryTablesApi(pgConnection, cartoData),
|
|
|
|
cdbRequest = new CdbRequest();
|
2014-07-30 19:46:46 +08:00
|
|
|
|
2013-02-25 23:53:57 +08:00
|
|
|
var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
2014-10-15 22:45:49 +08:00
|
|
|
cache_ttl: 60000, // milliseconds
|
|
|
|
metatile: 4,
|
|
|
|
bufferSize: 64,
|
|
|
|
statsInterval: 60000
|
2013-02-25 23:53:57 +08:00
|
|
|
});
|
|
|
|
|
2011-09-05 07:00:41 +08:00
|
|
|
var me = {
|
2014-02-05 22:14:47 +08:00
|
|
|
// This is for inline maps and table maps
|
|
|
|
base_url: global.environment.base_url_legacy || '/tiles/:table',
|
|
|
|
|
2014-02-05 02:04:59 +08:00
|
|
|
/// @deprecated with Windshaft-0.17.0
|
2014-02-05 22:14:47 +08:00
|
|
|
///base_url_notable: '/tiles',
|
|
|
|
|
|
|
|
// This is for Detached maps
|
|
|
|
//
|
2014-02-05 02:04:59 +08:00
|
|
|
// "maps" is the official, while
|
|
|
|
// "tiles/layergroup" is for backward compatibility up to 1.6.x
|
2014-02-05 22:14:47 +08:00
|
|
|
//
|
|
|
|
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
|
|
|
|
|
2012-09-20 00:52:13 +08:00
|
|
|
grainstore: {
|
2014-02-04 23:26:03 +08:00
|
|
|
map: {
|
|
|
|
// TODO: allow to specify in configuration
|
|
|
|
srid: 3857
|
|
|
|
},
|
2012-09-20 00:52:13 +08:00
|
|
|
datasource: global.environment.postgres,
|
2012-09-24 23:57:39 +08:00
|
|
|
cachedir: global.environment.millstone.cache_basedir,
|
2014-08-15 00:27:54 +08:00
|
|
|
mapnik_version: global.environment.mapnik_version,
|
2014-08-07 07:57:21 +08:00
|
|
|
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
|
2015-03-28 00:59:55 +08:00
|
|
|
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
|
2012-09-20 00:52:13 +08:00
|
|
|
},
|
2013-02-25 23:53:57 +08:00
|
|
|
mapnik: {
|
2014-09-19 01:06:45 +08:00
|
|
|
poolSize: rendererConfig.poolSize,
|
2013-02-25 23:53:57 +08:00
|
|
|
metatile: rendererConfig.metatile,
|
|
|
|
bufferSize: rendererConfig.bufferSize
|
|
|
|
},
|
2014-02-12 22:27:42 +08:00
|
|
|
statsd: global.environment.statsd,
|
2013-02-25 23:53:57 +08:00
|
|
|
renderCache: {
|
2014-10-15 22:45:49 +08:00
|
|
|
ttl: rendererConfig.cache_ttl,
|
|
|
|
statsInterval: rendererConfig.statsInterval
|
2013-02-25 23:53:57 +08:00
|
|
|
},
|
2014-12-02 01:43:40 +08:00
|
|
|
renderer: {
|
|
|
|
http: rendererConfig.http
|
|
|
|
},
|
2011-09-20 09:27:23 +08:00
|
|
|
redis: global.environment.redis,
|
2011-10-13 21:22:54 +08:00
|
|
|
enable_cors: global.environment.enable_cors,
|
2012-05-02 02:00:14 +08:00
|
|
|
varnish_host: global.environment.varnish.host,
|
|
|
|
varnish_port: global.environment.varnish.port,
|
2015-01-23 23:36:45 +08:00
|
|
|
varnish_http_port: global.environment.varnish.http_port,
|
2014-02-12 22:34:19 +08:00
|
|
|
varnish_secret: global.environment.varnish.secret,
|
2015-01-24 00:46:16 +08:00
|
|
|
varnish_purge_enabled: global.environment.varnish.purge_enabled,
|
2012-05-03 02:32:54 +08:00
|
|
|
cache_enabled: global.environment.cache_enabled,
|
2013-07-16 21:59:34 +08:00
|
|
|
log_format: global.environment.log_format,
|
|
|
|
useProfiler: global.environment.useProfiler
|
2012-05-03 02:32:54 +08:00
|
|
|
};
|
2014-03-04 22:36:08 +08:00
|
|
|
|
|
|
|
// Do not send unwatch on release
|
|
|
|
// See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
|
|
|
me.redis.unwatchOnRelease = false;
|
2012-05-03 02:32:54 +08:00
|
|
|
|
2014-09-24 17:42:36 +08:00
|
|
|
// Re-use redisPool
|
|
|
|
me.redis.pool = redisPool;
|
|
|
|
|
2015-02-09 21:46:52 +08:00
|
|
|
// Re-use pgConnection
|
|
|
|
me.pgConnection = pgConnection;
|
|
|
|
|
2015-01-30 22:30:13 +08:00
|
|
|
var templateMaps = new TemplateMaps(redisPool, {
|
|
|
|
max_user_templates: global.environment.maxUserTemplates
|
|
|
|
});
|
|
|
|
me.templateMaps = templateMaps;
|
|
|
|
|
2015-01-30 22:31:49 +08:00
|
|
|
var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
|
|
|
|
2013-03-13 23:45:15 +08:00
|
|
|
/* This whole block is about generating X-Cache-Channel { */
|
|
|
|
|
|
|
|
// TODO: review lifetime of elements of this cache
|
|
|
|
// NOTE: by-token indices should only be dropped when
|
|
|
|
// the corresponding layegroup is dropped, because
|
|
|
|
// we have no SQL after layer creation.
|
|
|
|
me.channelCache = {};
|
|
|
|
|
|
|
|
me.buildCacheChannel = function (dbName, tableNames){
|
2013-03-14 01:41:37 +08:00
|
|
|
return dbName + ':' + tableNames.join(',');
|
2013-03-13 23:45:15 +08:00
|
|
|
};
|
|
|
|
|
2014-02-19 17:08:25 +08:00
|
|
|
me.generateCacheChannel = function(app, req, callback){
|
2015-03-24 00:56:51 +08:00
|
|
|
// Build channelCache key
|
|
|
|
var dbName = req.params.dbname;
|
|
|
|
var cacheKey = [ dbName, req.params.token ].join(':');
|
|
|
|
|
2015-03-24 01:58:57 +08:00
|
|
|
// no token means no tables associated
|
|
|
|
if (!req.params.token) {
|
|
|
|
return callback(null, this.buildCacheChannel(dbName, []));
|
|
|
|
}
|
|
|
|
|
2015-03-24 00:56:51 +08:00
|
|
|
step(
|
|
|
|
function checkCached() {
|
|
|
|
if ( me.channelCache.hasOwnProperty(cacheKey) ) {
|
|
|
|
return callback(null, me.channelCache[cacheKey]);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
function extractSQL(err) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
// TODO: cached cache channel for token-based access should
|
|
|
|
// be constructed at renderer cache creation time
|
|
|
|
// See http://github.com/CartoDB/Windshaft-cartodb/issues/152
|
|
|
|
if ( ! app.mapStore ) {
|
|
|
|
throw new Error('missing channel cache for token ' + req.params.token);
|
|
|
|
}
|
|
|
|
var mapStore = app.mapStore;
|
|
|
|
step(
|
|
|
|
function loadFromStore() {
|
|
|
|
mapStore.load(req.params.token, this);
|
|
|
|
},
|
|
|
|
function getSQL(err, mapConfig) {
|
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('mapStore_load');
|
|
|
|
}
|
|
|
|
assert.ifError(err);
|
|
|
|
|
2015-03-24 01:58:57 +08:00
|
|
|
var queries = mapConfig.getLayers()
|
|
|
|
.map(function(lyr) {
|
|
|
|
return lyr.options.sql;
|
|
|
|
})
|
|
|
|
.filter(function(sql) {
|
|
|
|
return !!sql;
|
|
|
|
});
|
|
|
|
|
|
|
|
return queries.length ? queries.join(';') : null;
|
2015-03-24 00:56:51 +08:00
|
|
|
},
|
2015-03-24 01:58:57 +08:00
|
|
|
this
|
2015-03-24 00:56:51 +08:00
|
|
|
);
|
|
|
|
},
|
|
|
|
function findAffectedTables(err, sql) {
|
|
|
|
assert.ifError(err);
|
2015-03-24 01:58:57 +08:00
|
|
|
|
2015-03-24 00:56:51 +08:00
|
|
|
if ( ! sql ) {
|
2015-03-24 01:58:57 +08:00
|
|
|
throw new Error("this request doesn't need an X-Cache-Channel generated");
|
2015-03-24 00:56:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
queryTablesApi.getAffectedTablesInQuery(cdbRequest.userByReq(req), sql, this); // in addCacheChannel
|
|
|
|
},
|
|
|
|
function buildCacheChannel(err, tableNames) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
2015-03-24 01:58:57 +08:00
|
|
|
if (req.profiler) {
|
2015-03-24 00:56:51 +08:00
|
|
|
req.profiler.done('affectedTables');
|
|
|
|
}
|
|
|
|
|
|
|
|
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
|
2015-03-24 01:58:57 +08:00
|
|
|
me.channelCache[cacheKey] = cacheChannel;
|
|
|
|
|
2015-03-24 00:56:51 +08:00
|
|
|
return cacheChannel;
|
|
|
|
},
|
|
|
|
function finish(err, cacheChannel) {
|
|
|
|
callback(err, cacheChannel);
|
2014-02-19 14:19:41 +08:00
|
|
|
}
|
2015-03-24 00:56:51 +08:00
|
|
|
);
|
2013-03-13 23:45:15 +08:00
|
|
|
};
|
|
|
|
|
2012-09-25 00:57:48 +08:00
|
|
|
// Set the cache chanel info to invalidate the cache on the frontend server
|
|
|
|
//
|
|
|
|
// @param req The request object.
|
|
|
|
// The function will have no effect unless req.res exists.
|
|
|
|
// It is expected that req.params contains 'table' and 'dbname'
|
|
|
|
//
|
|
|
|
// @param cb function(err, channel) will be called when ready.
|
|
|
|
// the channel parameter will be null if nothing was added
|
|
|
|
//
|
2014-02-19 17:08:25 +08:00
|
|
|
me.addCacheChannel = function(app, req, cb) {
|
2012-09-25 00:57:48 +08:00
|
|
|
// skip non-GET requests, or requests for which there's no response
|
|
|
|
if ( req.method != 'GET' || ! req.res ) { cb(null, null); return; }
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.start('addCacheChannel');
|
|
|
|
}
|
2012-09-25 00:57:48 +08:00
|
|
|
var res = req.res;
|
2015-03-24 00:56:18 +08:00
|
|
|
if ( req.params.token ) {
|
2013-03-13 17:36:28 +08:00
|
|
|
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
|
|
|
|
} else {
|
|
|
|
var ttl = global.environment.varnish.ttl || 86400;
|
|
|
|
res.header('Cache-Control', 'no-cache,max-age='+ttl+',must-revalidate, public');
|
|
|
|
}
|
2013-03-13 23:45:15 +08:00
|
|
|
|
2013-07-15 19:14:06 +08:00
|
|
|
// Set Last-Modified header
|
2013-07-15 19:48:06 +08:00
|
|
|
var lastUpdated;
|
|
|
|
if ( req.params.cache_buster ) {
|
|
|
|
// Assuming cache_buster is a timestamp
|
|
|
|
// FIXME: store lastModified in the cache channel instead
|
|
|
|
lastUpdated = new Date(parseInt(req.params.cache_buster));
|
|
|
|
} else {
|
|
|
|
lastUpdated = new Date();
|
|
|
|
}
|
|
|
|
res.header('Last-Modified', lastUpdated.toUTCString());
|
2013-07-15 18:02:54 +08:00
|
|
|
|
2014-02-19 17:08:25 +08:00
|
|
|
me.generateCacheChannel(app, req, function(err, channel){
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('generateCacheChannel');
|
|
|
|
req.profiler.end();
|
|
|
|
}
|
2013-03-13 17:36:28 +08:00
|
|
|
if ( ! err ) {
|
|
|
|
res.header('X-Cache-Channel', channel);
|
2013-09-12 21:36:50 +08:00
|
|
|
cb(null, channel);
|
2012-10-24 15:40:05 +08:00
|
|
|
} else {
|
2013-03-13 17:36:28 +08:00
|
|
|
console.log('ERROR generating cache channel: ' + ( err.message ? err.message : err ));
|
|
|
|
// TODO: evaluate if we should bubble up the error instead
|
|
|
|
cb(null, 'ERROR');
|
2012-10-24 15:40:05 +08:00
|
|
|
}
|
2012-09-25 00:57:48 +08:00
|
|
|
});
|
2013-03-13 23:45:15 +08:00
|
|
|
};
|
|
|
|
|
2015-01-30 22:31:49 +08:00
|
|
|
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
|
2015-03-24 00:35:09 +08:00
|
|
|
mapConfigNamedLayersAdapter.getLayers(cdbRequest.userByReq(req), requestMapConfig.layers, pgConnection,
|
2015-03-16 07:27:14 +08:00
|
|
|
function(err, layers, datasource) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2015-01-30 22:31:49 +08:00
|
|
|
|
2015-03-16 07:27:14 +08:00
|
|
|
requestMapConfig.layers = layers;
|
|
|
|
return callback(null, requestMapConfig, datasource);
|
|
|
|
}
|
|
|
|
);
|
2015-01-30 22:31:49 +08:00
|
|
|
};
|
|
|
|
|
2013-04-02 19:30:49 +08:00
|
|
|
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
|
2013-03-13 23:45:15 +08:00
|
|
|
var token = response.layergroupid;
|
|
|
|
|
2015-03-24 00:35:09 +08:00
|
|
|
var username = cdbRequest.userByReq(req);
|
2013-04-12 23:28:34 +08:00
|
|
|
|
|
|
|
var tasksleft = 2; // redis key and affectedTables
|
|
|
|
var errors = [];
|
|
|
|
|
|
|
|
var done = function(err) {
|
|
|
|
if ( err ) {
|
|
|
|
errors.push('' + err);
|
|
|
|
}
|
|
|
|
if ( ! --tasksleft ) {
|
|
|
|
err = errors.length ? new Error(errors.join('\n')) : null;
|
|
|
|
callback(err);
|
|
|
|
}
|
2014-07-30 19:45:53 +08:00
|
|
|
};
|
2013-04-12 23:28:34 +08:00
|
|
|
|
2014-03-06 22:19:12 +08:00
|
|
|
// include in layergroup response the variables in serverMedata
|
|
|
|
// those variables are useful to send to the client information
|
|
|
|
// about how to reach this server or information about it
|
|
|
|
var serverMetadata = global.environment.serverMetadata;
|
|
|
|
if (serverMetadata) {
|
|
|
|
_.extend(response, serverMetadata);
|
|
|
|
}
|
|
|
|
|
2013-04-12 23:28:34 +08:00
|
|
|
// Don't wait for the mapview count increment to
|
|
|
|
// take place before proceeding. Error will be logged
|
|
|
|
// asyncronously
|
2013-06-04 19:29:36 +08:00
|
|
|
cartoData.incMapviewCount(username, mapconfig.stat_tag, function(err) {
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('incMapviewCount');
|
|
|
|
}
|
|
|
|
if ( err ) {
|
|
|
|
console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
|
|
|
}
|
2013-04-12 23:28:34 +08:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
2015-02-09 21:46:52 +08:00
|
|
|
var sql = mapconfig.layers.map(function(layer) {
|
|
|
|
return layer.options.sql;
|
|
|
|
}).join(';');
|
2013-03-13 23:45:15 +08:00
|
|
|
|
|
|
|
var dbName = req.params.dbname;
|
|
|
|
var cacheKey = dbName + ':' + token;
|
|
|
|
|
2015-03-16 07:21:55 +08:00
|
|
|
step(
|
2014-07-30 19:46:46 +08:00
|
|
|
function getAffectedTablesAndLastUpdatedTime() {
|
2015-02-09 21:46:52 +08:00
|
|
|
queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
|
2014-07-30 19:46:46 +08:00
|
|
|
},
|
|
|
|
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('queryTablesAndLastUpdated');
|
|
|
|
}
|
|
|
|
assert.ifError(err);
|
2014-07-30 19:46:46 +08:00
|
|
|
var cacheChannel = me.buildCacheChannel(dbName, result.affectedTables);
|
|
|
|
me.channelCache[cacheKey] = cacheChannel;
|
|
|
|
|
|
|
|
if (req.res && req.method == 'GET') {
|
|
|
|
var res = req.res;
|
2015-03-24 01:40:59 +08:00
|
|
|
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
|
|
|
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
2014-07-30 19:46:46 +08:00
|
|
|
res.header('Last-Modified', (new Date()).toUTCString());
|
|
|
|
res.header('X-Cache-Channel', cacheChannel);
|
2014-06-24 18:16:30 +08:00
|
|
|
}
|
2014-07-30 19:46:46 +08:00
|
|
|
|
|
|
|
// last update for layergroup cache buster
|
|
|
|
response.layergroupid = response.layergroupid + ':' + result.lastUpdatedTime;
|
|
|
|
response.last_updated = new Date(result.lastUpdatedTime).toISOString();
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
function finish(err) {
|
|
|
|
done(err);
|
2014-06-24 18:16:30 +08:00
|
|
|
}
|
2014-02-27 19:32:34 +08:00
|
|
|
);
|
2013-03-13 23:45:15 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* X-Cache-Channel generation } */
|
2012-09-25 00:57:48 +08:00
|
|
|
|
2013-12-18 00:35:12 +08:00
|
|
|
// 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.
|
|
|
|
//
|
2015-03-24 02:19:46 +08:00
|
|
|
me.authorizedBySigner = function(req, callback) {
|
|
|
|
if ( ! req.params.token || ! req.params.signer ) {
|
|
|
|
return callback(null, null); // no signer requested
|
|
|
|
}
|
|
|
|
|
|
|
|
var signer = req.params.signer;
|
|
|
|
var layergroup_id = req.params.token;
|
|
|
|
var auth_token = req.params.auth_token;
|
|
|
|
|
|
|
|
var mapStore = req.app.mapStore;
|
|
|
|
if (!mapStore) {
|
|
|
|
throw new Error('Unable to retrieve map configuration token');
|
2015-01-22 22:40:40 +08:00
|
|
|
}
|
|
|
|
|
2015-03-24 02:19:46 +08:00
|
|
|
mapStore.load(layergroup_id, function(err, mapConfig) {
|
|
|
|
assert.ifError(err);
|
2013-12-18 00:35:12 +08:00
|
|
|
|
2015-03-24 02:19:46 +08:00
|
|
|
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
|
|
|
|
|
|
|
|
return callback(null, authorized ? signer : null);
|
|
|
|
});
|
2013-12-18 00:35:12 +08:00
|
|
|
};
|
2013-12-06 20:32:37 +08:00
|
|
|
|
2013-12-17 18:43:56 +08:00
|
|
|
// Check if a request is authorized by api_key
|
|
|
|
//
|
|
|
|
// @param req express request object
|
|
|
|
// @param callback function(err, authorized)
|
2014-02-13 17:16:11 +08:00
|
|
|
// NOTE: authorized is expected to be 0 or 1 (integer)
|
2013-12-17 18:43:56 +08:00
|
|
|
//
|
|
|
|
me.authorizedByAPIKey = function(req, callback)
|
|
|
|
{
|
2014-02-13 17:16:11 +08:00
|
|
|
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 ) {
|
|
|
|
callback(null, 0); // no api key, no authorization...
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//console.log("given ApiKey: " + givenKey);
|
2015-03-24 00:35:09 +08:00
|
|
|
var user = cdbRequest.userByReq(req);
|
2015-03-16 07:21:55 +08:00
|
|
|
step(
|
2013-12-17 18:43:56 +08:00
|
|
|
function (){
|
|
|
|
cartoData.getUserMapKey(user, this);
|
|
|
|
},
|
|
|
|
function checkApiKey(err, val){
|
2015-03-24 00:56:18 +08:00
|
|
|
assert.ifError(err);
|
2014-02-13 17:16:11 +08:00
|
|
|
return ( val && givenKey == val ) ? 1 : 0;
|
2013-12-17 18:43:56 +08:00
|
|
|
},
|
|
|
|
function finish(err, authorized) {
|
|
|
|
callback(err, authorized);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2013-11-16 02:14:00 +08:00
|
|
|
/**
|
2013-12-06 20:32:37 +08:00
|
|
|
* Check access authorization
|
2013-11-16 02:14:00 +08:00
|
|
|
*
|
|
|
|
* @param req - standard req object. Importantly contains table and host information
|
2013-12-06 20:32:37 +08:00
|
|
|
* @param callback function(err, allowed) is access allowed not?
|
2013-11-16 02:14:00 +08:00
|
|
|
*/
|
2013-12-06 20:32:37 +08:00
|
|
|
me.authorize = function(req, callback) {
|
2013-11-16 02:14:00 +08:00
|
|
|
var that = this;
|
2015-03-24 00:35:09 +08:00
|
|
|
var user = cdbRequest.userByReq(req);
|
2013-11-16 02:14:00 +08:00
|
|
|
|
2015-03-16 07:21:55 +08:00
|
|
|
step(
|
2013-12-17 17:56:12 +08:00
|
|
|
function (){
|
2013-12-17 18:43:56 +08:00
|
|
|
that.authorizedByAPIKey(req, this);
|
2013-11-16 02:14:00 +08:00
|
|
|
},
|
2013-12-17 18:43:56 +08:00
|
|
|
function checkApiKey(err, authorized){
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('authorizedByAPIKey');
|
|
|
|
}
|
|
|
|
assert.ifError(err);
|
2013-11-16 02:14:00 +08:00
|
|
|
|
2013-12-06 20:32:37 +08:00
|
|
|
// if not authorized by api_key, continue
|
2013-12-18 00:35:12 +08:00
|
|
|
if (authorized !== 1) {
|
|
|
|
// not authorized by api_key,
|
|
|
|
// check if authorized by signer
|
|
|
|
that.authorizedBySigner(req, this);
|
|
|
|
return;
|
|
|
|
}
|
2013-12-06 20:32:37 +08:00
|
|
|
|
2013-12-17 00:34:58 +08:00
|
|
|
// authorized by api key, login as the given username and stop
|
2015-02-09 21:46:52 +08:00
|
|
|
pgConnection.setDBAuth(user, req.params, function(err) {
|
2013-12-06 20:32:37 +08:00
|
|
|
callback(err, true); // authorized (or error)
|
|
|
|
});
|
|
|
|
},
|
2013-12-18 00:35:12 +08:00
|
|
|
function checkSignAuthorized(err, signed_by){
|
2015-03-24 00:56:18 +08:00
|
|
|
assert.ifError(err);
|
2013-12-18 00:35:12 +08:00
|
|
|
|
|
|
|
if ( ! signed_by ) {
|
2014-03-04 01:06:39 +08:00
|
|
|
// request not authorized by signer.
|
|
|
|
|
|
|
|
// if no signer name was given, let dbparams and
|
|
|
|
// PostgreSQL do the rest.
|
|
|
|
//
|
|
|
|
if ( ! req.params.signer ) {
|
|
|
|
callback(null, true); // authorized so far
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if signer name was given, return no authorization
|
2014-03-04 01:13:06 +08:00
|
|
|
callback(null, false);
|
2014-02-18 01:20:18 +08:00
|
|
|
return;
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
|
2015-02-09 21:46:52 +08:00
|
|
|
pgConnection.setDBAuth(signed_by, req.params, function(err) {
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('setDBAuth');
|
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
callback(err, true); // authorized (or error)
|
|
|
|
});
|
2013-11-16 02:14:00 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2015-03-16 07:36:38 +08:00
|
|
|
// jshint maxcomplexity:10
|
2012-05-03 02:32:54 +08:00
|
|
|
/**
|
|
|
|
* Whitelist input and get database name & default geometry type from
|
|
|
|
* subdomain/user metadata held in CartoDB Redis
|
|
|
|
* @param req - standard express request obj. Should have host & table
|
|
|
|
* @param callback
|
|
|
|
*/
|
|
|
|
me.req2params = function(req, callback){
|
|
|
|
|
2013-03-23 01:55:59 +08:00
|
|
|
if ( req.query.lzma ) {
|
|
|
|
|
2013-04-19 22:16:20 +08:00
|
|
|
// Decode (from base64)
|
2015-03-16 07:27:14 +08:00
|
|
|
var lzma = new Buffer(req.query.lzma, 'base64')
|
|
|
|
.toString('binary')
|
|
|
|
.split('')
|
|
|
|
.map(function(c) {
|
|
|
|
return c.charCodeAt(0) - 128;
|
|
|
|
});
|
2013-03-23 01:55:59 +08:00
|
|
|
|
|
|
|
// Decompress
|
2014-08-15 01:54:45 +08:00
|
|
|
lzmaWorker.decompress(
|
2013-03-23 01:55:59 +08:00
|
|
|
lzma,
|
|
|
|
function(result) {
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
2015-03-27 00:19:16 +08:00
|
|
|
req.profiler.done('lzma');
|
2015-03-24 00:56:18 +08:00
|
|
|
}
|
2013-03-23 01:55:59 +08:00
|
|
|
try {
|
2014-07-30 19:45:53 +08:00
|
|
|
delete req.query.lzma;
|
|
|
|
_.extend(req.query, JSON.parse(result));
|
2013-03-23 01:55:59 +08:00
|
|
|
me.req2params(req, callback);
|
2013-09-12 21:36:50 +08:00
|
|
|
} catch (err) {
|
2013-03-23 01:55:59 +08:00
|
|
|
callback(new Error('Error parsing lzma as JSON: ' + err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-03-27 00:19:16 +08:00
|
|
|
req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST);
|
|
|
|
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
|
2012-05-03 02:32:54 +08:00
|
|
|
|
2015-03-24 00:35:09 +08:00
|
|
|
var user = cdbRequest.userByReq(req);
|
2014-02-27 23:40:59 +08:00
|
|
|
|
2013-04-04 19:15:50 +08:00
|
|
|
if ( req.params.token ) {
|
|
|
|
//console.log("Request parameters include token " + req.params.token);
|
|
|
|
var tksplit = req.params.token.split(':');
|
|
|
|
req.params.token = tksplit[0];
|
2015-03-24 00:56:18 +08:00
|
|
|
if ( tksplit.length > 1 ) {
|
|
|
|
req.params.cache_buster= tksplit[1];
|
|
|
|
}
|
2013-12-06 20:32:37 +08:00
|
|
|
tksplit = req.params.token.split('@');
|
|
|
|
if ( tksplit.length > 1 ) {
|
2014-02-10 22:30:35 +08:00
|
|
|
req.params.signer = tksplit.shift();
|
2015-03-24 00:56:18 +08:00
|
|
|
if ( ! req.params.signer ) {
|
|
|
|
req.params.signer = user;
|
|
|
|
}
|
2015-03-16 07:00:02 +08:00
|
|
|
else if ( req.params.signer !== user ) {
|
2015-03-16 07:27:14 +08:00
|
|
|
var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' +
|
|
|
|
user + '"');
|
2014-02-28 20:24:38 +08:00
|
|
|
err.http_status = 403;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-10 22:30:35 +08:00
|
|
|
if ( tksplit.length > 1 ) {
|
2015-03-16 07:16:36 +08:00
|
|
|
/*var template_hash = */tksplit.shift(); // unused
|
2014-02-10 22:30:35 +08:00
|
|
|
}
|
|
|
|
req.params.token = tksplit.shift();
|
2013-12-18 00:35:12 +08:00
|
|
|
//console.log("Request for token " + req.params.token + " with signature from " + req.params.signer);
|
2013-12-06 20:32:37 +08:00
|
|
|
}
|
2013-04-04 19:15:50 +08:00
|
|
|
}
|
|
|
|
|
2012-05-03 02:32:54 +08:00
|
|
|
// bring all query values onto req.params object
|
|
|
|
_.extend(req.params, req.query);
|
|
|
|
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('req2params.setup');
|
|
|
|
}
|
2013-07-16 21:59:34 +08:00
|
|
|
|
2015-03-16 07:21:55 +08:00
|
|
|
step(
|
2012-05-03 02:32:54 +08:00
|
|
|
function getPrivacy(){
|
2013-11-16 02:14:00 +08:00
|
|
|
me.authorize(req, this);
|
2012-05-03 02:32:54 +08:00
|
|
|
},
|
2014-03-04 01:06:39 +08:00
|
|
|
function gatekeep(err, authorized){
|
2015-03-24 00:56:18 +08:00
|
|
|
if (req.profiler) {
|
|
|
|
req.profiler.done('authorize');
|
|
|
|
}
|
|
|
|
assert.ifError(err);
|
2014-03-04 00:18:06 +08:00
|
|
|
if(!authorized) {
|
|
|
|
err = new Error("Sorry, you are unauthorized (permission denied)");
|
|
|
|
err.http_status = 403;
|
|
|
|
throw err;
|
|
|
|
}
|
2014-03-04 01:06:39 +08:00
|
|
|
return null;
|
2012-05-03 02:32:54 +08:00
|
|
|
},
|
2014-03-04 01:06:39 +08:00
|
|
|
function getDatabase(err){
|
2015-03-24 00:56:18 +08:00
|
|
|
assert.ifError(err);
|
2015-02-09 21:46:52 +08:00
|
|
|
pgConnection.setDBConn(user, req.params, this);
|
2012-05-03 02:32:54 +08:00
|
|
|
},
|
2015-02-10 23:57:43 +08:00
|
|
|
function finishSetup(err) {
|
2012-09-25 00:57:48 +08:00
|
|
|
if ( err ) { callback(err, req); return; }
|
|
|
|
|
2014-02-08 01:00:13 +08:00
|
|
|
// Add default database connection parameters
|
|
|
|
// if none given
|
|
|
|
_.defaults(req.params, {
|
|
|
|
dbuser: global.environment.postgres.user,
|
|
|
|
dbpassword: global.environment.postgres.password,
|
|
|
|
dbhost: global.environment.postgres.host,
|
|
|
|
dbport: global.environment.postgres.port
|
|
|
|
});
|
|
|
|
|
2014-02-19 17:08:25 +08:00
|
|
|
callback(null, req);
|
2012-05-03 02:32:54 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return me;
|
2014-02-19 13:45:29 +08:00
|
|
|
};
|