2018-10-23 23:45:42 +08:00
|
|
|
'use strict';
|
|
|
|
|
2015-07-13 21:05:03 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {PgConnection} pgConnection
|
|
|
|
* @param metadataBackend
|
|
|
|
* @param {MapStore} mapStore
|
|
|
|
* @param {TemplateMaps} templateMaps
|
|
|
|
* @constructor
|
2018-04-10 00:08:56 +08:00
|
|
|
* @type {AuthBackend}
|
2015-07-13 21:05:03 +08:00
|
|
|
*/
|
2019-10-22 01:07:24 +08:00
|
|
|
function AuthBackend (pgConnection, metadataBackend, mapStore, templateMaps) {
|
2015-07-13 21:05:03 +08:00
|
|
|
this.pgConnection = pgConnection;
|
|
|
|
this.metadataBackend = metadataBackend;
|
|
|
|
this.mapStore = mapStore;
|
|
|
|
this.templateMaps = templateMaps;
|
|
|
|
}
|
|
|
|
|
2018-04-10 00:08:56 +08:00
|
|
|
module.exports = AuthBackend;
|
2015-07-13 21:05:03 +08:00
|
|
|
|
2017-10-04 18:50:27 +08:00
|
|
|
// Check if the user is authorized by a signer
|
2015-07-13 21:05:03 +08:00
|
|
|
//
|
2017-10-04 18:50:27 +08:00
|
|
|
// @param res express response object
|
2015-07-13 21:05:03 +08:00
|
|
|
// @param callback function(err, signed_by) signed_by will be
|
|
|
|
// null if the request is not signed by anyone
|
|
|
|
// or will be a string cartodb username otherwise.
|
|
|
|
//
|
2019-10-22 01:07:24 +08:00
|
|
|
AuthBackend.prototype.authorizedBySigner = function (req, res, callback) {
|
|
|
|
if (!res.locals.token || !res.locals.signer) {
|
2015-07-13 21:05:03 +08:00
|
|
|
return callback(null, false); // no signer requested
|
|
|
|
}
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
2019-11-14 18:36:47 +08:00
|
|
|
var layergroupId = res.locals.token;
|
|
|
|
var authToken = req.query.auth_token;
|
2015-07-13 21:05:03 +08:00
|
|
|
|
2019-11-14 18:36:47 +08:00
|
|
|
this.mapStore.load(layergroupId, function (err, mapConfig) {
|
2015-07-13 21:05:03 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2019-11-14 18:36:47 +08:00
|
|
|
var authorized = self.templateMaps.isAuthorized(mapConfig.obj().template, authToken);
|
2015-07-13 21:05:03 +08:00
|
|
|
|
|
|
|
return callback(null, authorized);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-10-22 01:07:24 +08:00
|
|
|
function isValidApiKey (apikey) {
|
2018-02-15 00:31:05 +08:00
|
|
|
return apikey.type &&
|
|
|
|
apikey.user &&
|
|
|
|
apikey.databasePassword &&
|
|
|
|
apikey.databaseRole;
|
2018-02-08 01:20:56 +08:00
|
|
|
}
|
|
|
|
|
2015-07-13 21:05:03 +08:00
|
|
|
// Check if a request is authorized by api_key
|
|
|
|
//
|
|
|
|
// @param user
|
2018-02-15 19:50:42 +08:00
|
|
|
// @param res express response object
|
2015-07-13 21:05:03 +08:00
|
|
|
// @param callback function(err, authorized)
|
|
|
|
// NOTE: authorized is expected to be 0 or 1 (integer)
|
|
|
|
//
|
2019-10-22 01:07:24 +08:00
|
|
|
AuthBackend.prototype.authorizedByAPIKey = function (user, res, callback) {
|
2018-02-15 22:19:09 +08:00
|
|
|
const apikeyToken = res.locals.api_key;
|
2018-03-01 02:13:49 +08:00
|
|
|
const basicAuthUsername = res.locals.basicAuthUsername;
|
2018-02-15 19:50:42 +08:00
|
|
|
|
2019-10-22 01:07:24 +08:00
|
|
|
if (!apikeyToken) {
|
2018-02-08 01:20:56 +08:00
|
|
|
return callback(null, false); // no api key, no authorization...
|
2015-07-13 21:05:03 +08:00
|
|
|
}
|
|
|
|
|
2018-02-15 19:50:42 +08:00
|
|
|
this.metadataBackend.getApikey(user, apikeyToken, (err, apikey) => {
|
2018-02-08 01:20:56 +08:00
|
|
|
if (err) {
|
2018-02-20 01:48:02 +08:00
|
|
|
if (isNameNotFoundError(err)) {
|
2018-02-20 01:28:58 +08:00
|
|
|
err.http_status = 404;
|
|
|
|
}
|
|
|
|
|
2018-02-08 01:20:56 +08:00
|
|
|
return callback(err);
|
2015-07-13 21:05:03 +08:00
|
|
|
}
|
2018-02-15 00:31:05 +08:00
|
|
|
|
2019-10-22 01:07:24 +08:00
|
|
|
if (!isValidApiKey(apikey)) {
|
2018-02-08 02:12:14 +08:00
|
|
|
const error = new Error('Unauthorized');
|
|
|
|
error.type = 'auth';
|
|
|
|
error.subtype = 'api-key-not-found';
|
|
|
|
error.http_status = 401;
|
|
|
|
|
2018-02-08 20:07:25 +08:00
|
|
|
return callback(error);
|
|
|
|
}
|
|
|
|
|
2018-03-01 02:13:49 +08:00
|
|
|
if (!usernameMatches(basicAuthUsername, res.locals.user)) {
|
2018-02-16 00:49:47 +08:00
|
|
|
const error = new Error('Forbidden');
|
|
|
|
error.type = 'auth';
|
|
|
|
error.subtype = 'api-key-username-mismatch';
|
|
|
|
error.http_status = 403;
|
|
|
|
|
2018-02-20 01:28:58 +08:00
|
|
|
return callback(error);
|
2018-02-16 00:49:47 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 20:07:25 +08:00
|
|
|
if (!apikey.grantsMaps) {
|
|
|
|
const error = new Error('Forbidden');
|
|
|
|
error.type = 'auth';
|
|
|
|
error.subtype = 'api-key-does-not-grant-access';
|
|
|
|
error.http_status = 403;
|
|
|
|
|
2018-02-08 02:12:14 +08:00
|
|
|
return callback(error);
|
2018-02-08 01:20:56 +08:00
|
|
|
}
|
|
|
|
|
2018-04-06 21:26:11 +08:00
|
|
|
return callback(null, true, apikey);
|
2018-02-08 01:20:56 +08:00
|
|
|
});
|
2015-07-13 21:05:03 +08:00
|
|
|
};
|
|
|
|
|
2018-02-20 01:48:02 +08:00
|
|
|
function isNameNotFoundError (err) {
|
2019-10-22 01:07:24 +08:00
|
|
|
return err.message && err.message.indexOf('name not found') !== -1;
|
2018-02-20 01:48:02 +08:00
|
|
|
}
|
|
|
|
|
2018-03-01 02:13:49 +08:00
|
|
|
function usernameMatches (basicAuthUsername, requestUsername) {
|
|
|
|
return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
|
2018-02-20 01:48:02 +08:00
|
|
|
}
|
|
|
|
|
2015-07-13 21:05:03 +08:00
|
|
|
/**
|
|
|
|
* Check access authorization
|
|
|
|
*
|
|
|
|
* @param req - standard req object. Importantly contains table and host information
|
2017-10-05 18:44:03 +08:00
|
|
|
* @param res - standard res object. Contains the auth parameters in locals
|
2015-07-13 21:05:03 +08:00
|
|
|
* @param callback function(err, allowed) is access allowed not?
|
|
|
|
*/
|
2019-10-22 01:07:24 +08:00
|
|
|
AuthBackend.prototype.authorize = function (req, res, callback) {
|
2017-10-05 17:28:41 +08:00
|
|
|
var user = res.locals.user;
|
2015-07-13 21:05:03 +08:00
|
|
|
|
2018-02-15 19:50:42 +08:00
|
|
|
this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {
|
2018-02-08 01:48:59 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2015-07-13 21:05:03 +08:00
|
|
|
|
2018-02-08 01:48:59 +08:00
|
|
|
if (isAuthorizedByApikey) {
|
2018-02-08 18:29:17 +08:00
|
|
|
return this.pgConnection.setDBAuth(user, res.locals, 'regular', function (err) {
|
2018-02-08 01:48:59 +08:00
|
|
|
req.profiler.done('setDBAuth');
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2018-02-20 01:28:58 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 01:48:59 +08:00
|
|
|
callback(null, true);
|
2015-07-13 21:05:03 +08:00
|
|
|
});
|
2018-02-08 01:48:59 +08:00
|
|
|
}
|
|
|
|
|
2018-03-23 21:13:27 +08:00
|
|
|
this.authorizedBySigner(req, res, (err, isAuthorizedBySigner) => {
|
2015-07-13 21:05:03 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2018-02-20 01:28:58 +08:00
|
|
|
|
2018-02-08 01:48:59 +08:00
|
|
|
if (isAuthorizedBySigner) {
|
2018-02-08 18:29:17 +08:00
|
|
|
return this.pgConnection.setDBAuth(user, res.locals, 'master', function (err) {
|
2018-02-08 01:48:59 +08:00
|
|
|
req.profiler.done('setDBAuth');
|
2018-02-20 01:28:58 +08:00
|
|
|
|
2018-02-08 01:48:59 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2018-02-20 01:28:58 +08:00
|
|
|
}
|
2018-02-08 01:48:59 +08:00
|
|
|
|
|
|
|
callback(null, true);
|
2018-02-20 01:28:58 +08:00
|
|
|
});
|
2018-02-08 01:48:59 +08:00
|
|
|
}
|
2015-07-13 21:05:03 +08:00
|
|
|
|
2018-02-08 18:29:17 +08:00
|
|
|
// if no signer name was given, use default api key
|
2018-02-08 01:48:59 +08:00
|
|
|
if (!res.locals.signer) {
|
2018-02-08 18:29:17 +08:00
|
|
|
return this.pgConnection.setDBAuth(user, res.locals, 'default', function (err) {
|
|
|
|
req.profiler.done('setDBAuth');
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, true);
|
2018-02-20 01:28:58 +08:00
|
|
|
});
|
2015-07-13 21:05:03 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 01:48:59 +08:00
|
|
|
// if signer name was given, return no authorization
|
|
|
|
return callback(null, false);
|
|
|
|
});
|
|
|
|
});
|
2015-07-13 21:05:03 +08:00
|
|
|
};
|