Merge pull request #489 from CartoDB/remove-auth-fallback

Remove auth fallback
This commit is contained in:
Eneko Lakasta 2018-06-11 12:34:54 +02:00 committed by GitHub
commit f08ad3becd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 310 additions and 335 deletions

View File

@ -1,29 +1,38 @@
/** /**
* this module allows to auth user using an pregenerated api key * this module allows to auth user using an pregenerated api key
*/ */
function ApikeyAuth(req, metadataBackend, username, apikey) { function ApikeyAuth(req, metadataBackend, username, apikeyToken) {
this.req = req; this.req = req;
this.metadataBackend = metadataBackend; this.metadataBackend = metadataBackend;
this.username = username; this.username = username;
this.apikey = apikey; this.apikeyToken = apikeyToken;
} }
module.exports = ApikeyAuth; module.exports = ApikeyAuth;
function errorUserNotFoundMessageTemplate (user) { function usernameMatches(basicAuthUsername, requestUsername) {
return `Sorry, we can't find CARTO user '${user}'. Please check that you have entered the correct domain.`; return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
} }
ApikeyAuth.prototype.verifyCredentials = function (callback) { ApikeyAuth.prototype.verifyCredentials = function (callback) {
this.metadataBackend.getApikey(this.username, this.apikey, (err, apikey) => { this.metadataBackend.getApikey(this.username, this.apikeyToken, (err, apikey) => {
if (err) { if (err) {
err.http_status = 404; err.http_status = 500;
err.message = errorUserNotFoundMessageTemplate(this.username); err.message = 'Unexpected error';
return callback(err); return callback(err);
} }
if (isApiKeyFound(apikey)) { if (isApiKeyFound(apikey)) {
if (!usernameMatches(apikey.user, this.username)) {
const usernameError = new Error('Forbidden');
usernameError.type = 'auth';
usernameError.subtype = 'api-key-username-mismatch';
usernameError.http_status = 403;
return callback(usernameError);
}
if (!apikey.grantsSql) { if (!apikey.grantsSql) {
const forbiddenError = new Error('forbidden'); const forbiddenError = new Error('forbidden');
forbiddenError.http_status = 403; forbiddenError.http_status = 403;
@ -31,33 +40,28 @@ ApikeyAuth.prototype.verifyCredentials = function (callback) {
return callback(forbiddenError); return callback(forbiddenError);
} }
return callback(null, verifyRequest(this.apikey, this.apikey)); return callback(null, getAuthorizationLevel(apikey));
} } else {
const apiKeyNotFoundError = new Error('Unauthorized');
apiKeyNotFoundError.type = 'auth';
apiKeyNotFoundError.subtype = 'api-key-not-found';
apiKeyNotFoundError.http_status = 401;
// Auth API Fallback return callback(apiKeyNotFoundError);
this.metadataBackend.getAllUserDBParams(this.username, (err, dbParams) => { }
if (err) {
err.http_status = 404;
err.message = errorUserNotFoundMessageTemplate(this.username);
return callback(err);
}
callback(null, verifyRequest(this.apikey, dbParams.apikey));
});
}); });
}; };
ApikeyAuth.prototype.hasCredentials = function () { ApikeyAuth.prototype.hasCredentials = function () {
return !!this.apikey; return !!this.apikeyToken;
}; };
ApikeyAuth.prototype.getCredentials = function () { ApikeyAuth.prototype.getCredentials = function () {
return this.apikey; return this.apikeyToken;
}; };
function verifyRequest(apikey, requiredApikey) { function getAuthorizationLevel(apikey) {
return (apikey === requiredApikey && apikey !== 'default_public'); return apikey.type;
} }
function isApiKeyFound(apikey) { function isApiKeyFound(apikey) {

View File

@ -3,33 +3,33 @@ var ApiKeyAuth = require('./apikey'),
function AuthApi(req, requestParams) { function AuthApi(req, requestParams) {
this.req = req; this.req = req;
this.authBacked = getAuthBackend(req, requestParams); this.authBackend = getAuthBackend(req, requestParams);
this._hasCredentials = null; this._hasCredentials = null;
} }
AuthApi.prototype.getType = function () { AuthApi.prototype.getType = function () {
if (this.authBacked instanceof ApiKeyAuth) { if (this.authBackend instanceof ApiKeyAuth) {
return 'apiKey'; return 'apiKey';
} else if (this.authBacked instanceof OAuthAuth) { } else if (this.authBackend instanceof OAuthAuth) {
return 'oAuth'; return 'oAuth';
} }
}; };
AuthApi.prototype.hasCredentials = function() { AuthApi.prototype.hasCredentials = function() {
if (this._hasCredentials === null) { if (this._hasCredentials === null) {
this._hasCredentials = this.authBacked.hasCredentials(); this._hasCredentials = this.authBackend.hasCredentials();
} }
return this._hasCredentials; return this._hasCredentials;
}; };
AuthApi.prototype.getCredentials = function() { AuthApi.prototype.getCredentials = function() {
return this.authBacked.getCredentials(); return this.authBackend.getCredentials();
}; };
AuthApi.prototype.verifyCredentials = function(callback) { AuthApi.prototype.verifyCredentials = function(callback) {
if (this.hasCredentials()) { if (this.hasCredentials()) {
this.authBacked.verifyCredentials(callback); this.authBackend.verifyCredentials(callback);
} else { } else {
callback(null, false); callback(null, false);
} }

View File

@ -142,7 +142,8 @@ var oAuth = (function(){
}, false); }, false);
}, },
function finishValidation(err, hasValidSignature) { function finishValidation(err, hasValidSignature) {
return callback(err, hasValidSignature || null); const authorizationLevel = hasValidSignature ? 'master' : null;
return callback(err, authorizationLevel);
} }
); );
}; };

View File

