diff --git a/lib/cartodb/api/auth_api.js b/lib/cartodb/api/auth_api.js index 9b24a16e..ef874483 100644 --- a/lib/cartodb/api/auth_api.js +++ b/lib/cartodb/api/auth_api.js @@ -60,6 +60,7 @@ function isValidApiKey(apikey) { // 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... @@ -78,6 +79,15 @@ AuthApi.prototype.authorizedByAPIKey = function(user, res, 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'; diff --git a/lib/cartodb/controllers/named_maps_admin.js b/lib/cartodb/controllers/named_maps_admin.js index fe484f7f..b1c6a451 100644 --- a/lib/cartodb/controllers/named_maps_admin.js +++ b/lib/cartodb/controllers/named_maps_admin.js @@ -2,11 +2,11 @@ const { templateName } = require('../backends/template_maps'); const cors = require('../middleware/cors'); const userMiddleware = require('../middleware/user'); const localsMiddleware = require('../middleware/context/locals'); -const apikeyTokenMiddleware = require('../middleware/context/apikey-token'); +const apikeyCredentialsMiddleware = require('../middleware/context/apikey-credentials'); const apikeyMiddleware = [ localsMiddleware, - apikeyTokenMiddleware(), + apikeyCredentialsMiddleware(), ]; /** diff --git a/lib/cartodb/middleware/context/apikey-credentials.js b/lib/cartodb/middleware/context/apikey-credentials.js new file mode 100644 index 00000000..820bb8c6 --- /dev/null +++ b/lib/cartodb/middleware/context/apikey-credentials.js @@ -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(); +}; diff --git a/lib/cartodb/middleware/context/apikey-token.js b/lib/cartodb/middleware/context/apikey-token.js deleted file mode 100644 index db43d843..00000000 --- a/lib/cartodb/middleware/context/apikey-token.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -const getApikeyTokenFromRequest = require('../lib/get_api_key_token_from_request'); - -module.exports = () => function apikeyTokenMiddleware(req, res, next) { - res.locals.api_key = getApikeyTokenFromRequest(req); - return next(); -}; diff --git a/lib/cartodb/middleware/context/index.js b/lib/cartodb/middleware/context/index.js index 96b261a4..8922739f 100644 --- a/lib/cartodb/middleware/context/index.js +++ b/lib/cartodb/middleware/context/index.js @@ -1,7 +1,7 @@ const locals = require('./locals'); const cleanUpQueryParams = require('./clean-up-query-params'); const layergroupToken = require('./layergroup-token'); -const apikeyToken = require('./apikey-token'); +const apikeyCredentials = require('./apikey-credentials'); const authorize = require('./authorize'); const dbConnSetup = require('./db-conn-setup'); @@ -10,7 +10,7 @@ module.exports = function prepareContextMiddleware(authApi, pgConnection) { locals, cleanUpQueryParams(), layergroupToken, - apikeyToken(), + apikeyCredentials(), authorize(authApi), dbConnSetup(pgConnection) ]; diff --git a/lib/cartodb/middleware/lib/get_api_key_credentials_from_request.js b/lib/cartodb/middleware/lib/get_api_key_credentials_from_request.js new file mode 100644 index 00000000..397e972b --- /dev/null +++ b/lib/cartodb/middleware/lib/get_api_key_credentials_from_request.js @@ -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; +} diff --git a/lib/cartodb/middleware/lib/get_api_key_token_from_request.js b/lib/cartodb/middleware/lib/get_api_key_token_from_request.js deleted file mode 100644 index 3da6f1d6..00000000 --- a/lib/cartodb/middleware/lib/get_api_key_token_from_request.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const basicAuth = require('basic-auth'); - -module.exports = function getApiKeyTokenFromRequest(req) { - let apiKeyToken = null; - - for (var getter of apiKeyGetters) { - apiKeyToken = getter(req); - if (apiKeyTokenFound(apiKeyToken)) { - break; - } - } - - return apiKeyToken; -}; - -//-------------------------------------------------------------------------------- - -const apiKeyGetters = [ - getApikeyTokenFromHeaderAuthorization, - getApikeyTokenFromRequestQueryString, - getApikeyTokenFromRequestBody, -]; - -function getApikeyTokenFromHeaderAuthorization(req) { - const credentials = basicAuth(req); - - if (credentials) { - return credentials.pass; - } else { - return null; - } -} - -function getApikeyTokenFromRequestQueryString(req) { - if (req.query && req.query.api_key) { - return req.query.api_key; - } - - if (req.query && req.query.map_key) { - return req.query.map_key; - } - - return null; -} - -function getApikeyTokenFromRequestBody(req) { - if (req.body && req.body.api_key) { - return req.body.api_key; - } - - if (req.body && req.body.map_key) { - return req.body.map_key; - } - - return null; -} - -function apiKeyTokenFound(apiKeyToken) { - return !!apiKeyToken; -} diff --git a/test/unit/cartodb/prepare-context.test.js b/test/unit/cartodb/prepare-context.test.js index eabad095..61d7b514 100644 --- a/test/unit/cartodb/prepare-context.test.js +++ b/test/unit/cartodb/prepare-context.test.js @@ -10,7 +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 apikeyTokenMiddleware = require('../../../lib/cartodb/middleware/context/apikey-token'); +const apikeyCredentialsMiddleware = require('../../../lib/cartodb/middleware/context/apikey-credentials'); const localsMiddleware = require('../../../lib/cartodb/middleware/context/locals'); var windshaft = require('windshaft'); @@ -24,7 +24,7 @@ describe('prepare-context', function() { let cleanUpQueryParams; let dbConnSetup; let authorize; - let setApikeyToken; + let setApikeyCredentials; before(function() { var redisPool = new RedisPool(global.environment.redis); @@ -37,7 +37,7 @@ describe('prepare-context', function() { cleanUpQueryParams = cleanUpQueryParamsMiddleware(); authorize = authorizeMiddleware(authApi); dbConnSetup = dbConnSetupMiddleware(pgConnection); - setApikeyToken = apikeyTokenMiddleware(); + setApikeyCredentials = apikeyCredentialsMiddleware(); }); @@ -194,7 +194,7 @@ describe('prepare-context', function() { } }; var res = {}; - setApikeyToken(prepareRequest(req), prepareResponse(res), function (err) { + setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) { if (err) { return done(err); } @@ -215,7 +215,7 @@ describe('prepare-context', function() { } }; var res = {}; - setApikeyToken(prepareRequest(req), prepareResponse(res), function (err) { + setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) { if (err) { return done(err); } @@ -230,11 +230,30 @@ describe('prepare-context', function() { var req = { headers: { host: 'localhost', - authorization: 'Basic dXNlcjoxMjM0', // user: user, password: 1234 + authorization: 'Basic bG9jYWxob3N0OjEyMzQ=', // user: localhost, password: 1234 } }; var res = {}; - setApikeyToken(prepareRequest(req), prepareResponse(res), function (err) { + setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) { + if (err) { + return done(err); + } + var query = res.locals; + + assert.equal('1234', query.api_key); + done(); + }); + }); + + it('fail from http header with user mismatch', function (done) { + var req = { + headers: { + host: 'localhost', + authorization: 'Basic YW5vdGhlcl91c2VyOjEyMzQ=', // user: another_user, password: 1234 + } + }; + var res = {}; + setApikeyCredentials(prepareRequest(req), prepareResponse(res), function (err) { if (err) { return done(err); }