Move authorization to auth-api and extract it from user-database-service

This commit is contained in:
Daniel García Aubert 2018-02-22 11:46:34 +01:00
parent 6c92781600
commit b399abee18
7 changed files with 111 additions and 63 deletions

View File

@ -1,15 +1,47 @@
/**
* this module allows to auth user using an pregenerated api key
*/
function ApikeyAuth(req, apikey) {
function ApikeyAuth(req, metadataBackend, username, apikey) {
this.req = req;
this.metadataBackend = metadataBackend;
this.username = username;
this.apikey = apikey;
}
module.exports = ApikeyAuth;
ApikeyAuth.prototype.verifyCredentials = function (options, callback) {
callback(null, verifyRequest(this.apikey, options.apiKey));
this.metadataBackend.getApikey(this.username, this.apikey, (err, apikey) => {
if (err) {
err.http_status = 404;
err.message = "Sorry, we can't find CartoDB user '" + this.username + "'. " +
"Please check that you have entered the correct domain.";
return callback(err);
}
if (isApiKeyFound(apikey)) {
if (!apikey.grantsSql) {
const forbiddenError = new Error('forbidden');
forbiddenError.http_status = 403;
return callback(forbiddenError);
}
return callback(null, verifyRequest(this.apikey, this.apikey));
}
// Auth API Fallback
this.metadataBackend.getAllUserDBParams(this.username, function (err, dbParams) {
if (err) {
err.http_status = 404;
err.message = "Sorry, we can't find CartoDB user '" + this.username + "'. " +
"Please check that you have entered the correct domain.";
return callback(err);
}
callback(null, verifyRequest(this.apikey, dbParams.apikey));
});
});
};
ApikeyAuth.prototype.hasCredentials = function () {
@ -23,3 +55,10 @@ ApikeyAuth.prototype.getCredentials = function () {
function verifyRequest(apikey, requiredApikey) {
return (apikey === requiredApikey && apikey !== 'default_public');
}
function isApiKeyFound(apikey) {
return apikey.type !== null &&
apikey.user !== null &&
apikey.databasePassword !== null &&
apikey.databaseRole !== null;
}

View File

@ -1,7 +1,7 @@
var ApiKeyAuth = require('./apikey'),
OAuthAuth = require('./oauth');
function AuthApi(req,requestParams) {
function AuthApi(req, requestParams) {
this.req = req;
this.authBacked = getAuthBackend(req, requestParams);
@ -37,7 +37,7 @@ AuthApi.prototype.verifyCredentials = function(options, callback) {
function getAuthBackend(req, requestParams) {
if (requestParams.api_key) {
return new ApiKeyAuth(req, requestParams.api_key);
return new ApiKeyAuth(req, requestParams.metadataBackend, requestParams.user, requestParams.api_key);
} else {
return new OAuthAuth(req);
}

View File

@ -5,7 +5,8 @@ const { initializeProfilerMiddleware, finishProfilerMiddleware } = require('../m
const authorizationMiddleware = require('../middlewares/authorization');
const errorMiddleware = require('../middlewares/error');
function JobController(userDatabaseService, jobService, statsdClient) {
function JobController(metadataBackend, userDatabaseService, jobService, statsdClient) {
this.metadataBackend = metadataBackend;
this.userDatabaseService = userDatabaseService;
this.jobService = jobService;
this.statsdClient = statsdClient;
@ -16,6 +17,7 @@ module.exports = JobController;
JobController.prototype.route = function (app) {
const { base_url } = global.settings;
const jobMiddlewares = composeJobMiddlewares(
this.metadataBackend,
this.userDatabaseService,
this.jobService,
this.statsdClient
@ -27,14 +29,14 @@ JobController.prototype.route = function (app) {
app.delete(`${base_url}/sql/job/:job_id`, jobMiddlewares('cancel', cancelJob));
};
function composeJobMiddlewares (userDatabaseService, jobService, statsdClient) {
function composeJobMiddlewares (metadataBackend, userDatabaseService, jobService, statsdClient) {
return function jobMiddlewares (action, jobMiddleware) {
const forceToBeAuthenticated = true;
return [
initializeProfilerMiddleware('job'),
userMiddleware(),
authorizationMiddleware(userDatabaseService, forceToBeAuthenticated),
authorizationMiddleware(metadataBackend, userDatabaseService, forceToBeAuthenticated),
jobMiddleware(jobService),
setServedByDBHostHeader(),
finishProfilerMiddleware(),

View File

@ -18,7 +18,8 @@ const { initializeProfilerMiddleware } = require('../middlewares/profiler');
var ONE_YEAR_IN_SECONDS = 31536000; // 1 year time to live by default
function QueryController(userDatabaseService, tableCache, statsd_client) {
function QueryController(metadataBackend, userDatabaseService, tableCache, statsd_client) {
this.metadataBackend = metadataBackend;
this.statsd_client = statsd_client;
this.userDatabaseService = userDatabaseService;
this.queryTables = new CachedQueryTables(tableCache);
@ -29,7 +30,7 @@ QueryController.prototype.route = function (app) {
const queryMiddlewares = [
initializeProfilerMiddleware('query'),
userMiddleware(),
authorizationMiddleware(this.userDatabaseService),
authorizationMiddleware(this.metadataBackend ,this.userDatabaseService),
this.handleQuery.bind(this),
errorMiddleware()
];

View File

@ -1,7 +1,7 @@
const AuthApi = require('../auth/auth_api');
const basicAuth = require('basic-auth');
module.exports = function authorization (userDatabaseService, forceToBeAuthenticated = false) {
module.exports = function authorization (metadataBackend, userDatabaseService, forceToBeAuthenticated = false) {
return function authorizationMiddleware (req, res, next) {
const { user } = res.locals;
const credentials = getCredentialsFromRequest(req);
@ -12,27 +12,38 @@ module.exports = function authorization (userDatabaseService, forceToBeAuthentic
res.locals.api_key = credentials.apiKeyToken;
const params = Object.assign({}, res.locals, req.query, req.body);
const params = Object.assign({ metadataBackend }, res.locals, req.query, req.body);
const authApi = new AuthApi(req, params);
userDatabaseService.getConnectionParams(authApi, user, function (err, userDbParams, authDbParams, userLimits) {
if (req.profiler) {
req.profiler.done('setDBAuth');
}
authApi.verifyCredentials({}, function (err, authenticated) {
if (err) {
return next(err);
}
if (forceToBeAuthenticated && !userDbParams.authenticated) {
res.locals.authenticated = authenticated;
if (forceToBeAuthenticated && !authenticated) {
return next(new Error('permission denied'));
}
res.locals.userDbParams = userDbParams;
res.locals.authDbParams = authDbParams;
res.locals.userLimits = userLimits;
const apikeyToken = (authApi.getType() === 'apiKey') ? authApi.getCredentials() : undefined;
next();
userDatabaseService.getConnectionParams(user, apikeyToken, authenticated,
function (err, userDbParams, authDbParams, userLimits) {
if (req.profiler) {
req.profiler.done('setDBAuth');
}
if (err) {
return next(err);
}
res.locals.userDbParams = userDbParams;
res.locals.authDbParams = authDbParams;
res.locals.userLimits = userLimits;
next();
});
});
};
};

View File

@ -147,10 +147,10 @@ function App(statsClient) {
var genericController = new GenericController();
genericController.route(app);
var queryController = new QueryController(userDatabaseService, tableCache, statsClient);
var queryController = new QueryController(metadataBackend, userDatabaseService, tableCache, statsClient);
queryController.route(app);
var jobController = new JobController(userDatabaseService, jobService, statsClient);
var jobController = new JobController(metadataBackend, userDatabaseService, jobService, statsClient);
jobController.route(app);
var cacheStatusController = new CacheStatusController(tableCache);

View File

@ -24,7 +24,7 @@ function UserDatabaseService(metadataBackend) {
* @param {String} cdbUsername
* @param {Function} callback (err, dbParams, authDbParams)
*/
UserDatabaseService.prototype.getConnectionParams = function (authApi, cdbUsername, callback) {
UserDatabaseService.prototype.getConnectionParams = function (cdbUsername, apikeyToken, isAuthenticated, callback) {
var self = this;
var dbopts = {
@ -49,35 +49,26 @@ UserDatabaseService.prototype.getConnectionParams = function (authApi, cdbUserna
const next = this;
if (authApi.getType() !== 'apiKey') {
if (apikeyToken === undefined) {
return next(null, dbopts, dbParams);
}
const apikeyToken = authApi.getCredentials();
self.metadataBackend.getApikey(cdbUsername, apikeyToken, (err, apikey) => {
self.getApiKey(cdbUsername, apikeyToken, function (err, apikey) {
if (err) {
return next(err);
}
if (!isApiKeyFound(apikey)) {
if (!apikey) {
return next(null, dbopts, dbParams);
}
if (!apikey.grantsSql) {
const forbiddenError = new Error('forbidden');
forbiddenError.http_status = 403;
return next(forbiddenError);
}
dbParams.apikey = apikeyToken;
next(null, dbopts, dbParams, apikey);
});
},
function authenticate(err, dbopts, dbParams, apikey) {
var next = this;
function setDBAuth(err, dbopts, dbParams, apikey) {
const next = this;
if (err) {
return next(err);
@ -87,26 +78,6 @@ UserDatabaseService.prototype.getConnectionParams = function (authApi, cdbUserna
dbopts.dbname = dbParams.dbname;
dbopts.user = (!!dbParams.dbpublicuser) ? dbParams.dbpublicuser : global.settings.db_pubuser;
const opts = {
metadataBackend: self.metadataBackend,
apiKey: dbParams.apikey
};
authApi.verifyCredentials(opts, function (err, isAuthenticated) {
if (err) {
return next(err);
}
next(null, isAuthenticated, dbopts, dbParams, apikey);
});
},
function setDBAuth(err, isAuthenticated, dbopts, dbParams, apikey) {
const next = this;
if (err) {
return next(err);
}
var user = _.template(global.settings.db_user, {user_id: dbParams.dbuser});
var pass = null;
@ -139,15 +110,11 @@ UserDatabaseService.prototype.getConnectionParams = function (authApi, cdbUserna
return next(err);
}
self.metadataBackend.getUserTimeoutRenderLimits(cdbUsername, function (err, timeoutRenderLimit) {
self.getUserLimits(cdbUsername, isAuthenticated, function (err, userLimits) {
if (err) {
return next(err);
}
var userLimits = {
timeout: isAuthenticated ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
};
next(null, dbopts, authDbOpts, userLimits);
});
},
@ -161,4 +128,32 @@ UserDatabaseService.prototype.getConnectionParams = function (authApi, cdbUserna
);
};
UserDatabaseService.prototype.getApiKey = function (cdbUsername, apikeyToken, callback) {
this.metadataBackend.getApikey(cdbUsername, apikeyToken, (err, apikey) => {
if (err) {
return callback(err);
}
if (!isApiKeyFound(apikey)) {
return callback(null);
}
callback(null, apikey);
});
};
UserDatabaseService.prototype.getUserLimits = function (cdbUsername, isAuthenticated, callback) {
this.metadataBackend.getUserTimeoutRenderLimits(cdbUsername, function (err, timeoutRenderLimit) {
if (err) {
return callback(err);
}
var userLimits = {
timeout: isAuthenticated ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
};
callback(null, userLimits);
});
};
module.exports = UserDatabaseService;