Merge branch 'project-auth-api' into project-auth-api-fallback
This commit is contained in:
commit
7b21bd26d0
@ -56,21 +56,19 @@ function isValidApiKey(apikey) {
|
||||
// Check if a request is authorized by api_key
|
||||
//
|
||||
// @param user
|
||||
// @param req express request object
|
||||
// @param res express response object
|
||||
// @param callback function(err, authorized)
|
||||
// NOTE: authorized is expected to be 0 or 1 (integer)
|
||||
//
|
||||
AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
|
||||
var givenKey = req.query.api_key || req.query.map_key;
|
||||
if ( ! givenKey && req.body ) {
|
||||
// check also in request body
|
||||
givenKey = req.body.api_key || req.body.map_key;
|
||||
}
|
||||
if ( ! givenKey ) {
|
||||
AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
|
||||
const apikeyToken = res.locals.api_key;
|
||||
const apikeyUsername = res.locals.apikeyUsername;
|
||||
|
||||
if ( ! apikeyToken ) {
|
||||
return callback(null, false); // no api key, no authorization...
|
||||
}
|
||||
|
||||
this.metadataBackend.getApikey(user, givenKey, (err, apikey) => {
|
||||
this.metadataBackend.getApikey(user, apikeyToken, (err, apikey) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@ -98,6 +96,15 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (apikeyUsername && (apikeyUsername !== res.locals.user)) {
|
||||
const error = new Error('Forbidden');
|
||||
error.type = 'auth';
|
||||
error.subtype = 'api-key-username-mismatch';
|
||||
error.http_status = 403;
|
||||
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (!apikey.grantsMaps) {
|
||||
const error = new Error('Forbidden');
|
||||
error.type = 'auth';
|
||||
@ -121,7 +128,7 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
|
||||
AuthApi.prototype.authorize = function(req, res, callback) {
|
||||
var user = res.locals.user;
|
||||
|
||||
this.authorizedByAPIKey(user, req, (err, isAuthorizedByApikey) => {
|
||||
this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ PgConnection.prototype.setDBAuth = function(username, params, apikeyType, callba
|
||||
return callback();
|
||||
});
|
||||
} else if (apikeyType === 'regular') { //Actually it can be any type of api key
|
||||
this.metadataBackend.getApikey(username, params.api_key || params.map_key, (err, apikey) => {
|
||||
this.metadataBackend.getApikey(username, params.api_key, (err, apikey) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
const { templateName } = require('../backends/template_maps');
|
||||
const cors = require('../middleware/cors');
|
||||
const userMiddleware = require('../middleware/user');
|
||||
const localsMiddleware = require('../middleware/context/locals');
|
||||
const apikeyCredentialsMiddleware = require('../middleware/context/apikey-credentials');
|
||||
|
||||
const apikeyMiddleware = [
|
||||
localsMiddleware,
|
||||
apikeyCredentialsMiddleware(),
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {AuthApi} authApi
|
||||
@ -22,6 +29,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
`${base_url_templated}/`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
apikeyMiddleware,
|
||||
this.checkContentType('POST', 'POST TEMPLATE'),
|
||||
this.authorizedByAPIKey('create', 'POST TEMPLATE'),
|
||||
this.create()
|
||||
@ -31,6 +39,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
apikeyMiddleware,
|
||||
this.checkContentType('PUT', 'PUT TEMPLATE'),
|
||||
this.authorizedByAPIKey('update', 'PUT TEMPLATE'),
|
||||
this.update()
|
||||
@ -40,6 +49,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
apikeyMiddleware,
|
||||
this.authorizedByAPIKey('get', 'GET TEMPLATE'),
|
||||
this.retrieve()
|
||||
);
|
||||
@ -48,6 +58,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
apikeyMiddleware,
|
||||
this.authorizedByAPIKey('delete', 'DELETE TEMPLATE'),
|
||||
this.destroy()
|
||||
);
|
||||
@ -56,6 +67,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
`${base_url_templated}/`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
apikeyMiddleware,
|
||||
this.authorizedByAPIKey('list', 'GET TEMPLATE LIST'),
|
||||
this.list()
|
||||
);
|
||||
@ -69,8 +81,7 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label) {
|
||||
return function authorizedByAPIKeyMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
|
||||
this.authApi.authorizedByAPIKey(user, req, (err, authenticated) => {
|
||||
this.authApi.authorizedByAPIKey(user, res, (err, authenticated) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
10
lib/cartodb/middleware/context/apikey-credentials.js
Normal file
10
lib/cartodb/middleware/context/apikey-credentials.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const getApikeyCredentialsFromRequest = require('../lib/get_api_key_credentials_from_request');
|
||||
|
||||
module.exports = () => function apikeyTokenMiddleware(req, res, next) {
|
||||
const apikeyCredentials = getApikeyCredentialsFromRequest(req);
|
||||
res.locals.api_key = apikeyCredentials.token;
|
||||
res.locals.apikeyUsername = apikeyCredentials.username;
|
||||
return next();
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
const locals = require('./locals');
|
||||
const cleanUpQueryParams = require('./clean-up-query-params');
|
||||
const layergroupToken = require('./layergroup-token');
|
||||
const apikeyCredentials = require('./apikey-credentials');
|
||||
const authorize = require('./authorize');
|
||||
const dbConnSetup = require('./db-conn-setup');
|
||||
|
||||
@ -9,6 +10,7 @@ module.exports = function prepareContextMiddleware(authApi, pgConnection) {
|
||||
locals,
|
||||
cleanUpQueryParams(),
|
||||
layergroupToken,
|
||||
apikeyCredentials(),
|
||||
authorize(authApi),
|
||||
dbConnSetup(pgConnection)
|
||||
];
|
||||
|
@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const basicAuth = require('basic-auth');
|
||||
|
||||
module.exports = function getApiKeyCredentialsFromRequest(req) {
|
||||
let apikeyCredentials = {
|
||||
token: null,
|
||||
username: null,
|
||||
};
|
||||
|
||||
for (var getter of apikeyGetters) {
|
||||
apikeyCredentials = getter(req);
|
||||
if (apikeyTokenFound(apikeyCredentials)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apikeyCredentials;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
const apikeyGetters = [
|
||||
getApikeyTokenFromHeaderAuthorization,
|
||||
getApikeyTokenFromRequestQueryString,
|
||||
getApikeyTokenFromRequestBody,
|
||||
];
|
||||
|
||||
function getApikeyTokenFromHeaderAuthorization(req) {
|
||||
const credentials = basicAuth(req);
|
||||
|
||||
if (credentials) {
|
||||
return {
|
||||
username: credentials.username,
|
||||
token: credentials.pass
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
username: null,
|
||||
token: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getApikeyTokenFromRequestQueryString(req) {
|
||||
let token = null;
|
||||
|
||||
if (req.query && req.query.api_key) {
|
||||
token = req.query.api_key;
|
||||
} else if (req.query && req.query.map_key) {
|
||||
token = req.query.map_key;
|
||||
}
|
||||
|
||||
return {
|
||||
username: null,
|
||||
token: token,
|
||||
};
|
||||
}
|
||||
|
||||
function getApikeyTokenFromRequestBody(req) {
|
||||
let token = null;
|
||||
|
||||
if (req.body && req.body.api_key) {
|
||||
token = req.body.api_key;
|
||||
} else if (req.body && req.body.map_key) {
|
||||
token = req.body.map_key;
|
||||
}
|
||||
|
||||
return {
|
||||
username: null,
|
||||
token: token,
|
||||
};
|
||||
}
|
||||
|
||||
function apikeyTokenFound(apikey) {
|
||||
return !!apikey && !!apikey.token;
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
"Simon Martin <simon@carto.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"camshaft": "0.61.2",
|
||||
"cartodb-psql": "0.10.2",
|
||||
|
@ -10,6 +10,7 @@ var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
|
||||
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/middleware/context/clean-up-query-params');
|
||||
const authorizeMiddleware = require('../../../lib/cartodb/middleware/context/authorize');
|
||||
const dbConnSetupMiddleware = require('../../../lib/cartodb/middleware/context/db-conn-setup');
|
||||
const apikeyCredentialsMiddleware = require('../../../lib/cartodb/middleware/context/apikey-credentials');
|
||||
const localsMiddleware = require('../../../lib/cartodb/middleware/context/locals');
|
||||
|
||||
var windshaft = require('windshaft');
|
||||
@ -23,6 +24,7 @@ describe('prepare-context', function() {
|
||||
let cleanUpQueryParams;
|
||||
let dbConnSetup;
|
||||
let authorize;
|
||||
let setApikeyCredentials;
|
||||
|
||||
before(function() {
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
@ -35,6 +37,7 @@ describe('prepare-context', function() {
|
||||
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
|
||||
authorize = authorizeMiddleware(authApi);
|
||||
dbConnSetup = dbConnSetupMiddleware(pgConnection);
|
||||
setApikeyCredentials = apikeyCredentialsMiddleware();
|
||||
});
|
||||
|
||||
|
||||
@ -180,4 +183,66 @@ describe('prepare-context', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Set apikey token', function(){
|
||||
it('from query param', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
query: {
|
||||
api_key: '1234',
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('from body param', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
body: {
|
||||
api_key: '1234',
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('from http header', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
authorization: 'Basic bG9jYWxob3N0OjEyMzQ=', // user: localhost, password: 1234
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -143,6 +143,12 @@ balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
basic-auth@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba"
|
||||
dependencies:
|
||||
safe-buffer "5.1.1"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||
|
Loading…
Reference in New Issue
Block a user