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:
commit
537c5163b2
3
NEWS.md
3
NEWS.md
@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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';
|
||||||
|
@ -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';
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user