Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc52bb8751 | ||
|
|
47f42e4031 | ||
|
|
66a77cd255 | ||
|
|
0301cef1bb | ||
|
|
72b3f23f72 | ||
|
|
8685ef640e | ||
|
|
2d36521f92 | ||
|
|
ca4644f4ce | ||
|
|
58a462ab95 | ||
|
|
c2d4aace56 | ||
|
|
4d524e5969 | ||
|
|
8c74a39262 | ||
|
|
743b5388a3 | ||
|
|
4144ad2c7a | ||
|
|
fb4ef5f768 | ||
|
|
cbb85e5dd8 | ||
|
|
56bfed5a0e | ||
|
|
ff9af5f923 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
node_modules*
|
||||
config.status*
|
||||
config/environments/*.js
|
||||
.idea
|
||||
tools/munin/windshaft.conf
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
before_install:
|
||||
- sudo apt-add-repository --yes ppa:mapnik/v2.1.0
|
||||
- sudo apt-add-repository --yes ppa:ubuntugis/ppa
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install -q libmapnik-dev
|
||||
- sudo apt-get install -q libmapnik libmapnik-dev postgresql-9.1-postgis libsigc++-dev
|
||||
- createdb template_postgis
|
||||
- psql -c "CREATE EXTENSION postgis" template_postgis
|
||||
|
||||
@@ -11,9 +12,3 @@ env:
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#cartodb"
|
||||
use_notice: true
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
1. Ensure proper version in package.json
|
||||
2. Ensure NEWS section exists for the new version, review it, add release date
|
||||
3. Drop npm-shrinkwrap.json
|
||||
4. Run npm install
|
||||
5. Test (make check or npm test), fix if broken before proceeding
|
||||
6. Run npm shrinkwrap
|
||||
7. Commit package.json, npm-shrinwrap.json, NEWS
|
||||
8. Tag Major.Minor.Patch
|
||||
9. Announce
|
||||
10. Stub NEWS/package for next version
|
||||
|
||||
15
Makefile
15
Makefile
@@ -6,19 +6,16 @@ all:
|
||||
clean:
|
||||
rm -rf node_modules/*
|
||||
|
||||
distclean: clean
|
||||
rm config.status*
|
||||
|
||||
config.status--test:
|
||||
config/environments/test.js: config/environments/test.js.example Makefile
|
||||
./configure --environment=test
|
||||
|
||||
config/environments/test.js: config.status--test
|
||||
./config.status--test
|
||||
|
||||
check-local: config/environments/test.js
|
||||
./run_tests.sh ${RUNTESTFLAGS} \
|
||||
test/unit/cartodb/*.js \
|
||||
test/acceptance/*.js
|
||||
test/unit/cartodb/redis_pool.test.js \
|
||||
test/unit/cartodb/req2params.test.js \
|
||||
test/acceptance/cache_validator.js \
|
||||
test/acceptance/server.js \
|
||||
test/acceptance/multilayer.js
|
||||
|
||||
check-submodules:
|
||||
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \
|
||||
|
||||
92
NEWS.md
92
NEWS.md
@@ -1,95 +1,3 @@
|
||||
1.6.2 -- 2014-01-23
|
||||
-------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fix support for long (>64k chars) queries in layergroup creation (#111)
|
||||
|
||||
Enhancements:
|
||||
|
||||
* Enhance tools/show_style to accept an environment parameter and
|
||||
print XML style now it is not in redis anymore (#110)
|
||||
* Support CORS in template instanciation endpoint (#113)
|
||||
|
||||
1.6.1 -- 2014-01-15
|
||||
-------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Drop cache headers from error responses (#107)
|
||||
* Localize external CartoCSS resources at renderer creation time (#108)
|
||||
|
||||
1.6.0 -- 2014-01-10
|
||||
-------------------
|
||||
|
||||
New features:
|
||||
|
||||
* Add 'user_from_host' directive to generalize username extraction (#100)
|
||||
* Implement signed template maps (#98)
|
||||
|
||||
|
||||
Other changes:
|
||||
|
||||
* Update cartodb-redis dependency to "~0.3.0"
|
||||
* Update redis-server dependency to "2.4.0+"
|
||||
|
||||
1.5.2 -- 2013-12-05
|
||||
-------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fix configuration-level compatibility with versions prior to 1.5 (#96)
|
||||
* Fix use of old layergroups on mapnik upgrade (#97)
|
||||
|
||||
1.5.1 -- 2013-11-28
|
||||
-------------------
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Survive presence of malformed CartoCSS in redis (#94)
|
||||
* Accept useless point-transform:scale directives (#93)
|
||||
|
||||
1.5.0 -- 2013-11-19
|
||||
-------------------
|
||||
|
||||
NOTE: new configuration directives `postgres_auth_pass` and
|
||||
`postgres.password` added; see config/environments/*.example
|
||||
for documentation.
|
||||
|
||||
Improvements:
|
||||
|
||||
* Add support for configuring database connection passwords
|
||||
* Optionally read user-specific database_host and database_password
|
||||
from redis as per CartoDB-2.5.0 model (#88, #89)
|
||||
* Do not force ending dot in SQL-API hostname, for easier testing
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Return CORS headers when creating layergroups via GET (windshaft/#92)
|
||||
* Fix http status on database authentication error (windshaft/#94)
|
||||
* Fix text-face-name error at layergroup creation (windshaft/#93)
|
||||
|
||||
Other changes:
|
||||
|
||||
* CartoDB redis interaction delegated to "cartodb-redis" module
|
||||
|
||||
|
||||
1.4.1 -- 2013-11-08
|
||||
-------------------
|
||||
|
||||
* Fix support for exponential notation in CartoCSS filter values (#87)
|
||||
|
||||
1.4.0 -- 2013-10-31
|
||||
-------------------
|
||||
|
||||
* Add Support for Mapnik-2.2.0 (#78)
|
||||
|
||||
1.3.6 -- 2013-10-11
|
||||
-------------------
|
||||
|
||||
* Restore support for node-0.8.9 accidentally dropped by 1.3.5
|
||||
NOTE: needs removing node_modules/windshaft and re-running npm install
|
||||
|
||||
1.3.5 -- 2013-10-03
|
||||
-------------------
|
||||
|
||||
|
||||
16
README.md
16
README.md
@@ -1,8 +1,7 @@
|
||||
Windshaft-CartoDB
|
||||
==================
|
||||
|
||||
[]
|
||||
(http://travis-ci.org/CartoDB/Windshaft-cartodb)
|
||||
[](http://travis-ci.org/CartoDB/Windshaft-cartodb)
|
||||
|
||||
This is the CartoDB map tiler. It extends Windshaft with some extra
|
||||
functionality and custom filters for authentication
|
||||
@@ -12,10 +11,8 @@ functionality and custom filters for authentication
|
||||
* gets the default geometry type from the cartodb redis store
|
||||
* allows tiles to be styled individually
|
||||
* provides a link to varnish high speed cache
|
||||
* provides a ``infowindow`` endpoint for windshaft (DEPRECATED)
|
||||
* provides a ``map_metadata`` endpoint for windshaft (DEPRECATED)
|
||||
* provides signed template maps API
|
||||
(http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps)
|
||||
* provides a infowindow endpoint for windshaft
|
||||
* provides a ``map_metadata`` endpoint for windshaft
|
||||
|
||||
Requirements
|
||||
------------
|
||||
@@ -24,16 +21,13 @@ Requirements
|
||||
- node-0.8.x+
|
||||
- PostgreSQL-8.3+
|
||||
- PostGIS-1.5.0+
|
||||
- Redis 2.4.0+ (http://www.redis.io)
|
||||
- Redis 2.2.0+ (http://www.redis.io)
|
||||
- Mapnik 2.0 or 2.1
|
||||
|
||||
[for cache control]
|
||||
- CartoDB-SQL-API 1.0.0+
|
||||
- CartoDB 0.9.5+ (for ``CDB_QueryTables``)
|
||||
- Varnish (http://www.varnish-cache.org)
|
||||
|
||||
[for running the testsuite]
|
||||
- Imagemagick (http://www.imagemagick.org)
|
||||
- Varnish (https://www.varnish-cache.org)
|
||||
|
||||
Configure
|
||||
---------
|
||||
|
||||
9
app.js
9
app.js
@@ -18,15 +18,18 @@ if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
|
||||
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
;
|
||||
, CartodbWindshaft = require('./lib/cartodb/cartodb_windshaft');
|
||||
|
||||
// set environment specific variables
|
||||
global.settings = require(__dirname + '/config/settings');
|
||||
global.environment = require(__dirname + '/config/environments/' + ENV);
|
||||
_.extend(global.settings, global.environment);
|
||||
|
||||
// Include cartodb_windshaft only _after_ the "global" variable is set
|
||||
// Include cart_data.js only _after_ the "global" variable is set
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
|
||||
var CartodbWindshaft = require('./lib/cartodb/cartodb_windshaft');
|
||||
var cartoData = require('./lib/cartodb/carto_data');
|
||||
|
||||
var Windshaft = require('windshaft');
|
||||
var serverOptions = require('./lib/cartodb/server_options');
|
||||
|
||||
ws = CartodbWindshaft(serverOptions);
|
||||
|
||||
@@ -2,9 +2,6 @@ var config = {
|
||||
environment: 'development'
|
||||
,port: 8181
|
||||
,host: '127.0.0.1'
|
||||
// Regular expression pattern to extract username
|
||||
// from hostname. Must have a single grabbing block.
|
||||
,user_from_host: '^(.*)\\.localhost'
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -13,18 +10,12 @@ var config = {
|
||||
,enable_cors: true
|
||||
,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])'
|
||||
// Templated database username for authorized user
|
||||
// Supported labels: 'user_id' (read from redis)
|
||||
,postgres_auth_user: 'development_cartodb_user_<%= user_id %>'
|
||||
// Templated database password for authorized user
|
||||
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||
,postgres_auth_pass: '<%= user_password %>'
|
||||
,postgres: {
|
||||
// Parameters to pass to datasource plugin of mapnik
|
||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||
type: "postgis",
|
||||
user: "publicuser",
|
||||
password: "public",
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
|
||||
@@ -2,9 +2,6 @@ var config = {
|
||||
environment: 'production'
|
||||
,port: 8181
|
||||
,host: '127.0.0.1'
|
||||
// Regular expression pattern to extract username
|
||||
// from hostname. Must have a single grabbing block.
|
||||
,user_from_host: '^(.*)\\.cartodb\\.com$'
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -13,17 +10,11 @@ var config = {
|
||||
,enable_cors: 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])'
|
||||
// Templated database username for authorized user
|
||||
// Supported labels: 'user_id' (read from redis)
|
||||
,postgres_auth_user: 'cartodb_user_<%= user_id %>'
|
||||
// Templated database password for authorized user
|
||||
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||
,postgres_auth_pass: '<%= user_password %>'
|
||||
,postgres: {
|
||||
// Parameters to pass to datasource plugin of mapnik
|
||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||
user: "publicuser",
|
||||
password: "public",
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
|
||||
@@ -2,9 +2,6 @@ var config = {
|
||||
environment: 'production'
|
||||
,port: 8181
|
||||
,host: '127.0.0.1'
|
||||
// Regular expression pattern to extract username
|
||||
// from hostname. Must have a single grabbing block.
|
||||
,user_from_host: '^(.*)\\.cartodb\\.com$'
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -13,17 +10,11 @@ var config = {
|
||||
,enable_cors: 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]'
|
||||
// Templated database username for authorized user
|
||||
// Supported labels: 'user_id' (read from redis)
|
||||
,postgres_auth_user: 'cartodb_staging_user_<%= user_id %>'
|
||||
// Templated database password for authorized user
|
||||
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||
,postgres_auth_pass: '<%= user_password %>'
|
||||
,postgres: {
|
||||
// Parameters to pass to datasource plugin of mapnik
|
||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||
user: "publicuser",
|
||||
password: "public",
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
|
||||
@@ -2,9 +2,6 @@ var config = {
|
||||
environment: 'test'
|
||||
,port: 8888
|
||||
,host: '127.0.0.1'
|
||||
// Regular expression pattern to extract username
|
||||
// from hostname. Must have a single grabbing block.
|
||||
,user_from_host: '(.*)'
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -13,17 +10,11 @@ var config = {
|
||||
,enable_cors: true
|
||||
,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])'
|
||||
// Templated database username for authorized user
|
||||
// Supported labels: 'user_id' (read from redis)
|
||||
,postgres_auth_user: 'test_cartodb_user_<%= user_id %>'
|
||||
// Templated database password for authorized user
|
||||
// Supported labels: 'user_id', 'user_password' (both read from redis)
|
||||
,postgres_auth_pass: 'test_cartodb_user_<%= user_id %>_pass'
|
||||
,postgres: {
|
||||
// Parameters to pass to datasource plugin of mapnik
|
||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||
user: "testpublicuser",
|
||||
password: "public",
|
||||
user: "publicuser",
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
@@ -44,7 +35,7 @@ var config = {
|
||||
}
|
||||
,redis: {
|
||||
host: '127.0.0.1',
|
||||
port: 6335,
|
||||
port: 6333,
|
||||
// Max number of connections in each pool.
|
||||
// Users will be put on a queue when the limit is hit.
|
||||
// Set to maxConnection to have no possible queues.
|
||||
|
||||
32
configure
vendored
32
configure
vendored
@@ -17,15 +17,11 @@
|
||||
# --strk(2012-07-23)
|
||||
#
|
||||
|
||||
ENVDIR=config/environments
|
||||
|
||||
PGPORT=
|
||||
SQLAPI_PORT=
|
||||
PGPORT=5432
|
||||
SQLAPI_PORT=8080
|
||||
MAPNIK_VERSION=
|
||||
ENVIRONMENT=development
|
||||
|
||||
STATUS="$0 $*"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTION]"
|
||||
echo
|
||||
@@ -63,30 +59,20 @@ while test -n "$1"; do
|
||||
shift
|
||||
done
|
||||
|
||||
ENVEX=./${ENVDIR}/${ENVIRONMENT}.js.example
|
||||
|
||||
if [ -z "$PGPORT" ]; then
|
||||
PGPORT=`node -e "console.log(require('${ENVEX}').postgres.port)"`
|
||||
fi
|
||||
if [ -z "$SQLAPI_PORT" ]; then
|
||||
SQLAPI_PORT=`node -e "console.log(require('${ENVEX}').sqlapi.port)"`
|
||||
fi
|
||||
|
||||
echo "PGPORT: $PGPORT"
|
||||
echo "SQLAPI_PORT: $SQLAPI_PORT"
|
||||
echo "MAPNIK_VERSION: $MAPNIK_VERSION"
|
||||
echo "ENVIRONMENT: $ENVIRONMENT"
|
||||
|
||||
o=`dirname "${ENVEX}"`/`basename "${ENVEX}" .example`
|
||||
echo "Writing $o"
|
||||
# TODO: allow specifying configuration settings !
|
||||
for f in config/environments/${ENVIRONMENT}.js.example; do
|
||||
o=`dirname "$f"`/`basename "$f" .example`
|
||||
echo "Writing $o"
|
||||
|
||||
# See http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/
|
||||
sed -n "1h;1!H;\${;g;s/\(,postgres: {[^}]*port: *'\?\)[^',]*\('\?,\)/\1$PGPORT\2/;p;}" < "${ENVEX}" \
|
||||
# See http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/
|
||||
sed -n "1h;1!H;\${;g;s/\(,postgres: {[^}]*port: *'\?\)[^',]*\('\?,\)/\1$PGPORT\2/;p;}" < "$f" \
|
||||
| sed "s/mapnik_version:.*/mapnik_version: '$MAPNIK_VERSION'/" \
|
||||
| sed -n "1h;1!H;\${;g;s/\(,sqlapi: {[^}]*port: *'\?\)[^',]*\('\?,\)/\1$SQLAPI_PORT\2/;p;}" \
|
||||
> "$o"
|
||||
|
||||
STATUSFILE=config.status--${ENVIRONMENT}
|
||||
echo "Writing ${STATUSFILE}"
|
||||
echo ${STATUS} > ${STATUSFILE} && chmod +x ${STATUSFILE}
|
||||
|
||||
done
|
||||
|
||||
279
lib/cartodb/carto_data.js
Normal file
279
lib/cartodb/carto_data.js
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* User: simon
|
||||
* Date: 30/08/2011
|
||||
* Time: 21:10
|
||||
* Desc: CartoDB helper.
|
||||
* Retrieves dbname (based on subdomain/username)
|
||||
* and geometry type from the redis stores of cartodb
|
||||
*/
|
||||
|
||||
var strftime = require('strftime');
|
||||
|
||||
var RedisPool = require("./redis_pool")
|
||||
, _ = require('underscore')
|
||||
, Step = require('step');
|
||||
|
||||
module.exports = function() {
|
||||
var redis_pool = new RedisPool(global.environment.redis);
|
||||
|
||||
|
||||
var me = {
|
||||
user_metadata_db: 5,
|
||||
table_metadata_db: 0,
|
||||
user_key: "rails:users:<%= username %>",
|
||||
table_key: "rails:<%= database_name %>:<%= table_name %>",
|
||||
global_mapview_key: "user:<%= username %>:mapviews:global",
|
||||
tagged_mapview_key: "user:<%= username %>:mapviews:stat_tag:<%= stat_tag %>"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the database name for this particular subdomain/username
|
||||
*
|
||||
* @param req - standard express req object. importantly contains host information
|
||||
* @param callback - gets called with args(err, dbname)
|
||||
*/
|
||||
me.getDatabase = function(req, callback) {
|
||||
// strip subdomain from header host
|
||||
var username = req.headers.host.split('.')[0]
|
||||
var redisKey = _.template(this.user_key, {username: username});
|
||||
|
||||
this.retrieve(this.user_metadata_db, redisKey, 'database_name', function(err, dbname) {
|
||||
if ( err ) callback(err, null);
|
||||
else if ( dbname === null ) {
|
||||
callback(new Error("missing " + username + "'s dbname in redis (try CARTODB/script/restore_redis)"), null);
|
||||
}
|
||||
else callback(err, dbname);
|
||||
});
|
||||
};
|
||||
|
||||
me.userFromHostname = function(hostname) {
|
||||
return hostname.split('.')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment mapview count for a user
|
||||
*
|
||||
* @param username
|
||||
* @param stat_tag
|
||||
* @param callback will be called with the new value
|
||||
*/
|
||||
me.incMapviewCount = function(username, stat_tag, callback) {
|
||||
var that = this;
|
||||
var now = strftime("%Y%m%d", new Date());
|
||||
var redisKey;
|
||||
Step (
|
||||
function incrementGlobal() {
|
||||
redisKey = _.template(that.global_mapview_key, {username: username});
|
||||
that.redisCmd(me.user_metadata_db, 'ZINCRBY', [redisKey, 1, now], this);
|
||||
},
|
||||
function incrementTag(err, val) {
|
||||
if ( err ) throw err;
|
||||
if ( _.isUndefined(stat_tag) ) return 1;
|
||||
redisKey = _.template(that.tagged_mapview_key, {username: username, stat_tag: stat_tag});
|
||||
that.redisCmd(me.user_metadata_db, 'ZINCRBY', [redisKey, 1, now], this);
|
||||
},
|
||||
function finish(err, val) {
|
||||
if ( callback ) callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the user id for this particular subdomain/username
|
||||
*
|
||||
* @param req - standard express req object. importantly contains host information
|
||||
* @param callback
|
||||
*/
|
||||
me.getId= function(req, callback) {
|
||||
// strip subdomain from header host
|
||||
var username = req.headers.host.split('.')[0];
|
||||
var redisKey = _.template(this.user_key, {username: username});
|
||||
|
||||
this.retrieve(this.user_metadata_db, redisKey, 'id', function(err, dbname) {
|
||||
if ( err ) callback(err, null);
|
||||
else if ( dbname === null ) {
|
||||
callback(new Error("missing " + username + "'s dbuser in redis (try CARTODB/script/restore_redis)"), null);
|
||||
}
|
||||
else callback(err, dbname);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the user map key for this particular subdomain/username
|
||||
*
|
||||
* @param req - standard express req object. importantly contains host information
|
||||
* @param callback
|
||||
*/
|
||||
me.checkMapKey = function(req, callback) {
|
||||
// strip subdomain from header host
|
||||
var username = req.headers.host.split('.')[0];
|
||||
var redisKey = "rails:users:" + username;
|
||||
this.retrieve(this.user_metadata_db, redisKey, "map_key", function(err, val) {
|
||||
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;
|
||||
}
|
||||
callback(err, valid);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get privacy for cartodb table
|
||||
*
|
||||
* @param req - standard req object. Importantly contains table and host information
|
||||
* @param callback - is the table private or not?
|
||||
*/
|
||||
me.authorize= function(req, callback) {
|
||||
var that = this;
|
||||
|
||||
Step(
|
||||
function(){
|
||||
that.checkMapKey(req, this);
|
||||
},
|
||||
function checkIfInternal(err, check_result){
|
||||
if (err) throw err;
|
||||
if (check_result === 1) {
|
||||
// authorized by key, login as db owner
|
||||
that.getId(req, function(err, user_id) {
|
||||
if (err) throw err;
|
||||
var dbuser = _.template(global.settings.postgres_auth_user, {user_id: user_id});
|
||||
_.extend(req.params, {dbuser:dbuser});
|
||||
callback(err, true);
|
||||
});
|
||||
} else {
|
||||
return true; // continue to check if the table is public/private
|
||||
}
|
||||
}
|
||||
,function (err, data){
|
||||
if (err) throw err;
|
||||
that.getDatabase(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err) throw err;
|
||||
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
||||
|
||||
that.retrieve(that.table_metadata_db, redisKey, 'privacy', this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the geometry type for this particular table;
|
||||
* @param req - standard req object. Importantly contains table and host information
|
||||
* @param callback
|
||||
*/
|
||||
me.getGeometryType = function(req, callback){
|
||||
var that = this;
|
||||
|
||||
Step(
|
||||
function(){
|
||||
that.getDatabase(req, this)
|
||||
},
|
||||
function(err, data){
|
||||
if (err) throw err;
|
||||
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
||||
|
||||
that.retrieve(that.table_metadata_db, redisKey, 'the_geom_type', this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
me.getInfowindow = function(req, callback){
|
||||
var that = this;
|
||||
|
||||
Step(
|
||||
function(){
|
||||
that.getDatabase(req, this);
|
||||
},
|
||||
function(err, data) {
|
||||
if (err) throw err;
|
||||
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
||||
that.retrieve(that.table_metadata_db, redisKey, 'infowindow', this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
me.getMapMetadata = function(req, callback){
|
||||
var that = this;
|
||||
|
||||
Step(
|
||||
function(){
|
||||
that.getDatabase(req, this);
|
||||
},
|
||||
function(err, data) {
|
||||
if (err) throw err;
|
||||
var redisKey = _.template(that.table_key, {database_name: data, table_name: req.params.table});
|
||||
|
||||
that.retrieve(that.table_metadata_db, redisKey, 'map_metadata', this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Redis Hash lookup
|
||||
// @param callback will be invoked with args (err, reply)
|
||||
// note that reply is null when the key is missing
|
||||
me.retrieve = function(db, redisKey, hashKey, callback) {
|
||||
this.redisCmd(db,'HGET',[redisKey, hashKey], callback);
|
||||
};
|
||||
|
||||
// Redis Set member check
|
||||
me.inSet = function(db, setKey, member, callback) {
|
||||
this.redisCmd(db,'SISMEMBER',[setKey, member], callback);
|
||||
};
|
||||
|
||||
// Redis INCREMENT
|
||||
me.increment = function(db, key, callback) {
|
||||
this.redisCmd(db,'INCR', key, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use Redis
|
||||
*
|
||||
* @param db - redis database number
|
||||
* @param redisFunc - the redis function to execute
|
||||
* @param redisArgs - the arguments for the redis function in an array
|
||||
* @param callback - function to pass results too.
|
||||
*/
|
||||
me.redisCmd = function(db, redisFunc, redisArgs, callback) {
|
||||
var redisClient;
|
||||
|
||||
Step(
|
||||
function getRedisClient() {
|
||||
redis_pool.acquire(db, this);
|
||||
},
|
||||
function executeQuery(err, data) {
|
||||
if ( err ) throw err;
|
||||
redisClient = data;
|
||||
redisArgs.push(this);
|
||||
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
|
||||
},
|
||||
function releaseRedisClient(err, data) {
|
||||
if ( ! _.isUndefined(redisClient) ) redis_pool.release(db, redisClient);
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return me;
|
||||
}();
|
||||
@@ -2,11 +2,6 @@
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, Windshaft = require('windshaft')
|
||||
, redisPool = new require('redis-mpool')(global.environment.redis)
|
||||
// TODO: instanciate cartoData with redisPool
|
||||
, cartoData = require('cartodb-redis')(global.environment.redis)
|
||||
, SignedMaps = require('./signed_maps.js')
|
||||
, TemplateMaps = require('./template_maps.js')
|
||||
, Cache = require('./cache_validator');
|
||||
|
||||
var CartodbWindshaft = function(serverOptions) {
|
||||
@@ -28,9 +23,6 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
callback(err, req);
|
||||
}
|
||||
|
||||
serverOptions.signedMaps = new SignedMaps(redisPool);
|
||||
var templateMaps = new TemplateMaps(redisPool, serverOptions.signedMaps);
|
||||
|
||||
// boot
|
||||
var ws = new Windshaft.Server(serverOptions);
|
||||
|
||||
@@ -42,22 +34,6 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
return version;
|
||||
}
|
||||
|
||||
// Override sendError to drop added cache headers (if any)
|
||||
// See http://github.com/CartoDB/Windshaft-cartodb/issues/107
|
||||
var ws_sendError = ws.sendError;
|
||||
ws.sendError = function(res) {
|
||||
// NOTE: the "res" object will have no _headers when
|
||||
// faked by Windshaft, see
|
||||
// http://github.com/CartoDB/Windshaft-cartodb/issues/109
|
||||
//
|
||||
if ( res._headers ) {
|
||||
delete res._headers['cache-control'];
|
||||
delete res._headers['last-modified'];
|
||||
delete res._headers['x-cache-channel'];
|
||||
}
|
||||
ws_sendError.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to allow access to the layer to be used in the maps infowindow popup.
|
||||
*/
|
||||
@@ -119,381 +95,6 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// ---- Template maps interface starts @{
|
||||
|
||||
ws.userByReq = function(req) {
|
||||
return serverOptions.userByReq(req);
|
||||
}
|
||||
|
||||
var template_baseurl = serverOptions.base_url_notable + '/template';
|
||||
|
||||
// Add a template
|
||||
ws.post(template_baseurl, function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var cdbuser = ws.userByReq(req);
|
||||
Step(
|
||||
function checkPerms(){
|
||||
serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function addTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can create templated maps");
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
var next = this;
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
||||
throw new Error('template POST data must be of type application/json');
|
||||
var cfg = req.body;
|
||||
templateMaps.addTemplate(cdbuser, cfg, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_id){
|
||||
if ( err ) throw err;
|
||||
// NOTE: might omit "cdbuser" if == dbowner ...
|
||||
return { template_id: cdbuser + '@' + tpl_id };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
response = { error: ''+err };
|
||||
var statusCode = 400;
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'POST TEMPLATE', err.message);
|
||||
} else {
|
||||
res.send(response, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Update a template
|
||||
ws.put(template_baseurl + '/:template_id', function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var cdbuser = ws.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can list templated maps");
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
||||
throw new Error('template PUT data must be of type application/json');
|
||||
template = req.body;
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
err = new Error("Invalid template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
templateMaps.updTemplate(cdbuser, tpl_id, template, this);
|
||||
},
|
||||
function prepareResponse(err){
|
||||
if ( err ) throw err;
|
||||
return { template_id: cdbuser + '@' + tpl_id };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'PUT TEMPLATE', err.message);
|
||||
} else {
|
||||
res.send(response, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Get a specific template
|
||||
ws.get(template_baseurl + '/:template_id', function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var cdbuser = ws.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated users can get template maps");
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot get template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val){
|
||||
if ( err ) throw err;
|
||||
if ( ! tpl_val ) {
|
||||
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
// auth_id was added by ourselves,
|
||||
// so we remove it before returning to the user
|
||||
delete tpl_val.auth_id;
|
||||
return { template: tpl_val };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'GET TEMPLATE', err.message);
|
||||
} else {
|
||||
res.send(response, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Delete a specific template
|
||||
ws.delete(template_baseurl + '/:template_id', function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var cdbuser = ws.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated users can delete template maps");
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot find template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
templateMaps.delTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val){
|
||||
if ( err ) throw err;
|
||||
return { status: 'ok' };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'DELETE TEMPLATE', err.message);
|
||||
} else {
|
||||
res.send('', 204);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Get a list of owned templates
|
||||
ws.get(template_baseurl, function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var cdbuser = ws.userByReq(req);
|
||||
Step(
|
||||
function checkPerms(){
|
||||
serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function listTemplates(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can list templated maps");
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
templateMaps.listTemplates(cdbuser, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_ids){
|
||||
if ( err ) throw err;
|
||||
// NOTE: might omit "cbduser" if == dbowner ...
|
||||
var ids = _.map(tpl_ids, function(id) { return cdbuser + '@' + id; })
|
||||
return { template_ids: ids };
|
||||
},
|
||||
function finish(err, response){
|
||||
var statusCode = 200;
|
||||
if (err){
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'GET TEMPLATE LIST', err.message);
|
||||
} else {
|
||||
res.send(response, statusCode);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
ws.setDBParams = function(cdbuser, params, callback) {
|
||||
Step(
|
||||
function setAuth() {
|
||||
serverOptions.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if ( err ) throw err;
|
||||
serverOptions.setDBConn(cdbuser, params, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ws.options(template_baseurl + '/:template_id', function(req, res) {
|
||||
ws.doCORS(res, "Content-Type");
|
||||
return next();
|
||||
});
|
||||
|
||||
// Instantiate a template
|
||||
ws.post(template_baseurl + '/:template_id', function(req, res) {
|
||||
ws.doCORS(res);
|
||||
var that = this;
|
||||
var response = {};
|
||||
var template;
|
||||
var signedMaps = serverOptions.signedMaps;
|
||||
var layergroup;
|
||||
var layergroupid;
|
||||
var fakereq; // used for call to createLayergroup
|
||||
var cdbuser = ws.userByReq(req);
|
||||
// Format of template_id: [<template_owner>]@<template_id>
|
||||
var tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] ) cdbuser = tpl_id[0];
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
var auth_token = req.query.auth_token;
|
||||
Step(
|
||||
function getTemplate(){
|
||||
templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function checkAuthorized(err, data) {
|
||||
if ( err ) throw err;
|
||||
if ( ! data ) {
|
||||
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
template = data;
|
||||
var cert = templateMaps.getTemplateCertificate(template);
|
||||
var authorized = false;
|
||||
try {
|
||||
// authorizedByCert will throw if unauthorized
|
||||
authorized = signedMaps.authorizedByCert(cert, auth_token);
|
||||
} catch (err) {
|
||||
// we catch to add http_status
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
if ( ! authorized ) {
|
||||
err = new Error('Unauthorized template instanciation');
|
||||
err.http_status = 401;
|
||||
throw err;
|
||||
}
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
||||
throw new Error('template POST data must be of type application/json, it is instead ');
|
||||
var template_params = req.body;
|
||||
return templateMaps.instance(template, template_params);
|
||||
},
|
||||
function prepareParams(err, instance){
|
||||
if ( err ) throw err;
|
||||
layergroup = instance;
|
||||
fakereq = { query: {}, params: {}, headers: _.clone(req.headers) };
|
||||
ws.setDBParams(cdbuser, fakereq.params, this);
|
||||
},
|
||||
function createLayergroup(err) {
|
||||
if ( err ) throw err;
|
||||
ws.createLayergroup(layergroup, fakereq, this);
|
||||
},
|
||||
function signLayergroup(err, resp) {
|
||||
if ( err ) throw err;
|
||||
response = resp;
|
||||
var signer = cdbuser;
|
||||
var map_id = response.layergroupid.split(':')[0]; // dropping last_updated
|
||||
var crt_id = template.auth_id; // check ?
|
||||
if ( ! crt_id ) {
|
||||
var errmsg = "Template '" + tpl_id + "' of user '" + cdbuser + "' has no signature";
|
||||
// Is this really illegal ?
|
||||
// Maybe we could just return an unsigned layergroupid
|
||||
// in this case...
|
||||
err = new Error(errmsg);
|
||||
err.http_status = 403; // Forbidden, we refuse to respond to this
|
||||
throw err;
|
||||
}
|
||||
signedMaps.signMap(signer, map_id, crt_id, this);
|
||||
},
|
||||
function prepareResponse(err) {
|
||||
if ( err ) throw err;
|
||||
//console.log("Response from createLayergroup: "); console.dir(response);
|
||||
// Add the signature part to the token!
|
||||
response.layergroupid = cdbuser + '@' + response.layergroupid;
|
||||
return response;
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
ws.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err.message);
|
||||
} else {
|
||||
res.send(response, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
// ---- Template maps interface ends @}
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
|
||||
80
lib/cartodb/redis_pool.js
Normal file
80
lib/cartodb/redis_pool.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* RedisPool. A database specific redis pooling lib
|
||||
*
|
||||
*/
|
||||
|
||||
var redis = require('redis')
|
||||
, _ = require('underscore')
|
||||
, Pool = require('generic-pool').Pool;
|
||||
|
||||
// constructor.
|
||||
//
|
||||
// - `opts` {Object} optional config for redis and pooling
|
||||
var RedisPool = function(opts){
|
||||
var opts = opts || {};
|
||||
var defaults = {
|
||||
host: '127.0.0.1',
|
||||
port: '6379',
|
||||
max: 50,
|
||||
idleTimeoutMillis: 10000,
|
||||
reapIntervalMillis: 1000,
|
||||
log: false
|
||||
};
|
||||
var options = _.defaults(opts, defaults)
|
||||
|
||||
var me = {
|
||||
pools: {} // cached pools by DB name
|
||||
};
|
||||
|
||||
// Acquire resource.
|
||||
//
|
||||
// - `database` {String} redis database name
|
||||
// - `callback` {Function} callback to call once acquired. Takes the form
|
||||
// `callback(err, resource)`
|
||||
me.acquire = function(database, callback) {
|
||||
if (!this.pools[database]) {
|
||||
this.pools[database] = this.makePool(database);
|
||||
}
|
||||
this.pools[database].acquire(function(err,resource) {
|
||||
callback(err, resource);
|
||||
});
|
||||
};
|
||||
|
||||
// Release resource.
|
||||
//
|
||||
// - `database` {String} redis database name
|
||||
// - `resource` {Object} resource object to release
|
||||
me.release = function(database, resource) {
|
||||
this.pools[database] && this.pools[database].release(resource);
|
||||
};
|
||||
|
||||
// Factory for pool objects.
|
||||
me.makePool = function(database) {
|
||||
return Pool({
|
||||
name: database,
|
||||
create: function(callback){
|
||||
var client = redis.createClient(options.port, options.host);
|
||||
client.on('connect', function () {
|
||||
client.send_anyway = true;
|
||||
client.select(database);
|
||||
client.send_anyway = false;
|
||||
callback(null, client);
|
||||
});
|
||||
client.on('error', function (err) {
|
||||
callback(err, null);
|
||||
});
|
||||
},
|
||||
destroy: function(client) {
|
||||
return client.quit();
|
||||
},
|
||||
max: options.max,
|
||||
idleTimeoutMillis: options.idleTimeoutMillis,
|
||||
reapIntervalMillis: options.reapIntervalMillis,
|
||||
log: options.log
|
||||
});
|
||||
};
|
||||
|
||||
return me;
|
||||
};
|
||||
|
||||
module.exports = RedisPool;
|
||||
@@ -1,6 +1,6 @@
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, cartoData = require('cartodb-redis')(global.environment.redis)
|
||||
, cartoData = require('./carto_data')
|
||||
, Cache = require('./cache_validator')
|
||||
, mapnik = require('mapnik')
|
||||
, crypto = require('crypto')
|
||||
@@ -28,8 +28,7 @@ module.exports = function(){
|
||||
datasource: global.environment.postgres,
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
mapnik_version: global.environment.mapnik_version || mapnik.versions.mapnik,
|
||||
default_layergroup_ttl: 7200, // seconds (default is 300)
|
||||
gc_prob: 0.01 // default is 0.01 TODO: make configurable via env config
|
||||
default_layergroup_ttl: 7200 // seconds (defaultis 300)
|
||||
},
|
||||
mapnik: {
|
||||
metatile: rendererConfig.metatile,
|
||||
@@ -68,9 +67,7 @@ module.exports = function(){
|
||||
var api = global.environment.sqlapi;
|
||||
|
||||
// build up api string
|
||||
var sqlapi = api.protocol + '://' + username;
|
||||
if ( api.domain ) sqlapi += '.' + api.domain;
|
||||
sqlapi += ':' + api.port + '/api/' + api.version + '/sql'
|
||||
var sqlapi = api.protocol + '://' + username + '.' + api.domain + ':' + api.port + '/api/' + api.version + '/sql'
|
||||
|
||||
var qs = { q: sql }
|
||||
|
||||
@@ -78,17 +75,9 @@ module.exports = function(){
|
||||
if (_.isString(api_key) && api_key != '') { qs.api_key = api_key; }
|
||||
|
||||
// call sql api
|
||||
//
|
||||
// NOTE: using POST to avoid size limits:
|
||||
// Seehttp://github.com/CartoDB/Windshaft-cartodb/issues/111
|
||||
//
|
||||
// TODO: use "host" header to allow IP based specification
|
||||
// of sqlapi address (and avoid a DNS lookup)
|
||||
//
|
||||
request.post({url:sqlapi, body:qs, json:true},
|
||||
function(err, res, body){
|
||||
request.get({url:sqlapi, qs:qs, json:true}, function(err, res, body){
|
||||
if (err){
|
||||
console.log('ERROR connecting to SQL API on ' + sqlapi + ': ' + err);
|
||||
console.log('ERROR running connecting to SQL API on ' + sqlapi + ': ' + err);
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +185,7 @@ module.exports = function(){
|
||||
}
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var username = this.userByReq(req);
|
||||
var username = req.headers.host.split('.')[0];
|
||||
|
||||
// strip out windshaft/mapnik inserted sql if present
|
||||
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
|
||||
@@ -258,7 +247,7 @@ module.exports = function(){
|
||||
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
|
||||
var token = response.layergroupid;
|
||||
|
||||
var username = this.userByReq(req);
|
||||
var username = cartoData.userFromHostname(req.headers.host);
|
||||
|
||||
var tasksleft = 2; // redis key and affectedTables
|
||||
var errors = [];
|
||||
@@ -289,7 +278,7 @@ module.exports = function(){
|
||||
sql = sql.join(';');
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var usr = this.userByReq(req);
|
||||
var usr = req.headers.host.split('.')[0];
|
||||
var key = req.params.map_key;
|
||||
|
||||
var cacheKey = dbName + ':' + token;
|
||||
@@ -313,228 +302,6 @@ module.exports = function(){
|
||||
|
||||
/* X-Cache-Channel generation } */
|
||||
|
||||
me.re_userFromHost = new RegExp(
|
||||
global.environment.user_from_host ||
|
||||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
|
||||
);
|
||||
|
||||
me.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];
|
||||
}
|
||||
|
||||
// Set db authentication parameters to those of the given username
|
||||
//
|
||||
// @param username the cartodb username, mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set auth options into
|
||||
// added params are: "dbuser" and "dbpassword"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBAuth = function(username, params, callback) {
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
Step(
|
||||
function getId() {
|
||||
cartoData.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw err;
|
||||
user_params['user_id'] = user_id;
|
||||
var dbuser = _.template(auth_user, user_params);
|
||||
_.extend(params, {dbuser:dbuser});
|
||||
|
||||
// skip looking up user_password if postgres_auth_pass
|
||||
// doesn't contain the "user_password" label
|
||||
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) return null;
|
||||
|
||||
cartoData.getUserDBPass(username, 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(params, {dbpassword:dbpass});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
// mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set connection options into
|
||||
// added params are: "dbname", "dbhost"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBConn = function(dbowner, params, callback) {
|
||||
Step(
|
||||
function getDatabaseHost(){
|
||||
cartoData.getUserDBHost(dbowner, this);
|
||||
},
|
||||
function getDatabase(err, data){
|
||||
if(err) throw err;
|
||||
if ( data ) _.extend(params, {dbhost:data});
|
||||
cartoData.getUserDBName(dbowner, this);
|
||||
},
|
||||
function getGeometryType(err, data){
|
||||
if (err) throw err;
|
||||
if ( data ) _.extend(params, {dbname:data});
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Check if a request is authorized by a signer
|
||||
//
|
||||
// Any existing signature for the given request will verified
|
||||
// for authorization to this specific request (may require auth_token)
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps
|
||||
//
|
||||
// @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.
|
||||
//
|
||||
me.authorizedBySigner = function(req, callback)
|
||||
{
|
||||
if ( ! req.params.token || ! req.params.signer ) {
|
||||
//console.log("No signature provided"); // debugging
|
||||
callback(null, null); // no signer requested
|
||||
return;
|
||||
}
|
||||
|
||||
var signer = req.params.signer;
|
||||
var layergroup_id = req.params.token;
|
||||
var auth_token = req.params.auth_token;
|
||||
|
||||
console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + " with auth_token " + auth_token);
|
||||
|
||||
me.signedMaps.isAuthorized(signer, layergroup_id, auth_token,
|
||||
function(err, authorized) {
|
||||
callback(err, authorized ? signer : null);
|
||||
});
|
||||
};
|
||||
|
||||
// Check if a request is authorized by api_key
|
||||
//
|
||||
// @param req express request object
|
||||
// @param callback function(err, authorized)
|
||||
//
|
||||
me.authorizedByAPIKey = function(req, callback)
|
||||
{
|
||||
var user = me.userByReq(req);
|
||||
Step(
|
||||
function (){
|
||||
cartoData.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);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check access authorization
|
||||
*
|
||||
* @param req - standard req object. Importantly contains table and host information
|
||||
* @param callback function(err, allowed) is access allowed not?
|
||||
*/
|
||||
me.authorize = function(req, callback) {
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function (){
|
||||
that.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function checkApiKey(err, authorized){
|
||||
if (err) throw err;
|
||||
|
||||
// if not authorized by api_key, continue
|
||||
if (authorized !== 1) {
|
||||
// not authorized by api_key,
|
||||
// check if authorized by signer
|
||||
that.authorizedBySigner(req, this);
|
||||
return;
|
||||
}
|
||||
|
||||
// authorized by api key, login as the given username and stop
|
||||
that.setDBAuth(user, req.params, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
function checkSignAuthorized(err, signed_by){
|
||||
if (err) throw err;
|
||||
|
||||
if ( ! signed_by ) {
|
||||
// request not authorized by signer, continue
|
||||
// to check map privacy
|
||||
return null;
|
||||
}
|
||||
|
||||
// Authorized by "signed_by" !
|
||||
that.setDBAuth(signed_by, req.params, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
// NOTE: only used to get to table privacy
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getPrivacy(err, dbname){
|
||||
if (err) throw err;
|
||||
cartoData.getTablePrivacy(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, privacy){
|
||||
callback(err, privacy);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whitelist input and get database name & default geometry type from
|
||||
* subdomain/user metadata held in CartoDB Redis
|
||||
@@ -572,7 +339,7 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
}
|
||||
|
||||
// Whitelist query parameters and attach format
|
||||
var good_query = ['sql', 'geom_type', 'cache_buster', 'cache_policy', 'callback', 'interactivity', 'map_key', 'api_key', 'auth_token', 'style', 'style_version', 'style_convert', 'config' ];
|
||||
var good_query = ['sql', 'geom_type', 'cache_buster', 'cache_policy', 'callback', 'interactivity', 'map_key', 'api_key', 'style', 'style_version', 'style_convert', 'config' ];
|
||||
var bad_query = _.difference(_.keys(req.query), good_query);
|
||||
|
||||
_.each(bad_query, function(key){ delete req.query[key]; });
|
||||
@@ -583,13 +350,6 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
var tksplit = req.params.token.split(':');
|
||||
req.params.token = tksplit[0];
|
||||
if ( tksplit.length > 1 ) req.params.cache_buster= tksplit[1];
|
||||
tksplit = req.params.token.split('@');
|
||||
if ( tksplit.length > 1 ) {
|
||||
req.params.signer = this.userByReq(req);
|
||||
if ( tksplit[0] ) req.params.signer = tksplit[0];
|
||||
req.params.token = tksplit[1];
|
||||
//console.log("Request for token " + req.params.token + " with signature from " + req.params.signer);
|
||||
}
|
||||
}
|
||||
|
||||
// bring all query values onto req.params object
|
||||
@@ -599,22 +359,9 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
req.params.interactivity = req.params.interactivity || 'cartodb_id';
|
||||
|
||||
req.params.processXML = function(req, xml, callback) {
|
||||
|
||||
// Replace dbuser
|
||||
var dbuser = req.params.dbuser || global.environment.postgres.user;
|
||||
var dbuser = req.params.dbuser || global.settings.postgres.user;
|
||||
if ( ! me.rx_dbuser ) me.rx_dbuser = /(<Parameter name="user"><!\[CDATA\[)[^\]]*(]]><\/Parameter>)/g;
|
||||
xml = xml.replace(me.rx_dbuser, "$1" + dbuser + "$2");
|
||||
|
||||
// Replace dbpass
|
||||
var dbpass = req.params.dbpassword || global.environment.postgres.password;
|
||||
if ( ! me.rx_dbpass ) me.rx_dbpass = /(<Parameter name="password"><!\[CDATA\[)[^\]]*(]]><\/Parameter>)/g;
|
||||
xml = xml.replace(me.rx_dbpass, "$1" + dbpass + "$2");
|
||||
|
||||
// Replace or set dbhost
|
||||
var dbhost = req.params.dbhost || global.environment.postgres.host;
|
||||
if ( ! me.rx_dbhost ) me.rx_dbhost = /(<Parameter name="host"><!\[CDATA\[)[^\]]*(]]><\/Parameter>)/g;
|
||||
xml = xml.replace(me.rx_dbhost, "$1" + dbhost + "$2");
|
||||
|
||||
callback(null, xml);
|
||||
}
|
||||
|
||||
@@ -622,36 +369,37 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
|
||||
if (req.profiler) req.profiler.done('req2params.setup');
|
||||
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function getPrivacy(){
|
||||
me.authorize(req, this);
|
||||
cartoData.authorize(req, this);
|
||||
},
|
||||
function gatekeep(err, data){
|
||||
if (req.profiler) req.profiler.done('authorize');
|
||||
if (req.profiler) req.profiler.done('cartoData.authorize');
|
||||
if(err) throw err;
|
||||
if(data === "0") throw new Error("Sorry, you are unauthorized (permission denied)");
|
||||
return data;
|
||||
},
|
||||
function getDatabase(err, data){
|
||||
if(err) throw err;
|
||||
that.setDBConn(user, req.params, this);
|
||||
|
||||
cartoData.getDatabase(req, this);
|
||||
},
|
||||
function getGeometryType(err){
|
||||
if (req.profiler) req.profiler.done('cartoData.getDatabase');
|
||||
function getGeometryType(err, data){
|
||||
if (req.profiler) req.profiler.done('cartoData.getDatabase');
|
||||
if (err) throw err;
|
||||
cartoData.getTableGeometryType(req.params.dbname, req.params.table, this);
|
||||
_.extend(req.params, {dbname:data});
|
||||
|
||||
cartoData.getGeometryType(req, this);
|
||||
},
|
||||
function finishSetup(err, data){
|
||||
if (req.profiler) req.profiler.done('cartoData.getGeometryType');
|
||||
if (req.profiler) req.profiler.done('cartoData.getGeometryType');
|
||||
if ( err ) { callback(err, req); return; }
|
||||
|
||||
if (!_.isNull(data))
|
||||
_.extend(req.params, {geom_type: data});
|
||||
|
||||
that.addCacheChannel(req, function(err) {
|
||||
if (req.profiler) req.profiler.done('addCacheChannel');
|
||||
if (req.profiler) req.profiler.done('addCacheChannel');
|
||||
callback(err, req);
|
||||
});
|
||||
}
|
||||
@@ -665,23 +413,14 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
*/
|
||||
me.getInfowindow = function(req, callback){
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function(){
|
||||
// TODO: if this step really needed ?
|
||||
that.req2params(req, this);
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getInfowindow(err, dbname){
|
||||
if (err) throw err;
|
||||
cartoData.getTableInfowindow(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
if (err) callback(err, null);
|
||||
else cartoData.getInfowindow(data, callback);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -693,23 +432,14 @@ console.log("Checking authorization from signer " + signer + " for resource " +
|
||||
*/
|
||||
me.getMapMetadata = function(req, callback){
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function(){
|
||||
// TODO: if this step really needed ?
|
||||
that.req2params(req, this);
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getMapMetadata(err, dbname){
|
||||
if (err) throw err;
|
||||
cartoData.getTableMapMetadata(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
if (err) callback(err, null);
|
||||
else cartoData.getMapMetadata(data, callback);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
var crypto = require('crypto');
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
// Class handling map signatures and user certificates
|
||||
//
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps
|
||||
//
|
||||
// @param redis_pool an instance of a "redis-mpool"
|
||||
// See https://github.com/CartoDB/node-redis-mpool
|
||||
// Needs version 0.x.x of the API.
|
||||
//
|
||||
function SignedMaps(redis_pool) {
|
||||
this.redis_pool = redis_pool;
|
||||
|
||||
// Database containing signatures
|
||||
// TODO: allow configuring ?
|
||||
// NOTE: currently it is the same as
|
||||
// the one containing layergroups
|
||||
this.db_signatures = 0;
|
||||
|
||||
//
|
||||
// Map signatures in redis are reference to signature certificates
|
||||
// We have the following datastores:
|
||||
//
|
||||
// 1. User certificates: set of per-user authorization certificates
|
||||
// 2. Map signatures: set of per-map certificate references
|
||||
// 3. Certificate applications: set of per-certificate signed maps
|
||||
|
||||
// User certificates (HASH:crt_id->crt_val)
|
||||
this.key_map_crt = "map_crt|<%= signer %>";
|
||||
|
||||
// Map signatures (SET:crt_id)
|
||||
this.key_map_sig = "map_sig|<%= signer %>|<%= map_id %>";
|
||||
|
||||
// Certificates applications (SET:map_id)
|
||||
//
|
||||
// Everytime a map is signed, the map identifier (layergroup_id)
|
||||
// is added to this set. The purpose of this set is to drop
|
||||
// all map signatures when a certificate is removed
|
||||
//
|
||||
this.key_crt_sig = "crt_sig|<%= signer %>|<%= crt_id %>";
|
||||
|
||||
};
|
||||
|
||||
var o = SignedMaps.prototype;
|
||||
|
||||
//--------------- PRIVATE METHODS --------------------------------
|
||||
|
||||
o._acquireRedis = function(callback) {
|
||||
this.redis_pool.acquire(this.db_signatures, callback);
|
||||
};
|
||||
|
||||
o._releaseRedis = function(client) {
|
||||
this.redis_pool.release(this.db_signatures, client);
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function to communicate with redis
|
||||
*
|
||||
* @param redisFunc - the redis function to execute
|
||||
* @param redisArgs - the arguments for the redis function in an array
|
||||
* @param callback - function to pass results too.
|
||||
*/
|
||||
o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var redisClient;
|
||||
var that = this;
|
||||
var db = that.db_signatures;
|
||||
|
||||
Step(
|
||||
function getRedisClient() {
|
||||
that.redis_pool.acquire(db, this);
|
||||
},
|
||||
function executeQuery(err, data) {
|
||||
if ( err ) throw err;
|
||||
redisClient = data;
|
||||
redisArgs.push(this);
|
||||
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
|
||||
},
|
||||
function releaseRedisClient(err, data) {
|
||||
if ( ! _.isUndefined(redisClient) ) that.redis_pool.release(db, redisClient);
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
//--------------- PUBLIC API -------------------------------------
|
||||
|
||||
// Check if the given certificate authorizes waiver of "auth"
|
||||
o.authorizedByCert = function(cert, auth) {
|
||||
//console.log("Checking cert: "); console.dir(cert);
|
||||
if ( cert.version !== "0.0.1" ) {
|
||||
throw new Error("Unsupported certificate version " + cert.version);
|
||||
}
|
||||
|
||||
if ( ! cert.auth ) {
|
||||
throw new Error("No certificate authorization");
|
||||
}
|
||||
|
||||
if ( ! cert.auth.method ) {
|
||||
throw new Error("No certificate authorization method");
|
||||
}
|
||||
|
||||
// Open authentication certificates are always authorized
|
||||
if ( cert.auth.method === 'open' ) return true;
|
||||
|
||||
// Token based authentication requires valid token
|
||||
if ( cert.auth.method === 'token' ) {
|
||||
var found = cert.auth.valid_tokens.indexOf(auth);
|
||||
//if ( found !== -1 ) {
|
||||
//console.log("Token " + auth + " is found at position " + found + " in valid tokens " + cert.auth.valid_tokens);
|
||||
// return true;
|
||||
//} else return false;
|
||||
return cert.auth.valid_tokens.indexOf(auth) !== -1;
|
||||
}
|
||||
|
||||
throw new Error("Unsupported authentication method: " + cert.auth.method);
|
||||
};
|
||||
|
||||
// Check if shown credential are authorized to access a map
|
||||
// by the given signer.
|
||||
//
|
||||
// @param signer a signer name (cartodb username)
|
||||
// @param map_id a layergroup_id
|
||||
// @param auth an authentication token, or undefined if none
|
||||
// (can still be authorized by signature)
|
||||
//
|
||||
// @param callback function(Error, Boolean)
|
||||
//
|
||||
o.isAuthorized = function(signer, map_id, auth, callback) {
|
||||
var that = this;
|
||||
var authorized = false;
|
||||
var certificate_id_list;
|
||||
var missing_certificates = [];
|
||||
console.log("Check auth from signer '" + signer + "' on map '" + map_id + "' with auth '" + auth + "'");
|
||||
Step(
|
||||
function getMapSignatures() {
|
||||
var map_sig_key = _.template(that.key_map_sig, {signer:signer, map_id:map_id});
|
||||
that._redisCmd('SMEMBERS', [ map_sig_key ], this);
|
||||
},
|
||||
function getCertificates(err, crt_lst) {
|
||||
if ( err ) throw err;
|
||||
console.log("Map '" + map_id + "' is signed by " + crt_lst.length + " certificates of user '" + signer + "': " + crt_lst);
|
||||
certificate_id_list = crt_lst;
|
||||
if ( ! crt_lst.length ) {
|
||||
// No certs, avoid calling redis with short args list.
|
||||
// Next step expects a list of certificate values so
|
||||
// we directly send the empty list.
|
||||
return crt_lst;
|
||||
}
|
||||
var map_crt_key = _.template(that.key_map_crt, {signer:signer});
|
||||
that._redisCmd('HMGET', [ map_crt_key ].concat(crt_lst), this);
|
||||
},
|
||||
function checkCertificates(err, certs) {
|
||||
if ( err ) throw err;
|
||||
for (var i=0; i<certs.length; ++i) {
|
||||
var crt_id = certificate_id_list[i];
|
||||
if ( _.isNull(certs[i]) ) {
|
||||
missing_certificates.push(crt_id);
|
||||
continue;
|
||||
}
|
||||
var cert;
|
||||
try {
|
||||
//console.log("cert " + crt_id + ": " + certs[i]);
|
||||
cert = JSON.parse(certs[i]);
|
||||
authorized = that.authorizedByCert(cert, auth);
|
||||
} catch (err) {
|
||||
console.log("Certificate " + certificate_id_list[i] + " by user '" + signer + "' is malformed: " + err);
|
||||
continue;
|
||||
}
|
||||
if ( authorized ) {
|
||||
console.log("Access to map '" + map_id + "' authorized by cert '"
|
||||
+ certificate_id_list[i] + "' of user '" + signer + "'");
|
||||
//console.dir(cert);
|
||||
break; // no need to further check certs
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( missing_certificates.length ) {
|
||||
console.log("WARNING: map '" + map_id + "' is signed by '" + signer
|
||||
+ "' with " + missing_certificates.length
|
||||
+ " missing certificates: "
|
||||
+ missing_certificates + " (TODO: give cleanup instructions)");
|
||||
}
|
||||
callback(err, authorized);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Add an authorization certificate from a user.
|
||||
//
|
||||
// @param signer a signer name (cartodb username)
|
||||
// @param cert certificate object, see
|
||||
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps
|
||||
//
|
||||
// @param callback function(err, crt_id) return certificate id
|
||||
//
|
||||
// TODO: allow for requesting error when certificate already exists ?
|
||||
//
|
||||
o.addCertificate = function(signer, cert, callback) {
|
||||
var crt_val = JSON.stringify(cert);
|
||||
var crt_id = crypto.createHash('md5').update(crt_val).digest('hex');
|
||||
|
||||
var usr_crt_key = _.template(this.key_map_crt, {signer:signer});
|
||||
this._redisCmd('HSET', [ usr_crt_key, crt_id, crt_val ], function(err, created) {
|
||||
// NOTE: created would be 0 if the field already existed, 1 otherwise
|
||||
callback(err, crt_id);
|
||||
});
|
||||
};
|
||||
|
||||
// Remove an authorization certificate of a user, also removing
|
||||
// any signature made with the certificate.
|
||||
//
|
||||
// @param signer a signer name (cartodb username)
|
||||
// @param crt_id certificate identifier, as returned by addCertificate
|
||||
// @param callback function(err)
|
||||
//
|
||||
o.delCertificate = function(signer, crt_id, callback) {
|
||||
var db = this.db_signatures;
|
||||
var crt_sig_key = _.template(this.key_crt_sig, {signer:signer, crt_id:crt_id});
|
||||
var signed_map_list;
|
||||
var redis_client;
|
||||
var that = this;
|
||||
Step (
|
||||
function getRedisClient() {
|
||||
that._acquireRedis(this);
|
||||
},
|
||||
function removeCertificate(err, data) {
|
||||
if ( err ) throw err;
|
||||
redis_client = data;
|
||||
// Remove the certificate (would be enough to stop authorizing uses)
|
||||
var usr_crt_key = _.template(that.key_map_crt, {signer:signer});
|
||||
redis_client.HDEL(usr_crt_key, crt_id, this);
|
||||
},
|
||||
function getMapSignatures(err, deleted) {
|
||||
if ( err ) throw err;
|
||||
if ( ! deleted ) {
|
||||
// debugging (how can this be possible?)
|
||||
console.log("WARNING: authorization certificate '" + crt_id
|
||||
+ "' by user '" + signer + "' did not exist on delete request");
|
||||
}
|
||||
// Get all signatures by this certificate
|
||||
redis_client.SMEMBERS(crt_sig_key, this);
|
||||
},
|
||||
function delMapSignaturesReference(err, map_id_list) {
|
||||
if ( err ) throw err;
|
||||
signed_map_list = map_id_list;
|
||||
console.log("Certificate '" + crt_id + "' from user '" + signer
|
||||
+ "' was used to sign " + signed_map_list.length + " maps");
|
||||
redis_client.DEL(crt_sig_key, this);
|
||||
},
|
||||
function delMapSignatures(err) {
|
||||
if ( err ) throw err;
|
||||
var crt_sig_key = _.template(that.key_crt_sig, {signer:signer, crt_id:crt_id});
|
||||
var tx = redis_client.MULTI();
|
||||
for (var i=0; i<signed_map_list.length; ++i) {
|
||||
var map_id = signed_map_list[i];
|
||||
var map_sig_key = _.template(that.key_map_sig, {signer:signer, map_id:map_id});
|
||||
//console.log("Queuing removal of '" + crt_id + "' from '" + map_sig_key + "'");
|
||||
tx.SREM( map_sig_key, crt_id )
|
||||
}
|
||||
tx.EXEC(this);
|
||||
},
|
||||
function reportTransaction(err, rets) {
|
||||
if ( err ) throw err;
|
||||
for (var i=0; i<signed_map_list.length; ++i) {
|
||||
var ret = rets[i];
|
||||
if ( ! ret ) {
|
||||
console.log("No signature with certificate '" + crt_id
|
||||
+ "' of user '" + signer + "' found in map '"
|
||||
+ signed_map_list[i] + "'");
|
||||
} else {
|
||||
console.log("Signature with certificate '" + crt_id
|
||||
+ "' of user '" + signer + "' removed from map '"
|
||||
+ signed_map_list[i] + "'");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( ! _.isUndefined(redis_client) ) {
|
||||
that._releaseRedis(redis_client);
|
||||
}
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Sign a map with a certificate reference
|
||||
//
|
||||
// @param signer a signer name (cartodb username)
|
||||
// @param map_id a layergroup_id
|
||||
// @param crt_id signature certificate identifier
|
||||
//
|
||||
// @param callback function(Error)
|
||||
//
|
||||
o.signMap = function(signer, map_id, crt_id, callback) {
|
||||
var that = this;
|
||||
Step(
|
||||
function addMapSignature() {
|
||||
var map_sig_key = _.template(that.key_map_sig, {signer:signer, map_id:map_id});
|
||||
console.log("Adding " + crt_id + " to " + map_sig_key);
|
||||
that._redisCmd('SADD', [ map_sig_key, crt_id ], this);
|
||||
},
|
||||
function addCertificateUsage(err) {
|
||||
// Add the map to the set of maps signed by the given cert
|
||||
if ( err ) throw err;
|
||||
var crt_sig_key = _.template(that.key_crt_sig, {signer:signer, crt_id:crt_id});
|
||||
that._redisCmd('SADD', [ crt_sig_key, map_id ], this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Sign a map with a full certificate
|
||||
//
|
||||
// @param signer a signer name (cartodb username)
|
||||
// @param map_id a layergroup_id
|
||||
// @param cert_id signature certificate identifier
|
||||
//
|
||||
// @param callback function(Error, String) return certificate id
|
||||
//
|
||||
o.addSignature = function(signer, map_id, cert, callback) {
|
||||
var that = this;
|
||||
var certificate_id;
|
||||
Step(
|
||||
function addCertificate() {
|
||||
that.addCertificate(signer, cert, this);
|
||||
},
|
||||
function signMap(err, cert_id) {
|
||||
if ( err ) throw err;
|
||||
if ( ! cert_id ) throw new Error("addCertificate returned no certificate id");
|
||||
certificate_id = cert_id;
|
||||
that.signMap(signer, map_id, cert_id, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err, certificate_id);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = SignedMaps;
|
||||
@@ -1,586 +0,0 @@
|
||||
var crypto = require('crypto');
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
// Templates in this hash (keyed as <username>@<template_name>)
|
||||
// are being worked on.
|
||||
var user_template_locks = {};
|
||||
|
||||
// Class handling map templates
|
||||
//
|
||||
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps
|
||||
//
|
||||
// @param redis_pool an instance of a "redis-mpool"
|
||||
// See https://github.com/CartoDB/node-redis-mpool
|
||||
// Needs version 0.x.x of the API.
|
||||
//
|
||||
// @param signed_maps an instance of a "signed_maps" class,
|
||||
// See signed_maps.js
|
||||
//
|
||||
function TemplateMaps(redis_pool, signed_maps) {
|
||||
this.redis_pool = redis_pool;
|
||||
this.signed_maps = signed_maps;
|
||||
|
||||
// Database containing templates
|
||||
// TODO: allow configuring ?
|
||||
// NOTE: currently it is the same as
|
||||
// the one containing layergroups
|
||||
this.db_signatures = 0;
|
||||
|
||||
//
|
||||
// Map templates are owned by a user that specifies access permissions
|
||||
// for their instances.
|
||||
//
|
||||
// We have the following datastores:
|
||||
//
|
||||
// 1. User teplates: set of per-user map templates
|
||||
// NOTE: each template would have an associated auth
|
||||
// reference, see signed_maps.js
|
||||
|
||||
// User templates (HASH:tpl_id->tpl_val)
|
||||
this.key_usr_tpl = "map_tpl|<%= owner %>";
|
||||
|
||||
// User template locks (HASH:tpl_id->ctime)
|
||||
this.key_usr_tpl_lck = "map_tpl|<%= owner %>|locks";
|
||||
|
||||
};
|
||||
|
||||
var o = TemplateMaps.prototype;
|
||||
|
||||
//--------------- PRIVATE METHODS --------------------------------
|
||||
|
||||
o._acquireRedis = function(callback) {
|
||||
this.redis_pool.acquire(this.db_signatures, callback);
|
||||
};
|
||||
|
||||
o._releaseRedis = function(client) {
|
||||
this.redis_pool.release(this.db_signatures, client);
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function to communicate with redis
|
||||
*
|
||||
* @param redisFunc - the redis function to execute
|
||||
* @param redisArgs - the arguments for the redis function in an array
|
||||
* @param callback - function to pass results too.
|
||||
*/
|
||||
o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var redisClient;
|
||||
var that = this;
|
||||
var db = that.db_signatures;
|
||||
|
||||
Step(
|
||||
function getRedisClient() {
|
||||
that.redis_pool.acquire(db, this);
|
||||
},
|
||||
function executeQuery(err, data) {
|
||||
if ( err ) throw err;
|
||||
redisClient = data;
|
||||
redisArgs.push(this);
|
||||
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
|
||||
},
|
||||
function releaseRedisClient(err, data) {
|
||||
if ( ! _.isUndefined(redisClient) ) that.redis_pool.release(db, redisClient);
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// @param callback function(err, obtained)
|
||||
o._obtainTemplateLock = function(owner, tpl_id, callback) {
|
||||
var usr_tpl_lck_key = _.template(this.key_usr_tpl_lck, {owner:owner});
|
||||
var that = this;
|
||||
var gotLock = false;
|
||||
Step (
|
||||
function obtainLock() {
|
||||
var ctime = Date.now();
|
||||
that._redisCmd('HSETNX', [usr_tpl_lck_key, tpl_id, ctime], this);
|
||||
},
|
||||
function checkLock(err, locked) {
|
||||
if ( err ) throw err;
|
||||
if ( ! locked ) {
|
||||
// Already locked
|
||||
// TODO: unlock if expired ?
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' is locked");
|
||||
}
|
||||
return gotLock = true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err, gotLock);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// @param callback function(err, deleted)
|
||||
o._releaseTemplateLock = function(owner, tpl_id, callback) {
|
||||
var usr_tpl_lck_key = _.template(this.key_usr_tpl_lck, {owner:owner});
|
||||
this._redisCmd('HDEL', [usr_tpl_lck_key, tpl_id], callback);
|
||||
};
|
||||
|
||||
o._reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/;
|
||||
o._checkInvalidTemplate = function(template) {
|
||||
if ( template.version != '0.0.1' ) {
|
||||
return new Error("Unsupported template version " + template.version);
|
||||
}
|
||||
var tplname = template.name;
|
||||
if ( ! tplname ) {
|
||||
return new Error("Missing template name");
|
||||
}
|
||||
if ( ! tplname.match(this._reValidIdentifier) ) {
|
||||
return new Error("Invalid characters in template name '" + tplname + "'");
|
||||
}
|
||||
|
||||
var phold = template.placeholders;
|
||||
for (var k in phold) {
|
||||
if ( ! k.match(this._reValidIdentifier) ) {
|
||||
return new Error("Invalid characters in placeholder name '" + k + "'");
|
||||
}
|
||||
if ( ! phold[k].hasOwnProperty('default') ) {
|
||||
return new Error("Missing default for placeholder '" + k + "'");
|
||||
}
|
||||
if ( ! phold[k].hasOwnProperty('type') ) {
|
||||
return new Error("Missing type for placeholder '" + k + "'");
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: run more checks over template format ?
|
||||
};
|
||||
|
||||
//--------------- PUBLIC API -------------------------------------
|
||||
|
||||
// Extract a signature certificate from a template
|
||||
//
|
||||
// The certificate will be ready to be passed to
|
||||
// SignedMaps.addCertificate or SignedMaps.authorizedByCert
|
||||
//
|
||||
o.getTemplateCertificate = function(template) {
|
||||
var cert = {
|
||||
version: '0.0.1',
|
||||
template_id: template.name,
|
||||
auth: template.auth
|
||||
};
|
||||
return cert;
|
||||
};
|
||||
|
||||
// Add a template
|
||||
//
|
||||
// NOTE: locks user+template_name or fails
|
||||
//
|
||||
// @param owner cartodb username of the template owner
|
||||
//
|
||||
// @param template layergroup template, see
|
||||
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
|
||||
//
|
||||
// @param callback function(err, tpl_id)
|
||||
// Return template identifier (only valid for given user)
|
||||
//
|
||||
o.addTemplate = function(owner, template, callback) {
|
||||
var invalidError = this._checkInvalidTemplate(template);
|
||||
if ( invalidError ) {
|
||||
callback(invalidError);
|
||||
return;
|
||||
}
|
||||
var tplname = template.name;
|
||||
|
||||
// Procedure:
|
||||
//
|
||||
// 0. Obtain a lock for user+template_name, fail if impossible
|
||||
// 1. Check no other template exists with the same name
|
||||
// 2. Install certificate extracted from template, extending
|
||||
// it to contain a name to properly salt things out.
|
||||
// 3. Modify the template object to reference certificate by id
|
||||
// 4. Install template
|
||||
// 5. Release lock
|
||||
//
|
||||
//
|
||||
|
||||
var usr_tpl_key = _.template(this.key_usr_tpl, {owner:owner});
|
||||
var gotLock = false;
|
||||
var that = this;
|
||||
Step(
|
||||
// try to obtain a lock
|
||||
function obtainLock() {
|
||||
that._obtainTemplateLock(owner, tplname, this);
|
||||
},
|
||||
function getExistingTemplate(err, locked) {
|
||||
if ( err ) throw err;
|
||||
if ( ! locked ) {
|
||||
// Already locked
|
||||
throw new Error("Template '" + tplname + "' of user '" + owner + "' is locked");
|
||||
}
|
||||
gotLock = true;
|
||||
that._redisCmd('HEXISTS', [ usr_tpl_key, tplname ], this);
|
||||
},
|
||||
function installCertificate(err, exists) {
|
||||
if ( err ) throw err;
|
||||
if ( exists ) {
|
||||
throw new Error("Template '" + tplname + "' of user '" + owner + "' already exists");
|
||||
}
|
||||
var cert = that.getTemplateCertificate(template);
|
||||
that.signed_maps.addCertificate(owner, cert, this);
|
||||
},
|
||||
function installTemplate(err, crt_id) {
|
||||
if ( err ) throw err;
|
||||
template.auth_id = crt_id;
|
||||
var tpl_val = JSON.stringify(template);
|
||||
that._redisCmd('HSET', [ usr_tpl_key, tplname, tpl_val ], this);
|
||||
},
|
||||
function releaseLock(err, newfield) {
|
||||
if ( ! err && ! newfield ) {
|
||||
console.log("ERROR: addTemplate overridden existing template '"
|
||||
+ tplname + "' of '" + owner
|
||||
+ "' -- HSET returned " + overridden + ": someone added it without locking ?");
|
||||
// TODO: how to recover this ?!
|
||||
}
|
||||
|
||||
if ( ! gotLock ) {
|
||||
if ( err ) throw err;
|
||||
return null;
|
||||
}
|
||||
|
||||
// release the lock
|
||||
var next = this;
|
||||
that._releaseTemplateLock(owner, tplname, function(e, d) {
|
||||
if ( e ) {
|
||||
console.log("Error removing lock on template '" + tplname
|
||||
+ "' of user '" + owner + "': " + e);
|
||||
} else if ( ! d ) {
|
||||
console.log("ERROR: lock on template '" + tplname
|
||||
+ "' of user '" + owner + "' externally removed during insert!");
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err, tplname);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Delete a template
|
||||
//
|
||||
// NOTE: locks user+template_name or fails
|
||||
//
|
||||
// Also deletes associated authentication certificate, which
|
||||
// in turn deletes all instance signatures
|
||||
//
|
||||
// @param owner cartodb username of the template owner
|
||||
//
|
||||
// @param tpl_id template identifier as returned
|
||||
// by addTemplate or listTemplates
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
o.delTemplate = function(owner, tpl_id, callback) {
|
||||
var usr_tpl_key = _.template(this.key_usr_tpl, {owner:owner});
|
||||
var gotLock = false;
|
||||
var that = this;
|
||||
Step(
|
||||
// try to obtain a lock
|
||||
function obtainLock() {
|
||||
that._obtainTemplateLock(owner, tpl_id, this);
|
||||
},
|
||||
function getExistingTemplate(err, locked) {
|
||||
if ( err ) throw err;
|
||||
if ( ! locked ) {
|
||||
// Already locked
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' is locked");
|
||||
}
|
||||
gotLock = true;
|
||||
that._redisCmd('HGET', [ usr_tpl_key, tpl_id ], this);
|
||||
},
|
||||
function delCertificate(err, tplval) {
|
||||
if ( err ) throw err;
|
||||
if ( ! tplval ) {
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
|
||||
}
|
||||
var tpl = JSON.parse(tplval);
|
||||
if ( ! tpl.auth_id ) {
|
||||
// not sure this is an error, in case we'll ever
|
||||
// allow unsigned templates...
|
||||
console.log("ERROR: installed template '" + tpl_id
|
||||
+ "' of user '" + owner + "' has no auth_id reference: "); console.dir(tpl);
|
||||
return null;
|
||||
}
|
||||
var next = this;
|
||||
that.signed_maps.delCertificate(owner, tpl.auth_id, function(err) {
|
||||
if ( err ) {
|
||||
var msg = "ERROR: could not delete certificate '"
|
||||
+ tpl.auth_id + "' associated with template '"
|
||||
+ tpl_id + "' of user '" + owner + "': " + err;
|
||||
// I'm actually not sure we want this event to be fatal
|
||||
// (avoiding a deletion of the template itself)
|
||||
next(new Error(msg));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
function delTemplate(err) {
|
||||
if ( err ) throw err;
|
||||
that._redisCmd('HDEL', [ usr_tpl_key, tpl_id ], this);
|
||||
},
|
||||
function releaseLock(err, deleted) {
|
||||
if ( ! err && ! deleted ) {
|
||||
console.log("ERROR: template '" + tpl_id
|
||||
+ "' of user '" + owner + "' externally removed during delete!");
|
||||
}
|
||||
|
||||
if ( ! gotLock ) {
|
||||
if ( err ) throw err;
|
||||
return null;
|
||||
}
|
||||
|
||||
// release the lock
|
||||
var next = this;
|
||||
that._releaseTemplateLock(owner, tpl_id, function(e, d) {
|
||||
if ( e ) {
|
||||
console.log("Error removing lock on template '" + tpl_id
|
||||
+ "' of user '" + owner + "': " + e);
|
||||
} else if ( ! d ) {
|
||||
console.log("ERROR: lock on template '" + tpl_id
|
||||
+ "' of user '" + owner + "' externally removed during delete!");
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Update a template
|
||||
//
|
||||
// NOTE: locks user+template_name or fails
|
||||
//
|
||||
// Also deletes and re-creates associated authentication certificate,
|
||||
// which in turn deletes all instance signatures
|
||||
//
|
||||
// @param owner cartodb username of the template owner
|
||||
//
|
||||
// @param tpl_id template identifier as returned by addTemplate
|
||||
//
|
||||
// @param template layergroup template, see
|
||||
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
o.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
|
||||
var invalidError = this._checkInvalidTemplate(template);
|
||||
if ( invalidError ) {
|
||||
callback(invalidError);
|
||||
return;
|
||||
}
|
||||
|
||||
var tplname = template.name;
|
||||
|
||||
if ( tpl_id != tplname ) {
|
||||
callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + tplname + "')"));
|
||||
return;
|
||||
}
|
||||
|
||||
var usr_tpl_key = _.template(this.key_usr_tpl, {owner:owner});
|
||||
var gotLock = false;
|
||||
var that = this;
|
||||
Step(
|
||||
// try to obtain a lock
|
||||
function obtainLock() {
|
||||
that._obtainTemplateLock(owner, tpl_id, this);
|
||||
},
|
||||
function getExistingTemplate(err, locked) {
|
||||
if ( err ) throw err;
|
||||
if ( ! locked ) {
|
||||
// Already locked
|
||||
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' is locked");
|
||||
}
|
||||
gotLock = true;
|
||||
that._redisCmd('HGET', [ usr_tpl_key, tpl_id ], this);
|
||||
},
|
||||
function delOldCertificate(err, tplval) {
|
||||
if ( err ) throw err;
|
||||
if ( ! tplval ) {
|
||||
throw new Error("Template '" + tpl_id + "' of user '"
|
||||
+ owner +"' does not exist");
|
||||
}
|
||||
var tpl = JSON.parse(tplval);
|
||||
if ( ! tpl.auth_id ) {
|
||||
// not sure this is an error, in case we'll ever
|
||||
// allow unsigned templates...
|
||||
console.log("ERROR: installed template '" + tpl_id
|
||||
+ "' of user '" + owner + "' has no auth_id reference: "); console.dir(tpl);
|
||||
return null;
|
||||
}
|
||||
var next = this;
|
||||
that.signed_maps.delCertificate(owner, tpl.auth_id, function(err) {
|
||||
if ( err ) {
|
||||
var msg = "ERROR: could not delete certificate '"
|
||||
+ tpl.auth_id + "' associated with template '"
|
||||
+ tpl_id + "' of user '" + owner + "': " + err;
|
||||
// I'm actually not sure we want this event to be fatal
|
||||
// (avoiding a deletion of the template itself)
|
||||
next(new Error(msg));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
function installNewCertificate(err) {
|
||||
if ( err ) throw err;
|
||||
var cert = that.getTemplateCertificate(template);
|
||||
that.signed_maps.addCertificate(owner, cert, this);
|
||||
},
|
||||
function updTemplate(err, crt_id) {
|
||||
if ( err ) throw err;
|
||||
template.auth_id = crt_id;
|
||||
var tpl_val = JSON.stringify(template);
|
||||
that._redisCmd('HSET', [ usr_tpl_key, tplname, tpl_val ], this);
|
||||
},
|
||||
function releaseLock(err, newfield) {
|
||||
if ( ! err && newfield ) {
|
||||
console.log("ERROR: template '" + tpl_id
|
||||
+ "' of user '" + owner + "' externally removed during update!");
|
||||
}
|
||||
|
||||
if ( ! gotLock ) {
|
||||
if ( err ) throw err;
|
||||
return null;
|
||||
}
|
||||
|
||||
// release the lock
|
||||
var next = this;
|
||||
that._releaseTemplateLock(owner, tpl_id, function(e, d) {
|
||||
if ( e ) {
|
||||
console.log("Error removing lock on template '" + tpl_id
|
||||
+ "' of user '" + owner + "': " + e);
|
||||
} else if ( ! d ) {
|
||||
console.log("ERROR: lock on template '" + tpl_id
|
||||
+ "' of user '" + owner + "' externally removed during update!");
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// List user templates
|
||||
//
|
||||
// @param owner cartodb username of the templates owner
|
||||
//
|
||||
// @param callback function(err, tpl_id_list)
|
||||
// Returns a list of template identifiers
|
||||
//
|
||||
o.listTemplates = function(owner, callback) {
|
||||
var usr_tpl_key = _.template(this.key_usr_tpl, {owner:owner});
|
||||
this._redisCmd('HKEYS', [ usr_tpl_key ], callback);
|
||||
};
|
||||
|
||||
// Get a templates
|
||||
//
|
||||
// @param owner cartodb username of the template owner
|
||||
//
|
||||
// @param tpl_id template identifier as returned
|
||||
// by addTemplate or listTemplates
|
||||
//
|
||||
// @param callback function(err, template)
|
||||
// Return full template definition
|
||||
//
|
||||
o.getTemplate = function(owner, tpl_id, callback) {
|
||||
var usr_tpl_key = _.template(this.key_usr_tpl, {owner:owner});
|
||||
var that = this;
|
||||
Step(
|
||||
function getTemplate() {
|
||||
that._redisCmd('HGET', [ usr_tpl_key, tpl_id ], this);
|
||||
},
|
||||
function parseTemplate(err, tpl_val) {
|
||||
if ( err ) throw err;
|
||||
var tpl = JSON.parse(tpl_val);
|
||||
// Should we strip auth_id ?
|
||||
return tpl;
|
||||
},
|
||||
function finish(err, tpl) {
|
||||
callback(err, tpl);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Perform placeholder substitutions on a template
|
||||
//
|
||||
// @param template a template object (will not be modified)
|
||||
//
|
||||
// @param params an object containing named subsitution parameters
|
||||
// Only the ones found in the template's placeholders object
|
||||
// will be used, with missing ones taking default values.
|
||||
//
|
||||
// @returns a layergroup configuration
|
||||
//
|
||||
// @throws Error on malformed template or parameter
|
||||
//
|
||||
o._reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/;
|
||||
o._reCSSColorName = /^[a-zA-Z]+$/;
|
||||
o._reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
|
||||
o._replaceVars = function(str, params) {
|
||||
//return _.template(str, params); // lazy way, possibly dangerous
|
||||
// Construct regular expressions for each param
|
||||
if ( ! params._re ) {
|
||||
params._re = {};
|
||||
for (var k in params) {
|
||||
params._re[k] = RegExp("<%= " + k + " %>", "g");
|
||||
}
|
||||
}
|
||||
for (var k in params) str = str.replace(params._re[k], params[k]);
|
||||
return str;
|
||||
};
|
||||
o.instance = function(template, params) {
|
||||
var all_params = {};
|
||||
var phold = template.placeholders;
|
||||
for (var k in phold) {
|
||||
var val = params.hasOwnProperty(k) ? params[k] : phold[k].default;
|
||||
var type = phold[k].type;
|
||||
// properly escape
|
||||
if ( type === 'sql_literal' ) {
|
||||
// duplicate any single-quote
|
||||
val = val.replace(/'/g, "''");
|
||||
}
|
||||
else if ( type === 'sql_ident' ) {
|
||||
// duplicate any double-quote
|
||||
val = val.replace(/"/g, '""');
|
||||
}
|
||||
else if ( type === 'number' ) {
|
||||
// check it's a number
|
||||
if ( ! val.match(this._reNumber) ) {
|
||||
throw new Error("Invalid number value for template parameter '"
|
||||
+ k + "': " + val);
|
||||
}
|
||||
}
|
||||
else if ( type === 'css_color' ) {
|
||||
// check it only contains letters or
|
||||
// starts with # and only contains hexdigits
|
||||
if ( ! val.match(this._reCSSColorName) && ! val.match(this._reCSSColorVal) ) {
|
||||
throw new Error("Invalid css_color value for template parameter '"
|
||||
+ k + "': " + val);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// NOTE: should be checked at template create/update time
|
||||
throw new Error("Invalid placeholder type '" + type + "'");
|
||||
}
|
||||
all_params[k] = val;
|
||||
}
|
||||
|
||||
// NOTE: we're deep-cloning the layergroup here
|
||||
var layergroup = JSON.parse(JSON.stringify(template.layergroup));
|
||||
for (var i=0; i<layergroup.layers.length; ++i) {
|
||||
var lyropt = layergroup.layers[i].options;
|
||||
lyropt.cartocss = this._replaceVars(lyropt.cartocss, all_params);
|
||||
lyropt.sql = this._replaceVars(lyropt.sql, all_params);
|
||||
// Anything else ?
|
||||
}
|
||||
return layergroup;
|
||||
};
|
||||
|
||||
module.exports = TemplateMaps;
|
||||
251
npm-shrinkwrap.json
generated
251
npm-shrinkwrap.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "1.6.2",
|
||||
"version": "1.3.5",
|
||||
"dependencies": {
|
||||
"node-varnish": {
|
||||
"version": "0.1.1"
|
||||
@@ -9,44 +9,34 @@
|
||||
"version": "1.3.3"
|
||||
},
|
||||
"windshaft": {
|
||||
"version": "0.15.0",
|
||||
"version": "0.13.7",
|
||||
"dependencies": {
|
||||
"grainstore": {
|
||||
"version": "0.16.0",
|
||||
"version": "0.13.11",
|
||||
"dependencies": {
|
||||
"carto": {
|
||||
"version": "0.9.5-cdb2",
|
||||
"from": "http://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
|
||||
"version": "0.9.3-cdb6",
|
||||
"from": "git://github.com/CartoDB/carto.git#0.9.3-cdb6",
|
||||
"dependencies": {
|
||||
"underscore": {
|
||||
"version": "1.4.4"
|
||||
"mapnik-reference": {
|
||||
"version": "5.0.0-cdb1",
|
||||
"from": "git://github.com/CartoDB/mapnik-reference.git#cdb-5.0"
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.2.8",
|
||||
"version": "0.1.14",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.5.8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.5"
|
||||
"version": "0.5.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapnik-reference": {
|
||||
"version": "5.0.7"
|
||||
"version": "5.0.6"
|
||||
},
|
||||
"millstone": {
|
||||
"version": "0.6.8",
|
||||
"version": "0.6.5",
|
||||
"dependencies": {
|
||||
"underscore": {
|
||||
"version": "1.5.2"
|
||||
@@ -55,7 +45,7 @@
|
||||
"version": "2.26.0",
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "0.6.6"
|
||||
"version": "0.6.5"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.0"
|
||||
@@ -128,139 +118,52 @@
|
||||
}
|
||||
},
|
||||
"srs": {
|
||||
"version": "0.3.8"
|
||||
"version": "0.3.3"
|
||||
},
|
||||
"zipfile": {
|
||||
"version": "0.4.3"
|
||||
"version": "0.4.1"
|
||||
},
|
||||
"sqlite3": {
|
||||
"version": "2.2.0",
|
||||
"version": "2.1.17",
|
||||
"dependencies": {
|
||||
"node-pre-gyp": {
|
||||
"version": "0.2.5",
|
||||
"progress": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"tar.gz": {
|
||||
"version": "0.1.1",
|
||||
"dependencies": {
|
||||
"nopt": {
|
||||
"version": "2.1.2",
|
||||
"fstream": {
|
||||
"version": "0.1.24",
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.0.4"
|
||||
"rimraf": {
|
||||
"version": "2.2.2"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "0.0.6",
|
||||
"dependencies": {
|
||||
"ansi": {
|
||||
"version": "0.2.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "2.1.0"
|
||||
},
|
||||
"tar": {
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.18",
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.7"
|
||||
},
|
||||
"fstream": {
|
||||
"version": "0.1.25",
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tar-pack": {
|
||||
"version": "2.0.0",
|
||||
"commander": {
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
"uid-number": {
|
||||
"version": "0.0.3"
|
||||
},
|
||||
"once": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"debug": {
|
||||
"version": "0.7.4"
|
||||
},
|
||||
"fstream": {
|
||||
"version": "0.1.25",
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fstream-ignore": {
|
||||
"version": "0.0.7",
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"dependencies": {
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.24"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "1.2.3"
|
||||
"keypress": {
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.0.0-rc8",
|
||||
"dependencies": {
|
||||
"xml2js": {
|
||||
"version": "0.2.4",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.6.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "0.4.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "0.3.2",
|
||||
"dependencies": {
|
||||
"optimist": {
|
||||
"version": "0.3.7",
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.2.6"
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,9 +190,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "2.0.4"
|
||||
},
|
||||
"express": {
|
||||
"version": "2.5.11",
|
||||
"dependencies": {
|
||||
@@ -329,7 +229,8 @@
|
||||
}
|
||||
},
|
||||
"tilelive-mapnik": {
|
||||
"version": "0.6.5",
|
||||
"version": "0.5.0",
|
||||
"from": "git://github.com/Vizzuality/tilelive-mapnik.git#5908346",
|
||||
"dependencies": {
|
||||
"eio": {
|
||||
"version": "0.2.2"
|
||||
@@ -350,93 +251,57 @@
|
||||
"step": {
|
||||
"version": "0.0.5"
|
||||
},
|
||||
"request": {
|
||||
"version": "2.9.202"
|
||||
"generic-pool": {
|
||||
"version": "2.0.3"
|
||||
},
|
||||
"cartodb-redis": {
|
||||
"version": "0.3.0"
|
||||
"redis": {
|
||||
"version": "0.8.3"
|
||||
},
|
||||
"redis-mpool": {
|
||||
"version": "0.0.3",
|
||||
"hiredis": {
|
||||
"version": "0.1.15",
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.0.4"
|
||||
},
|
||||
"hiredis": {
|
||||
"version": "0.1.16",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.1.1"
|
||||
}
|
||||
}
|
||||
"bindings": {
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.9.202"
|
||||
},
|
||||
"mapnik": {
|
||||
"version": "0.7.25"
|
||||
"version": "0.7.22"
|
||||
},
|
||||
"strftime": {
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"lzma": {
|
||||
"version": "1.2.3"
|
||||
},
|
||||
"strftime": {
|
||||
"version": "0.6.2"
|
||||
},
|
||||
"semver": {
|
||||
"version": "1.1.4"
|
||||
},
|
||||
"redis": {
|
||||
"version": "0.8.6"
|
||||
},
|
||||
"mocha": {
|
||||
"version": "1.14.0",
|
||||
"version": "1.2.1",
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.0.0"
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"growl": {
|
||||
"version": "1.7.0"
|
||||
"version": "1.5.1"
|
||||
},
|
||||
"jade": {
|
||||
"version": "0.26.3",
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "1.0.7"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"debug": {
|
||||
"version": "0.7.4"
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.3.5"
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.2.3",
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.5.0"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1"
|
||||
}
|
||||
}
|
||||
"version": "0.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
package.json
19
package.json
@@ -1,11 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "1.6.2",
|
||||
"version": "1.3.5",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
],
|
||||
"url": "https://github.com/CartoDB/Windshaft-cartodb",
|
||||
"licenses": [{
|
||||
"type": "BSD",
|
||||
@@ -24,18 +21,18 @@
|
||||
"dependencies": {
|
||||
"node-varnish": "0.1.1",
|
||||
"underscore" : "~1.3.3",
|
||||
"windshaft" : "~0.15.0",
|
||||
"windshaft" : "~0.13.7",
|
||||
"step": "0.0.x",
|
||||
"generic-pool": "~2.0.3",
|
||||
"redis": "~0.8.3",
|
||||
"hiredis": "~0.1.14",
|
||||
"request": "2.9.202",
|
||||
"cartodb-redis": "~0.3.0",
|
||||
"redis-mpool": "~0.0.2",
|
||||
"mapnik": "~0.7.22",
|
||||
"mapnik": "~0.7.14",
|
||||
"strftime": "~0.6.0",
|
||||
"lzma": "~1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.14.0",
|
||||
"redis": "~0.8.3",
|
||||
"strftime": "~0.6.0",
|
||||
"mocha": "1.2.1",
|
||||
"semver": "~1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
54
run_tests.sh
54
run_tests.sh
@@ -1,9 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
OPT_CREATE_REDIS=yes # create the redis test environment
|
||||
OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
|
||||
OPT_DROP_REDIS=yes # drop the redis test environment
|
||||
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
|
||||
OPT_CREATE=yes # create the test environment
|
||||
OPT_DROP=yes # drop the test environment
|
||||
|
||||
cd $(dirname $0)
|
||||
BASEDIR=$(pwd)
|
||||
@@ -13,7 +11,7 @@ REDIS_PORT=`node -e "console.log(require('${BASEDIR}/config/environments/test.js
|
||||
export REDIS_PORT
|
||||
|
||||
cleanup() {
|
||||
if test x"$OPT_DROP_REDIS" = xyes; then
|
||||
if test x"$OPT_DROP" = xyes; then
|
||||
if test x"$PID_REDIS" = x; then
|
||||
PID_REDIS=$(cat ${BASEDIR}/redis.pid)
|
||||
if test x"$PID_REDIS" = x; then
|
||||
@@ -21,13 +19,9 @@ cleanup() {
|
||||
return;
|
||||
fi
|
||||
fi
|
||||
echo "Killing test redis pid ${PID_REDIS}"
|
||||
echo "Cleaning up"
|
||||
kill ${PID_REDIS}
|
||||
fi
|
||||
if test x"$OPT_DROP_PGSQL" = xyes; then
|
||||
# TODO: drop postgresql ?
|
||||
echo "Dropping PostgreSQL test database isn't implemented yet"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_and_exit() {
|
||||
@@ -45,32 +39,12 @@ die() {
|
||||
trap 'cleanup_and_exit' 1 2 3 5 9 13
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
# This is kept for backward compatibility
|
||||
if test "$1" = "--nodrop"; then
|
||||
OPT_DROP_REDIS=no
|
||||
OPT_DROP_PGSQL=no
|
||||
OPT_DROP=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--nodrop-pg"; then
|
||||
OPT_DROP_PGSQL=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--nodrop-redis"; then
|
||||
OPT_DROP_REDIS=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--nocreate-pg"; then
|
||||
OPT_CREATE_PGSQL=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--nocreate-redis"; then
|
||||
OPT_CREATE_REDIS=no
|
||||
shift
|
||||
continue
|
||||
# This is kept for backward compatibility
|
||||
elif test "$1" = "--nocreate"; then
|
||||
OPT_CREATE_REDIS=no
|
||||
OPT_CREATE_PGSQL=no
|
||||
OPT_CREATE=no
|
||||
shift
|
||||
continue
|
||||
else
|
||||
@@ -88,25 +62,15 @@ fi
|
||||
|
||||
TESTS=$@
|
||||
|
||||
if test x"$OPT_CREATE_REDIS" = xyes; then
|
||||
if test x"$OPT_CREATE" = xyes; then
|
||||
echo "Starting redis on port ${REDIS_PORT}"
|
||||
echo "port ${REDIS_PORT}" | redis-server - > ${BASEDIR}/test.log &
|
||||
PID_REDIS=$!
|
||||
echo ${PID_REDIS} > ${BASEDIR}/redis.pid
|
||||
fi
|
||||
|
||||
PREPARE_DB_OPTS=
|
||||
if test x"$OPT_CREATE_PGSQL" != xyes; then
|
||||
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --skip-pg"
|
||||
echo "Preparing the environment"
|
||||
cd ${BASEDIR}/test/support; sh prepare_db.sh || die "database preparation failure"; cd -
|
||||
fi
|
||||
if test x"$OPT_CREATE_REDIS" != xyes; then
|
||||
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --skip-redis"
|
||||
fi
|
||||
|
||||
echo "Preparing the environment"
|
||||
cd ${BASEDIR}/test/support
|
||||
sh prepare_db.sh ${PREPARE_DB_OPTS} || die "database preparation failure"
|
||||
cd -
|
||||
|
||||
PATH=node_modules/.bin/:$PATH
|
||||
|
||||
|
||||
@@ -19,14 +19,6 @@ var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
// Check that the response headers do not request caching
|
||||
// Throws on failure
|
||||
function checkNoCache(res) {
|
||||
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control')); // is this correct ?
|
||||
assert.ok(!res.headers.hasOwnProperty('last-modified')); // is this correct ?
|
||||
}
|
||||
|
||||
suite('multilayer', function() {
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
@@ -107,7 +99,7 @@ suite('multilayer', function() {
|
||||
// Check X-Cache-Channel
|
||||
cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
var dbname = 'test_cartodb_user_1_db'
|
||||
var dbname = 'cartodb_test_user_1_db'
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
@@ -164,7 +156,7 @@ suite('multilayer', function() {
|
||||
errors.push(err.message);
|
||||
console.log("Error: " + err);
|
||||
}
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
redis_client.keys("map_style|cartodb_test_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
@@ -237,7 +229,7 @@ suite('multilayer', function() {
|
||||
// Check X-Cache-Channel
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
var dbname = 'test_cartodb_user_1_db'
|
||||
var dbname = 'cartodb_test_user_1_db'
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
@@ -270,7 +262,7 @@ suite('multilayer', function() {
|
||||
// Check X-Cache-Channel
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
var dbname = 'test_cartodb_user_1_db'
|
||||
var dbname = 'cartodb_test_user_1_db'
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
@@ -329,7 +321,7 @@ suite('multilayer', function() {
|
||||
errors.push(err.message);
|
||||
console.log("Error: " + err);
|
||||
}
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
redis_client.keys("map_style|cartodb_test_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
@@ -425,7 +417,7 @@ suite('multilayer', function() {
|
||||
var next = this;
|
||||
// trip epoch
|
||||
expected_token = expected_token.split(':')[0];
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
redis_client.keys("map_style|cartodb_test_user_1_db|~" + expected_token, function(err, matches) {
|
||||
redis_client.del(matches, next);
|
||||
});
|
||||
},
|
||||
@@ -468,35 +460,6 @@ suite('multilayer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Also tests that server doesn't crash:
|
||||
// see http://github.com/CartoDB/Windshaft-cartodb/issues/109
|
||||
test("layergroup creation fails if sql is bogus", function(done) {
|
||||
var layergroup = {
|
||||
stat_tag: 'random_tag',
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select bogus(0,0) as the_geom_webmercator',
|
||||
cartocss: '#layer { polygon-fill:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
var msg = parsed.errors[0];
|
||||
assert.ok(msg.match(/bogus.*exist/), msg);
|
||||
checkNoCache(res);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("layergroup with 2 private-table layers", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
@@ -562,7 +525,7 @@ suite('multilayer', function() {
|
||||
// Check X-Cache-Channel
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
var dbname = 'test_cartodb_user_1_db'
|
||||
var dbname = 'cartodb_test_user_1_db'
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
next(err);
|
||||
});
|
||||
@@ -650,7 +613,7 @@ suite('multilayer', function() {
|
||||
errors.push(err.message);
|
||||
console.log("Error: " + err);
|
||||
}
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
redis_client.keys("map_style|cartodb_test_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
@@ -685,9 +648,7 @@ suite('multilayer', function() {
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var errmsg = parsed.errors[0];
|
||||
assert.ok(errmsg.match(/text-face-name.*Dejagnu/), parsed.errors.toString());
|
||||
assert.deepEqual(parsed, {"errors":["style0:1:10 Invalid value for text-name, the type expression is expected. cartodb_id (of type keyword) was given."]});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -721,250 +682,6 @@ suite('multilayer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/87
|
||||
test("exponential notation in CartoCSS filter values", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select .4 as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#s [n<=.2e-2] { marker-fill:red; }',
|
||||
cartocss_version: '2.1.0',
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/93
|
||||
test("accepts unused directives", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#layer { point-transform:"scale(20)"; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
|
||||
Step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
}
|
||||
else {
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
expected_last_updated_epoch = token_components[1];
|
||||
}
|
||||
next(null, res);
|
||||
});
|
||||
},
|
||||
function do_get_tile(err)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "image/png");
|
||||
assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', 2,
|
||||
function(err, similarity) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
var errors = [];
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
console.log("Error: " + err);
|
||||
}
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) done(new Error(errors));
|
||||
else done(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/91
|
||||
// and https://github.com/CartoDB/Windshaft-cartodb/issues/38
|
||||
test("tiles for private tables can be fetched with api_key", function(done) {
|
||||
var errors = [];
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select * from test_table_private_1 LIMIT 0",
|
||||
cartocss: '#layer { marker-fill:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
|
||||
Step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function check_result(err, res) {
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
}
|
||||
else {
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
expected_last_updated_epoch = token_components[1];
|
||||
}
|
||||
next(null, res);
|
||||
},
|
||||
function do_get_tile(err)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function check_get_tile(err, res) {
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
return null;
|
||||
},
|
||||
function cleanup(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( ! expected_token ) return null;
|
||||
var next = this;
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
next();
|
||||
});
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
console.log("Error: " + err);
|
||||
}
|
||||
if ( errors.length ) done(new Error(errors));
|
||||
else done(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// SQL strings can be of arbitrary length, when using POST
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/111
|
||||
test("sql string can be very long", function(done){
|
||||
var long_val = 'pretty';
|
||||
for (var i=0; i<1024; ++i) long_val += ' long'
|
||||
long_val += ' string';
|
||||
var sql = "SELECT ";
|
||||
for (var i=0; i<16; ++i)
|
||||
sql += "'" + long_val + "'::text as pretty_long_field_name_" + i + ", ";
|
||||
sql += "cartodb_id, the_geom_webmercator FROM gadm4 g";
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: sql,
|
||||
cartocss: '#layer { marker-fill:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
var errors = [];
|
||||
var expected_token;
|
||||
Step(
|
||||
function do_post()
|
||||
{
|
||||
var data = JSON.stringify(layergroup);
|
||||
assert.ok(data.length > 1024*64);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: data
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function check_result(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
return null;
|
||||
},
|
||||
function cleanup(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( ! expected_token ) return null;
|
||||
var next = this;
|
||||
redis_client.keys("map_style|test_cartodb_user_1_db|~" + expected_token, function(err, matches) {
|
||||
if ( err ) errors.push(err.message);
|
||||
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
|
||||
redis_client.del(matches, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
next();
|
||||
});
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push('' + err);
|
||||
if ( errors.length ) done(new Error(errors.join(',')));
|
||||
else done(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
|
||||
// This test will add map_style records, like
|
||||
|
||||
@@ -21,21 +21,13 @@ suite('server', function() {
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
|
||||
var mapnik_version = global.environment.mapnik_version || mapnik.versions.mapnik;
|
||||
var test_database = 'test_cartodb_user_1_db';
|
||||
var default_style;
|
||||
if ( semver.satisfies(mapnik_version, '<2.1.0') ) {
|
||||
var default_style = semver.satisfies(mapnik.versions.mapnik, '<2.1.0')
|
||||
?
|
||||
// 2.0.0 default
|
||||
default_style = '#<%= table %>{marker-fill: #FF6600;marker-opacity: 1;marker-width: 8;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}';
|
||||
}
|
||||
else if ( semver.satisfies(mapnik_version, '<2.2.0') ) {
|
||||
'#<%= table %>{marker-fill: #FF6600;marker-opacity: 1;marker-width: 8;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}'
|
||||
:
|
||||
// 2.1.0 default
|
||||
default_style = '#<%= table %>[mapnik-geometry-type=1] {marker-fill: #FF6600;marker-opacity: 1;marker-width: 16;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}#<%= table %>[mapnik-geometry-type=2] {line-color:#FF6600; line-width:1; line-opacity: 0.7;}#<%= table %>[mapnik-geometry-type=3] {polygon-fill:#FF6600; polygon-opacity: 0.7; line-opacity:1; line-color: #FFFFFF;}';
|
||||
}
|
||||
else {
|
||||
// 2.2.0+ default
|
||||
default_style = '#<%= table %>["mapnik::geometry_type"=1] {marker-fill: #FF6600;marker-opacity: 1;marker-width: 16;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}#<%= table %>["mapnik::geometry_type"=2] {line-color:#FF6600; line-width:1; line-opacity: 0.7;}#<%= table %>["mapnik::geometry_type"=3] {polygon-fill:#FF6600; polygon-opacity: 0.7; line-opacity:1; line-color: #FFFFFF;}';
|
||||
}
|
||||
'#<%= table %>[mapnik-geometry-type=1] {marker-fill: #FF6600;marker-opacity: 1;marker-width: 16;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}#<%= table %>[mapnik-geometry-type=2] {line-color:#FF6600; line-width:1; line-opacity: 0.7;}#<%= table %>[mapnik-geometry-type=3] {polygon-fill:#FF6600; polygon-opacity: 0.7; line-opacity:1; line-color: #FFFFFF;}';
|
||||
|
||||
// A couple of styles to use during testing
|
||||
var test_style_black_200 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:10}";
|
||||
@@ -103,11 +95,11 @@ suite('server', function() {
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200,
|
||||
headers: { 'X-Cache-Channel': test_database+':my_table' },
|
||||
headers: { 'X-Cache-Channel': 'cartodb_test_user_1_db:my_table' },
|
||||
}, function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.style, _.template(default_style, {table: 'my_table'}));
|
||||
assert.equal(parsed.style_version, mapnik_version);
|
||||
assert.equal(parsed.style_version, mapnik.versions.mapnik);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -125,7 +117,6 @@ suite('server', function() {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
assert.deepEqual(JSON.parse(res.body),
|
||||
{error: 'Sorry, you are unauthorized (permission denied)'});
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -142,8 +133,7 @@ suite('server', function() {
|
||||
// FIXME: should be 401 Unauthorized
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(JSON.parse(res.body),
|
||||
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
{error:"missing unknown_user's dbname in redis (try CARTODB/script/restore_redis)"});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -160,48 +150,11 @@ suite('server', function() {
|
||||
var parsed = JSON.parse(res.body);
|
||||
var style = _.template(default_style, {table: 'test_table_private_1'});
|
||||
assert.equal(parsed.style, style);
|
||||
assert.equal(parsed.style_version, mapnik_version);
|
||||
assert.equal(parsed.style_version, mapnik.versions.mapnik);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/94
|
||||
test("get'ing unrenderable style", function(done) {
|
||||
var base_key = 'map_style|'+test_database+'|issue94';
|
||||
var style = '#s{bogus}';
|
||||
Step(
|
||||
function checkRedis() {
|
||||
redis_client.keys(base_key+'*', this);
|
||||
},
|
||||
function setupRedisBase(err, matches) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(matches.length, 0);
|
||||
redis_client.set(base_key,
|
||||
JSON.stringify({ style: style }),
|
||||
this);
|
||||
},
|
||||
function getStyle(err) {
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
headers: {host: 'localhost'},
|
||||
url: '/tiles/issue94/style',
|
||||
method: 'GET'
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkStyle(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.style, style);
|
||||
return null
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// POST STYLE
|
||||
@@ -214,12 +167,9 @@ suite('server', function() {
|
||||
url: '/tiles/my_table/style',
|
||||
method: 'POST'
|
||||
},{
|
||||
status: 400,
|
||||
body: '{"error":"must send style information"}'
|
||||
}, function(res) {
|
||||
assert.equal(res.statusCode, 400);
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
done();
|
||||
});
|
||||
}, function() { done(); });
|
||||
});
|
||||
|
||||
test("post'ing bad style returns 400 with error", function(done){
|
||||
@@ -240,11 +190,10 @@ suite('server', function() {
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
data: querystring.stringify({style: '#my_table3{'})
|
||||
},{}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.ok( RegExp(/missing closing/i).test(res.body) );
|
||||
done();
|
||||
});
|
||||
},{
|
||||
status: 400,
|
||||
body: /Missing closing/
|
||||
}, function() { done(); });
|
||||
});
|
||||
|
||||
test("post'ing multiple bad styles returns 400 with error array", function(done){
|
||||
@@ -356,7 +305,7 @@ suite('server', function() {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.style, style);
|
||||
assert.equal(parsed.style_version, mapnik_version);
|
||||
assert.equal(parsed.style_version, mapnik.versions.mapnik);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -384,7 +333,7 @@ suite('server', function() {
|
||||
var parsed = JSON.parse(res.body);
|
||||
// NOTE: no transform expected for the specific style
|
||||
assert.equal(parsed.style, style);
|
||||
assert.equal(parsed.style_version, mapnik_version);
|
||||
assert.equal(parsed.style_version, mapnik.versions.mapnik);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -483,7 +432,7 @@ suite('server', function() {
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200,
|
||||
headers: { 'X-Cache-Channel': test_database+':my_tablez' },
|
||||
headers: { 'X-Cache-Channel': 'cartodb_test_user_1_db:my_tablez' },
|
||||
body: '{"infowindow":null}'
|
||||
}, function() { done(); });
|
||||
});
|
||||
@@ -536,7 +485,7 @@ suite('server', function() {
|
||||
// FIXME: should be 401 Unauthorized
|
||||
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(JSON.parse(res.body),
|
||||
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
|
||||
{error:"missing unknown_user's dbname in redis (try CARTODB/script/restore_redis)"});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -567,7 +516,7 @@ suite('server', function() {
|
||||
},{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/javascript; charset=utf-8; charset=utf-8',
|
||||
'X-Cache-Channel': test_database+':gadm4' }
|
||||
'X-Cache-Channel': 'cartodb_test_user_1_db:gadm4' }
|
||||
}, function() { done(); });
|
||||
});
|
||||
|
||||
@@ -619,7 +568,7 @@ suite('server', function() {
|
||||
// FIXME: should be 401 Unauthorized
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(JSON.parse(res.body),
|
||||
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
|
||||
{error:"missing unknown_user's dbname in redis (try CARTODB/script/restore_redis)"});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -682,7 +631,7 @@ suite('server', function() {
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'image/png', 'X-Cache-Channel': test_database+':gadm4' }
|
||||
headers: { 'Content-Type': 'image/png', 'X-Cache-Channel': 'cartodb_test_user_1_db:gadm4' }
|
||||
}, function() { done(); });
|
||||
});
|
||||
|
||||
@@ -770,9 +719,7 @@ suite('server', function() {
|
||||
// FIXME: should be 401 Unauthorized
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(JSON.parse(res.body),
|
||||
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'),
|
||||
"Unexpected Cache-Control: " + res.headers['cache-control']);
|
||||
{error:"missing unknown_user's dbname in redis (try CARTODB/script/restore_redis)"});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -793,9 +740,6 @@ suite('server', function() {
|
||||
}, function(res) {
|
||||
// 401 Unauthorized
|
||||
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
|
||||
// Failed in 1.6.0 of https://github.com/CartoDB/Windshaft-cartodb/issues/107
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'),
|
||||
"Unexpected Cache-Control: " + res.headers['cache-control']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -819,79 +763,6 @@ suite('server', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/88
|
||||
test("get'ing a tile from a user-specific database should return an expected tile", function(done){
|
||||
var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'});
|
||||
var backupDBHost = global.environment.postgres.host;
|
||||
global.environment.postgres.host = '6.6.6.6';
|
||||
Step (
|
||||
function() {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
headers: {host: 'cartodb250user'},
|
||||
url: '/tiles/test_table/15/16046/12354.png?cache_buster=4.10&' + 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.environment.postgres.host = backupDBHost;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/89
|
||||
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){
|
||||
var style = querystring.stringify({style: test_style_black_210, style_version: '2.1.0'});
|
||||
assert.response(server, {
|
||||
@@ -1083,7 +954,8 @@ suite('server', function() {
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
if ( err ) done(err);
|
||||
else done();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1098,30 +970,24 @@ suite('server', function() {
|
||||
var sqlapi;
|
||||
Step(
|
||||
function sendRequest(err) {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
headers: {host: 'localhost'},
|
||||
url: '/tiles/gadm4/6/31/24.png?' + querystring.stringify(qo),
|
||||
method: 'GET'
|
||||
},{}, function(res) { next(null, res); });
|
||||
},{}, this);
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
if ( err ) throw err;
|
||||
function checkResponse(res) {
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var ct = res.headers['content-type'];
|
||||
assert.equal(ct, 'image/png');
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert(cc);
|
||||
var dbname = 'test_cartodb_user_1_db'
|
||||
var dbname = 'cartodb_test_user_1_db'
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
assert.equal(sentquery.api_key, qo.map_key);
|
||||
assert.equal(sentquery.q, 'SELECT CDB_QueryTables($windshaft$' + qo.sql + '$windshaft$)');
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1137,24 +1003,19 @@ suite('server', function() {
|
||||
var sqlapi;
|
||||
Step(
|
||||
function sendRequest(err) {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
headers: {host: 'localhost'},
|
||||
url: '/tiles/gadm4/6/31/24.png?' + querystring.stringify(qo),
|
||||
method: 'GET'
|
||||
},{}, function(res) { next(null, res); });
|
||||
},{}, this);
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
if ( err ) throw err;
|
||||
function checkResponse(res) {
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var ct = res.headers['content-type'];
|
||||
assert.equal(ct, 'image/png');
|
||||
// does NOT send an x-cache-channel
|
||||
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1189,7 +1050,6 @@ suite('server', function() {
|
||||
method: 'DELETE'
|
||||
},{}, function(res) {
|
||||
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1221,7 +1081,6 @@ suite('server', function() {
|
||||
},{}, function(res) {
|
||||
// FIXME: should be 401 instead
|
||||
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -1274,7 +1133,6 @@ suite('server', function() {
|
||||
method: 'DELETE'
|
||||
},{}, function(res) {
|
||||
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
|
||||
assert.ok(!res.headers.hasOwnProperty('cache-control'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,936 +0,0 @@
|
||||
var assert = require('../support/assert');
|
||||
var tests = module.exports = {};
|
||||
var _ = require('underscore');
|
||||
var redis = require('redis');
|
||||
var querystring = require('querystring');
|
||||
var semver = require('semver');
|
||||
var mapnik = require('mapnik');
|
||||
var Step = require('step');
|
||||
var strftime = require('strftime');
|
||||
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
|
||||
var redis_stats_db = 5;
|
||||
|
||||
require(__dirname + '/../support/test_helper');
|
||||
|
||||
var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures';
|
||||
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
suite('template_api', function() {
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu
|
||||
var expected_last_updated = new Date(expected_last_updated_epoch).toISOString();
|
||||
|
||||
suiteSetup(function(done){
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
// TODO: check redis is clean ?
|
||||
});
|
||||
|
||||
var template_acceptance1 = {
|
||||
version: '0.0.1',
|
||||
name: 'acceptance1',
|
||||
auth: { method: 'open' },
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator from test_table limit 2 offset 2',
|
||||
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
} }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
test("can add template, returning id", function(done) {
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var expected_tpl_id = "localhost@acceptance1";
|
||||
var post_request_1 = {
|
||||
url: '/tiles/template',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
Step(
|
||||
function postUnauthenticated()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, post_request_1, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function postTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'), res.body);
|
||||
err = parsed.error;
|
||||
assert.ok(err.match(/only.*authenticated.*user/i),
|
||||
'Unexpected error response: ' + err);
|
||||
post_request_1.url += '?api_key=1234';
|
||||
var next = this;
|
||||
assert.response(server, post_request_1, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function rePostTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { template_id: expected_tpl_id };
|
||||
assert.deepEqual(parsedBody, expectedBody);
|
||||
var next = this;
|
||||
assert.response(server, post_request_1, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkFailure(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
|
||||
assert.ok(parsedBody.error.match(/already exists/i),
|
||||
'Unexpected error for pre-existing template name: ' + parsedBody.error);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length != 2 ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
} else {
|
||||
if ( todrop.indexOf('map_tpl|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_tpl|localhost' key in redis"));
|
||||
}
|
||||
if ( todrop.indexOf('map_crt|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_crt|localhost' key in redis"));
|
||||
}
|
||||
}
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("instance endpoint should return CORS headers", function(done){
|
||||
Step(function postTemplate1(err, res) {
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost.localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
};
|
||||
assert.response(server, post_request, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function testCORS() {
|
||||
assert.response(server, {
|
||||
url: '/tiles/template/acceptance1',
|
||||
method: 'OPTIONS'
|
||||
},{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Headers': 'X-Requested-With, X-Prototype-Version, X-CSRF-Token, Content-Type',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
}, function() { done(); });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test("can list templates", function(done) {
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var tplid1, tplid2;
|
||||
Step(
|
||||
function postTemplate1(err, res)
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function postTemplate2(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tplid1 = parsed.template_id;
|
||||
|
||||
var next = this;
|
||||
var backup_name = template_acceptance1.name;
|
||||
template_acceptance1.name += '_new';
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
template_acceptance1.name = backup_name;
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function litsTemplatesUnauthenticated(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tplid2 = parsed.template_id;
|
||||
var next = this;
|
||||
var get_request = {
|
||||
url: '/tiles/template',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function litsTemplates(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
'Missing error from response: ' + res.body);
|
||||
err = parsed.error;
|
||||
assert.ok(err.match(/authenticated user/), err);
|
||||
var next = this;
|
||||
var get_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkList(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_ids'),
|
||||
"Missing 'template_ids' from response body: " + res.body);
|
||||
var ids = parsed.template_ids;
|
||||
assert.equal(ids.length, 2);
|
||||
assert.ok(ids.indexOf(tplid1) != -1,
|
||||
'Missing "' + tplid1 + "' from list response: " + ids.join(','));
|
||||
assert.ok(ids.indexOf(tplid2) != -1,
|
||||
'Missing "' + tplid2 + "' from list response: " + ids.join(','));
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length != 2 ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
} else {
|
||||
if ( todrop.indexOf('map_tpl|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_tpl|localhost' key in redis"));
|
||||
}
|
||||
if ( todrop.indexOf('map_crt|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_crt|localhost' key in redis"));
|
||||
}
|
||||
}
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("can update template", function(done) {
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function postTemplate(err, res)
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function putMisnamedTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var backup_name = template_acceptance1.name;
|
||||
template_acceptance1.name = 'changed_name';
|
||||
var put_request = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
template_acceptance1.name = backup_name;
|
||||
var next = this;
|
||||
assert.response(server, put_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function putUnexistentTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
|
||||
assert.ok(parsedBody.error.match(/cannot update name/i),
|
||||
'Unexpected error for invalid update: ' + parsedBody.error);
|
||||
var put_request = {
|
||||
url: '/tiles/template/unexistent/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, put_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function putValidTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
|
||||
assert.ok(parsedBody.error.match(/cannot update name/i),
|
||||
'Unexpected error for invalid update: ' + parsedBody.error);
|
||||
var put_request = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, put_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkValidUpate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
assert.equal(tpl_id, parsed.template_id);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length != 2 ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
} else {
|
||||
if ( todrop.indexOf('map_tpl|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_tpl|localhost' key in redis"));
|
||||
}
|
||||
if ( todrop.indexOf('map_crt|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_crt|localhost' key in redis"));
|
||||
}
|
||||
}
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("can get a template by id", function(done) {
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function postTemplate(err, res)
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function getTemplateUnauthorized(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function getTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401, res.statusCode + ": " + res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
|
||||
assert.ok(parsedBody.error.match(/only.*authenticated.*user/i),
|
||||
'Unexpected error for unauthenticated template get: ' + parsedBody.error);
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkReturnTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template'),
|
||||
"Missing 'template' from response body: " + res.body);
|
||||
assert.deepEqual(template_acceptance1, parsed.template);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length != 2 ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
} else {
|
||||
if ( todrop.indexOf('map_tpl|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_tpl|localhost' key in redis"));
|
||||
}
|
||||
if ( todrop.indexOf('map_crt|localhost') == -1 ) {
|
||||
errors.push(new Error("Missing 'map_crt|localhost' key in redis"));
|
||||
}
|
||||
}
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("can delete a template by id", function(done) {
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function postTemplate(err, res)
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
}
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function getTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function deleteTemplateUnauthorized(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template'),
|
||||
"Missing 'template' from response body: " + res.body);
|
||||
assert.deepEqual(template_acceptance1, parsed.template);
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, del_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function deleteTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401, res.statusCode + ": " + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
"Missing 'error' from response body: " + res.body);
|
||||
assert.ok(parsed.error.match(/only.*authenticated.*user/i),
|
||||
'Unexpected error for unauthenticated template get: ' + parsed.error);
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, del_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function getMissingTemplate(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body);
|
||||
assert.ok(!res.body, 'Unexpected body in DELETE /template response');
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkGetFailure(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
"Missing 'error' from response body: " + res.body);
|
||||
assert.ok(parsed.error.match(/cannot find/i),
|
||||
'Unexpected error for missing template: ' + parsed.error);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
} else {
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("can instanciate a template by id", function(done) {
|
||||
|
||||
// This map fetches data from a private table
|
||||
var template_acceptance2 = {
|
||||
version: '0.0.1',
|
||||
name: 'acceptance1',
|
||||
auth: { method: 'token', valid_tokens: ['valid1','valid2'] },
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select * from test_table_private_1 LIMIT 0",
|
||||
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
} }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var template_params = {};
|
||||
|
||||
var errors = [];
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
var layergroupid;
|
||||
Step(
|
||||
function postTemplate(err, res)
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance2)
|
||||
}
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function instanciateNoAuth(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('template_id'),
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function instanciateAuth(err, res)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401,
|
||||
'Unexpected success instanciating template with no auth: '
|
||||
+ res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
"Missing 'error' from response body: " + res.body);
|
||||
assert.ok(parsed.error.match(/unauthorized/i),
|
||||
'Unexpected error for unauthorized instance : ' + parsed.error);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function fetchTileNoAuth(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200,
|
||||
'Instantiating template: ' + res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('layergroupid'),
|
||||
"Missing 'layergroupid' from response body: " + res.body);
|
||||
layergroupid = parsed.layergroupid;
|
||||
assert.ok(layergroupid.match(/^localhost@/),
|
||||
"Returned layergroupid does not start with signer name: "
|
||||
+ layergroupid);
|
||||
assert.ok(parsed.hasOwnProperty('last_updated'),
|
||||
"Missing 'last_updated' from response body: " + res.body);
|
||||
// TODO: check value of last_updated ?
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb0/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function fetchTileAuth(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401,
|
||||
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
"Missing 'error' from response body: " + res.body);
|
||||
assert.ok(parsed.error.match(/permission denied/i),
|
||||
'Unexpected error for unauthorized instance '
|
||||
+ '(expected /permission denied): ' + parsed.error);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkTile(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200,
|
||||
'Unexpected error for authorized instance: '
|
||||
+ res.statusCode + ' -- ' + res.body);
|
||||
assert.equal(res.headers['content-type'], "image/png");
|
||||
return null;
|
||||
},
|
||||
function deleteTemplate(err)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, del_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function fetchTileDeleted(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 204,
|
||||
'Deleting template: ' + res.statusCode + ':' + res.body);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, get_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function checkTileDeleted(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 401,
|
||||
'Unexpected statusCode fetch tile after signature revokal: '
|
||||
+ res.statusCode + ':' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('error'),
|
||||
"Missing 'error' from response body: " + res.body);
|
||||
assert.ok(parsed.error.match(/permission denied/i),
|
||||
'Unexpected error for unauthorized access : ' + parsed.error);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push(err);
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) errors.push(err.message);
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt)|/) )
|
||||
return m;
|
||||
});
|
||||
if ( todrop.length ) {
|
||||
errors.push(new Error("Unexpected keys in redis: " + todrop));
|
||||
redis_client.del(todrop, function(err) {
|
||||
if ( err ) errors.push(err.message);
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
} else {
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("template instantiation raises mapviews counter", function(done) {
|
||||
var layergroup = {
|
||||
stat_tag: 'random_tag',
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select 1 as cartodb_id, !pixel_height! as h, '
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
cartocss: '#layer { polygon-fill:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: 'stat_gathering',
|
||||
auth: { method: 'open' },
|
||||
layergroup: layergroup
|
||||
};
|
||||
var statskey = "user:localhost:mapviews";
|
||||
var redis_stats_client = redis.createClient(global.environment.redis.port);
|
||||
var template_id; // will be set on template post
|
||||
var now = strftime("%Y%m%d", new Date());
|
||||
var errors = [];
|
||||
Step(
|
||||
function clean_stats()
|
||||
{
|
||||
var next = this;
|
||||
redis_stats_client.select(redis_stats_db, function(err) {
|
||||
if ( err ) next(err);
|
||||
else redis_stats_client.del(statskey+':global', next);
|
||||
});
|
||||
},
|
||||
function do_post_tempate(err)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function instantiateTemplate(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
template_id = JSON.parse(res.body).template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + template_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify({})
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, post_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function check_global_stats(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200,
|
||||
'Instantiating template: ' + res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('layergroupid'),
|
||||
"Missing 'layergroupid' from response body: " + res.body);
|
||||
layergroupid = parsed.layergroupid;
|
||||
redis_stats_client.zscore(statskey + ":global", now, this);
|
||||
},
|
||||
function check_tag_stats(err, val) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(val, 1, "Expected score of " + now + " in "
|
||||
+ statskey + ":global to be 1, got " + val);
|
||||
redis_stats_client.zscore(statskey+':stat_tag:random_tag', now, this);
|
||||
},
|
||||
function check_tag_stats_value(err, val) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(val, 1, "Expected score of " + now + " in "
|
||||
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val);
|
||||
return null;
|
||||
},
|
||||
function deleteTemplate(err)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + template_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
var next = this;
|
||||
assert.response(server, del_request, {},
|
||||
function(res) { next(null, res); });
|
||||
},
|
||||
function cleanup_stats(err, res) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body);
|
||||
if ( err ) errors.push('' + err);
|
||||
redis_client.del([statskey+':global', statskey+':stat_tag:'+layergroup.stat_tag], this);
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) errors.push('' + err);
|
||||
if ( errors.length ) done(new Error(errors.join(',')));
|
||||
else done(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
|
||||
// This test will add map_style records, like
|
||||
// 'map_style|null|publicuser|my_table',
|
||||
redis_client.keys("map_*", function(err, keys) {
|
||||
var todrop = _.map(keys, function(m) {
|
||||
if ( m.match(/^map_(tpl|crt|sig)|/) ) return m;
|
||||
});
|
||||
redis_client.del(todrop, function(err) {
|
||||
redis_client.select(5, function(err) {
|
||||
redis_client.keys("user:localhost:mapviews*", function(err, keys) {
|
||||
redis_client.del(keys, function(err) {
|
||||
sqlapi_server.close(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -5,60 +5,31 @@ var o = function(port, cb) {
|
||||
|
||||
this.queries = [];
|
||||
var that = this;
|
||||
|
||||
this.sqlapi_server = http.createServer(function(req,res) {
|
||||
//console.log("server got request with method " + req.method);
|
||||
var query;
|
||||
if ( req.method == 'GET' ) {
|
||||
query = url.parse(req.url, true).query;
|
||||
that.handleQuery(query, res);
|
||||
}
|
||||
else if ( req.method == 'POST') {
|
||||
var data = '';
|
||||
req.on('data', function(chunk) {
|
||||
//console.log("GOT Chunk " + chunk);
|
||||
data += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
//console.log("Data is: "); console.dir(data);
|
||||
query = JSON.parse(data);
|
||||
//console.log("Parsed is: "); console.dir(query);
|
||||
//console.log("handleQuery is " + that.handleQuery);
|
||||
that.handleQuery(query, res);
|
||||
});
|
||||
}
|
||||
else {
|
||||
that.handleQuery('SQLAPIEmu does not support method' + req.method, res);
|
||||
}
|
||||
var query = url.parse(req.url, true).query;
|
||||
that.queries.push(query);
|
||||
if ( query.q.match('SQLAPIERROR') ) {
|
||||
res.statusCode = 400;
|
||||
res.write(JSON.stringify({'error':'Some error occurred'}));
|
||||
} else if ( query.q.match('EPOCH.* as max') ) {
|
||||
// This is the structure of the known query sent by tiler
|
||||
var row = {
|
||||
'max': 1234567890.123
|
||||
};
|
||||
res.write(JSON.stringify({rows: [ row ]}));
|
||||
} else {
|
||||
var qs = JSON.stringify(query);
|
||||
var row = {
|
||||
// This is the structure of the known query sent by tiler
|
||||
'cdb_querytables': '{' + qs + '}',
|
||||
'max': qs
|
||||
};
|
||||
res.write(JSON.stringify({rows: [ row ]}));
|
||||
}
|
||||
res.end();
|
||||
}).listen(port, cb);
|
||||
};
|
||||
|
||||
o.prototype.handleQuery = function(query, res) {
|
||||
this.queries.push(query);
|
||||
if ( query.q.match('SQLAPIERROR') ) {
|
||||
res.statusCode = 400;
|
||||
res.write(JSON.stringify({'error':'Some error occurred'}));
|
||||
} else if ( query.q.match('EPOCH.* as max') ) {
|
||||
// This is the structure of the known query sent by tiler
|
||||
var row = {
|
||||
'max': 1234567890.123
|
||||
};
|
||||
res.write(JSON.stringify({rows: [ row ]}));
|
||||
} else {
|
||||
var qs = JSON.stringify(query);
|
||||
var row = {
|
||||
// This is the structure of the known query sent by tiler
|
||||
'cdb_querytables': '{' + qs + '}',
|
||||
'max': qs
|
||||
};
|
||||
var out_obj = {rows: [ row ]};
|
||||
var out = JSON.stringify(out_obj);
|
||||
res.write(out);
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
|
||||
o.prototype.close = function(cb) {
|
||||
this.sqlapi_server.close(cb);
|
||||
};
|
||||
|
||||
@@ -10,102 +10,28 @@
|
||||
# TODO: fix that
|
||||
#
|
||||
|
||||
PREPARE_REDIS=yes
|
||||
PREPARE_PGSQL=yes
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
if test "$1" = "--skip-pg"; then
|
||||
PREPARE_PGSQL=no
|
||||
shift; continue
|
||||
elif test "$1" = "--skip-redis"; then
|
||||
PREPARE_REDIS=no
|
||||
shift; continue
|
||||
fi
|
||||
done
|
||||
|
||||
die() {
|
||||
msg=$1
|
||||
echo "${msg}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# This is where postgresql connection parameters are read from
|
||||
TESTENV=../../config/environments/test.js
|
||||
if [ \! -r ${TESTENV} ]; then
|
||||
echo "Cannot read ${TESTENV}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TESTUSERID=1
|
||||
|
||||
TESTUSER=`node -e "console.log(require('${TESTENV}').postgres_auth_user || '')"`
|
||||
if test -z "$TESTUSER"; then
|
||||
echo "Missing postgres_auth_user from ${TESTENV}" >&2
|
||||
exit 1
|
||||
fi
|
||||
TESTUSER=`echo ${TESTUSER} | sed "s/<%= user_id %>/${TESTUSERID}/"`
|
||||
|
||||
TESTPASS=`node -e "console.log(require('${TESTENV}').postgres_auth_pass || 'test')"`
|
||||
# TODO: should postgres_auth_pass be optional ?
|
||||
if test -z "$TESTPASS"; then
|
||||
echo "Missing postgres_auth_pass from ${TESTENV}" >&2
|
||||
exit 1
|
||||
fi
|
||||
TESTPASS=`echo ${TESTPASS} | sed "s/<%= user_id %>/${TESTUSERID}/"`
|
||||
|
||||
TEST_DB="${TESTUSER}_db"
|
||||
|
||||
# NOTE: will be set by caller trough environment
|
||||
TEST_DB="cartodb_test_user_1_db"
|
||||
if test -z "$REDIS_PORT"; then REDIS_PORT=6333; fi
|
||||
|
||||
PUBLICUSER=`node -e "console.log(require('${TESTENV}').postgres.user || 'xxx')"`
|
||||
PUBLICPASS=`node -e "console.log(require('${TESTENV}').postgres.password || 'xxx')"`
|
||||
echo "PUBLICUSER: ${PUBLICUSER}"
|
||||
echo "PUBLICPASS: ${PUBLICPASS}"
|
||||
echo "TESTUSER: ${TESTUSER}"
|
||||
echo "TESTPASS: ${TESTPASS}"
|
||||
echo "preparing postgres..."
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
psql "${TEST_DB}" < ./sql/windshaft.test.sql
|
||||
psql "${TEST_DB}" < ./sql/gadm4.sql
|
||||
|
||||
if test x"$PREPARE_PGSQL" = xyes; then
|
||||
|
||||
echo "preparing postgres..."
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
|
||||
cat sql/windshaft.test.sql sql/gadm4.sql |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
sed "s/:TESTUSER/${TESTUSER}/" |
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
psql ${TEST_DB}
|
||||
|
||||
fi
|
||||
|
||||
if test x"$PREPARE_REDIS" = xyes; then
|
||||
|
||||
echo "preparing redis..."
|
||||
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET rails:users:localhost id ${TESTUSERID} \
|
||||
database_name '${TEST_DB}' \
|
||||
map_key 1234
|
||||
SADD rails:users:localhost:map_key 1235
|
||||
EOF
|
||||
|
||||
# A user configured as with cartodb-2.5.0+
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET rails:users:cartodb250user id ${TESTUSERID} \
|
||||
database_name "${TEST_DB}" \
|
||||
database_host "localhost" \
|
||||
database_password "${TESTPASS}" \
|
||||
map_key 4321
|
||||
EOF
|
||||
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 0
|
||||
HSET rails:${TEST_DB}:my_table infowindow "this, that, the other"
|
||||
HSET rails:${TEST_DB}:test_table_private_1 privacy "0"
|
||||
EOF
|
||||
|
||||
fi
|
||||
echo "preparing redis..."
|
||||
echo "HSET rails:users:localhost id 1" | redis-cli -p ${REDIS_PORT} -n 5
|
||||
echo 'HSET rails:users:localhost database_name "'"${TEST_DB}"'"' | redis-cli -p ${REDIS_PORT} -n 5
|
||||
echo "HSET rails:users:localhost map_key 1234" | redis-cli -p ${REDIS_PORT} -n 5
|
||||
echo "SADD rails:users:localhost:map_key 1235" | 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}"':test_table_private_1 privacy "0"' | redis-cli -p ${REDIS_PORT} -n 0
|
||||
|
||||
echo "Finished preparing data. Ready to run tests"
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ CREATE TABLE gadm4 (
|
||||
the_geom_webmercator geometry(MultiPolygon,3857)
|
||||
);
|
||||
|
||||
--GRANT ALL ON TABLE gadm4 TO postgres;
|
||||
GRANT ALL ON TABLE gadm4 TO :PUBLICUSER;
|
||||
GRANT ALL ON TABLE gadm4 TO postgres;
|
||||
GRANT ALL ON TABLE gadm4 TO publicuser;
|
||||
CREATE SEQUENCE gadm4_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
--ALTER TABLE public.gadm4_seq OWNER TO postgres;
|
||||
ALTER TABLE public.gadm4_seq OWNER TO postgres;
|
||||
SELECT pg_catalog.setval('gadm4_seq', 58, false);
|
||||
ALTER TABLE gadm4 ALTER COLUMN cartodb_id SET DEFAULT nextval('gadm4_seq'::regclass);
|
||||
|
||||
@@ -98,6 +98,6 @@ ALTER TABLE ONLY gadm4
|
||||
|
||||
CREATE INDEX bdll25_provincias_4326_2_the_geom_webmercator_idx ON gadm4 USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE gadm4 TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE gadm4 TO :PUBLICUSER;
|
||||
GRANT ALL ON TABLE gadm4 TO test_cartodb_user_1;
|
||||
GRANT SELECT ON TABLE gadm4 TO publicuser;
|
||||
|
||||
|
||||
@@ -15,13 +15,11 @@ SET search_path = public, pg_catalog;
|
||||
SET default_tablespace = '';
|
||||
SET default_with_oids = false;
|
||||
|
||||
-- public user role
|
||||
DROP USER IF EXISTS :PUBLICUSER;
|
||||
CREATE USER :PUBLICUSER WITH PASSWORD ':PUBLICPASS';
|
||||
-- publicuser role
|
||||
CREATE USER publicuser;
|
||||
|
||||
-- db owner role
|
||||
DROP USER IF EXISTS :TESTUSER;
|
||||
CREATE USER :TESTUSER WITH PASSWORD ':TESTPASS';
|
||||
CREATE USER test_cartodb_user_1;
|
||||
|
||||
-- first table
|
||||
CREATE TABLE test_table (
|
||||
@@ -65,8 +63,8 @@ ALTER TABLE ONLY test_table ADD CONSTRAINT test_table_pkey PRIMARY KEY (cartodb_
|
||||
CREATE INDEX test_table_the_geom_idx ON test_table USING gist (the_geom);
|
||||
CREATE INDEX test_table_the_geom_webmercator_idx ON test_table USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE test_table TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE test_table TO :PUBLICUSER;
|
||||
GRANT ALL ON TABLE test_table TO test_cartodb_user_1;
|
||||
GRANT SELECT ON TABLE test_table TO publicuser;
|
||||
|
||||
-- second table
|
||||
CREATE TABLE test_table_2 (
|
||||
@@ -110,8 +108,8 @@ ALTER TABLE ONLY test_table_2 ADD CONSTRAINT test_table_2_pkey PRIMARY KEY (cart
|
||||
CREATE INDEX test_table_2_the_geom_idx ON test_table_2 USING gist (the_geom);
|
||||
CREATE INDEX test_table_2_the_geom_webmercator_idx ON test_table_2 USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE test_table_2 TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE test_table_2 TO :PUBLICUSER;
|
||||
GRANT ALL ON TABLE test_table_2 TO test_cartodb_user_1;
|
||||
GRANT SELECT ON TABLE test_table_2 TO publicuser;
|
||||
|
||||
-- third table
|
||||
CREATE TABLE test_table_3 (
|
||||
@@ -155,8 +153,8 @@ ALTER TABLE ONLY test_table_3 ADD CONSTRAINT test_table_3_pkey PRIMARY KEY (cart
|
||||
CREATE INDEX test_table_3_the_geom_idx ON test_table_3 USING gist (the_geom);
|
||||
CREATE INDEX test_table_3_the_geom_webmercator_idx ON test_table_3 USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE test_table_3 TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE test_table_3 TO :PUBLICUSER;
|
||||
GRANT ALL ON TABLE test_table_3 TO test_cartodb_user_1;
|
||||
GRANT SELECT ON TABLE test_table_3 TO publicuser;
|
||||
|
||||
-- private table
|
||||
CREATE TABLE test_table_private_1 (
|
||||
@@ -175,4 +173,4 @@ CREATE TABLE test_table_private_1 (
|
||||
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE test_table_private_1 TO :TESTUSER;
|
||||
GRANT ALL ON TABLE test_table_private_1 TO test_cartodb_user_1;
|
||||
|
||||
63
test/unit/cartodb/redis_pool.test.js
Normal file
63
test/unit/cartodb/redis_pool.test.js
Normal file
@@ -0,0 +1,63 @@
|
||||
var assert = require('../../support/assert')
|
||||
, _ = require('underscore')
|
||||
, RedisPool = require('../../../lib/cartodb/redis_pool')
|
||||
, tests = module.exports = {};
|
||||
|
||||
suite('redis_pool', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var test_opts = require('../../support/config').redis_pool;
|
||||
|
||||
var redis_pool = new RedisPool(test_opts);
|
||||
|
||||
test('RedisPool object exists', function(done){
|
||||
assert.ok(RedisPool);
|
||||
done();
|
||||
});
|
||||
|
||||
test('RedisPool can create new redis_pool objects with default settings', function(done){
|
||||
var redis_pool = new RedisPool();
|
||||
done();
|
||||
});
|
||||
|
||||
test('RedisPool can create new redis_pool objects with specific settings', function(done){
|
||||
var redis_pool = new RedisPool(_.extend({host:'127.0.0.1', port: '6379'}, test_opts));
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
test('pool object has an acquire function', function(done){
|
||||
var found=false;
|
||||
var functions = _.functions(redis_pool);
|
||||
for (var i=0; i<functions.length; ++i) {
|
||||
if ( functions[i] == 'acquire' ) { found=true; break; }
|
||||
}
|
||||
assert.ok(found);
|
||||
done();
|
||||
});
|
||||
|
||||
test('calling aquire returns a redis client object that can get/set', function(done){
|
||||
redis_pool.acquire(0, function(err, client){
|
||||
if ( err ) { done(err); return; }
|
||||
client.set("key","value");
|
||||
client.get("key", function(err,data){
|
||||
assert.equal(data, "value");
|
||||
redis_pool.release(0, client); // needed to exit tests
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
test('calling aquire on another DB returns a redis client object that can get/set', function(done){
|
||||
redis_pool.acquire(2, function(err, client){
|
||||
if ( err ) { done(err); return; }
|
||||
client.set("key","value");
|
||||
client.get("key", function(err,data){
|
||||
assert.equal(data, "value");
|
||||
redis_pool.release(2, client); // needed to exit tests
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -15,12 +15,12 @@ suite('req2params', function() {
|
||||
|
||||
test('cleans up request', function(done){
|
||||
opts.req2params({headers: { host:'localhost' }, query: {dbuser:'hacker',dbname:'secret'}}, function(err, req) {
|
||||
if ( err ) { done(err); return; }
|
||||
if ( err ) { console.log(err); throw new Error(err); }
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
assert.equal(req.params.dbname, 'test_cartodb_user_1_db', 'could forge dbname: '+ req.params.dbname);
|
||||
assert.equal(req.params.dbname, 'cartodb_test_user_1_db', 'could forge dbname: '+ req.params.dbname);
|
||||
assert.ok(!req.params.hasOwnProperty('dbuser'), 'could inject dbuser ('+req.params.dbuser+')');
|
||||
done();
|
||||
});
|
||||
@@ -28,14 +28,14 @@ suite('req2params', function() {
|
||||
|
||||
test('sets dbname from redis metadata', function(done){
|
||||
opts.req2params({headers: { host:'localhost' }, query: {} }, function(err, req) {
|
||||
if ( err ) { done(err); return; }
|
||||
if ( err ) { console.log(err); throw new Error(err); }
|
||||
//console.dir(req);
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
// database_name for user "localhost" (see test/support/prepare_db.sh)
|
||||
assert.equal(req.params.dbname, 'test_cartodb_user_1_db');
|
||||
assert.equal(req.params.dbname, 'cartodb_test_user_1_db');
|
||||
// unauthenticated request gets no dbuser
|
||||
assert.ok(!req.params.hasOwnProperty('dbuser'), 'could inject dbuser ('+req.params.dbuser+')');
|
||||
done();
|
||||
@@ -44,14 +44,14 @@ suite('req2params', function() {
|
||||
|
||||
test('sets also dbuser for authenticated requests', function(done){
|
||||
opts.req2params({headers: { host:'localhost' }, query: {map_key: '1234'} }, function(err, req) {
|
||||
if ( err ) { done(err); return; }
|
||||
if ( err ) { console.log(err); throw new Error(err); }
|
||||
//console.dir(req);
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
// database_name for user "localhost" (see test/support/prepare_db.sh)
|
||||
assert.equal(req.params.dbname, 'test_cartodb_user_1_db');
|
||||
assert.equal(req.params.dbname, 'cartodb_test_user_1_db');
|
||||
// id for user "localhost" (see test/support/prepare_db.sh)
|
||||
assert.equal(req.params.dbuser, 'test_cartodb_user_1');
|
||||
|
||||
@@ -70,8 +70,7 @@ suite('req2params', function() {
|
||||
cache_buster: 5
|
||||
};
|
||||
test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
|
||||
opts.req2params({ headers: { host:'localhost' }, query: { non_included: 'toberemoved', api_key: 'test', style: 'override', lzma: data }}, function(err, req) {
|
||||
if ( err ) { done(err); return; }
|
||||
opts.req2params({ query: { non_included: 'toberemoved', api_key: 'test', style: 'override', lzma: data }}, function(err, req) {
|
||||
var query = req.params
|
||||
assert.equal(qo.style, query.style)
|
||||
assert.equal(qo.style_version, query.style_version)
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
var assert = require('assert')
|
||||
//, _ = require('underscore')
|
||||
, RedisPool = require('redis-mpool')
|
||||
, SignedMaps = require('../../../lib/cartodb/signed_maps.js')
|
||||
, test_helper = require('../../support/test_helper')
|
||||
, Step = require('step')
|
||||
, tests = module.exports = {};
|
||||
|
||||
suite('signed_maps', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redis_pool = RedisPool(global.environment.redis);
|
||||
|
||||
test('can sign map with open and token-based auth', function(done) {
|
||||
var smap = new SignedMaps(redis_pool);
|
||||
assert.ok(smap);
|
||||
var sig = 'sig1';
|
||||
var map = 'map1';
|
||||
var tok = 'tok1';
|
||||
var crt = {
|
||||
version:'0.0.1',
|
||||
layergroup_id:map,
|
||||
auth: {}
|
||||
};
|
||||
var crt1_id; // by token
|
||||
var crt2_id; // open
|
||||
Step(
|
||||
function() {
|
||||
smap.isAuthorized(sig,map,tok,this);
|
||||
},
|
||||
function checkAuthFailure1(err, authorized) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(!authorized, "unexpectedly authorized");
|
||||
crt.auth.method = 'token';
|
||||
crt.auth.valid_tokens = [tok];
|
||||
smap.addSignature(sig, map, crt, this)
|
||||
},
|
||||
function getCert1(err, id) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(id, "undefined signature id");
|
||||
crt1_id = id; // keep note of it
|
||||
//console.log("Certificate 1 is " + crt1_id);
|
||||
smap.isAuthorized(sig,map,'',this);
|
||||
},
|
||||
function checkAuthFailure2(err, authorized) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(!authorized, "unexpectedly authorized");
|
||||
smap.isAuthorized(sig,map,tok,this);
|
||||
},
|
||||
function checkAuthSuccess1(err, authorized) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(authorized, "unauthorized :(");
|
||||
crt.auth.method = 'open';
|
||||
delete crt.auth.valid_tokens;
|
||||
smap.addSignature(sig, map, crt, this)
|
||||
},
|
||||
function getCert2(err, id) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(id, "undefined signature id");
|
||||
crt2_id = id; // keep note of it
|
||||
//console.log("Certificate 2 is " + crt2_id);
|
||||
smap.isAuthorized(sig,map,'arbitrary',this);
|
||||
},
|
||||
function checkAuthSuccess2_delCert2(err, authorized) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(authorized, "unauthorized :(");
|
||||
var next = this;
|
||||
smap.delCertificate(sig, crt2_id, function(e) {
|
||||
if (e) next(e);
|
||||
else smap.isAuthorized(sig,map,'arbitrary',next);
|
||||
});
|
||||
},
|
||||
function checkAuthFailure3_delCert2(err, authorized) {
|
||||
if ( err ) throw err;
|
||||
assert.ok(!authorized, "unexpectedly authorized");
|
||||
smap.delCertificate(sig, crt1_id, this);
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,400 +0,0 @@
|
||||
var assert = require('assert')
|
||||
//, _ = require('underscore')
|
||||
, RedisPool = require('redis-mpool')
|
||||
, SignedMaps = require('../../../lib/cartodb/signed_maps.js')
|
||||
, TemplateMaps = require('../../../lib/cartodb/template_maps.js')
|
||||
, test_helper = require('../../support/test_helper')
|
||||
, Step = require('step')
|
||||
, tests = module.exports = {};
|
||||
|
||||
suite('template_maps', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redis_pool = RedisPool(global.environment.redis);
|
||||
var signed_maps = new SignedMaps(redis_pool);
|
||||
|
||||
test('does not accept template with unsupported version', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'6.6.6',
|
||||
name:'k', auth: {}, layergroup: {} };
|
||||
Step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
function checkFailed(err) {
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/unsupported.*version/i), err);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept template with missing name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
auth: {}, layergroup: {} };
|
||||
Step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
function checkFailed(err) {
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/missing.*name/i), err);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept template with invalid name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
auth: {}, layergroup: {} };
|
||||
var invalidnames = [ "ab|", "a b", "a@b", "1ab", "_x", "", " x", "x " ];
|
||||
var testNext = function() {
|
||||
if ( ! invalidnames.length ) { done(); return; }
|
||||
var n = invalidnames.pop();
|
||||
tpl.name = n;
|
||||
tmap.addTemplate('me', tpl, function(err) {
|
||||
if ( ! err ) {
|
||||
done(new Error("Unexpected success with invalid name '" + n + "'"));
|
||||
}
|
||||
else if ( ! err.message.match(/template.*name/i) ) {
|
||||
done(new Error("Unexpected error message with invalid name '" + n
|
||||
+ "': " + err));
|
||||
}
|
||||
else {
|
||||
testNext();
|
||||
}
|
||||
});
|
||||
};
|
||||
testNext();
|
||||
});
|
||||
|
||||
test('does not accept template with invalid placeholder name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
name: "valid", placeholders: {},
|
||||
auth: {}, layergroup: {} };
|
||||
var invalidnames = [ "ab|", "a b", "a@b", "1ab", "_x", "", " x", "x " ];
|
||||
var testNext = function() {
|
||||
if ( ! invalidnames.length ) { done(); return; }
|
||||
var n = invalidnames.pop();
|
||||
tpl.placeholders = {};
|
||||
tpl.placeholders[n] = { type:'number', default:1 };
|
||||
tmap.addTemplate('me', tpl, function(err) {
|
||||
if ( ! err ) {
|
||||
done(new Error("Unexpected success with invalid name '" + n + "'"));
|
||||
}
|
||||
else if ( ! err.message.match(/invalid.*name/i) ) {
|
||||
done(new Error("Unexpected error message with invalid name '" + n
|
||||
+ "': " + err));
|
||||
}
|
||||
else {
|
||||
testNext();
|
||||
}
|
||||
});
|
||||
};
|
||||
testNext();
|
||||
});
|
||||
|
||||
test('does not accept template with missing placeholder default', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
name: "valid", placeholders: { v: {} },
|
||||
auth: {}, layergroup: {} };
|
||||
tmap.addTemplate('me', tpl, function(err) {
|
||||
if ( ! err ) {
|
||||
done(new Error("Unexpected success with missing placeholder default"));
|
||||
}
|
||||
else if ( ! err.message.match(/missing default/i) ) {
|
||||
done(new Error("Unexpected error message with missing placeholder default: "
|
||||
+ err));
|
||||
}
|
||||
else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('does not accept template with missing placeholder type', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
name: "valid", placeholders: { v: { default:1 } },
|
||||
auth: {}, layergroup: {} };
|
||||
tmap.addTemplate('me', tpl, function(err) {
|
||||
if ( ! err ) {
|
||||
done(new Error("Unexpected success with missing placeholder type"));
|
||||
}
|
||||
else if ( ! err.message.match(/missing type/i) ) {
|
||||
done(new Error("Unexpected error message with missing placeholder default: "
|
||||
+ err));
|
||||
}
|
||||
else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('add, get and delete a valid template', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
var tpl = { version:'0.0.1',
|
||||
name: 'first', auth: {}, layergroup: {} };
|
||||
Step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
function addOmonimousTemplate(err, id) {
|
||||
if ( err ) throw err;
|
||||
tpl_id = id;
|
||||
assert.equal(tpl_id, 'first');
|
||||
expected_failure = true;
|
||||
// should fail, as it already exists
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
function getTemplate(err) {
|
||||
if ( ! expected_failure && err ) throw err;
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/already exists/i), err);
|
||||
tmap.getTemplate('me', tpl_id, this);
|
||||
},
|
||||
function delTemplate(err, got_tpl) {
|
||||
if ( err ) throw err;
|
||||
assert.deepEqual(got_tpl, tpl);
|
||||
tmap.delTemplate('me', tpl_id, this);
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('add multiple templates, list them', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
var tpl1 = { version:'0.0.1', name: 'first', auth: {}, layergroup: {} };
|
||||
var tpl1_id;
|
||||
var tpl2 = { version:'0.0.1', name: 'second', auth: {}, layergroup: {} };
|
||||
var tpl2_id;
|
||||
Step(
|
||||
function addTemplate1() {
|
||||
tmap.addTemplate('me', tpl1, this);
|
||||
},
|
||||
function addTemplate2(err, id) {
|
||||
if ( err ) throw err;
|
||||
tpl1_id = id;
|
||||
tmap.addTemplate('me', tpl2, this);
|
||||
},
|
||||
function listTemplates(err, id) {
|
||||
if ( err ) throw err;
|
||||
tpl2_id = id;
|
||||
tmap.listTemplates('me', this);
|
||||
},
|
||||
function checkTemplates(err, ids) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(ids.length, 2);
|
||||
assert.ok(ids.indexOf(tpl1_id) != -1, ids.join(','));
|
||||
assert.ok(ids.indexOf(tpl2_id) != -1, ids.join(','));
|
||||
return null;
|
||||
},
|
||||
function delTemplate1(err) {
|
||||
if ( tpl1_id ) {
|
||||
var next = this;
|
||||
tmap.delTemplate('me', tpl1_id, function(e) {
|
||||
if ( err || e ) next(new Error(err + '; ' + e));
|
||||
else next();
|
||||
});
|
||||
} else {
|
||||
if ( err ) throw err;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
function delTemplate2(err) {
|
||||
if ( tpl2_id ) {
|
||||
var next = this;
|
||||
tmap.delTemplate('me', tpl2_id, function(e) {
|
||||
if ( err || e ) next(new Error(err + '; ' + e));
|
||||
else next();
|
||||
});
|
||||
} else {
|
||||
if ( err ) throw err;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('update templates', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
var owner = 'me';
|
||||
var tpl = { version:'0.0.1',
|
||||
name: 'first',
|
||||
auth: { method: 'open' },
|
||||
layergroup: {}
|
||||
};
|
||||
var tpl_id;
|
||||
Step(
|
||||
function addTemplate() {
|
||||
tmap.addTemplate(owner, tpl, this);
|
||||
},
|
||||
// Updating template name should fail
|
||||
function updateTemplateName(err, id) {
|
||||
if ( err ) throw err;
|
||||
tpl_id = id;
|
||||
expected_failure = true;
|
||||
tpl.name = 'second';
|
||||
tmap.updTemplate(owner, tpl_id, tpl, this);
|
||||
},
|
||||
function updateTemplateAuth(err) {
|
||||
if ( err && ! expected_failure) throw err;
|
||||
expected_failure = false;
|
||||
assert.ok(err);
|
||||
tpl.name = 'first';
|
||||
tpl.auth.method = 'token';
|
||||
tmap.updTemplate(owner, tpl_id, tpl, this);
|
||||
},
|
||||
function updateTemplateWithInvalid(err) {
|
||||
if ( err ) throw err;
|
||||
tpl.version = '999.999.999';
|
||||
expected_failure = true;
|
||||
tmap.updTemplate(owner, tpl_id, tpl, this);
|
||||
},
|
||||
function updateUnexistentTemplate(err) {
|
||||
if ( err && ! expected_failure) throw err;
|
||||
expected_failure = false;
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/unsupported.*version/i), err);
|
||||
tpl.version = '0.0.1';
|
||||
expected_failure = true;
|
||||
tmap.updTemplate(owner, 'unexistent', tpl, this);
|
||||
},
|
||||
function delTemplate(err) {
|
||||
if ( err && ! expected_failure) throw err;
|
||||
expected_failure = false;
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/cannot update name/i), err);
|
||||
tmap.delTemplate(owner, tpl_id, this);
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('instanciate templates', function() {
|
||||
var tmap = new TemplateMaps(redis_pool, signed_maps);
|
||||
assert.ok(tmap);
|
||||
|
||||
var tpl1 = {
|
||||
version: '0.0.1',
|
||||
name: 'acceptance1',
|
||||
auth: { method: 'open' },
|
||||
placeholders: {
|
||||
fill: { type: "css_color", default: "red" },
|
||||
color: { type: "css_color", default: "#a0fF9A" },
|
||||
name: { type: "sql_literal", default: "test" },
|
||||
zoom: { type: "number", default: "0" },
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
global_cartocss_version: '2.0.2',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select '<%= name %>' || id, g from t",
|
||||
cartocss: '#layer { marker-fill:<%= fill %>; }'
|
||||
} },
|
||||
{ options: {
|
||||
sql: "select fun('<%= name %>') g from x",
|
||||
cartocss: '#layer { line-color:<%= color %>; marker-fill:<%= color %>; }'
|
||||
} },
|
||||
{ options: {
|
||||
sql: "select g from x",
|
||||
cartocss: '#layer[zoom=<%=zoom%>] { }'
|
||||
} }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var inst = tmap.instance(tpl1, {});
|
||||
|
||||
var lyr = inst.layers[0].options;
|
||||
assert.equal(lyr.sql, "select 'test' || id, g from t");
|
||||
assert.equal(lyr.cartocss, '#layer { marker-fill:red; }');
|
||||
|
||||
lyr = inst.layers[1].options;
|
||||
assert.equal(lyr.sql, "select fun('test') g from x");
|
||||
assert.equal(lyr.cartocss, '#layer { line-color:#a0fF9A; marker-fill:#a0fF9A; }');
|
||||
|
||||
inst = tmap.instance(tpl1, {color:'yellow', name:"it's dangerous"});
|
||||
|
||||
lyr = inst.layers[0].options;
|
||||
assert.equal(lyr.sql, "select 'it''s dangerous' || id, g from t");
|
||||
assert.equal(lyr.cartocss, '#layer { marker-fill:red; }');
|
||||
|
||||
lyr = inst.layers[1].options;
|
||||
assert.equal(lyr.sql, "select fun('it''s dangerous') g from x");
|
||||
assert.equal(lyr.cartocss, '#layer { line-color:yellow; marker-fill:yellow; }');
|
||||
|
||||
// Invalid css_color
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'##ff00ff'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid css_color 2 (too few digits)
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'#ff'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid css_color 3 (too many digits)
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'#1234567'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid number
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'#'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid number/i), err);
|
||||
|
||||
// Invalid number 2
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'23e'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid number/i), err);
|
||||
|
||||
// Valid number
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'-.23e10'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(!err);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
verbose=no
|
||||
tiler_url=http://dev.localhost.lan:8181/tiles/template
|
||||
apikey=${CDB_APIKEY}
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "-v"; then
|
||||
verbose=yes
|
||||
elif test "$1" = "-k"; then
|
||||
shift
|
||||
apikey="$1"
|
||||
elif test "$1" = "-u"; then
|
||||
shift
|
||||
tiler_url="$1"
|
||||
elif test -z "$cfg"; then
|
||||
cfg="$1"
|
||||
else
|
||||
echo "Unused parameter $1" >&2
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$cfg"; then
|
||||
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_config>" >&2
|
||||
echo "Default <tiler_url> is ${tiler_url}" >&2
|
||||
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="curl -skH Content-Type:application/json --data-binary @- ${tiler_url}?api_key=${apikey}"
|
||||
if test x${verbose} = xyes; then
|
||||
cmd="${cmd} -v"
|
||||
fi
|
||||
res=`cat ${cfg} | tr '\n' ' ' | ${cmd}`
|
||||
if test $? -gt 0; then
|
||||
echo "curl command failed: ${cmd}"
|
||||
fi
|
||||
|
||||
if test x${verbose} = xyes; then
|
||||
echo "${res}"
|
||||
fi
|
||||
|
||||
# Successful response contains no space
|
||||
echo "$res" | grep " " && { echo $res && exit 1; }
|
||||
|
||||
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
|
||||
echo $tok
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
verbose=no
|
||||
tiler_url=http://dev.localhost.lan:8181/tiles/template
|
||||
apikey=${CDB_APIKEY}
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "-v"; then
|
||||
verbose=yes
|
||||
elif test "$1" = "-k"; then
|
||||
shift
|
||||
apikey="$1"
|
||||
elif test "$1" = "-u"; then
|
||||
shift
|
||||
tiler_url="$1"
|
||||
elif test -z "$tpl"; then
|
||||
tpl="$1"
|
||||
else
|
||||
echo "Unused parameter $1" >&2
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$tpl"; then
|
||||
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_id>" >&2
|
||||
echo "Default <tiler_url> is ${tiler_url}" >&2
|
||||
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="curl -X DELETE -skH Content-Type:application/json ${tiler_url}/${tpl}?api_key=${apikey}"
|
||||
if test x${verbose} = xyes; then
|
||||
cmd="${cmd} -v"
|
||||
fi
|
||||
res=`${cmd}`
|
||||
if test $? -gt 0; then
|
||||
echo "curl command failed: ${cmd}"
|
||||
fi
|
||||
|
||||
if test x${verbose} = xyes; then
|
||||
echo "${res}"
|
||||
fi
|
||||
|
||||
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
|
||||
echo $tok
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
verbose=no
|
||||
tiler_url=http://dev.localhost.lan:8181/tiles/template
|
||||
apikey=${CDB_APIKEY}
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "-v"; then
|
||||
verbose=yes
|
||||
elif test "$1" = "-k"; then
|
||||
shift
|
||||
apikey="$1"
|
||||
elif test "$1" = "-u"; then
|
||||
shift
|
||||
tiler_url="$1"
|
||||
elif test -z "$tpl"; then
|
||||
tpl="$1"
|
||||
elif test -z "$cfg"; then
|
||||
cfg="$1"
|
||||
else
|
||||
echo "Unused parameter $1" >&2
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$tpl"; then
|
||||
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_id> [<template_params>]" >&2
|
||||
echo "Default <tiler_url> is ${tiler_url}" >&2
|
||||
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test -z "$cfg"; then
|
||||
cfg="/dev/null"
|
||||
fi
|
||||
|
||||
tiler_url="${tiler_url}/${tpl}"
|
||||
|
||||
cmd="curl -X POST -skH Content-Type:application/json --data-binary @- ${tiler_url}?api_key=${apikey}"
|
||||
if test x${verbose} = xyes; then
|
||||
cmd="${cmd} -v"
|
||||
fi
|
||||
res=`cat ${cfg} | tr '\n' ' ' | ${cmd}`
|
||||
if test $? -gt 0; then
|
||||
echo "curl command failed: ${cmd}"
|
||||
fi
|
||||
|
||||
if test x${verbose} = xyes; then
|
||||
echo "${res}"
|
||||
fi
|
||||
|
||||
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
|
||||
echo $tok
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
verbose=no
|
||||
tiler_url=http://dev.localhost.lan:8181/tiles/template
|
||||
apikey=${CDB_APIKEY}
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "-k"; then
|
||||
shift
|
||||
apikey="$1"
|
||||
elif test "$1" = "-u"; then
|
||||
shift
|
||||
tiler_url="$1"
|
||||
elif test -z "$tpl"; then
|
||||
tpl="$1"
|
||||
else
|
||||
echo "Unused parameter $1" >&2
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$tpl"; then
|
||||
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_config>" >&2
|
||||
echo "Default <tiler_url> is ${tiler_url}" >&2
|
||||
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
basedir=$(cd $(dirname $0); cd ..; pwd)
|
||||
export CDB_APIKEY=${apikey}
|
||||
max=3000000
|
||||
i=0
|
||||
while test "$i" -le "$max"; do
|
||||
tpln=`cat ${tpl} | sed "s/\"name\":\"\(.*\)\"/\"name\":\"\1${i}\"/"`
|
||||
tpl_id=`echo ${tpln} | ${basedir}/create_template -u ${tiler_url} /dev/stdin`
|
||||
if test $? -ne 0; then
|
||||
echo $tpl_id >&2
|
||||
break
|
||||
fi
|
||||
tpl_id=`echo ${tpln} | ${basedir}/update_template -u ${tiler_url} ${tpl_id} /dev/stdin`
|
||||
if test $? -ne 0; then
|
||||
echo $tpl_id >&2
|
||||
break
|
||||
fi
|
||||
out=`${basedir}/delete_template -u ${tiler_url} ${tpl_id}`
|
||||
if test $? -ne 0; then
|
||||
echo $out >&2
|
||||
break
|
||||
fi
|
||||
i=$((i+1))
|
||||
if test `expr $i % 100` -eq 0; then
|
||||
echo -n "."
|
||||
fi
|
||||
done
|
||||
@@ -2,84 +2,28 @@
|
||||
|
||||
# TODO: port to node, if you really need it
|
||||
|
||||
ENV='development';
|
||||
BASEDIR=`cd $(dirname $0)/../; pwd`
|
||||
REDIS_PORT=6379 # default port
|
||||
|
||||
|
||||
if test -z "$1"; then
|
||||
echo "Usage: $0 [--env <environment>] <username> [<tablename>|~<token>]" >&2
|
||||
echo " environment defaults to 'development'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
username=""
|
||||
token=""
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "--env"; then
|
||||
shift; ENV="$1"; shift
|
||||
elif test -z "$username"; then
|
||||
username="$1"; shift
|
||||
elif test -z "$token"; then
|
||||
token="$1"; shift
|
||||
else
|
||||
echo "Unused option $1" >&2
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Using environment '${ENV}'"
|
||||
|
||||
CONFIG="${BASEDIR}/config/environments/${ENV}.js"
|
||||
REDIS_PORT=`node -e "console.log(require('${CONFIG}').redis.port)"`
|
||||
if test $? -ne 0; then
|
||||
echo "Usage: $0 <username> [<tablename>|~<token>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
username="$1"
|
||||
token="$2"
|
||||
|
||||
dbname=`redis-cli -p ${REDIS_PORT} -n 5 hget "rails:users:${username}" "database_name"`
|
||||
if test $? -ne 0; then
|
||||
exit 1
|
||||
fi
|
||||
if test -z "${dbname}"; then
|
||||
echo "Username ${username} unknown by redis on port ${REDIS_PORT} (try CARTODB/script/restore_redis?)" >&2
|
||||
echo "Username ${username} unknown by redis (try CARTODB/script/restore_redis?)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Database name for user ${username}: ${dbname}" # only if verbose?
|
||||
if test -n "$token"; then
|
||||
rec=`redis-cli get "map_style|${dbname}|${token}"`
|
||||
if test -z "${rec}"; then
|
||||
echo "${token}: no such map style known by redis on port ${REDIS_PORT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
#echo "${rec}"
|
||||
escrec=`echo "${rec}" | sed -e 's/\\\\/\\\\\\\\/g'`
|
||||
#echo "${escrec}"
|
||||
node <<EOF
|
||||
var x=JSON.parse('${escrec}');
|
||||
console.log('style: ' + x.style);
|
||||
console.log('version: ' + x.version);
|
||||
|
||||
global.environment = require('${CONFIG}');
|
||||
var serverOptions = require('${BASEDIR}/lib/cartodb/server_options'); // _after_ setting global.environment
|
||||
var grainstore = require('${BASEDIR}/node_modules/windshaft/node_modules/grainstore/lib/grainstore');
|
||||
var mml_store = new grainstore.MMLStore(serverOptions.redis, serverOptions.grainstore);
|
||||
var builderconfig = {dbname:'${dbname}'};
|
||||
if ( '${token}'.match(/^~/) ) {
|
||||
builderconfig.token = '${token}'.substring(1);
|
||||
} else {
|
||||
builderconfig.table = '${token}';
|
||||
}
|
||||
var mml_builder = mml_store.mml_builder(builderconfig,
|
||||
function(err, payload) {
|
||||
if ( err ) throw err;
|
||||
mml_builder.toXML(function(err, xml) {
|
||||
if ( err ) throw err;
|
||||
console.log('- XML - ');
|
||||
console.log(xml);
|
||||
});
|
||||
});
|
||||
EOF
|
||||
#echo "${rec}" | sed -e 's/\\n/\n/g' -e 's/\\//g'
|
||||
redis-cli get "map_style|${dbname}|${token}" | sed -e 's/\\n/\n/g' -e 's/\\//g'
|
||||
else
|
||||
redis-cli keys "map_style|${dbname}|*"
|
||||
fi
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
verbose=no
|
||||
tiler_url=http://dev.localhost.lan:8181/tiles/template
|
||||
apikey=${CDB_APIKEY}
|
||||
|
||||
while test -n "$1"; do
|
||||
if test "$1" = "-v"; then
|
||||
verbose=yes
|
||||
elif test "$1" = "-k"; then
|
||||
shift
|
||||
apikey="$1"
|
||||
elif test "$1" = "-u"; then
|
||||
shift
|
||||
tiler_url="$1"
|
||||
elif test -z "$tpl"; then
|
||||
tpl="$1"
|
||||
elif test -z "$cfg"; then
|
||||
cfg="$1"
|
||||
else
|
||||
echo "Unused parameter $1" >&2
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$cfg"; then
|
||||
echo "Usage: $0 [-v] [-k <api_key>] [-u <tiler_url>] <template_id> <template_config>" >&2
|
||||
echo "Default <tiler_url> is ${tiler_url}" >&2
|
||||
echo "Default <api_key> is read from CDB_APIKEY env variable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="curl -X PUT -skH Content-Type:application/json --data-binary @- ${tiler_url}/${tpl}?api_key=${apikey}"
|
||||
if test x${verbose} = xyes; then
|
||||
cmd="${cmd} -v"
|
||||
fi
|
||||
res=`cat ${cfg} | tr '\n' ' ' | ${cmd}`
|
||||
if test $? -gt 0; then
|
||||
echo "curl command failed: ${cmd}"
|
||||
fi
|
||||
|
||||
if test x${verbose} = xyes; then
|
||||
echo "${res}"
|
||||
fi
|
||||
|
||||
tok=`echo "$res" | sed 's/.*"template_id":"\([^"]*\)".*/\1/'`
|
||||
echo $tok
|
||||
Reference in New Issue
Block a user