@ -51,13 +51,13 @@ JobController.prototype.route = function (app) {
function composeJobMiddlewares (metadataBackend, userDatabaseService, jobService, statsdClient, userLimitsService) { function composeJobMiddlewares (metadataBackend, userDatabaseService, jobService, statsdClient, userLimitsService) {
return function jobMiddlewares (action, jobMiddleware, endpointGroup) { return function jobMiddlewares (action, jobMiddleware, endpointGroup) {
const forceToBeAuthenticated = true; const forceToBeMaster = true;
return [ return [
initializeProfilerMiddleware('job'), initializeProfilerMiddleware('job'),
userMiddleware(), userMiddleware(metadataBackend),
rateLimitsMiddleware(userLimitsService, endpointGroup), rateLimitsMiddleware(userLimitsService, endpointGroup),
authorizationMiddleware(metadataBackend, forceToBeAuthenticated), authorizationMiddleware(metadataBackend, forceToBeMaster),
connectionParamsMiddleware(userDatabaseService), connectionParamsMiddleware(userDatabaseService),
jobMiddleware(jobService), jobMiddleware(jobService),
setServedByDBHostHeader(), setServedByDBHostHeader(),

View File

@ -33,12 +33,14 @@ function QueryController(metadataBackend, userDatabaseService, tableCache, stats
QueryController.prototype.route = function (app) { QueryController.prototype.route = function (app) {
const { base_url } = global.settings; const { base_url } = global.settings;
const forceToBeMaster = false;
const queryMiddlewares = endpointGroup => { const queryMiddlewares = endpointGroup => {
return [ return [
initializeProfilerMiddleware('query'), initializeProfilerMiddleware('query'),
userMiddleware(), userMiddleware(this.metadataBackend),
rateLimitsMiddleware(this.userLimitsService, endpointGroup), rateLimitsMiddleware(this.userLimitsService, endpointGroup),
authorizationMiddleware(this.metadataBackend), authorizationMiddleware(this.metadataBackend, forceToBeMaster),
connectionParamsMiddleware(this.userDatabaseService), connectionParamsMiddleware(this.userDatabaseService),
timeoutLimitsMiddleware(this.metadataBackend), timeoutLimitsMiddleware(this.metadataBackend),
this.handleQuery.bind(this), this.handleQuery.bind(this),
@ -68,7 +70,7 @@ QueryController.prototype.handleQuery = function (req, res, next) {
var filename = requestedFilename; var filename = requestedFilename;
var requestedSkipfields = params.skipfields; var requestedSkipfields = params.skipfields;
const { user: username, userDbParams: dbopts, authDbParams, userLimits, authenticated } = res.locals; const { user: username, userDbParams: dbopts, authDbParams, userLimits, authorizationLevel } = res.locals;
var skipfields; var skipfields;
var dp = params.dp; // decimal point digits (defaults to 6) var dp = params.dp; // decimal point digits (defaults to 6)
@ -143,7 +145,7 @@ QueryController.prototype.handleQuery = function (req, res, next) {
var pg = new PSQL(authDbParams); var pg = new PSQL(authDbParams);
var skipCache = authenticated; var skipCache = authorizationLevel === 'master';
self.queryTables.getAffectedTablesFromQuery(pg, sql, skipCache, function(err, result) { self.queryTables.getAffectedTablesFromQuery(pg, sql, skipCache, function(err, result) {
if (err) { if (err) {
@ -162,7 +164,7 @@ QueryController.prototype.handleQuery = function (req, res, next) {
} }
checkAborted('setHeaders'); checkAborted('setHeaders');
if(!pgEntitiesAccessValidator.validate(affectedTables, authenticated)) { if(!pgEntitiesAccessValidator.validate(affectedTables, authorizationLevel)) {
const syntaxError = new SyntaxError("system tables are forbidden"); const syntaxError = new SyntaxError("system tables are forbidden");
syntaxError.http_status = 403; syntaxError.http_status = 403;
throw(syntaxError); throw(syntaxError);

View File

@ -1,7 +1,7 @@
const AuthApi = require('../auth/auth_api'); const AuthApi = require('../auth/auth_api');
const basicAuth = require('basic-auth'); const basicAuth = require('basic-auth');
module.exports = function authorization (metadataBackend, forceToBeAuthenticated = false) { module.exports = function authorization (metadataBackend, forceToBeMaster = false) {
return function authorizationMiddleware (req, res, next) { return function authorizationMiddleware (req, res, next) {
const { user } = res.locals; const { user } = res.locals;
const credentials = getCredentialsFromRequest(req); const credentials = getCredentialsFromRequest(req);
@ -19,7 +19,7 @@ module.exports = function authorization (metadataBackend, forceToBeAuthenticated
const params = Object.assign({ metadataBackend }, res.locals, req.query, req.body); const params = Object.assign({ metadataBackend }, res.locals, req.query, req.body);
const authApi = new AuthApi(req, params); const authApi = new AuthApi(req, params);
authApi.verifyCredentials(function (err, authenticated) { authApi.verifyCredentials(function (err, authorizationLevel) {
if (req.profiler) { if (req.profiler) {
req.profiler.done('authorization'); req.profiler.done('authorization');
} }
@ -28,9 +28,9 @@ module.exports = function authorization (metadataBackend, forceToBeAuthenticated
return next(err); return next(err);
} }
res.locals.authenticated = authenticated; res.locals.authorizationLevel = authorizationLevel;
if (forceToBeAuthenticated && !authenticated) { if (forceToBeMaster && authorizationLevel !== 'master') {
return next(new Error('permission denied')); return next(new Error('permission denied'));
} }

View File

@ -1,8 +1,8 @@
module.exports = function connectionParams (userDatabaseService) { module.exports = function connectionParams (userDatabaseService) {
return function connectionParamsMiddleware (req, res, next) { return function connectionParamsMiddleware (req, res, next) {
const { user, api_key: apikeyToken, authenticated } = res.locals; const { user, api_key: apikeyToken, authorizationLevel } = res.locals;
userDatabaseService.getConnectionParams(user, apikeyToken, authenticated, userDatabaseService.getConnectionParams(user, apikeyToken, authorizationLevel,
function (err, userDbParams, authDbParams) { function (err, userDbParams, authDbParams) {
if (req.profiler) { if (req.profiler) {
req.profiler.done('getConnectionParams'); req.profiler.done('getConnectionParams');

View File

@ -1,6 +1,6 @@
module.exports = function timeoutLimits (metadataBackend) { module.exports = function timeoutLimits (metadataBackend) {
return function timeoutLimitsMiddleware (req, res, next) { return function timeoutLimitsMiddleware (req, res, next) {
const { user, authenticated } = res.locals; const { user, authorizationLevel } = res.locals;
metadataBackend.getUserTimeoutRenderLimits(user, function (err, timeoutRenderLimit) { metadataBackend.getUserTimeoutRenderLimits(user, function (err, timeoutRenderLimit) {
if (req.profiler) { if (req.profiler) {
@ -12,7 +12,7 @@ module.exports = function timeoutLimits (metadataBackend) {
} }
const userLimits = { const userLimits = {
timeout: authenticated ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic timeout: (authorizationLevel === 'master') ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
}; };
res.locals.userLimits = userLimits; res.locals.userLimits = userLimits;

View File

@ -1,10 +1,36 @@
const CdbRequest = require('../models/cartodb_request'); const CdbRequest = require('../models/cartodb_request');
module.exports = function user () { module.exports = function user(metadataBackend) {
const cdbRequest = new CdbRequest(); const cdbRequest = new CdbRequest();
return function userMiddleware (req, res, next) { return function userMiddleware (req, res, next) {
res.locals.user = cdbRequest.userByReq(req); res.locals.user = getUserNameFromRequest(req, cdbRequest);
next();
checkUserExists(metadataBackend, res.locals.user, function(err, userExists) {
if (err || !userExists) {
const error = new Error('Unauthorized');
error.type = 'auth';
error.subtype = 'user-not-found';
error.http_status = 404;
error.message = errorUserNotFoundMessageTemplate(res.locals.user);
next(error);
}
return next();
});
}; };
}; };
function getUserNameFromRequest(req, cdbRequest) {
return cdbRequest.userByReq(req);
}
function checkUserExists(metadataBackend, userName, callback) {
metadataBackend.getUserId(userName, function(err) {
callback(err, !err);
});
}
function errorUserNotFoundMessageTemplate(user) {
return `Sorry, we can't find CARTO user '${user}'. Please check that you have entered the correct domain.`;
}

View File

@ -30,8 +30,6 @@ var JobBackend = require('../batch/job_backend');
var JobCanceller = require('../batch/job_canceller'); var JobCanceller = require('../batch/job_canceller');
var JobService = require('../batch/job_service'); var JobService = require('../batch/job_service');
var UserDatabaseMetadataService = require('../batch/user_database_metadata_service');
var cors = require('./middlewares/cors'); var cors = require('./middlewares/cors');
var GenericController = require('./controllers/generic_controller'); var GenericController = require('./controllers/generic_controller');
@ -160,8 +158,7 @@ function App(statsClient) {
var jobPublisher = new JobPublisher(redisPool); var jobPublisher = new JobPublisher(redisPool);
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend); var jobCanceller = new JobCanceller();
var jobCanceller = new JobCanceller(userDatabaseMetadataService);
var jobService = new JobService(jobBackend, jobCanceller); var jobService = new JobService(jobBackend, jobCanceller);
var genericController = new GenericController(); var genericController = new GenericController();

View File

@ -15,7 +15,7 @@ const FORBIDDEN_ENTITIES = {
}; };
const Validator = { const Validator = {
validate(affectedTables, authenticated) { validate(affectedTables, authorizationLevel) {
let hardValidationResult = true; let hardValidationResult = true;
let softValidationResult = true; let softValidationResult = true;
@ -24,7 +24,7 @@ const Validator = {
hardValidationResult = this.hardValidation(affectedTables.tables); hardValidationResult = this.hardValidation(affectedTables.tables);
} }
if (!authenticated) { if (authorizationLevel !== 'master') {
softValidationResult = this.softValidation(affectedTables.tables); softValidationResult = this.softValidation(affectedTables.tables);
} }
} }

View File

@ -1,5 +1,3 @@
const _ = require('underscore');
function isApiKeyFound(apikey) { function isApiKeyFound(apikey) {
return apikey.type !== null && return apikey.type !== null &&
apikey.user !== null && apikey.user !== null &&
@ -15,6 +13,10 @@ function errorUserNotFoundMessageTemplate (user) {
return `Sorry, we can't find CARTO user '${user}'. Please check that you have entered the correct domain.`; return `Sorry, we can't find CARTO user '${user}'. Please check that you have entered the correct domain.`;
} }
function isOauthAuthorization({ apikeyToken, authorizationLevel }) {
return (authorizationLevel === 'master') && !apikeyToken;
}
/** /**
* Callback is invoked with `dbParams` and `authDbParams`. * Callback is invoked with `dbParams` and `authDbParams`.
* `dbParams` depends on AuthApi verification so it might return a public user with just SELECT permission, where * `dbParams` depends on AuthApi verification so it might return a public user with just SELECT permission, where
@ -25,7 +27,7 @@ function errorUserNotFoundMessageTemplate (user) {
* @param {String} cdbUsername * @param {String} cdbUsername
* @param {Function} callback (err, dbParams, authDbParams) * @param {Function} callback (err, dbParams, authDbParams)
*/ */
UserDatabaseService.prototype.getConnectionParams = function (username, apikeyToken, authenticated, callback) { UserDatabaseService.prototype.getConnectionParams = function (username, apikeyToken, authorizationLevel, callback) {
this.metadataBackend.getAllUserDBParams(username, (err, dbParams) => { this.metadataBackend.getAllUserDBParams(username, (err, dbParams) => {
if (err) { if (err) {
err.http_status = 404; err.http_status = 404;
@ -34,37 +36,14 @@ UserDatabaseService.prototype.getConnectionParams = function (username, apikeyTo
return callback(err); return callback(err);
} }
const dbopts = { const commonDBConfiguration = {
port: global.settings.db_port, port: global.settings.db_port,
pass: global.settings.db_pubuser_pass host: dbParams.dbhost,
dbname: dbParams.dbname,
}; };
dbopts.host = dbParams.dbhost; this.metadataBackend.getMasterApikey(username, (err, masterApikey) => {
dbopts.dbname = dbParams.dbname;
dbopts.user = (!!dbParams.dbpublicuser) ? dbParams.dbpublicuser : global.settings.db_pubuser;
const user = _.template(global.settings.db_user, {user_id: dbParams.dbuser});
let pass = null;
if (global.settings.hasOwnProperty('db_user_pass')) {
pass = _.template(global.settings.db_user_pass, {
user_id: dbParams.dbuser,
user_password: dbParams.dbpass
});
}
if (authenticated) {
dbopts.user = user;
dbopts.pass = pass;
}
let authDbOpts = _.defaults({ user: user, pass: pass }, dbopts);
if (!apikeyToken) {
return callback(null, dbopts, authDbOpts);
}
this.metadataBackend.getApikey(username, apikeyToken, (err, apikey) => {
if (err) { if (err) {
err.http_status = 404; err.http_status = 404;
err.message = errorUserNotFoundMessageTemplate(username); err.message = errorUserNotFoundMessageTemplate(username);
@ -72,16 +51,53 @@ UserDatabaseService.prototype.getConnectionParams = function (username, apikeyTo
return callback(err); return callback(err);
} }
if (!isApiKeyFound(apikey)) { if (!isApiKeyFound(masterApikey)) {
return callback(null, dbopts, authDbOpts); const apiKeyNotFoundError = new Error('Unauthorized');
apiKeyNotFoundError.type = 'auth';
apiKeyNotFoundError.subtype = 'api-key-not-found';
apiKeyNotFoundError.http_status = 401;
return callback(apiKeyNotFoundError);
} }
dbopts.user = apikey.databaseRole; const masterDBConfiguration = Object.assign({
dbopts.pass = apikey.databasePassword; user: masterApikey.databaseRole,
pass: masterApikey.databasePassword
},
commonDBConfiguration);
authDbOpts = _.defaults({ user: user, pass: pass }, dbopts); if (isOauthAuthorization({ apikeyToken, authorizationLevel})) {
callback(null, masterDBConfiguration, masterDBConfiguration);
}
callback(null, dbopts, authDbOpts); // Default Api key fallback
apikeyToken = apikeyToken || 'default_public';
this.metadataBackend.getApikey(username, apikeyToken, (err, apikey) => {
if (err) {
err.http_status = 404;
err.message = errorUserNotFoundMessageTemplate(username);
return callback(err);
}
if (!isApiKeyFound(apikey)) {
const apiKeyNotFoundError = new Error('Unauthorized');
apiKeyNotFoundError.type = 'auth';
apiKeyNotFoundError.subtype = 'api-key-not-found';
apiKeyNotFoundError.http_status = 401;
return callback(apiKeyNotFoundError);
}
const DBConfiguration = Object.assign({
user: apikey.databaseRole,
pass: apikey.databasePassword
},
commonDBConfiguration);
callback(null, DBConfiguration, masterDBConfiguration);
});
}); });
}); });
}; };

View File

@ -21,7 +21,7 @@ module.exports = function batchFactory (metadataBackend, redisPool, name, statsd
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var queryRunner = new QueryRunner(userDatabaseMetadataService); var queryRunner = new QueryRunner(userDatabaseMetadataService);
var jobCanceller = new JobCanceller(userDatabaseMetadataService); var jobCanceller = new JobCanceller();
var jobService = new JobService(jobBackend, jobCanceller); var jobService = new JobService(jobBackend, jobCanceller);
var jobRunner = new JobRunner(jobService, jobQueue, queryRunner, metadataBackend, statsdClient); var jobRunner = new JobRunner(jobService, jobQueue, queryRunner, metadataBackend, statsdClient);
var logger = new BatchLogger(loggerPath); var logger = new BatchLogger(loggerPath);

View File

@ -2,24 +2,26 @@
var PSQL = require('cartodb-psql'); var PSQL = require('cartodb-psql');
function JobCanceller(userDatabaseMetadataService) { function JobCanceller() {
this.userDatabaseMetadataService = userDatabaseMetadataService;
} }
module.exports = JobCanceller; module.exports = JobCanceller;
JobCanceller.prototype.cancel = function (job, callback) { JobCanceller.prototype.cancel = function (job, callback) {
this.userDatabaseMetadataService.getUserMetadata(job.data.user, function (err, userDatabaseMetadata) {
if (err) {
return callback(err);
}
doCancel(job.data.job_id, userDatabaseMetadata, callback); const dbConfiguration = {
}); host: job.data.host,
port: job.data.port,
dbname: job.data.dbname,
user: job.data.dbuser,
pass: job.data.pass,
};
doCancel(job.data.job_id, dbConfiguration, callback);
}; };
function doCancel(job_id, userDatabaseMetadata, callback) { function doCancel(job_id, dbConfiguration, callback) {
var pg = new PSQL(userDatabaseMetadata); var pg = new PSQL(dbConfiguration);
getQueryPID(pg, job_id, function (err, pid) { getQueryPID(pg, job_id, function (err, pid) {
if (err) { if (err) {

View File

@ -18,13 +18,9 @@ QueryRunner.prototype.run = function (job_id, sql, user, timeout, dbparams, call
return this._run(dbparams, job_id, sql, timeout, callback); return this._run(dbparams, job_id, sql, timeout, callback);
} }
this.userDatabaseMetadataService.getUserMetadata(user, (err, userDBParams) => { const dbConfigurationError = new Error('Batch Job DB misconfiguration');
if (err) {
return callback(err);
}
this._run(userDBParams, job_id, sql, timeout, callback); return callback(dbConfigurationError);
});
}; };
QueryRunner.prototype._run = function (dbparams, job_id, sql, timeout, callback) { QueryRunner.prototype._run = function (dbparams, job_id, sql, timeout, callback) {

View File

@ -1,7 +1,5 @@
'use strict'; 'use strict';
var _ = require('underscore');
function UserDatabaseMetadataService(metadataBackend) { function UserDatabaseMetadataService(metadataBackend) {
this.metadataBackend = metadataBackend; this.metadataBackend = metadataBackend;
} }
@ -23,20 +21,9 @@ UserDatabaseMetadataService.prototype.parseMetadataToDatabase = function (userDa
var dbopts = {}; var dbopts = {};
dbopts.pass = dbParams.dbpass || global.settings.db_pubuser_pass;
dbopts.port = dbParams.dbport || global.settings.db_batch_port || global.settings.db_port; dbopts.port = dbParams.dbport || global.settings.db_batch_port || global.settings.db_port;
dbopts.host = dbParams.dbhost; dbopts.host = dbParams.dbhost;
dbopts.dbname = dbParams.dbname; dbopts.dbname = dbParams.dbname;
dbopts.user = (!!dbParams.dbpublicuser) ? dbParams.dbpublicuser : global.settings.db_pubuser;
// batch is secure so it's going to be authenticated by default
dbopts.authenticated = true;
dbopts.user = _.template(global.settings.db_user, { user_id: dbParams.dbuser });
dbopts.pass = _.template(global.settings.db_user_pass, {
user_id: dbParams.dbuser,
user_password: dbParams.dbpass
});
return dbopts; return dbopts;
}; };

View File

@ -23,7 +23,7 @@
"bunyan": "1.8.1", "bunyan": "1.8.1",
"cartodb-psql": "0.12.0", "cartodb-psql": "0.12.0",
"cartodb-query-tables": "0.2.0", "cartodb-query-tables": "0.2.0",
"cartodb-redis": "1.0.0", "cartodb-redis": "git://github.com/CartoDB/node-cartodb-redis.git#remove-auth-fallback",
"debug": "2.2.0", "debug": "2.2.0",
"express": "~4.13.3", "express": "~4.13.3",
"log4js": "cartodb/log4js-node#cdb", "log4js": "cartodb/log4js-node#cdb",

View File

@ -6,6 +6,16 @@ var assert = require('../support/assert');
describe('app.auth', function() { describe('app.auth', function() {
var scenarios = [ var scenarios = [
{
desc: 'no api key should fallback to default api key',
url: "/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4",
statusCode: 200
},
{
desc: 'invalid api key should return 401',
url: "/api/v1/sql?api_key=THIS_API_KEY_NOT_EXIST&q=SELECT%20*%20FROM%20untitle_table_4",
statusCode: 401
},
{ {
desc: 'valid api key should allow insert in protected tables', desc: 'valid api key should allow insert in protected tables',
url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('app_auth_test1')", url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('app_auth_test1')",
@ -18,13 +28,8 @@ describe('app.auth', function() {
}, },
{ {
desc: 'invalid api key should NOT allow insert in protected tables', desc: 'invalid api key should NOT allow insert in protected tables',
url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')", url: "/api/v1/sql?api_key=THIS_API_KEY_NOT_EXIST&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('R')",
statusCode: 403 statusCode: 401
},
{
desc: 'invalid api key (old redis location) should NOT allow insert in protected tables',
url: "/api/v1/sql?api_key=1235&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')",
statusCode: 403
}, },
{ {
desc: 'no api key should NOT allow insert in protected tables', desc: 'no api key should NOT allow insert in protected tables',

View File

@ -18,33 +18,18 @@ describe('Auth API', function () {
}); });
}); });
// TODO: this is obviously a really dangerous sceneario, but in order to not break it('should fail when using a wrong API key', function (done) {
// some uses cases (i.e: new carto.js examples) and keep backwards compatiblity we will keep it during some time. this.testClient = new TestClient({ apiKey: 'THIS_API_KEY_DOES_NOT_EXIST' });
// It should be fixed as soon as possible
it('should get result from query using a wrong API key', function (done) {
this.testClient = new TestClient({ apiKey: 'wrong' });
this.testClient.getResult(publicSQL, (err, result) => {
assert.ifError(err);
assert.equal(result.length, 6);
done();
});
});
// TODO: this is obviously a really dangerous sceneario, but in order to not break
// some uses cases (i.e: new carto.js examples) and keep backwards compatiblity we will keep it during some time.
// It should be fixed as soon as possible
it('should fail while fetching data (private dataset) and using a wrong API key', function (done) {
this.testClient = new TestClient({ apiKey: 'wrong' });
const expectedResponse = { const expectedResponse = {
response: { response: {
status: 403 status: 401
} }
}; };
this.testClient.getResult(privateSQL, expectedResponse, (err, result) => { this.testClient.getResult(publicSQL, expectedResponse, (err, result) => {
assert.ifError(err); assert.ifError(err);
assert.equal(result.error, 'permission denied for relation private_table'); assert.equal(result.error, 'Unauthorized');
done(); done();
}); });
}); });
@ -106,63 +91,9 @@ describe('Auth API', function () {
}); });
}); });
describe('Fallback', function () {
it('should get result from query using master apikey (fallback) and a granted dataset', function (done) {
this.testClient = new TestClient({ apiKey: '4321', host: 'cartofante.cartodb.com' });
this.testClient.getResult(scopedSQL, (err, result) => {
assert.ifError(err);
assert.equal(result.length, 4);
done();
});
});
it('should fail while getting result from query using metadata and scoped dataset', function (done) {
this.testClient = new TestClient({ host: 'cartofante.cartodb.com' });
const expectedResponse = {
response: {
status: 403
},
anonymous: true
};
this.testClient.getResult(privateSQL, expectedResponse, (err, result) => {
assert.ifError(err);
assert.equal(result.error, 'permission denied for relation private_table');
done();
});
});
it('should insert and delete values on scoped datase using the master apikey', function (done) {
this.testClient = new TestClient({ apiKey: 4321, host: 'cartofante.cartodb.com' });
const insertSql = "INSERT INTO scoped_table_1(name) VALUES('wadus1')";
this.testClient.getResult(insertSql, (err, rows, body) => {
assert.ifError(err);
assert.ok(body.hasOwnProperty('time'));
assert.equal(body.total_rows, 1);
assert.equal(rows.length, 0);
const deleteSql = "DELETE FROM scoped_table_1 WHERE name = 'wadus1'";
this.testClient.getResult(deleteSql, (err, rows, body) => {
assert.ifError(err);
assert.ok(body.hasOwnProperty('time'));
assert.equal(body.total_rows, 1);
assert.equal(rows.length, 0);
done();
});
});
});
});
describe('Batch API', function () { describe('Batch API', function () {
it('should create a job with regular api key and get it done', function (done) { it('should create a job with master api key and get it done', function (done) {
this.testClient = new BatchTestClient({ apiKey: 'regular1' }); this.testClient = new BatchTestClient({ apiKey: '1234' });
this.testClient.createJob({ query: scopedSQL }, (err, jobResult) => { this.testClient.createJob({ query: scopedSQL }, (err, jobResult) => {
if (err) { if (err) {
@ -184,21 +115,42 @@ describe('Auth API', function () {
it('should create a job with regular api key and get it failed', function (done) { it('should create a job with regular api key and get it failed', function (done) {
this.testClient = new BatchTestClient({ apiKey: 'regular1' }); this.testClient = new BatchTestClient({ apiKey: 'regular1' });
this.testClient.createJob({ query: privateSQL }, (err, jobResult) => { this.testClient.createJob({ query: privateSQL }, { response: 403 }, (err, response) => {
if (err) { if (err) {
return done(err); return done(err);
} }
jobResult.getStatus(function (err, job) { const body = JSON.parse(response.body);
if (err) { assert.equal(body.error, 'permission denied');
return done(err); done();
} });
});
assert.equal(job.status, JobStatus.FAILED); it('should create a job with default public api key and get it failed', function (done) {
assert.equal(job.failed_reason, 'permission denied for relation private_table'); this.testClient = new BatchTestClient({ apiKey: 'default_public' });
done(); this.testClient.createJob({ query: publicSQL }, { response: 403 }, (err, response) => {
}); if (err) {
return done(err);
}
const body = JSON.parse(response.body);
assert.equal(body.error, 'permission denied');
done();
});
});
it('should create a job with fallback default public api key and get it failed', function (done) {
this.testClient = new BatchTestClient();
this.testClient.createJob({ query: publicSQL }, { response: 403, anonymous: true }, (err, response) => {
if (err) {
return done(err);
}
const body = JSON.parse(response.body);
assert.equal(body.error, 'permission denied');
done();
}); });
}); });
@ -267,77 +219,49 @@ describe('Auth API', function () {
}); });
}); });
// TODO: this is obviously a really dangerous sceneario, but in order to not break it('should fail when querying using a wrong API key', function (done) {
// some uses cases (i.e: new carto.js examples) and to keep backwards compatiblity this.testClient = new TestClient({ authorization: 'vizzuality:THIS_API_KEY_DOES_NOT_EXIST' });
// we will keep it during some time. It should be fixed as soon as possible
it('should get result from query using a wrong API key and quering to public dataset', function (done) {
this.testClient = new TestClient({ authorization: 'vizzuality:wrong' });
this.testClient.getResult(publicSQL, { anonymous: true }, (err, result) => {
assert.ifError(err);
assert.equal(result.length, 6);
done();
});
});
// TODO: this is obviously a really dangerous sceneario, but in order to not break
// some uses cases (i.e: new carto.js examples) and to keep backwards compatiblity
// we will keep it during some time. It should be fixed as soon as possible
it('should fail while fetching data (private dataset) and using a wrong API key', function (done) {
this.testClient = new TestClient({ authorization: 'vizzuality:wrong' });
const expectedResponse = { const expectedResponse = {
response: { response: {
status: 403 status: 401
}, },
anonymous: true anonymous: true
}; };
this.testClient.getResult(privateSQL, expectedResponse, (err, result) => { this.testClient.getResult(publicSQL, expectedResponse, (err, result) => {
assert.ifError(err); assert.ifError(err);
assert.equal(result.error, 'permission denied for relation private_table'); assert.equal(result.error, 'Unauthorized');
done(); done();
}); });
}); });
describe('Batch API', function () { describe('Batch API', function () {
it('should create a job with regular api key and get it done', function (done) { it('should create a job with regular api key and get it failed', function (done) {
this.testClient = new BatchTestClient({ authorization: 'vizzuality:regular1' }); this.testClient = new BatchTestClient({ authorization: 'vizzuality:regular1', response: 403 });
this.testClient.createJob({ query: scopedSQL }, { anonymous: true }, (err, jobResult) => { this.testClient.createJob({ query: scopedSQL }, { anonymous: true }, (err, response) => {
if (err) { if (err) {
return done(err); return done(err);
} }
jobResult.getStatus(function (err, job) { const body = JSON.parse(response.body);
if (err) { assert.equal(body.error, 'permission denied');
return done(err); done();
}
assert.equal(job.status, JobStatus.DONE);
done();
});
}); });
}); });
it('should create a job with regular api key and get it failed', function (done) { it('should create a job with default api key and get it failed', function (done) {
this.testClient = new BatchTestClient({ authorization: 'vizzuality:regular1' }); this.testClient = new BatchTestClient({ authorization: 'vizzuality:default_public', response: 403 });
this.testClient.createJob({ query: privateSQL }, { anonymous: true }, (err, jobResult) => { this.testClient.createJob({ query: privateSQL }, { anonymous: true }, (err, response) => {
if (err) { if (err) {
return done(err); return done(err);
} }
jobResult.getStatus(function (err, job) { const body = JSON.parse(response.body);
if (err) { assert.equal(body.error, 'permission denied');
return done(err); done();
}
assert.equal(job.status, JobStatus.FAILED);
assert.equal(job.failed_reason, 'permission denied for relation private_table');
done();
});
}); });
}); });

View File

@ -7,7 +7,6 @@ var JobPublisher = require('../../../batch/pubsub/job-publisher');
var JobQueue = require('../../../batch/job_queue'); var JobQueue = require('../../../batch/job_queue');
var JobBackend = require('../../../batch/job_backend'); var JobBackend = require('../../../batch/job_backend');
var JobService = require('../../../batch/job_service'); var JobService = require('../../../batch/job_service');
var UserDatabaseMetadataService = require('../../../batch/user_database_metadata_service');
var JobCanceller = require('../../../batch/job_canceller'); var JobCanceller = require('../../../batch/job_canceller');
var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() }); var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() });
@ -18,8 +17,7 @@ describe('batch module', function() {
var jobPublisher = new JobPublisher(pool); var jobPublisher = new JobPublisher(pool);
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend); var jobCanceller = new JobCanceller();
var jobCanceller = new JobCanceller(userDatabaseMetadataService);
var jobService = new JobService(jobBackend, jobCanceller); var jobService = new JobService(jobBackend, jobCanceller);
before(function (done) { before(function (done) {
@ -37,7 +35,11 @@ describe('batch module', function() {
var data = { var data = {
user: username, user: username,
query: sql, query: sql,
host: dbInstance host: dbInstance,
dbname: 'cartodb_test_user_1_db',
dbuser: 'test_cartodb_user_1',
port: 5432,
pass: 'test_cartodb_user_1_pass',
}; };
jobService.create(data, function (err, job) { jobService.create(data, function (err, job) {
@ -60,7 +62,6 @@ describe('batch module', function() {
if (err) { if (err) {
done(err); done(err);
} }
assert.equal(job.status, 'running'); assert.equal(job.status, 'running');
self.batch.drain(function () { self.batch.drain(function () {

View File

@ -79,7 +79,7 @@ describe('job module', function() {
}); });
}); });
it('POST /api/v2/sql/job with wrong api key should respond with 403 permission denied', function (done){ it('POST /api/v2/sql/job with wrong api key should respond with 401 permission denied', function (done){
assert.response(server, { assert.response(server, {
url: '/api/v2/sql/job?api_key=wrong', url: '/api/v2/sql/job?api_key=wrong',
headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
@ -88,10 +88,10 @@ describe('job module', function() {
query: "SELECT * FROM untitle_table_4" query: "SELECT * FROM untitle_table_4"
}) })
}, { }, {
status: 403 status: 401
}, function(err, res) { }, function(err, res) {
var error = JSON.parse(res.body); var error = JSON.parse(res.body);
assert.deepEqual(error, { error: [ 'permission denied' ] }); assert.deepEqual(error, { error: [ 'Unauthorized' ] });
done(); done();
}); });
}); });
@ -134,16 +134,16 @@ describe('job module', function() {
}); });
}); });
it('GET /api/v2/sql/job/:job_id with wrong api key should respond with 403 permission denied', function (done){ it('GET /api/v2/sql/job/:job_id with wrong api key should respond with 401 permission denied', function (done){
assert.response(server, { assert.response(server, {
url: '/api/v2/sql/job/' + job.job_id + '?api_key=wrong', url: '/api/v2/sql/job/' + job.job_id + '?api_key=wrong',
headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'GET' method: 'GET'
}, { }, {
status: 403 status: 401
}, function(err, res) { }, function(err, res) {
var error = JSON.parse(res.body); var error = JSON.parse(res.body);
assert.deepEqual(error, { error: [ 'permission denied' ] }); assert.deepEqual(error, { error: ['Unauthorized'] });
done(); done();
}); });
}); });
@ -182,16 +182,16 @@ describe('job module', function() {
}); });
}); });
it('DELETE /api/v2/sql/job/:job_id with wrong api key should respond with 403 permission denied', function (done){ it('DELETE /api/v2/sql/job/:job_id with wrong api key should respond with 401 permission denied', function (done){
assert.response(server, { assert.response(server, {
url: '/api/v2/sql/job/' + job.job_id + '?api_key=wrong', url: '/api/v2/sql/job/' + job.job_id + '?api_key=wrong',
headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'host': 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'DELETE' method: 'DELETE'
}, { }, {
status: 403 status: 401
}, function(err, res) { }, function(err, res) {
var error = JSON.parse(res.body); var error = JSON.parse(res.body);
assert.deepEqual(error, { error: [ 'permission denied' ] }); assert.deepEqual(error, { error: ['Unauthorized'] });
done(); done();
}); });
}); });

View File

@ -74,7 +74,7 @@ describe('query-tables-api', function() {
}); });
it('should skip cache to retrieve affected tables', function(done) { it('should skip cache to retrieve affected tables', function(done) {
var authenticatedRequest = { var masterRequest = {
url: '/api/v1/sql?' + qs.stringify({ url: '/api/v1/sql?' + qs.stringify({
q: 'SELECT * FROM untitle_table_4', q: 'SELECT * FROM untitle_table_4',
api_key: '1234' api_key: '1234'
@ -84,7 +84,7 @@ describe('query-tables-api', function() {
}, },
method: 'GET' method: 'GET'
}; };
assert.response(server, authenticatedRequest, RESPONSE_OK, function(err) { assert.response(server, masterRequest, RESPONSE_OK, function(err) {
assert.ok(!err, err); assert.ok(!err, err);
getCacheStatus(function(err, cacheStatus) { getCacheStatus(function(err, cacheStatus) {

View File

@ -10,7 +10,6 @@ var JobQueue = require('../../../batch/job_queue');
var JobBackend = require('../../../batch/job_backend'); var JobBackend = require('../../../batch/job_backend');
var JobService = require('../../../batch/job_service'); var JobService = require('../../../batch/job_service');
var UserDatabaseMetadataService = require('../../../batch/user_database_metadata_service');
var JobCanceller = require('../../../batch/job_canceller'); var JobCanceller = require('../../../batch/job_canceller');
var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() }); var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() });
@ -19,8 +18,7 @@ describe('job queue', function () {
var jobPublisher = new JobPublisher(pool); var jobPublisher = new JobPublisher(pool);
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend); var jobCanceller = new JobCanceller();
var jobCanceller = new JobCanceller(userDatabaseMetadataService);
var jobService = new JobService(jobBackend, jobCanceller); var jobService = new JobService(jobBackend, jobCanceller);
var userA = 'userA'; var userA = 'userA';

View File

@ -11,7 +11,6 @@ var JobQueue = require(BATCH_SOURCE + 'job_queue');
var JobBackend = require(BATCH_SOURCE + 'job_backend'); var JobBackend = require(BATCH_SOURCE + 'job_backend');
var JobPublisher = require(BATCH_SOURCE + 'pubsub/job-publisher'); var JobPublisher = require(BATCH_SOURCE + 'pubsub/job-publisher');
var jobStatus = require(BATCH_SOURCE + 'job_status'); var jobStatus = require(BATCH_SOURCE + 'job_status');
var UserDatabaseMetadataService = require(BATCH_SOURCE + 'user_database_metadata_service');
var JobCanceller = require(BATCH_SOURCE + 'job_canceller'); var JobCanceller = require(BATCH_SOURCE + 'job_canceller');
var PSQL = require('cartodb-psql'); var PSQL = require('cartodb-psql');
@ -19,7 +18,6 @@ var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() });
var jobPublisher = new JobPublisher(redisUtils.getPool()); var jobPublisher = new JobPublisher(redisUtils.getPool());
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend);
var JobFactory = require(BATCH_SOURCE + 'models/job_factory'); var JobFactory = require(BATCH_SOURCE + 'models/job_factory');
var USER = 'vizzuality'; var USER = 'vizzuality';
@ -30,7 +28,6 @@ var HOST = 'localhost';
// in order to test query cancelation/draining // in order to test query cancelation/draining
function runQueryHelper(job, callback) { function runQueryHelper(job, callback) {
var job_id = job.job_id; var job_id = job.job_id;
var user = job.user;
var sql = job.query; var sql = job.query;
job.status = jobStatus.RUNNING; job.status = jobStatus.RUNNING;
@ -40,22 +37,24 @@ function runQueryHelper(job, callback) {
return callback(err); return callback(err);
} }
userDatabaseMetadataService.getUserMetadata(user, function (err, userDatabaseMetadata) { const dbConfiguration = {
host: job.host,
port: job.port,
dbname: job.dbname,
user: job.dbuser,
pass: job.pass,
};
const pg = new PSQL(dbConfiguration);
sql = '/* ' + job_id + ' */ ' + sql;
pg.eventedQuery(sql, function (err, query) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
var pg = new PSQL(userDatabaseMetadata); callback(null, query);
sql = '/* ' + job_id + ' */ ' + sql;
pg.eventedQuery(sql, function (err, query) {
if (err) {
return callback(err);
}
callback(null, query);
});
}); });
}); });
} }
@ -65,12 +64,16 @@ function createWadusJob(query) {
return JobFactory.create(JSON.parse(JSON.stringify({ return JobFactory.create(JSON.parse(JSON.stringify({
user: USER, user: USER,
query: query, query: query,
host: HOST host: HOST,
dbname: 'cartodb_test_user_1_db',
dbuser: 'test_cartodb_user_1',
port: 5432,
pass: 'test_cartodb_user_1_pass',
}))); })));
} }
describe('job canceller', function() { describe('job canceller', function() {
var jobCanceller = new JobCanceller(userDatabaseMetadataService); var jobCanceller = new JobCanceller();
after(function (done) { after(function (done) {
redisUtils.clean('batch:*', done); redisUtils.clean('batch:*', done);

View File

@ -23,7 +23,7 @@ var jobPublisher = new JobPublisher(redisUtils.getPool());
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend); var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend);
var jobCanceller = new JobCanceller(userDatabaseMetadataService); var jobCanceller = new JobCanceller();
var jobService = new JobService(jobBackend, jobCanceller); var jobService = new JobService(jobBackend, jobCanceller);
var queryRunner = new QueryRunner(userDatabaseMetadataService); var queryRunner = new QueryRunner(userDatabaseMetadataService);
var StatsD = require('node-statsd').StatsD; var StatsD = require('node-statsd').StatsD;
@ -35,7 +35,11 @@ var HOST = 'localhost';
var JOB = { var JOB = {
user: USER, user: USER,
query: QUERY, query: QUERY,
host: HOST host: HOST,
dbname: 'cartodb_test_user_1_db',
dbuser: 'test_cartodb_user_1',
port: 5432,
pass: 'test_cartodb_user_1_pass',
}; };
describe('job runner', function() { describe('job runner', function() {

View File

@ -11,7 +11,6 @@ var JobQueue = require(BATCH_SOURCE + 'job_queue');
var JobBackend = require(BATCH_SOURCE + 'job_backend'); var JobBackend = require(BATCH_SOURCE + 'job_backend');
var JobPublisher = require(BATCH_SOURCE + 'pubsub/job-publisher'); var JobPublisher = require(BATCH_SOURCE + 'pubsub/job-publisher');
var jobStatus = require(BATCH_SOURCE + 'job_status'); var jobStatus = require(BATCH_SOURCE + 'job_status');
var UserDatabaseMetadataService = require(BATCH_SOURCE + 'user_database_metadata_service');
var JobCanceller = require(BATCH_SOURCE + 'job_canceller'); var JobCanceller = require(BATCH_SOURCE + 'job_canceller');
var JobService = require(BATCH_SOURCE + 'job_service'); var JobService = require(BATCH_SOURCE + 'job_service');
var PSQL = require('cartodb-psql'); var PSQL = require('cartodb-psql');
@ -20,8 +19,7 @@ var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() });
var jobPublisher = new JobPublisher(redisUtils.getPool()); var jobPublisher = new JobPublisher(redisUtils.getPool());
var jobQueue = new JobQueue(metadataBackend, jobPublisher); var jobQueue = new JobQueue(metadataBackend, jobPublisher);
var jobBackend = new JobBackend(metadataBackend, jobQueue); var jobBackend = new JobBackend(metadataBackend, jobQueue);
var userDatabaseMetadataService = new UserDatabaseMetadataService(metadataBackend); var jobCanceller = new JobCanceller();
var jobCanceller = new JobCanceller(userDatabaseMetadataService);
var USER = 'vizzuality'; var USER = 'vizzuality';
var QUERY = 'select pg_sleep(0)'; var QUERY = 'select pg_sleep(0)';
@ -29,7 +27,12 @@ var HOST = 'localhost';
var JOB = { var JOB = {
user: USER, user: USER,
query: QUERY, query: QUERY,
host: HOST host: HOST,
dbname: 'cartodb_test_user_1_db',
dbuser: 'test_cartodb_user_1',
port: 5432,
pass: 'test_cartodb_user_1_pass',
}; };
function createWadusDataJob() { function createWadusDataJob() {
@ -40,7 +43,6 @@ function createWadusDataJob() {
// in order to test query cancelation/draining // in order to test query cancelation/draining
function runQueryHelper(job, callback) { function runQueryHelper(job, callback) {
var job_id = job.job_id; var job_id = job.job_id;
var user = job.user;
var sql = job.query; var sql = job.query;
job.status = jobStatus.RUNNING; job.status = jobStatus.RUNNING;
@ -50,22 +52,24 @@ function runQueryHelper(job, callback) {
return callback(err); return callback(err);
} }
userDatabaseMetadataService.getUserMetadata(user, function (err, userDatabaseMetadata) { const dbConfiguration = {
host: job.host,
port: job.port,
dbname: job.dbname,
user: job.dbuser,
pass: job.pass,
};
var pg = new PSQL(dbConfiguration);
sql = '/* ' + job_id + ' */ ' + sql;
pg.eventedQuery(sql, function (err, query) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
var pg = new PSQL(userDatabaseMetadata); callback(null, query);
sql = '/* ' + job_id + ' */ ' + sql;
pg.eventedQuery(sql, function (err, query) {
if (err) {
return callback(err);
}
callback(null, query);
});
}); });
}); });
} }

View File

@ -188,7 +188,7 @@ HMSET api_keys:vizzuality:default_public \
user "vizzuality" \ user "vizzuality" \
type "default" \ type "default" \
grants_sql "true" \ grants_sql "true" \
database_role "test_windshaft_publicuser" \ database_role "testpublicuser" \
database_password "public" database_password "public"
EOF EOF
@ -230,7 +230,7 @@ HMSET api_keys:cartodb250user:default_public \
user "cartodb250user" \ user "cartodb250user" \
type "default" \ type "default" \
grants_sql "true" \ grants_sql "true" \
database_role "test_windshaft_publicuser" \ database_role "testpublicuser" \
database_password "public" database_password "public"
EOF EOF

View File

@ -80,7 +80,12 @@ BatchTestClient.prototype.createJob = function(job, override, callback) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
return callback(null, new JobResult(JSON.parse(res.body), this, override));
if (res.statusCode < 400) {
return callback(null, new JobResult(JSON.parse(res.body), this, override));
} else {
return callback(null, res);
}
}.bind(this) }.bind(this)
); );
}; };

View File

@ -99,7 +99,7 @@ it('can return user for verified signature', function(done){
oAuth.verifyRequest(req, metadataBackend, function(err, data){ oAuth.verifyRequest(req, metadataBackend, function(err, data){
assert.ok(!err, err); assert.ok(!err, err);
assert.equal(data, 1); assert.equal(data, 'master');
done(); done();
}); });
}); });
@ -120,7 +120,7 @@ it('can return user for verified signature (for other allowed domains)', functio
oAuth.verifyRequest(req, metadataBackend, function(err, data){ oAuth.verifyRequest(req, metadataBackend, function(err, data){
oAuth.getAllowedHosts = oAuthGetAllowedHostsFn; oAuth.getAllowedHosts = oAuthGetAllowedHostsFn;
assert.ok(!err, err); assert.ok(!err, err);
assert.equal(data, 1); assert.equal(data, 'master');
done(); done();
}); });
}); });

View File

@ -97,56 +97,56 @@ describe('pg entities access validator with validatePGEntitiesAccess enabled', f
}); });
it('validate function: should not be validated', function () { it('validate function: should not be validated', function () {
let authenticated = true; let authorizationLevel = 'master';
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel),
false false
); );
authenticated = false; authorizationLevel = 'regular';
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCarto }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesCartodbKO }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPgcatalog }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesInfo }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesPublicKO }, authorizationLevel),
false false
); );
assert.strictEqual( assert.strictEqual(
pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authenticated), pgEntitiesAccessValidator.validate({ tables: fakeAffectedTablesTopologyKO }, authorizationLevel),
false false
); );
}); });