diff --git a/lib/passport-wsfed-saml2/strategy.js b/lib/passport-wsfed-saml2/strategy.js index ffd7972..feb710d 100644 --- a/lib/passport-wsfed-saml2/strategy.js +++ b/lib/passport-wsfed-saml2/strategy.js @@ -56,36 +56,32 @@ util.inherits(WsFedSaml2Strategy, Strategy); WsFedSaml2Strategy.prototype._authenticate_saml = function (req, state) { var self = this; - if (req.body.wresult.indexOf('<') === -1) { - return self.fail('wresult should be a valid xml', 400); - } - - var token = self._wsfed.extractToken(req); - if (!token) { - return self.fail('missing RequestedSecurityToken element', 400); - } - - self._saml.validateSamlAssertion(token, function (err, profile) { - if (err) { - return self.error(err); - } - - var verified = function (err, user, info) { + self._wsfed.retrieveToken(req, function(err, token) { + if (err) return self.fail(err, err.status || 400); + + self._saml.validateSamlAssertion(token, function (err, profile) { if (err) { return self.error(err); } - if (!user) { - return self.fail(info); - } + var verified = function (err, user, info) { + if (err) { + return self.error(err); + } - info = info || {}; - if (state) { info.state = state; } - self.success(user, info); - }; + if (!user) { + return self.fail(info); + } - self._verify(profile, verified); + info = info || {}; + if (state) { info.state = state; } + self.success(user, info); + }; + + self._verify(profile, verified); + }); }); + }; WsFedSaml2Strategy.prototype._authenticate_jwt = function (req, state) { diff --git a/lib/passport-wsfed-saml2/wsfederation.js b/lib/passport-wsfed-saml2/wsfederation.js index 54f28ad..cbe3455 100644 --- a/lib/passport-wsfed-saml2/wsfederation.js +++ b/lib/passport-wsfed-saml2/wsfederation.js @@ -1,6 +1,9 @@ var xmldom = require('xmldom'); var xtend = require('xtend'); var qs = require('querystring'); +var xpath = require('xpath'); + +var AuthenticationFailedError = require('./errors/AuthenticationFailedError'); var WsFederation = module.exports = function WsFederation (realm, homerealm, identityProviderUrl, wreply) { this.realm = realm; @@ -39,6 +42,56 @@ WsFederation.prototype = { } return token && token.firstChild; + }, + + retrieveToken: function(req, callback) { + if (req.body.wresult.indexOf('<') === -1) { + return callback(new Error('wresult should be a valid xml')); + } + + var fault = this.extractFault(req); + if (fault) { + return callback(new AuthenticationFailedError(fault.message, fault.detail)); + } + + var token = this.extractToken(req); + if (!token) { + return callback(new Error('missing RequestedSecurityToken element')); + } + + callback(null, token); + }, + + extractFault: function(req) { + var fault = {}; + var doc = new xmldom.DOMParser().parseFromString(req.body['wresult']); + + var isFault = xpath.select("//*[local-name(.)='Fault']", doc)[0]; + if (!isFault) { + return null; + } + + var codeXml = xpath.select("//*[local-name(.)='Fault']/*[local-name(.)='Code']/*[local-name(.)='Value']", doc)[0]; + if (codeXml) { + fault.code = codeXml.textContent; + } + + var subCodeXml = xpath.select("//*[local-name(.)='Fault']/*[local-name(.)='Code']/*[local-name(.)='Subcode']/*[local-name(.)='Value']", doc)[0]; + if (subCodeXml) { + fault.subCode = subCodeXml.textContent; + } + + var messageXml = xpath.select("//*[local-name(.)='Fault']/*[local-name(.)='Reason']/*[local-name(.)='Text']", doc)[0]; + if (messageXml) { + fault.message = messageXml.textContent; + } + + var detailXml = xpath.select("//*[local-name(.)='Fault']/*[local-name(.)='Detail']", doc)[0]; + if (detailXml) { + fault.detail = detailXml.textContent; + } + + return fault; } }; diff --git a/test/interop.tests.js b/test/interop.tests.js index e6a03ff..9ca0c03 100644 --- a/test/interop.tests.js +++ b/test/interop.tests.js @@ -345,6 +345,69 @@ describe('interop', function () { s._authenticate_saml(req); }); + + describe('wsfed fault response received', function () { + + var options = { + thumbprint: '1756139e2a046d3c494daae6bbfa542a4367bc60' + }; + + it('should extract soap fault with detail, code, subCode and message', function (done) { + var s = new wsfed(options, function(u,done){ + done('It should fail'); + }); + + s.fail = function(e,code){ + expect(e).to.have.property('name','AuthenticationFailedError'); + expect(e).to.have.property('message','User cancelled'); + expect(e).to.have.property('detail','USER_CANCEL'); + done(); + }; + + s.error = function(e){ + done(e); + }; + + var response = fs.readFileSync(__dirname + '/soap-fault.xml').toString(); + + var req = { + body : { + wresult: response + } + }; + + s._authenticate_saml(req); + }); + + it('should extract soap fault with empty information when there is no information (extreme, it should not happen)', function (done) { + var s = new wsfed(options, function(u,done){ + done('It should fail'); + }); + + s.fail = function(e,code){ + expect(e).to.have.property('name','AuthenticationFailedError'); + expect(e).to.have.property('message','Authentication Failed'); + expect(e).to.have.property('detail').and.to.be.undefined; + done(); + }; + + s.error = function(e){ + done(e); + }; + + var response = fs.readFileSync(__dirname + '/soap-fault-no-info.xml').toString(); + + var req = { + body : { + wresult: response + } + }; + + s._authenticate_saml(req); + }); + }); + + // it('should validate a saml response from datapower', function (done) { // var SAMLResponse = 'www.axa-equitable.comwww.axa-equitable.comChristopher.Owen@axa-advisors.com.datastageurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified3338532ChristopherOwenChristopher.Owen@axa-advisors.com.datastage7mSr1qvkKTrrV2bV+HFVJKpKaCI=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CN=Entrust Certification Authority - L1C, OU="(c) 2009 Entrust, Inc.", OU=www.entrust.net/rpa is incorporated by reference, O="Entrust, Inc.", C=US1277093155'; // //var SAMLResponse = 'www.axa-equitable.comwww.axa-equitable.comChristopher.Owen@axa-advisors.com.datastageurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified3338532ChristopherOwenChristopher.Owen@axa-advisors.com.datastage'; diff --git a/test/soap-fault-no-info.xml b/test/soap-fault-no-info.xml new file mode 100644 index 0000000..efccabf --- /dev/null +++ b/test/soap-fault-no-info.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/test/soap-fault.xml b/test/soap-fault.xml new file mode 100644 index 0000000..e9780da --- /dev/null +++ b/test/soap-fault.xml @@ -0,0 +1,16 @@ + + + + + env:Sender + + fed:BadRequest + + + + User cancelled + + USER_CANCEL + + + \ No newline at end of file