added body_hash to oauth check and stopped firing exception if incomplete oauth variables sent
This commit is contained in:
parent
164f3725a7
commit
ce4413cbda
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ logs/
|
||||
pids/
|
||||
*.sock
|
||||
test/tmp/*
|
||||
node_modules/
|
||||
node_modules/
|
||||
.idea/*
|
@ -53,7 +53,7 @@ app.get('/api/v1/', function(req, res){
|
||||
oAuth.verifyRequest(req, this);
|
||||
},
|
||||
function querySql(err, user_id){
|
||||
if (err && err.message !== 'incomplete oauth tokens in request') throw err;
|
||||
if (err) throw err;
|
||||
pg = new PSQL(user_id, database, limit, offset);
|
||||
pg.query(sql, this);
|
||||
},
|
||||
|
@ -4,51 +4,47 @@ var RedisPool = require("./redis_pool")
|
||||
, OAuthUtil = require('oauth-client')
|
||||
, url = require('url')
|
||||
, Step = require('step');
|
||||
_.mixin(require('underscore.string'));
|
||||
_.mixin(require('underscore.string'));
|
||||
|
||||
var oAuth = function(){
|
||||
var me = {
|
||||
oauth_database: 3,
|
||||
oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>"
|
||||
};
|
||||
|
||||
oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>"
|
||||
};
|
||||
|
||||
// oauth token cases:
|
||||
// * in GET request
|
||||
// * in header
|
||||
// * in header
|
||||
me.parseTokens = function(req){
|
||||
var query_oauth = _.clone(req.query);
|
||||
var header_oauth = {};
|
||||
var oauth_variables = ['oauth_consumer_key',
|
||||
'oauth_token',
|
||||
'oauth_signature_method',
|
||||
var oauth_variables = ['oauth_body_hash',
|
||||
'oauth_consumer_key',
|
||||
'oauth_token',
|
||||
'oauth_signature_method',
|
||||
'oauth_signature',
|
||||
'oauth_timestamp',
|
||||
'oauth_nonce',
|
||||
'oauth_version'];
|
||||
|
||||
// pull only oauth tokens out of query
|
||||
var non_oauth = _.difference(_.keys(query_oauth), oauth_variables);
|
||||
_.each(non_oauth, function(key){ delete query_oauth[key]; });
|
||||
// pull only oauth tokens out of query
|
||||
var non_oauth = _.difference(_.keys(query_oauth), oauth_variables);
|
||||
_.each(non_oauth, function(key){ delete query_oauth[key]; });
|
||||
|
||||
// pull oauth tokens out of header
|
||||
var header_string = req.headers.authorization;
|
||||
if (!_.isUndefined(header_string)) {
|
||||
if (!_.isUndefined(header_string)) {
|
||||
_.each(oauth_variables, function(oauth_key){
|
||||
var matched_string = header_string.match(new RegExp(oauth_key + '=\"([^\"]+)\"'))
|
||||
if (!_.isNull(matched_string))
|
||||
header_oauth[oauth_key] = decodeURIComponent(matched_string[1]);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//merge header and query oauth tokens. preference given to header oauth
|
||||
var oauth = _.defaults(header_oauth, query_oauth);
|
||||
if (_.keys(oauth).length !== oauth_variables.length) {
|
||||
throw new Error('incomplete oauth tokens in request');
|
||||
} else {
|
||||
return oauth;
|
||||
}
|
||||
}
|
||||
|
||||
return _.defaults(header_oauth, query_oauth);
|
||||
};
|
||||
|
||||
// remove oauthy tokens from an object
|
||||
me.splitParams = function(obj) {
|
||||
var removed = null;
|
||||
@ -62,46 +58,17 @@ var oAuth = function(){
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
// oauth token cases:
|
||||
// * in GET request
|
||||
// * in header
|
||||
// WARNING DEPRECATED
|
||||
me.parseParams = function(req){
|
||||
var oauth_token = null;
|
||||
};
|
||||
|
||||
if (_.isString(req.query.oauth_token)){
|
||||
oauth_token = _.trim(req.query.oauth_token);
|
||||
} else if (_.isString(req.headers.authorization)) {
|
||||
oauth_token = req.headers.authorization.match(/oauth_token=\"([^\"]+)\"/)[1]
|
||||
}
|
||||
|
||||
return (_.isString(oauth_token)) ? oauth_token : null
|
||||
}
|
||||
|
||||
// find user from redis oauth token DB
|
||||
// WARNING DEPRECATION IMMINENT
|
||||
me.getUserId = function(access_key, callback){
|
||||
var that = this;
|
||||
RedisPool.acquire(this.oauth_database, function(client){
|
||||
var redisClient = client;
|
||||
redisClient.hget(_.template(that.oauth_user_key, {oauth_access_key: access_key}), "user_id", function(err, user_id){
|
||||
RedisPool.release(that.oauth_database, redisClient);
|
||||
return callback(err, user_id)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// do new fancy get User ID
|
||||
me.verifyRequest = function(req, callback){
|
||||
var that = this;
|
||||
var http = arguments['2'];
|
||||
var passed_tokens;
|
||||
var ohash;
|
||||
var ohash;
|
||||
var signature;
|
||||
|
||||
|
||||
Step(
|
||||
function getTokensFromURL(){
|
||||
return oAuth.parseTokens(req);
|
||||
@ -117,54 +84,44 @@ var oAuth = function(){
|
||||
var consumer = OAuthUtil.createConsumer(ohash.consumer_key, ohash.consumer_secret);
|
||||
var access_token = OAuthUtil.createToken(ohash.access_token_token, ohash.access_token_secret);
|
||||
var signer = OAuthUtil.createHmac(consumer, access_token);
|
||||
|
||||
|
||||
var method = req.method;
|
||||
var host = req.headers.host;
|
||||
var path = http ? 'http://' + host + req.route.path : 'https://' + host + req.route.path;
|
||||
that.splitParams(req.query);
|
||||
|
||||
|
||||
// remove signature from passed_tokens
|
||||
signature = passed_tokens.oauth_signature;
|
||||
delete passed_tokens['oauth_signature'];
|
||||
|
||||
|
||||
var base64;
|
||||
var joined = {};
|
||||
|
||||
_.extend(joined, req.body ? req.body : null);
|
||||
_.extend(joined, passed_tokens);
|
||||
_.extend(joined, req.query);
|
||||
|
||||
|
||||
return signer.sign(method, path, joined);
|
||||
},
|
||||
function checkSignature(err, data){
|
||||
if (err) throw err;
|
||||
function checkSignature(err, data){
|
||||
if (err) throw err;
|
||||
callback(err, (signature === data && !_.isUndefined(data)) ? ohash.user_id : null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
me.getOAuthHash = function(access_key, callback){
|
||||
var that = this;
|
||||
RedisPool.acquire(this.oauth_database, function(client){
|
||||
var redisClient = client;
|
||||
redisClient.HGETALL(_.template(that.oauth_user_key, {oauth_access_key: access_key}), function(err, data){
|
||||
RedisPool.release(that.oauth_database, redisClient);
|
||||
return callback(err, data)
|
||||
});
|
||||
return callback(err, data)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// helper method
|
||||
// GONNA DEPRECATE
|
||||
me.authorize = function(req, callback){
|
||||
var oauth_params = this.parseParams(req);
|
||||
return this.getUserId(oauth_params, callback);
|
||||
}
|
||||
|
||||
|
||||
return me;
|
||||
}();
|
||||
|
||||
module.exports = oAuth;
|
||||
|
||||
|
||||
// get the user secrets and shit by the consumer id, then do the signature. if ok, return user id.
|
||||
module.exports = oAuth;
|
@ -8,8 +8,10 @@ var _ = require('underscore')
|
||||
//
|
||||
// * intended for use with pg_bouncer
|
||||
// * defaults to connecting with a "READ ONLY" user to given DB if not passed a specific user_id
|
||||
var PSQL = function(user_id, db, limit, offset){
|
||||
if (!_.isString(user_id) && !_.isString(db)) throw new Error("database user or database name must be specified")
|
||||
var PSQL = function(user_id, db, limit, offset){
|
||||
|
||||
var error_text = "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please specify a database name."
|
||||
if (!_.isString(user_id) && !_.isString(db)) throw new Error(error_text);
|
||||
|
||||
var me = {
|
||||
public_user: "publicuser"
|
||||
|
@ -58,7 +58,7 @@ namespace :deploy do
|
||||
end
|
||||
|
||||
task :restart, :roles => :app, :except => { :no_release => true } do
|
||||
#sudo "stop #{application}"
|
||||
sudo "stop #{application}"
|
||||
sudo "start #{application}"
|
||||
end
|
||||
|
||||
|
@ -3,7 +3,7 @@ module.exports.environment = 'test';
|
||||
module.exports.db_base_name = 'cartodb_test_user_<%= user_id %>_db';
|
||||
module.exports.db_user = 'test_cartodb_user_<%= user_id %>';
|
||||
module.exports.db_host = 'localhost';
|
||||
module.exports.db_port = '6432';
|
||||
module.exports.db_port = '5432';
|
||||
module.exports.redis_host = '127.0.0.1';
|
||||
module.exports.redis_port = 6379;
|
||||
module.exports.redisPool = 50;
|
||||
|
@ -31,109 +31,11 @@ tests['test oauth database key'] = function(){
|
||||
assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>");
|
||||
};
|
||||
|
||||
tests['test parse params from request query'] = function(){
|
||||
var req = {query:{oauth_token:"test_token"}}
|
||||
assert.equal(oAuth.parseParams(req), "test_token");
|
||||
};
|
||||
|
||||
tests['test parse params from request header'] = function(){
|
||||
var req = {query:{}, headers:{authorization:"oauth_token=\"test_token\""}}
|
||||
assert.equal(oAuth.parseParams(req), "test_token");
|
||||
};
|
||||
|
||||
tests['test parse null empty token from request'] = function(){
|
||||
var req = {query:{}, headers:{}}
|
||||
assert.equal(oAuth.parseParams(req), null);
|
||||
}
|
||||
|
||||
tests['test getUserId returns null user when passed null token'] = function(){
|
||||
oAuth.getUserId(null, function(err,user_id){
|
||||
assert.equal(user_id, null);
|
||||
});
|
||||
}
|
||||
|
||||
tests['test getUserId error returns null when passed null token'] = function(){
|
||||
oAuth.getUserId(null, function(err,user_id){
|
||||
assert.equal(err, null);
|
||||
});
|
||||
}
|
||||
|
||||
tests['returns a user id if there is an oauth_token in redis'] = function(){
|
||||
var redisClient = redis.createClient();
|
||||
redisClient.select(oAuth.oauth_database);
|
||||
|
||||
// make a dummy user_id/token combo and test
|
||||
redisClient.HSET(_.template(oAuth.oauth_user_key, {oauth_access_key: "test_token"}), 'user_id', 9999, function(){
|
||||
oAuth.getUserId("test_token", function(err,user_id){
|
||||
assert.equal(user_id, 9999);
|
||||
redisClient.quit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
tests['authorize returns null if no oauth_token'] = function(){
|
||||
var req = {query:{}, headers:{}}
|
||||
|
||||
oAuth.authorize(req, function(err,user_id){
|
||||
assert.equal(user_id, null);
|
||||
});
|
||||
}
|
||||
|
||||
tests['authorize returns user_id if valid query oauth_token set'] = function(){
|
||||
var redisClient = redis.createClient();
|
||||
redisClient.select(oAuth.oauth_database);
|
||||
|
||||
// make a dummy user_id/token combo and test
|
||||
redisClient.HSET(_.template(oAuth.oauth_user_key, {oauth_access_key: "test_token_1"}), 'user_id', 9999, function(){
|
||||
var req = {query:{oauth_token:"test_token_1"}, headers:{}}
|
||||
oAuth.authorize(req, function(err,user_id){
|
||||
assert.equal(user_id, 9999);
|
||||
redisClient.quit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
tests['authorize returns user_id if valid header oauth_token set'] = function(){
|
||||
var redisClient = redis.createClient();
|
||||
redisClient.select(oAuth.oauth_database);
|
||||
|
||||
// make a dummy user_id/token combo and test
|
||||
redisClient.HSET(_.template(oAuth.oauth_user_key, {oauth_access_key: "test_token_2"}), 'user_id', 9999, function(){
|
||||
req = {query:{}, headers:{authorization:"oauth_token=\"test_token_2\""}}
|
||||
oAuth.authorize(req, function(err,user_id){
|
||||
assert.equal(user_id, 9999);
|
||||
redisClient.quit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
tests['test parse tokens from empty request raises exception'] = function(){
|
||||
var req = {query:{}, headers:{}}
|
||||
assert.throws(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
};
|
||||
|
||||
tests['test parse tokens from half baked headers raises exception'] = function(){
|
||||
var req = {query:{}, headers:{authorization:"blah"}}
|
||||
assert.throws(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
};
|
||||
|
||||
tests['test parse tokens from half filled headers raises exception'] = function(){
|
||||
var req = {query:{}, headers:{authorization:part_oauth_header}}
|
||||
assert.throws(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
};
|
||||
|
||||
tests['test parse tokens from full headers does not raise exception'] = function(){
|
||||
var req = {query:{}, headers:{authorization:full_oauth_header}}
|
||||
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
};
|
||||
|
||||
tests['test parse some normal tokens raises exception'] = function(){
|
||||
var req = {query:oauth_data_2, headers:{}}
|
||||
assert.throws(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
};
|
||||
|
||||
tests['test parse all normal tokens raises no exception'] = function(){
|
||||
var req = {query:oauth_data, headers:{}}
|
||||
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
|
||||
@ -156,10 +58,10 @@ tests['test headers take presedence over query parameters'] = function(){
|
||||
// hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR time sometime
|
||||
|
||||
//the headers for this are:
|
||||
var real_oauth_header = 'OAuth realm="http://vizzuality.testhost.lan/",oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",oauth_signature_method="HMAC-SHA1", oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",oauth_timestamp="1313581372",oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",oauth_version="1.0"'
|
||||
var real_oauth_header = 'OAuth realm="http://vizzuality.testhost.lan/",oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",oauth_signature_method="HMAC-SHA1", oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",oauth_timestamp="1313581372",oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",oauth_version="1.0"';
|
||||
|
||||
tests['test can access oauth hash for a user based on access token (oauth_token)'] = function(){
|
||||
var req = {query:{}, headers:{authorization:real_oauth_header}}
|
||||
var req = {query:{}, headers:{authorization:real_oauth_header}};
|
||||
var tokens = oAuth.parseTokens(req);
|
||||
|
||||
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
|
||||
@ -168,8 +70,9 @@ tests['test can access oauth hash for a user based on access token (oauth_token)
|
||||
};
|
||||
|
||||
tests['test non existant oauth hash for a user based on oauth_token returns empty hash'] = function(){
|
||||
var req = {query:{}, headers:{authorization:full_oauth_header}}
|
||||
var tokens = oAuth.parseTokens(req);
|
||||
var req = {query:{}, headers:{authorization:full_oauth_header}};
|
||||
var tokens = oAuth.parseTokens(req);
|
||||
|
||||
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
|
||||
assert.eql(data, {})
|
||||
});
|
||||
@ -180,24 +83,24 @@ tests['can return user for verified signature'] = function(){
|
||||
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
|
||||
method: 'GET',
|
||||
route: {path: '/api/v1/tables'}
|
||||
}
|
||||
};
|
||||
|
||||
oAuth.verifyRequest(req, function(err, data){
|
||||
assert.eql(data, 1);
|
||||
}, true)
|
||||
}
|
||||
};
|
||||
|
||||
tests['returns null user for unverified signatures'] = function(){
|
||||
var req = {query:{},
|
||||
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
|
||||
method: 'GET',
|
||||
route: {path: '/api/v1/tables'}
|
||||
}
|
||||
};
|
||||
|
||||
oAuth.verifyRequest(req, function(err, data){
|
||||
assert.eql(data, null);
|
||||
}, true)
|
||||
}
|
||||
};
|
||||
|
||||
tests['returns null user for no oauth'] = function(){
|
||||
var req = {
|
||||
@ -205,10 +108,10 @@ tests['returns null user for no oauth'] = function(){
|
||||
headers:{},
|
||||
method: 'GET',
|
||||
route: {path: '/api/v1/tables'}
|
||||
}
|
||||
|
||||
assert.throws(function(){
|
||||
oAuth.verifyRequest(req, function(err, data){}, true);
|
||||
}, /incomplete oauth tokens in request/)
|
||||
}
|
||||
};
|
||||
|
||||
oAuth.verifyRequest(req,function(err,data){
|
||||
assert.eql(data, null);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ exports['test throws error if no args passed to constructor'] = function(){
|
||||
try{
|
||||
var pg = new PSQL();
|
||||
} catch (err){
|
||||
assert.equal(err.message, "database user or database name must be specified");
|
||||
assert.equal(err.message, "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please specify a database name.");
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user