Merge pull request #327 from CartoDB/oauth-multiple-domains

Allow to setup more than one domain to validate oauth against
This commit is contained in:
Raul Ochoa 2016-07-07 15:16:23 +02:00 committed by GitHub
commit 537c5163b2
5 changed files with 80 additions and 37 deletions

View File

@ -1,6 +1,9 @@
1.33.1 - 2016-mm-dd 1.33.1 - 2016-mm-dd
------------------- -------------------
New features:
* Allow to setup more than one domain to validate oauth against.
1.33.0 - 2016-07-01 1.33.0 - 2016-07-01
------------------- -------------------

View File

@ -3,6 +3,8 @@ var _ = require('underscore');
var OAuthUtil = require('oauth-client'); var OAuthUtil = require('oauth-client');
var step = require('step'); var step = require('step');
var assert = require('assert'); var assert = require('assert');
var CdbRequest = require('../models/cartodb_request');
var cdbReq = new CdbRequest();
var oAuth = (function(){ var oAuth = (function(){
var me = { var me = {
@ -60,79 +62,87 @@ var oAuth = (function(){
return removed; return removed;
}; };
me.getAllowedHosts= function() {
var oauthConfig = global.settings.oauth || {};
return oauthConfig.allowedHosts || ['carto.com', 'cartodb.com'];
};
// do new fancy get User ID // do new fancy get User ID
me.verifyRequest = function(req, metadataBackend, callback) { me.verifyRequest = function(req, metadataBackend, callback) {
var that = this; var that = this;
//TODO: review this //TODO: review this
var httpProto = req.protocol; var httpProto = req.protocol;
var passed_tokens; if(!httpProto || (httpProto !== 'http' && httpProto !== 'https')) {
var ohash; var msg = "Unknown HTTP protocol " + httpProto + ".";
var unknownProtocolErr = new Error(msg);
unknownProtocolErr.http_status = 500;
return callback(unknownProtocolErr);
}
var username = cdbReq.userByReq(req);
var requestTokens;
var signature; var signature;
step( step(
function getTokensFromURL(){ function getTokensFromURL(){
return oAuth.parseTokens(req); return oAuth.parseTokens(req);
}, },
function getOAuthHash(err, data){ function getOAuthHash(err, _requestTokens) {
assert.ifError(err); assert.ifError(err);
// this is oauth request only if oauth headers are present // this is oauth request only if oauth headers are present
this.is_oauth_request = !_.isEmpty(data); this.is_oauth_request = !_.isEmpty(_requestTokens);
if (this.is_oauth_request) { if (this.is_oauth_request) {
passed_tokens = data; requestTokens = _requestTokens;
that.getOAuthHash(metadataBackend, passed_tokens.oauth_token, this); that.getOAuthHash(metadataBackend, requestTokens.oauth_token, this);
} else { } else {
return null; return null;
} }
}, },
function regenerateSignature(err, data){ function regenerateSignature(err, oAuthHash){
assert.ifError(err); assert.ifError(err);
if (!this.is_oauth_request) { if (!this.is_oauth_request) {
return null; return null;
} }
ohash = data; var consumer = OAuthUtil.createConsumer(oAuthHash.consumer_key, oAuthHash.consumer_secret);
var consumer = OAuthUtil.createConsumer(ohash.consumer_key, ohash.consumer_secret); var access_token = OAuthUtil.createToken(oAuthHash.access_token_token, oAuthHash.access_token_secret);
var access_token = OAuthUtil.createToken(ohash.access_token_token, ohash.access_token_secret);
var signer = OAuthUtil.createHmac(consumer, access_token); var signer = OAuthUtil.createHmac(consumer, access_token);
var method = req.method; var method = req.method;
var host = req.headers.host; var hostsToValidate = {};
var requestHost = req.headers.host;
hostsToValidate[requestHost] = true;
that.getAllowedHosts().forEach(function(allowedHost) {
hostsToValidate[username + '.' + allowedHost] = true;
});
if(!httpProto || (httpProto !== 'http' && httpProto !== 'https')) {
var msg = "Unknown HTTP protocol " + httpProto + ".";
err = new Error(msg);
err.http_status = 500;
callback(err);
return;
}
var path = httpProto + '://' + host + req.path;
that.splitParams(req.query); that.splitParams(req.query);
// remove signature from passed_tokens
signature = passed_tokens.oauth_signature;
delete passed_tokens.oauth_signature;
var joined = {};
// remove oauth_signature from body // remove oauth_signature from body
if(req.body) { if(req.body) {
delete req.body.oauth_signature; delete req.body.oauth_signature;
} }
_.extend(joined, req.body ? req.body : null); signature = requestTokens.oauth_signature;
_.extend(joined, passed_tokens); // remove signature from requestTokens
_.extend(joined, req.query); delete requestTokens.oauth_signature;
var requestParams = _.extend({}, req.body, requestTokens, req.query);
return signer.sign(method, path, joined); var hosts = Object.keys(hostsToValidate);
var requestSignatures = hosts.map(function(host) {
var url = httpProto + '://' + host + req.path;
return signer.sign(method, url, requestParams);
});
return requestSignatures.reduce(function(validSignature, requestSignature) {
if (signature === requestSignature && !_.isUndefined(requestSignature)) {
validSignature = true;
}
return validSignature;
}, false);
}, },
function checkSignature(err, data){ function finishValidation(err, hasValidSignature) {
assert.ifError(err); return callback(err, hasValidSignature || null);
//console.log(data + " should equal the provided signature: " + signature);
callback(err, (signature === data && !_.isUndefined(data)) ? true : null);
} }
); );
}; };

View File

@ -85,4 +85,7 @@ module.exports.health = {
username: 'development', username: 'development',
query: 'select 1' query: 'select 1'
}; };
module.exports.oauth = {
allowedHosts: ['carto.com', 'cartodb.com']
};
module.exports.disabled_file = 'pids/disabled'; module.exports.disabled_file = 'pids/disabled';

View File

@ -74,4 +74,7 @@ module.exports.health = {
username: 'vizzuality', username: 'vizzuality',
query: 'select 1' query: 'select 1'
}; };
module.exports.oauth = {
allowedHosts: ['localhost.lan:8080', 'localhostdb.lan:8080']
};
module.exports.disabled_file = 'pids/disabled'; module.exports.disabled_file = 'pids/disabled';

View File

@ -78,7 +78,7 @@ it('test can access oauth hash for a user based on access token (oauth_token)',
}); });
it('test non existant oauth hash for a user based on oauth_token returns empty hash', function(done){ it('test non existant oauth hash for a user based on oauth_token returns empty hash', function(done){
var req = {query:{}, headers:{authorization:full_oauth_header}}; var req = {query:{}, params: { user: 'vizzuality' }, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req); var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(metadataBackend, tokens.oauth_token, function(err, data){ oAuth.getOAuthHash(metadataBackend, tokens.oauth_token, function(err, data){
@ -91,6 +91,7 @@ it('test non existant oauth hash for a user based on oauth_token returns empty h
it('can return user for verified signature', function(done){ it('can return user for verified signature', function(done){
var req = {query:{}, var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' }, headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
params: { user: 'vizzuality' },
protocol: 'http', protocol: 'http',
method: 'GET', method: 'GET',
path: '/api/v1/tables' path: '/api/v1/tables'
@ -103,9 +104,31 @@ it('can return user for verified signature', function(done){
}); });
}); });
it('can return user for verified signature (for other allowed domains)', function(done){
var oAuthGetAllowedHostsFn = oAuth.getAllowedHosts;
oAuth.getAllowedHosts = function() {
return ['testhost.lan', 'testhostdb.lan'];
};
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhostdb.lan' },
params: { user: 'vizzuality' },
protocol: 'http',
method: 'GET',
path: '/api/v1/tables'
};
oAuth.verifyRequest(req, metadataBackend, function(err, data){
oAuth.getAllowedHosts = oAuthGetAllowedHostsFn;
assert.ok(!err, err);
assert.equal(data, 1);
done();
});
});
it('returns null user for unverified signatures', function(done){ it('returns null user for unverified signatures', function(done){
var req = {query:{}, var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' }, headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
params: { user: 'vizzuality' },
protocol: 'http', protocol: 'http',
method: 'GET', method: 'GET',
path: '/api/v1/tables' path: '/api/v1/tables'
@ -121,6 +144,7 @@ it('returns null user for no oauth', function(done){
var req = { var req = {
query:{}, query:{},
headers:{}, headers:{},
params: { user: 'vizzuality' },
protocol: 'http', protocol: 'http',
method: 'GET', method: 'GET',
path: '/api/v1/tables' path: '/api/v1/tables'