Skip to content

Commit

Permalink
Support redirect flows
Browse files Browse the repository at this point in the history
  • Builds on node-saml#16
  • Reverts 638ce6e
  • Implements node-saml#191
  • Loading branch information
stavros-wb committed Apr 11, 2018
1 parent c791677 commit fb6851f
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 14 deletions.
157 changes: 146 additions & 11 deletions lib/passport-saml/saml.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,19 +551,10 @@ SAML.prototype.validatePostResponse = function (container, callback) {

if(inResponseTo){
inResponseTo = inResponseTo.length ? inResponseTo[0].nodeValue : null;
}

if(self.options.validateInResponseTo){
if (inResponseTo) {
return Q.ninvoke(self.cacheProvider, 'get', inResponseTo)
.then(function(result) {
if (!result)
throw new Error('InResponseTo is not valid');
return Q();
});
if(inResponseTo) {
return self.validateInResponseTo(inResponseTo);
}
} else {
return Q();
}
})
.then(function() {
Expand Down Expand Up @@ -689,6 +680,150 @@ SAML.prototype.validatePostResponse = function (container, callback) {
.done();
};

SAML.prototype.validateInResponseTo = function(inResponseTo) {
if(this.options.validateInResponseTo){
return Q.ninvoke(this.cacheProvider, 'get', inResponseTo)
.then(function(result) {
if (!result)
throw new Error('InResponseTo is not valid');
return Q();
});
} else {
return Q();
}
}

SAML.prototype.validateRedirect = function(container, callback) {
var self = this;
var type = container.SAMLRequest ? 'SAMLRequest' : 'SAMLResponse';

var data = new Buffer(container[type], "base64");
zlib.inflateRaw(data, function(err, inflated) {
if (err) {
return callback(err);
}

var dom = new xmldom.DOMParser().parseFromString(inflated.toString());
var parserConfig = {
explicitRoot: true,
tagNameProcessors: [xml2js.processors.stripPrefix]
};
var parser = new xml2js.Parser(parserConfig);
parser.parseString(inflated, function (err, doc) {
if (err) {
return callback(err);
}

Q.fcall(function () {
return type === 'SAMLResponse' ? self.verifyLogoutResponse(doc) : self.verifyLogoutRequest(doc);
})
.then(function() {
var signature = container.Signature;
if (signature) {
if (self.options.cert) {
return self.certsToCheck()
.then(function(certs) {
var urlString = type + '=' + encodeURIComponent(container[type]);

if (container.RelayState) {
urlString += '&RelayState=' +
encodeURIComponent(container.RelayState);
}

urlString += '&SigAlg=' + encodeURIComponent(container.SigAlg);

var hasValidQuerySignature = certs.some(function (cert) {
return validateSignatureForRedirect(
urlString, signature, container.SigAlg, cert
);
});

if (!hasValidQuerySignature) {
throw 'Invalid signature';
}
})
}
}
return Q();
})
.then(function () {
processValidlySignedSamlLogout(self, doc, callback);
})
.fail(function(err) {
callback(err);
});
});
});
};

function validateSignatureForRedirect (urlString, signature, alg, cert) {
var supportedAlgs = crypto.getHashes().filter(
function(h) { return new RegExp(h).test(alg) }
)

if (supportedAlgs.length === 0) {
throw alg + ' is not supported';
}

var verifier = crypto.createVerify(supportedAlgs[supportedAlgs.length-1]);
verifier.update(urlString);

return verifier.verify(cert, signature, 'base64');
}

function processValidlySignedSamlLogout(self, doc, callback) {
var response = doc.LogoutResponse;
var request = doc.LogoutRequest;

if (response){
return callback(null, null, true);
} else if (request) {
processValidlySignedPostRequest(self, doc, callback);
} else {
throw new Error('Unknown SAML response message');
}
}

SAML.prototype.verifyLogoutRequest = function (doc) {
this.verifyIssuer(doc.LogoutRequest);
var nowMs = new Date().getTime();
var conditions = doc.LogoutRequest.$;
var conErr = this.checkTimestampsValidityError(
nowMs, conditions.NotBefore, conditions.NotOnOrAfter
);
if (conErr) {
throw conErr;
}
}

SAML.prototype.verifyLogoutResponse = function (doc) {
var self = this;

return Q.fcall(function() {
var statusCode = doc.LogoutResponse.Status[0].StatusCode[0].$.Value;
if (statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success")
throw 'Bad status code: ' + statusCode;

self.verifyIssuer(doc.LogoutResponse);
var inResponseTo = doc.LogoutResponse.$.InResponseTo;
if (inResponseTo) {
return self.validateInResponseTo(inResponseTo);
}

return Q(true);
});
}

SAML.prototype.verifyIssuer = function (samlMessage) {
var issuer = samlMessage.Issuer;
if (issuer && this.options.samlIssuer) {
if (issuer[0] !== this.options.samlIssuer)
throw 'Unknown SAML issuer. Expected: ' + this.options.samlIssuer + ' Received: ' + issuer[0];
} else {
throw 'Missing SAML issuer';
}
}

SAML.prototype.processValidlySignedAssertion = function(xml, inResponseTo, callback) {
var self = this;
var msg;
Expand Down
8 changes: 5 additions & 3 deletions lib/passport-saml/strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ Strategy.prototype.authenticate = function (req, options) {
}
}

if (req.body && req.body.SAMLResponse) {
this._saml.validatePostResponse(req.body, validateCallback);
if (req.query && (req.query.SAMLResponse || req.query.SAMLRequest)) {
this._saml.validateRedirect(req.query, validateCallback);
} else if (req.body && req.body.SAMLResponse) {
this._saml.validatePostResponse(req.body, validateCallback);
} else if (req.body && req.body.SAMLRequest) {
this._saml.validatePostRequest(req.body, validateCallback);
this._saml.validatePostRequest(req.body, validateCallback);
} else {
var requestHandler = {
'login-request': function() {
Expand Down
6 changes: 6 additions & 0 deletions test/static/idp_slo_redirect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"SAMLRequest": "fVLbjpswEP0V5PeADQSIlaBGiipF2u62TdWHvkQDHhdUY1N72O7nF8iutL2+WeNzm6PZBxjMKO/cVzfRR/w+YaDoaTA2yPXnwCZvpYPQB2lhwCCplZfjuzuZxlyO3pFrnWGvKP9nQAjoqXeWRefTgV0rzVFrhRmvQOmmACy1wHTX5CLfbUvdZinX2yIrNIs+ow8z88BmoZkewoRnGwgszSMuqg3PN0J8ErnkmRTZFxad5m16C7SyOqJRJkmvxtg8dvGAcss5T2CiLlmCJ0AhIY+gAjmLC/AajGPRvaMH++CPmtD/4VStTvV+UZBrJl8/OxnXgulcIJnyxWmBpItsMiCBAoJ47MZ98pp607mfezufosv75fFhAtPrfvH++woseuv8APTv5kUs1kmvNnqFShygN0elPIbA6rnER+/Cmx/Of4PGYNy64TnXLcot1ygvM3wu82wVPtVXzhtdNqnaqp3K2l3JU1EitqgbIXZ5gzrLihQKUeFN7Df+y/CX46t/Ag==",
"RelayState": "_a4653e9109c4639a2165159db4ca31e4f0ca505654",
"SigAlg": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"Signature": "HxvhXqy+UE8osj/i9PX76alvOeltoM7XaXwrYLDpOfNTcFruEhm2wMWUgqjw7XMpdDVGdEpuDwXxCByVmxwUKRBQOCORMQbzwRzPzu5jlPmYu9M0Q6oJK7S4Rlnpgxtfmbs0rlt56DCeb53JPJI1AvBdv9kfAghgVJbU0wrUDQTOkeQVwYYunJ6TaZAF4l2LqpKvwUIyReVML65UJR/T4GOGIr/QWnuRf1haq6AxhpbDdeWhgrv/CPNEmpzr/kvkDHK664Z0ruvLUlpyFUbfbYq/YQmctdr0WncyJzPnkzuw6t83pe333qLognwH8DzXS7A4ygUvZJ5/iTlr8CaYOg=="
}
5 changes: 5 additions & 0 deletions test/static/sp_slo_redirect.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"SAMLResponse": "fZHNasMwEIRfxeie6Mdx4ojEUJpLIL00IYdewtra1gZZMt516ePXTluaQgnoImm+mWF3Q9D6zh7iWxz4GamLgTD5aH0ge/3aiqEPNgI1ZAO0SJYre3x4OlgzV7brI8cqenGD3CeACHtuYhDJfrcVl6VS68Va5ZlbA5bG5SkqnSKkRhuzMtUKl/miVIivIjljTyO5FaPRiBMNuA/EEHh8UjqfqcVM65PObGrG8yKSHRI3AfhK1cydlbJx3dy/1/MWbaaUkjBwLafiEpgk9wiOOAachBfycUwKP5M5xbHyau1KjStw2lSg3TLFrBTFZrKw11J98R3lYwW+jsTWqClqkpjJV7bI4IBh3tXdRt6im6+NHBl4oL+3x+gwOYMf8P6M6aq2x6GqkEjI4ivh11T+t/XiEw==",
"SigAlg": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"Signature": "RRcmtS0Qku3Y5HfSQYKax07dZOnb+CvvtP72v6ws/kttG4DYaNkRy0YmgDjrSLiVNooJe69LMb3pnHsW2PN7pKKe1p/TroGfk/7BW/QRgl3jvnLeM19qvhb3WYgwmeEXWvuX2rhopGCF2bVuzz9UDPZEDTQ6Botq39QkNTYfn1EpTlGHGQ7k/f9l2rX8DoVOu+v9Ov+PIUd0wSYiT1Nw/Nv2/xoMP+jDQCfU/x+D01wcInwlceaIKkYNb/VX3e9E3GzhOljJiUEVA7t9CEDMOACgfhiof9m/70txwhxGWPzE0XrBIIpuGp8NPU9QaiVDymbd9wzDVtll6abpx2t14A=="
}
6 changes: 6 additions & 0 deletions test/static/sp_slo_redirect_failure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"SAMLResponse": "fZHLasMwEEV/xWif6OG8LBxDaTaBdJOELLoJY2taG2zJ9YxLP7+229IUSkAbaebcezWTEjR1aw/hNfR8RGqDJ4w+mtqTnUpb0XfeBqCKrIcGyXJhTw9PB2vmyrZd4FCEWtwg9wkgwo6r4EW0323FdaVUskjUZukSwNy4TYxKxwix0casTbHG1WaRK8QXEV2wo4HcikFowIl63Hti8Dw8Kb2ZqcVM67Ne2tgM51lEOySuPPBElcytlbJy7bx+L+cN2qVSSkLPpRyDS2CS3CE44uBxbLxSHQYn/zOZcxgirxOXa1yD06YA7VYxLnORpaOEnUJ12bdVHQqoy0BsjRqtxhYz6soGGRwwzNuyTeUtmn5t5MTAPf29PQaH0QXqHu/PmKZue8S3fvg/dkJmXx6/svK/vWef",
"SigAlg": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"Signature": "gS9wWeutxU9nUplBYaJdOkP3vxSyyyE6HlHGBBIbLYYJVzZMVHmpeR1c4z0EirUySg1XOrlWcJxwt9YpDQue2e/XExIokc9EcgiAKNAAshYeewLfM6VGxn48rpkU4rE35kiw1Jiydm/cx/JfatBYc1YT74TyjwckNZ/pj08+7ILI6rTESkkBRfACZZ7zzFL+K41OiiKcgs3TZNP4GfalzrXClQ5eELA1NG3JfxkzUTwApjkNCTVSBQcWRa/nyko+PDKPVG1U7dEe16csYfae2u9EkP81seQqgAPsqOyrASQtuNqf+vZhN9aCb25Q4X26soA0QDSzegNb82TdifhhDQ==",
"RelayState": "http://idp.lvh.me:5000"
}
140 changes: 140 additions & 0 deletions test/tests.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fb6851f

Please sign in to comment.