Removes TemplateMaps dependency on SignedMaps

- Token validation is done against the template
 - Template is always extended with default values for auth and placeholders
 - MapConfig is extended, in order to validate auth_toknes, with template info:
    - template name
    - template auth
 - No more locks to create, update or delete templates
    - Trusting in redis' hash semantics
    - Some tradeoffs:
        * A client having more templates than allowed by a race condition
        between limit (HLEN) check and creation (HSET)
        * Updating a template could happen while the deleting it, resulting in
        in a new template
        * Templates already instantiated will be accessible thrught their
        layergroup so it is possible to continue requesting tiles/grids/etc.
 - Authorization is now handled by template maps
This commit is contained in:
Raul Ochoa
2015-01-22 15:40:40 +01:00
parent 64b5a64e1b
commit e8ab3a48c6
6 changed files with 259 additions and 402 deletions
+2 -6
View File
@@ -1,7 +1,6 @@
var _ = require('underscore')
, Step = require('step')
, Windshaft = require('windshaft')
, SignedMaps = require('./signed_maps.js')
, TemplateMaps = require('./template_maps.js')
, Cache = require('./cache_validator')
, os = require('os')
@@ -49,13 +48,10 @@ var CartodbWindshaft = function(serverOptions) {
//
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
var signedMaps = new SignedMaps(redisPool);
serverOptions.signedMaps = signedMaps;
var templateMapsOpts = {
max_user_templates: global.environment.maxUserTemplates
};
var templateMaps = new TemplateMaps(redisPool, signedMaps, templateMapsOpts);
var templateMaps = new TemplateMaps(redisPool, templateMapsOpts);
serverOptions.templateMaps = templateMaps;
// boot
@@ -142,7 +138,7 @@ var CartodbWindshaft = function(serverOptions) {
var TemplateMapsController = require('./controllers/template_maps'),
templateMapsController = new TemplateMapsController(
ws, serverOptions, templateMaps, serverOptions.signedMaps, cartoData, template_baseurl
ws, serverOptions, templateMaps, cartoData, template_baseurl
);
templateMapsController.register(ws);
+16 -39
View File
@@ -1,11 +1,10 @@
var Step = require('step');
var _ = require('underscore');
function TemplateMapsController(app, serverOptions, templateMaps, signedMaps, metadataBackend, templateBaseUrl) {
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl) {
this.app = app;
this.serverOptions = serverOptions;
this.templateMaps = templateMaps;
this.signedMaps = signedMaps;
this.metadataBackend = metadataBackend;
this.templateBaseUrl = templateBaseUrl;
}
@@ -350,7 +349,6 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
this.app.doCORS(res);
var response = {};
var template;
var layergroup;
var fakereq; // used for call to createLayergroup
@@ -373,20 +371,20 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
function getTemplate(){
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function checkAuthorized(err, data) {
function checkAuthorized(err, templateValue) {
if ( req.profiler ) req.profiler.done('getTemplate');
if ( err ) throw err;
if ( ! data ) {
if ( ! templateValue ) {
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
err.http_status = 404;
throw err;
}
template = data;
var cert = self.templateMaps.getTemplateCertificate(template);
template = templateValue;
var authorized = false;
try {
// authorizedByCert will throw if unauthorized
authorized = self.signedMaps.authorizedByCert(cert, auth_token);
authorized = self.templateMaps.isAuthorized(template, auth_token);
} catch (err) {
// we catch to add http_status
err.http_status = 403;
@@ -397,11 +395,11 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
err.http_status = 403;
throw err;
}
/*if ( (! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') && req.query.callback === undefined) {
throw new Error('template POST data must be of type application/json, it is instead ');
}*/
//var template_params = req.body;
if ( req.profiler ) req.profiler.done('authorizedByCert');
if (req.profiler) {
req.profiler.done('authorizedByCert');
}
return self.templateMaps.instance(template, template_params);
},
function prepareParams(err, instance){
@@ -426,34 +424,13 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
fakereq.params.api_key = val;
self.app.createLayergroup(layergroup, fakereq, this);
},
function signLayergroup(err, resp) {
// NOTE: createLayergroup uses profiler.start()/end() internally
//if ( req.profiler ) req.profiler.done('createLayergroup');
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
function prepareResponse(err, layergroup) {
if ( err ) {
throw err;
}
self.signedMaps.signMap(signer, map_id, crt_id, this);
},
function prepareResponse(err) {
if ( req.profiler ) req.profiler.done('signMap');
if ( err ) throw err;
//console.log("Response from createLayergroup: "); console.dir(response);
// Add the signature part to the token!
var tplhash = self.templateMaps.fingerPrint(template).substring(0,8);
if ( req.profiler ) req.profiler.done('fingerPrint');
response.layergroupid = cdbuser + '@' + tplhash + '@' + response.layergroupid;
return response;
layergroup.layergroupid = cdbuser + '@' + tplhash + '@' + layergroup.layergroupid;
return layergroup;
},
callback
);
+13 -4
View File
@@ -515,11 +515,20 @@ module.exports = function(redisPool) {
var auth_token = req.params.auth_token;
//console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + " with auth_token " + auth_token);
var mapStore = req.app.mapStore;
if (!mapStore) {
throw new Error('Unable to retrieve map configuration token');
}
mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
throw err;
}
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
callback(null, authorized ? signer : null);
});
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
+166 -280
View File
@@ -11,16 +11,12 @@ var crypto = require('crypto'),
// 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
//
// @param opts TemplateMap options. Supported elements:
// 'max_user_templates' limit on the number of per-user
//
//
function TemplateMaps(redis_pool, signed_maps, opts) {
function TemplateMaps(redis_pool, opts) {
this.redis_pool = redis_pool;
this.signed_maps = signed_maps;
this.opts = opts || {};
// Database containing templates
@@ -152,12 +148,23 @@ o._checkInvalidTemplate = function(template) {
}
}
// Check certificate validity
var cert = this.getTemplateCertificate(template);
var err = this.signed_maps.checkInvalidCertificate(cert);
if ( err ) return err;
var auth = template.auth || {};
// TODO: run more checks over template format ?
switch ( auth.method ) {
case 'open':
break;
case 'token':
if ( ! _.isArray(auth.valid_tokens) )
return new Error("Invalid 'token' authentication: missing valid_tokens");
if ( ! auth.valid_tokens.length )
return new Error("Invalid 'token' authentication: no valid_tokens");
break;
default:
return new Error("Unsupported authentication method: " + auth.method);
break;
}
return false;
};
//--------------- PUBLIC API -------------------------------------
@@ -175,6 +182,15 @@ o.getTemplateCertificate = function(template) {
};
};
function templateDefaults(template) {
var templateAuth = _.defaults({}, template.auth || {}, {
method: 'open'
});
return _.defaults({ auth: templateAuth }, template, {
placeholders: {}
});
}
// Add a template
//
// NOTE: locks user+template_name or fails
@@ -188,102 +204,54 @@ o.getTemplateCertificate = function(template) {
// 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;
var self = this;
// Procedure:
//
// - Check against limit
// 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
//
//
template = templateDefaults(template);
var usr_tpl_key = this.key_usr_tpl({owner:owner});
var gotLock = false;
var that = this;
var limit = that._userTemplateLimit();
Step(
function checkLimit() {
if ( ! limit ) return 0;
that._redisCmd('HLEN', [ usr_tpl_key ], this);
},
// try to obtain a lock
function obtainLock(err, len) {
if ( err ) throw err;
if ( limit && len >= limit ) {
throw new Error("User '" + owner + "' reached limit on number of templates (" + len + "/" + limit + ")");
}
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 ( err && ! gotLock ) throw err;
// 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);
var invalidError = this._checkInvalidTemplate(template);
if ( invalidError ) {
return callback(invalidError);
}
var templateName = template.name;
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
var limit = this._userTemplateLimit();
Step(
function checkLimit() {
if ( ! limit ) {
return 0;
}
self._redisCmd('HLEN', [ userTemplatesKey ], this);
},
function installTemplateIfDoesNotExist(err, numberOfTemplates) {
if ( err ) {
throw err;
}
if ( limit && numberOfTemplates >= limit ) {
throw new Error("User '" + owner + "' reached limit on number of templates " +
"("+ numberOfTemplates + "/" + limit + ")");
}
self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
},
function validateInstallation(err, wasSet) {
if ( err ) {
throw err;
}
if ( ! wasSet ) {
throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists");
}
return true;
},
function finish(err) {
callback(err, templateName);
}
);
};
// 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
@@ -292,82 +260,24 @@ o.addTemplate = function(owner, template, callback) {
// @param callback function(err)
//
o.delTemplate = function(owner, tpl_id, callback) {
var usr_tpl_key = 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();
var self = this;
Step(
function deleteTemplate() {
self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
},
function handleDeletion(err, deleted) {
if (err) {
throw err;
}
if (!deleted) {
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
}
return true;
},
function finish(err) {
callback(err);
}
});
},
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
@@ -387,104 +297,50 @@ o.delTemplate = function(owner, tpl_id, callback) {
// @param callback function(err)
//
o.updTemplate = function(owner, tpl_id, template, callback) {
var self = this;
var invalidError = this._checkInvalidTemplate(template);
if ( invalidError ) {
callback(invalidError);
return;
}
template = templateDefaults(template);
var tplname = template.name;
var invalidError = this._checkInvalidTemplate(template);
if ( tpl_id != tplname ) {
callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + tplname + "')"));
return;
}
var usr_tpl_key = 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);
if ( invalidError ) {
return callback(invalidError);
}
);
var templateName = template.name;
if ( tpl_id != templateName ) {
return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')"));
}
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
Step(
function getExistingTemplate() {
self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
},
function updateTemplate(err, currentTemplate) {
if (err) {
throw err;
}
if (!currentTemplate) {
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
}
self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
},
function handleTemplateUpdate(err, didSetNewField) {
if (err) {
throw err;
}
if (didSetNewField) {
console.warn('New template created on update operation');
}
return true;
},
function finish(err) {
callback(err);
}
);
};
// List user templates
@@ -509,20 +365,43 @@ o.listTemplates = function(owner, callback) {
// Return full template definition
//
o.getTemplate = function(owner, tpl_id, callback) {
var that = this;
Step(
function getTemplate() {
that._redisCmd('HGET', [ that.key_usr_tpl({owner:owner}), tpl_id ], this);
},
function parseTemplate(err, tpl_val) {
if ( err ) throw err;
// Should we strip auth_id ?
return JSON.parse(tpl_val);
},
function finish(err, tpl) {
callback(err, tpl);
var self = this;
Step(
function getTemplate() {
self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
},
function parseTemplate(err, tpl_val) {
if ( err ) throw err;
return JSON.parse(tpl_val);
},
function finish(err, tpl) {
callback(err, tpl);
}
);
};
o.isAuthorized = function(template, authTokens) {
if (!template) {
return false;
}
);
authTokens = _.isArray(authTokens) ? authTokens : [authTokens];
var templateAuth = template.auth;
if (!templateAuth) {
return false;
}
if (templateAuth.method === 'open') {
return true;
}
if (templateAuth.method === 'token') {
return _.intersection(templateAuth.valid_tokens, authTokens).length > 0;
}
return false;
};
// Perform placeholder substitutions on a template
@@ -594,6 +473,13 @@ o.instance = function(template, params) {
if ( lyropt.sql) lyropt.sql = _replaceVars(lyropt.sql, all_params);
// Anything else ?
}
// extra information about the template
layergroup.template = {
name: template.name,
auth: template.auth
};
return layergroup;
};
+44 -54
View File
@@ -57,6 +57,29 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
}
};
function makeTemplate(templateName) {
return {
version: '0.0.1',
name: templateName || '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'
} }
]
}
};
}
function extendDefaultsTemplate(template) {
return _.extend({}, template, {auth: {method: 'open'}, placeholders: {}});
}
test("can add template, returning id", function(done) {
var errors = [];
@@ -118,15 +141,12 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
if ( m.match(/^map_(tpl|crt)|/) )
return m;
});
if ( todrop.length != 2 ) {
if ( todrop.length !== 1 ) {
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);
@@ -457,15 +477,12 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
if ( m.match(/^map_(tpl|crt)|/) )
return m;
});
if ( todrop.length != 2 ) {
if ( todrop.length !== 1 ) {
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);
@@ -492,7 +509,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
url: '/tiles/template?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_acceptance1)
data: JSON.stringify(makeTemplate())
}
assert.response(server, post_request, {},
function(res) { next(null, res); });
@@ -530,7 +547,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
url: '/tiles/template/unexistent/?api_key=1234',
method: 'PUT',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_acceptance1)
data: JSON.stringify(makeTemplate())
}
var next = this;
assert.response(server, put_request, {},
@@ -548,7 +565,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
method: 'PUT',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_acceptance1)
data: JSON.stringify(makeTemplate())
}
var next = this;
assert.response(server, put_request, {},
@@ -572,15 +589,12 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
if ( m.match(/^map_(tpl|crt)|/) )
return m;
});
if ( todrop.length != 2 ) {
if ( todrop.length !== 1 ) {
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);
@@ -607,7 +621,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
url: '/tiles/template?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_acceptance1)
data: JSON.stringify(makeTemplate())
}
assert.response(server, post_request, {},
function(res) { next(null, res); });
@@ -653,7 +667,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('template'),
"Missing 'template' from response body: " + res.body);
assert.deepEqual(template_acceptance1, parsed.template);
assert.deepEqual(extendDefaultsTemplate(makeTemplate()), parsed.template);
return null;
},
function finish(err) {
@@ -664,15 +678,12 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
if ( m.match(/^map_(tpl|crt)|/) )
return m;
});
if ( todrop.length != 2 ) {
if ( todrop.length !== 1 ) {
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);
@@ -699,7 +710,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
url: '/tiles/template?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_acceptance1)
data: JSON.stringify(makeTemplate())
}
assert.response(server, post_request, {},
function(res) { next(null, res); });
@@ -728,7 +739,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('template'),
"Missing 'template' from response body: " + res.body);
assert.deepEqual(template_acceptance1, parsed.template);
assert.deepEqual(extendDefaultsTemplate(makeTemplate()), parsed.template);
var del_request = {
url: '/tiles/template/' + tpl_id,
method: 'DELETE',
@@ -1011,16 +1022,10 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkTileDeleted(err, res) {
function checkTileAvailable(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 403,
'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);
assert.equal(res.statusCode, 200, 'Tile should be accessible');
assert.equal(res.headers['content-type'], "image/png");
return null;
},
function finish(err) {
@@ -1230,16 +1235,10 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkTileDeleted(err, res) {
function checkTorqueTileAvailable(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 403,
'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);
assert.equal(res.statusCode, 200, 'Torque tile should be accessible');
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
return null;
},
function finish(err) {
@@ -1426,16 +1425,10 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkTileDeleted(err, res) {
function checkLayerAttributesAvailable(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 403,
'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);
assert.equal(res.statusCode, 200, 'Layer attributes should be accessible');
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
return null;
},
function finish(err) {
@@ -1906,15 +1899,12 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
if ( m.match(/^map_(tpl|crt)|/) )
return m;
});
if ( todrop.length != 2 ) {
if ( todrop.length !== 1 ) {
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);
+18 -19
View File
@@ -1,17 +1,16 @@
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')
, _ = require('underscore')
, 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);
var validTemplate = {
version:'0.0.1',
@@ -22,7 +21,7 @@ suite('template_maps', function() {
var owner = 'me';
test('does not accept template with unsupported version', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'6.6.6',
name:'k', auth: {}, layergroup: {} };
@@ -42,7 +41,7 @@ suite('template_maps', function() {
});
test('does not accept template with missing name', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
auth: {}, layergroup: {} };
@@ -62,7 +61,7 @@ suite('template_maps', function() {
});
test('does not accept template with invalid name', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
auth: {}, layergroup: {} };
@@ -88,7 +87,7 @@ suite('template_maps', function() {
});
test('does not accept template with invalid placeholder name', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
name: "valid", placeholders: {},
@@ -116,7 +115,7 @@ suite('template_maps', function() {
});
test('does not accept template with missing placeholder default', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
name: "valid", placeholders: { v: {} },
@@ -136,7 +135,7 @@ suite('template_maps', function() {
});
test('does not accept template with missing placeholder type', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
name: "valid", placeholders: { v: { default:1 } },
@@ -158,7 +157,7 @@ suite('template_maps', function() {
// See http://github.com/CartoDB/Windshaft-cartodb/issues/128
test('does not accept template with invalid token auth (undefined tokens)',
function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
name: "invalid_auth1", placeholders: { },
@@ -178,7 +177,7 @@ suite('template_maps', function() {
});
test('add, get and delete a valid template', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
var tpl_id;
@@ -204,7 +203,7 @@ suite('template_maps', function() {
},
function delTemplate(err, got_tpl) {
if ( err ) throw err;
assert.deepEqual(got_tpl, tpl);
assert.deepEqual(got_tpl, _.extend({}, tpl, {auth: {method: 'open'}, placeholders: {}}));
tmap.delTemplate('me', tpl_id, this);
},
function finish(err) {
@@ -214,7 +213,7 @@ suite('template_maps', function() {
});
test('add multiple templates, list them', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
var tpl1 = { version:'0.0.1', name: 'first', auth: {}, layergroup: {} };
@@ -273,7 +272,7 @@ suite('template_maps', function() {
});
test('update templates', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
var owner = 'me';
@@ -333,7 +332,7 @@ suite('template_maps', function() {
});
test('instanciate templates', function() {
var tmap = new TemplateMaps(redis_pool, signed_maps);
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl1 = {
@@ -431,7 +430,7 @@ suite('template_maps', function() {
// Can set a limit on the number of user templates
test('can limit number of user templates', function(done) {
var tmap = new TemplateMaps(redis_pool, signed_maps, {
var tmap = new TemplateMaps(redis_pool, {
max_user_templates: 2
});
assert.ok(tmap);
@@ -530,7 +529,7 @@ suite('template_maps', function() {
test('_obtainTemplateLock with no previous value, happy case', function(done) {
runWithRedisStubbed({hget: null, hset: 1}, function() {
var templateMaps = new TemplateMaps(redis_pool, signed_maps);
var templateMaps = new TemplateMaps(redis_pool);
templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) {
assert.ok(!err);
@@ -542,7 +541,7 @@ suite('template_maps', function() {
test('_obtainTemplateLock no lock for non expired ttl, simulates obtaining two locks at same time', function(done) {
runWithRedisStubbed({hget: Date.now()}, function() {
var templateMaps = new TemplateMaps(redis_pool, signed_maps);
var templateMaps = new TemplateMaps(redis_pool);
templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) {
assert.ok(!!err);
@@ -561,7 +560,7 @@ suite('template_maps', function() {
};
var lockTtl = 1000;
runWithRedisStubbed({hget: Date.now() - lockTtl, hset: true}, function() {
var templateMaps = new TemplateMaps(redis_pool, signed_maps, {lock_ttl: lockTtl});
var templateMaps = new TemplateMaps(redis_pool, {lock_ttl: lockTtl});
templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) {
assert.ok(!!err);
@@ -582,7 +581,7 @@ suite('template_maps', function() {
};
var lockTtl = 1000;
runWithRedisStubbed({hget: Date.now() - lockTtl - 1, hset: true}, function() {
var templateMaps = new TemplateMaps(redis_pool, signed_maps, {lock_ttl: lockTtl});
var templateMaps = new TemplateMaps(redis_pool, {lock_ttl: lockTtl});
templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) {
assert.ok(!err);