diff --git a/app/controllers/query_controller.js b/app/controllers/query_controller.js index 3c959264a..e3bc8c692 100644 --- a/app/controllers/query_controller.js +++ b/app/controllers/query_controller.js @@ -5,7 +5,8 @@ var step = require('step'); var assert = require('assert'); var PSQL = require('cartodb-psql'); var CachedQueryTables = require('../services/cached-query-tables'); -const pgEntitiesAccessValidator = require('../services/pg-entities-access-validator'); +const PGEntitiesAccessValidator = require('../services/pg-entities-access-validator'); +const pgEntitiesAccessValidator = new PGEntitiesAccessValidator(); var queryMayWrite = require('../utils/query_may_write'); var formats = require('../models/formats'); diff --git a/app/services/pg-entities-access-validator.js b/app/services/pg-entities-access-validator.js index e2d902623..521d9fc59 100644 --- a/app/services/pg-entities-access-validator.js +++ b/app/services/pg-entities-access-validator.js @@ -14,48 +14,60 @@ const FORBIDDEN_ENTITIES = { ] }; -const Validator = { +function isForbiddenTable (schema, table) { + return FORBIDDEN_ENTITIES[schema] && FORBIDDEN_ENTITIES[schema].includes(table); +} + +function isForbiddenSchema (schema) { + return FORBIDDEN_ENTITIES[schema] && FORBIDDEN_ENTITIES[schema][0] === '*'; +} + +function isForbiddenEntity (entity) { + const { schema_name: schema, table_name: table } = entity; + + return isForbiddenSchema(schema) || isForbiddenTable(schema, table); +} + +function isSystemEntity (entity) { + const { table_name: table } = entity; + return table.match(/\bpg_/); +} + +module.exports = class PGEntitiesAccessValidator { validate(affectedTables, authorizationLevel) { let hardValidationResult = true; let softValidationResult = true; if (!!affectedTables && affectedTables.tables) { if (global.settings.validatePGEntitiesAccess) { - hardValidationResult = this.hardValidation(affectedTables.tables); + hardValidationResult = this._hardValidation(affectedTables.tables); } if (authorizationLevel !== 'master') { - softValidationResult = this.softValidation(affectedTables.tables); + softValidationResult = this._softValidation(affectedTables.tables); } } return hardValidationResult && softValidationResult; - }, + } - hardValidation(tables) { + _hardValidation(tables) { for (let table of tables) { - if (FORBIDDEN_ENTITIES[table.schema_name] && FORBIDDEN_ENTITIES[table.schema_name].length && - ( - FORBIDDEN_ENTITIES[table.schema_name][0] === '*' || - FORBIDDEN_ENTITIES[table.schema_name].includes(table.table_name) - ) - ) { + if (isForbiddenEntity(table)) { return false; } } return true; - }, + } - softValidation(tables) { + _softValidation(tables) { for (let table of tables) { - if (table.table_name.match(/\bpg_/)) { + if (isSystemEntity(table)) { return false; } } return true; } -}; - -module.exports = Validator; +}; diff --git a/test/acceptance/export/involved-tables.js b/test/acceptance/export/involved-tables.js new file mode 100644 index 000000000..f72489024 --- /dev/null +++ b/test/acceptance/export/involved-tables.js @@ -0,0 +1,101 @@ +require('../../helper'); + +const server = require('../../../app/server')(); +const assert = require('../../support/assert'); +const querystring = require('querystring'); + +describe('Read restriction on "geometry_columns"', function() { + describe('OGR formats', function () { + it('GET /api/v1/sql downloads KML file from a private table', function (done){ + const query = querystring.stringify({ + api_key: 1234, + q: "SELECT * FROM private_table LIMIT 1", + format: "kml", + filename: 'private_table' + }); + assert.response(server, { + url: '/api/v1/sql?' + query, + headers: { host: 'vizzuality.cartodb.com' }, + method: 'GET' + },{ }, function(err, res){ + assert.equal(res.statusCode, 200, res.body); + + const cd = res.headers['content-disposition']; + + assert.ok(/filename=private_table.kml/gi.test(cd), cd); + assert.equal(res.headers['content-type'], 'application/kml; charset=utf-8'); + + done(); + }); + }); + + it('GET /api/v1/sql downloads a KML file from a public table', function (done){ + const query = querystring.stringify({ + q: "SELECT * FROM populated_places_simple_reduced LIMIT 1", + format: "kml", + filename: 'populated_places_simple_reduced' + }); + assert.response(server, { + url: '/api/v1/sql?' + query, + headers: { host: 'vizzuality.cartodb.com' }, + method: 'GET' + },{ }, function(err, res){ + assert.equal(res.statusCode, 200, res.body); + + const cd = res.headers['content-disposition']; + + assert.ok(/filename=populated_places_simple_reduced.kml/gi.test(cd), cd); + assert.equal(res.headers['content-type'], 'application/kml; charset=utf-8'); + + done(); + }); + }); + + it('GET /api/v1/sql downloads a KML file from "geometry_columns" view', function (done){ + const query = querystring.stringify({ + q: "SELECT * FROM geometry_columns LIMIT 1", + format: "kml", + filename: 'geometry_columns' + }); + + assert.response(server, { + url: '/api/v1/sql?' + query, + headers: { host: 'vizzuality.cartodb.com' }, + method: 'GET' + },{ }, function(err, res){ + assert.equal(res.statusCode, 200, res.body); + + const cd = res.headers['content-disposition']; + + assert.ok(/filename=geometry_columns.kml/gi.test(cd), cd); + assert.equal(res.headers['content-type'], 'application/kml; charset=utf-8'); + + done(); + }); + }); + + it('GET /api/v1/sql downloads a KML file with master api_key from "geometry_columns" view', function (done){ + const query = querystring.stringify({ + api_key: 1234, + q: "SELECT * FROM geometry_columns LIMIT 1", + format: "kml", + filename: 'geometry_columns' + }); + + assert.response(server, { + url: '/api/v1/sql?' + query, + headers: { host: 'vizzuality.cartodb.com' }, + method: 'GET' + },{ }, function(err, res){ + assert.equal(res.statusCode, 200, res.body); + + const cd = res.headers['content-disposition']; + + assert.ok(/filename=geometry_columns.kml/gi.test(cd), cd); + assert.equal(res.headers['content-type'], 'application/kml; charset=utf-8'); + + done(); + }); + }); + }); +}); diff --git a/test/unit/pg-entities-access-validator.test.js b/test/unit/pg-entities-access-validator.test.js index 80d15e597..8934d16e6 100644 --- a/test/unit/pg-entities-access-validator.test.js +++ b/test/unit/pg-entities-access-validator.test.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const pgEntitiesAccessValidator = require('../../app/services/pg-entities-access-validator'); +const PGEntitiesAccessValidator = require('../../app/services/pg-entities-access-validator'); const fakeAffectedTables = [{ schema_name: 'schema', @@ -72,6 +72,8 @@ const fakeAffectedTablesTopologyKO = [ describe('pg entities access validator with validatePGEntitiesAccess enabled', function () { + const pgEntitiesAccessValidator = new PGEntitiesAccessValidator(); + before(function() { global.settings.validatePGEntitiesAccess = true; }); @@ -99,75 +101,75 @@ describe('pg entities access validator with validatePGEntitiesAccess enabled', f it('validate function: should not be validated', function () { let authorizationLevel = 'master'; assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel), false ); - + authorizationLevel = 'regular'; assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel), false ); assert.strictEqual( - pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel), + pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel), false ); }); - it('hardValidation function', function () { - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTables), true); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesCartodbOK), true); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesPublicOK), true); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesTopologyOK), true); - - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesCarto), false); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesCartodbKO), false); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesPgcatalog), false); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesInfo), false); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesPublicKO), false); - assert.strictEqual(pgEntitiesAccessValidator.hardValidation(fakeAffectedTablesTopologyKO), false); + it('_hardValidation function', function () { + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTables), true); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesCartodbOK), true); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesPublicOK), true); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesTopologyOK), true); + + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesCarto), false); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesCartodbKO), false); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesPgcatalog), false); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesInfo), false); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesPublicKO), false); + assert.strictEqual(pgEntitiesAccessValidator._hardValidation(fakeAffectedTablesTopologyKO), false); }); - it('softValidation function', function () { - assert.strictEqual(pgEntitiesAccessValidator.softValidation(fakeAffectedTablesCartodbKO), true); - assert.strictEqual(pgEntitiesAccessValidator.softValidation(fakeAffectedTablesPgcatalog), false); + it('_softValidation function', function () { + assert.strictEqual(pgEntitiesAccessValidator._softValidation(fakeAffectedTablesCartodbKO), true); + assert.strictEqual(pgEntitiesAccessValidator._softValidation(fakeAffectedTablesPgcatalog), false); }); });