diff --git a/lib/cartodb/models/mapconfig_named_layers_adapter.js b/lib/cartodb/models/mapconfig_named_layers_adapter.js new file mode 100644 index 00000000..e75b771b --- /dev/null +++ b/lib/cartodb/models/mapconfig_named_layers_adapter.js @@ -0,0 +1,76 @@ +var queue = require('queue-async'); +var _ = require('underscore'); + +function MapConfigNamedLayersAdapter(templateMaps) { + this.templateMaps = templateMaps; +} + +module.exports = MapConfigNamedLayersAdapter; + +MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, callback) { + var self = this; + + var adaptLayersQueue = queue(layers.length); + + function adaptLayer(layer, callback) { + if (layer.type === 'named') { + + if (!layer.options.name) { + return callback(new Error('Missing Named Map name in layer options')); + } + + var templateName = layer.options.name; + var templateConfigParams = layer.options.config || {}; + var templateAuthTokens = layer.options.auth_tokens; + + self.templateMaps.getTemplate(username, templateName, function(err, template) { + if (err) { + return callback(err); + } + + if (self.templateMaps.isAuthorized(template, templateAuthTokens)) { + var nestedNamedLayers = template.layergroup.layers.filter(function(layer) { + return layer.type === 'named'; + }); + + if (nestedNamedLayers.length > 0) { + var nestedNamedMapsError = new Error('Nested named layers are not allowed'); + // nestedNamedMapsError.http_status = 400; + return callback(nestedNamedMapsError); + } + + try { + var templateLayergroupConfig = self.templateMaps.instance(template, templateConfigParams); + return callback(null, templateLayergroupConfig.layers); + } catch (err) { + return callback(err); + } + } else { + var unauthorizedError = new Error('Unauthorized template instantiation'); + // unauthorizedError.http_status = 403; + return callback(unauthorizedError); + } + }); + } else { + return callback(null, layer); + } + } + + layers.forEach(function(layer) { + adaptLayersQueue.defer(adaptLayer, layer); + }); + + function layersAdaptQueueFinish(err, layers) { + if (err) { + return callback(err); + } + + if (!layers || layers.length === 0) { + return callback(new Error('Missing layers array from layergroup config')); + } + + return callback(null, _.flatten(layers)); + } + + adaptLayersQueue.awaitAll(layersAdaptQueueFinish); +}; diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index 399f259e..99321634 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -5,6 +5,7 @@ var QueryTablesApi = require('./api/query_tables_api'); var crypto = require('crypto'); var LZMA = require('lzma').LZMA; var TemplateMaps = require('./template_maps.js'); +var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter'); // This is for backward compatibility with 1.3.3 if ( _.isUndefined(global.environment.sqlapi.domain) ) { @@ -111,6 +112,8 @@ module.exports = function(redisPool) { }); me.templateMaps = templateMaps; + var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); + /* This whole block is about generating X-Cache-Channel { */ // TODO: review lifetime of elements of this cache @@ -303,6 +306,17 @@ module.exports = function(redisPool) { }); }; + me.beforeLayergroupCreate = function(req, requestMapConfig, callback) { + mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, function(err, layers) { + if (err) { + return callback(err); + } + + requestMapConfig.layers = layers; + return callback(null, requestMapConfig) + }); + }; + me.afterLayergroupCreate = function(req, mapconfig, response, callback) { var token = response.layergroupid;