diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index 636a355a..d5d16454 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -493,10 +493,6 @@ module.exports = function(redisPool) { // 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 diff --git a/lib/cartodb/signed_maps.js b/lib/cartodb/signed_maps.js deleted file mode 100644 index 43bf13be..00000000 --- a/lib/cartodb/signed_maps.js +++ /dev/null @@ -1,397 +0,0 @@ -var crypto = require('crypto'); -var Step = require('step'); -var _ = require('underscore'); - -var debug = global.environment ? global.environment.debug : undefined; - -// 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); - } - ); -}; - -o._getAuthMethod = function(auth) { - return auth.method || 'open'; -}; - -//--------------- PUBLIC API ------------------------------------- - -/// Check formal validity of a certificate -// -/// Return an Error instance if invalid, null otherwise -/// -o.checkInvalidCertificate = function(cert) { - //console.log("Checking cert: "); console.dir(cert); - if ( cert.version !== "0.0.1" ) { - return new Error("Unsupported certificate version " + cert.version); - } - - if ( ! cert.auth ) { -console.log("Cert is : "); console.dir(cert); - return new Error("No certificate authorization"); - } - - var method = this._getAuthMethod(cert.auth); - - switch ( method ) { - case 'open': - break; - case 'token': - if ( ! _.isArray(cert.auth.valid_tokens) ) - return new Error("Invalid 'token' authentication: missing valid_tokens"); - if ( ! cert.auth.valid_tokens.length ) - return new Error("Invalid 'token' authentication: no valid_tokens"); - break; - default: - return new Error("Unsupported authentication method: " + cert.auth.method); - break; - } - - return null; // all valid -} - -// Check if the given certificate authorizes waiver of "auth" -o.authorizedByCert = function(cert, auth) { - auth = _.isArray(auth) ? auth : [auth]; - - var err = this.checkInvalidCertificate(cert); - if ( err ) throw err; - - var method = this._getAuthMethod(cert.auth); - - // Open authentication certificates are always authorized - if ( method === 'open' ) return true; - - // Token based authentication requires valid token - if ( method === 'token' ) { - return _.intersection(cert.auth.valid_tokens, auth).length > 0; - } - - 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 redisClient; - var db = that.db_signatures; - var authorized = false; - var certificate_id_list; - var missing_certificates = []; - if ( debug ) { - console.log("Check auth from signer '" + signer + "' on map '" + map_id + "' with auth '" + auth + "'"); - } - Step( - function getRedisClient() { - that.redis_pool.acquire(db, this); - }, - function getMapSignatures(err, client) { - if ( err ) throw err; - redisClient = client; - var map_sig_key = _.template(that.key_map_sig, {signer:signer, map_id:map_id}); - redisClient.SMEMBERS(map_sig_key, this); - //that._redisCmd('SMEMBERS', [ map_sig_key ], this); - }, - function getCertificates(err, crt_lst) { - if ( err ) throw err; - if ( debug ) { - console.log("Map '" + map_id + "' is signed by " + crt_lst.length + " certificates of user '" + signer); - } - 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); - redisClient.HMGET(map_crt_key, crt_lst, this); - }, - function checkCertificates(err, certs) { - if ( err ) throw err; - for (var i=0; itpl_val) this.key_usr_tpl = dot.template("map_tpl|{{=it.owner}}"); @@ -52,14 +50,6 @@ o._userTemplateLimit = function() { return this.opts['max_user_templates'] || 0; }; -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 * @@ -89,35 +79,6 @@ o._redisCmd = function(redisFunc, redisArgs, callback) { ); }; -// @param callback function(err, obtained) -o._obtainTemplateLock = function(owner, tpl_id, callback) { - var that = this, - lockKey = this.key_usr_tpl_lck({owner:owner}); - Step ( - function obtainLock() { - that._redisCmd('HGET', [lockKey, tpl_id], this); - }, - function checkLock(err, lockTime) { - if (err) { throw err; } - - var _newLockTime = Date.now(); - if (!lockTime || ((_newLockTime - lockTime) > that.lock_ttl)) { - that._redisCmd('HSET', [lockKey, tpl_id, _newLockTime], this); - } else { - throw new Error("Template '" + tpl_id + "' of user '" + owner + "' is locked"); - } - }, - function finish(err, hsetValue) { - callback(err, !!hsetValue); - } - ); -}; - -// @param callback function(err, deleted) -o._releaseTemplateLock = function(owner, tpl_id, callback) { - this._redisCmd('HDEL', [this.key_usr_tpl_lck({owner:owner}), tpl_id], callback); -}; - var _reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/; o._checkInvalidTemplate = function(template) { if ( template.version != '0.0.1' ) { @@ -167,21 +128,6 @@ o._checkInvalidTemplate = function(template) { return false; }; -//--------------- 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) { - return { - version: '0.0.1', - template_id: template.name, - auth: template.auth - }; -}; - function templateDefaults(template) { var templateAuth = _.defaults({}, template.auth || {}, { method: 'open' @@ -191,6 +137,8 @@ function templateDefaults(template) { }); } +//--------------- PUBLIC API ------------------------------------- + // Add a template // // NOTE: locks user+template_name or fails diff --git a/test/unit/cartodb/template_maps.test.js b/test/unit/cartodb/template_maps.test.js index efe80d53..6abda6e3 100644 --- a/test/unit/cartodb/template_maps.test.js +++ b/test/unit/cartodb/template_maps.test.js @@ -510,88 +510,4 @@ suite('template_maps', function() { ); }); - var redisCmdFunc = TemplateMaps.prototype._redisCmd; - - function runWithRedisStubbed(stubbedCommands, func) { - TemplateMaps.prototype._redisCmd = function(redisFunc, redisArgs, callback) { - redisFunc = redisFunc.toLowerCase(); - if (stubbedCommands.hasOwnProperty(redisFunc)) { - callback(null, stubbedCommands[redisFunc]); - } else { - throw 'Unknown command'; - } - }; - - func(); - - TemplateMaps.prototype._redisCmd = redisCmdFunc; - } - - test('_obtainTemplateLock with no previous value, happy case', function(done) { - runWithRedisStubbed({hget: null, hset: 1}, function() { - var templateMaps = new TemplateMaps(redis_pool); - - templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) { - assert.ok(!err); - assert.ok(gotLock); - done(); - }); - }); - }); - - 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); - - templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) { - assert.ok(!!err); - assert.equal(gotLock, false); - done(); - }); - }); - }); - - - test('_obtainTemplateLock no lock for non expired ttl, last millisecond of valid ttl', function(done) { - var nowValue = Date.now(), - nowFunc = Date.now; - Date.now = function() { - return nowValue; - }; - var lockTtl = 1000; - runWithRedisStubbed({hget: Date.now() - lockTtl, hset: true}, function() { - var templateMaps = new TemplateMaps(redis_pool, {lock_ttl: lockTtl}); - - templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) { - assert.ok(!!err); - assert.equal(gotLock, false); - - Date.now = nowFunc; - - done(); - }); - }); - }); - - test('_obtainTemplateLock gets lock for expired ttl, first millisecond of invalid ttl', function(done) { - var nowValue = Date.now(), - nowFunc = Date.now; - Date.now = function() { - return nowValue; - }; - var lockTtl = 1000; - runWithRedisStubbed({hget: Date.now() - lockTtl - 1, hset: true}, function() { - var templateMaps = new TemplateMaps(redis_pool, {lock_ttl: lockTtl}); - - templateMaps._obtainTemplateLock(owner, validTemplate.name, function(err, gotLock) { - assert.ok(!err); - assert.ok(gotLock); - - Date.now = nowFunc; - - done(); - }); - }); - }); - });