From 1ca788adcc5d5a6f6a68d7fc0da8b738c39fd000 Mon Sep 17 00:00:00 2001 From: Deng Jianfeng Date: Fri, 22 Jan 2016 15:20:10 +0800 Subject: [PATCH 1/2] Add nonce callback function to options. --- lib/passport-http/strategies/digest.js | 21 +++-- test/strategies/digest-test.js | 123 ++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 10 deletions(-) diff --git a/lib/passport-http/strategies/digest.js b/lib/passport-http/strategies/digest.js index f0433e8..afa1cea 100644 --- a/lib/passport-http/strategies/digest.js +++ b/lib/passport-http/strategies/digest.js @@ -80,6 +80,7 @@ function DigestStrategy(options, secret, validate) { if (options.qop) { this._qop = (Array.isArray(options.qop)) ? options.qop : [ options.qop ]; } + this._nonce = options.nonce || function() { return nonce(32); }; } /** @@ -96,7 +97,7 @@ util.inherits(DigestStrategy, passport.Strategy); */ DigestStrategy.prototype.authenticate = function(req) { var authorization = req.headers['authorization']; - if (!authorization) { return this.fail(this._challenge()); } + if (!authorization) { return this.fail(this._challenge(req)); } var parts = authorization.split(' ') if (parts.length < 2) { return this.fail(400); } @@ -104,13 +105,13 @@ DigestStrategy.prototype.authenticate = function(req) { var scheme = parts[0] , params = parts.slice(1).join(' '); - if (!/Digest/i.test(scheme)) { return this.fail(this._challenge()); } + if (!/Digest/i.test(scheme)) { return this.fail(this._challenge(req)); } var creds = parse(params); if (Object.keys(creds).length === 0) { return this.fail(400); } if (!creds.username) { - return this.fail(this._challenge()); + return this.fail(this._challenge(req)); } if (req.url !== creds.uri) { return this.fail(400); @@ -130,7 +131,7 @@ DigestStrategy.prototype.authenticate = function(req) { // value. this._secret(creds.username, function(err, user, password) { if (err) { return self.error(err); } - if (!user) { return self.fail(self._challenge()); } + if (!user) { return self.fail(self._challenge(req)); } var ha1; if (!creds.algorithm || creds.algorithm === 'MD5') { @@ -177,7 +178,7 @@ DigestStrategy.prototype.authenticate = function(req) { } if (creds.response != digest) { - return self.fail(self._challenge()); + return self.fail(self._challenge(req)); } else { if (self._validate) { self._validate({ @@ -188,7 +189,7 @@ DigestStrategy.prototype.authenticate = function(req) { }, function(err, valid) { if (err) { return self.error(err); } - if (!valid) { return self.fail(self._challenge()); } + if (!valid) { return self.fail(self._challenge(req)); } self.success(user); }); } else { @@ -203,7 +204,7 @@ DigestStrategy.prototype.authenticate = function(req) { * * @api private */ -DigestStrategy.prototype._challenge = function() { +DigestStrategy.prototype._challenge = function(req) { // TODO: For maximum flexibility, a mechanism for delegating the generation // of the nonce and opaque data to the application would be useful. @@ -211,9 +212,11 @@ DigestStrategy.prototype._challenge = function() { if (this._domain) { challenge += ', domain="' + this._domain.join(' ') + '"'; } - challenge += ', nonce="' + nonce(32) + '"'; + var nonce = (typeof this._nonce == 'function') ? this._nonce(req) : this._nonce; + challenge += ', nonce="' + nonce + '"'; if (this._opaque) { - challenge += ', opaque="' + this._opaque + '"'; + var opaque = (typeof this._opaque == 'function') ? this._opaque(req) : this._opaque; + challenge += ', opaque="' + opaque + '"'; } if (this._algorithm) { challenge += ', algorithm=' + this._algorithm; diff --git a/test/strategies/digest-test.js b/test/strategies/digest-test.js index 28115ec..6db3739 100644 --- a/test/strategies/digest-test.js +++ b/test/strategies/digest-test.js @@ -1202,6 +1202,46 @@ vows.describe('DigestStrategy').addBatch({ }, }, + 'strategy handling a request without authorization credentials with opaque function option set': { + topic: function() { + var strategy = new DigestStrategy({ opaque: function(req) { return req.ip; } }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.ip = '192.168.1.66'; + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="192.168.1.66"$/); + }, + }, + }, + 'strategy handling a request without authorization credentials with algorithm option set': { topic: function() { var strategy = new DigestStrategy({ algorithm: 'MD5-sess' }, @@ -1318,7 +1358,88 @@ vows.describe('DigestStrategy').addBatch({ }, }, }, - + + ///////////////////////////////////////////////////////////////////////////////////////////// + 'strategy handling a request without authorization credentials with nonce option set': { + topic: function() { + var strategy = new DigestStrategy({ nonce: '8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC' }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC"$/); + }, + }, + }, + + 'strategy handling a request without authorization credentials with nonce function option set': { + topic: function() { + var strategy = new DigestStrategy({ nonce: function(req) { return req.ip; } }, + function(username, done) { + done(null, { username: username }, 'secret'); + }, + function(options, done) { + done(null, true); + } + ); + return strategy; + }, + + 'after augmenting with actions': { + topic: function(strategy) { + var self = this; + var req = {}; + strategy.success = function(user) { + self.callback(new Error('should not be called')); + } + strategy.fail = function(challenge) { + self.callback(null, challenge); + } + + req.ip = '192.168.1.66'; + req.url = '/'; + req.headers = {}; + process.nextTick(function () { + strategy.authenticate(req); + }); + }, + + 'should fail authentication with challenge' : function(err, challenge) { + // fail action was called, resulting in test callback + assert.isNull(err); + assert.match(challenge, /^Digest realm="Users", nonce="192.168.1.66"$/); + }, + }, + }, + /////////////////////////////////////////////////////////////////////////////////////////////// + 'strategy constructed without a secret callback or validate callback': { 'should throw an error': function (strategy) { assert.throws(function() { new DigestStrategy() }); From 50be15c94fd2f389eaf404120477d16c3318da55 Mon Sep 17 00:00:00 2001 From: Deng Jianfeng Date: Fri, 22 Jan 2016 15:43:04 +0800 Subject: [PATCH 2/2] fix typo --- test/strategies/digest-test.js | 58 ++++++++++++++++------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/test/strategies/digest-test.js b/test/strategies/digest-test.js index 6db3739..978955a 100644 --- a/test/strategies/digest-test.js +++ b/test/strategies/digest-test.js @@ -1163,9 +1163,9 @@ vows.describe('DigestStrategy').addBatch({ }, }, - 'strategy handling a request without authorization credentials with opaque option set': { + 'strategy handling a request without authorization credentials with nonce option set': { topic: function() { - var strategy = new DigestStrategy({ opaque: 'abcdefg1234' }, + var strategy = new DigestStrategy({ nonce: '8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC' }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1197,14 +1197,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="abcdefg1234"$/); + assert.match(challenge, /^Digest realm="Users", nonce="8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC"$/); }, }, }, - - 'strategy handling a request without authorization credentials with opaque function option set': { + + 'strategy handling a request without authorization credentials with nonce function option set': { topic: function() { - var strategy = new DigestStrategy({ opaque: function(req) { return req.ip; } }, + var strategy = new DigestStrategy({ nonce: function(req) { return req.ip; } }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1237,14 +1237,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="192.168.1.66"$/); + assert.match(challenge, /^Digest realm="Users", nonce="192.168.1.66"$/); }, }, }, - - 'strategy handling a request without authorization credentials with algorithm option set': { + + 'strategy handling a request without authorization credentials with opaque option set': { topic: function() { - var strategy = new DigestStrategy({ algorithm: 'MD5-sess' }, + var strategy = new DigestStrategy({ opaque: 'abcdefg1234' }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1276,14 +1276,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", algorithm=MD5-sess$/); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="abcdefg1234"$/); }, }, }, - 'strategy handling a request without authorization credentials with qop option set': { + 'strategy handling a request without authorization credentials with opaque function option set': { topic: function() { - var strategy = new DigestStrategy({ qop: 'auth' }, + var strategy = new DigestStrategy({ opaque: function(req) { return req.ip; } }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1304,7 +1304,8 @@ vows.describe('DigestStrategy').addBatch({ strategy.fail = function(challenge) { self.callback(null, challenge); } - + + req.ip = '192.168.1.66'; req.url = '/'; req.headers = {}; process.nextTick(function () { @@ -1315,14 +1316,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth"$/); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="192.168.1.66"$/); }, }, }, - 'strategy handling a request without authorization credentials with multiple qop options set': { + 'strategy handling a request without authorization credentials with algorithm option set': { topic: function() { - var strategy = new DigestStrategy({ qop: ['auth', 'auth-int'] }, + var strategy = new DigestStrategy({ algorithm: 'MD5-sess' }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1354,15 +1355,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth,auth-int"$/); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", algorithm=MD5-sess$/); }, }, }, - - ///////////////////////////////////////////////////////////////////////////////////////////// - 'strategy handling a request without authorization credentials with nonce option set': { + + 'strategy handling a request without authorization credentials with qop option set': { topic: function() { - var strategy = new DigestStrategy({ nonce: '8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC' }, + var strategy = new DigestStrategy({ qop: 'auth' }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1394,14 +1394,14 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="8yDNoEqNDPEFHPgmMPDsk2sKqzddhIiC"$/); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth"$/); }, }, }, - - 'strategy handling a request without authorization credentials with nonce function option set': { + + 'strategy handling a request without authorization credentials with multiple qop options set': { topic: function() { - var strategy = new DigestStrategy({ nonce: function(req) { return req.ip; } }, + var strategy = new DigestStrategy({ qop: ['auth', 'auth-int'] }, function(username, done) { done(null, { username: username }, 'secret'); }, @@ -1422,8 +1422,7 @@ vows.describe('DigestStrategy').addBatch({ strategy.fail = function(challenge) { self.callback(null, challenge); } - - req.ip = '192.168.1.66'; + req.url = '/'; req.headers = {}; process.nextTick(function () { @@ -1434,11 +1433,10 @@ vows.describe('DigestStrategy').addBatch({ 'should fail authentication with challenge' : function(err, challenge) { // fail action was called, resulting in test callback assert.isNull(err); - assert.match(challenge, /^Digest realm="Users", nonce="192.168.1.66"$/); + assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth,auth-int"$/); }, }, }, - /////////////////////////////////////////////////////////////////////////////////////////////// 'strategy constructed without a secret callback or validate callback': { 'should throw an error': function (strategy) {