Skip to content

Commit

Permalink
Merge pull request #7 from boxyhq/validate-inresponseto
Browse files Browse the repository at this point in the history
Validate InResponseTo
  • Loading branch information
deepakprabhakara authored Sep 9, 2021
2 parents 16a8fbb + 40ef1db commit f7b5d32
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 11 deletions.
8 changes: 8 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ saml.validate = function validate(rawAssertion, options, cb) {
cb(new Error('Invalid audience.'));
return;
}

if (options.inResponseTo && assertion.inResponseTo !== options.inResponseTo) {
cb(new Error('Invalid InResponseTo.'));
return;
}

parseAttributes(assertion, tokenHandler, cb);
});
Expand Down Expand Up @@ -101,6 +106,9 @@ function parseXmlAndVersion (rawAssertion, cb) {
return;
}

var tokenHandler = tokenHandlers[version];
assertion.inResponseTo = tokenHandler.getInResponseTo(xml);

cb(null, assertion, version);
});
}
Expand Down
4 changes: 4 additions & 0 deletions lib/saml11.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ saml11.validateExpiration = function validateExpiration(assertion) {

return !(now < notBefore || now > notOnOrAfter);
};

saml11.getInResponseTo = function validateAudience(xml) {
return _.get(xml, 'Response.@.ResponseID');
};
4 changes: 4 additions & 0 deletions lib/saml20.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ saml20.validateExpiration = function validateExpiration(assertion) {
var now = new Date();
return !(now < notBefore || now > notOnOrAfter);
};

saml20.getInResponseTo = function validateAudience(xml) {
return getProp(xml, 'Response.@.InResponseTo');
};
18 changes: 9 additions & 9 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "@boxyhq/saml20",
"version": "0.1.6",
"version": "0.1.7",
"description": "SAML 2.0 and 1.1 token parser for Node.js",
"main": "./lib/index.js",
"dependencies": {
"lodash": "^4.17.21",
"thumbprint": "^0.0.1",
"xml-crypto": "^2.1.3",
"xml2js": "^0.4.23",
"@xmldom/xmldom": "0.7.2"
"@xmldom/xmldom": "0.7.4"
},
"repository": {
"type": "git",
Expand Down
41 changes: 41 additions & 0 deletions test/assets/saml20.validResponse.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfx13f546a0-04f0-ca2a-e3ab-c477db0644dc" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#pfx13f546a0-04f0-ca2a-e3ab-c477db0644dc"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>LvGS1oVn0GClCCwpeiuMKHdZkN4=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>JmHdhq38jnSpYNn4EFbRVCF49ankAdBwQ+5gatF3ZFF3QHc7UuZ8k8azNSJAxuE1PqSmVr+abtX/0roICJyUcTRJkh/1BGWX8LG5njEHe4MVoRWmRzXwHzYaXB+cRbH05ZUufZR30yfolJTEbXPJJ7Le35rXgDJoc7P3JXyAKoo=</ds:SignatureValue>
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
136 changes: 136 additions & 0 deletions test/lib.saml20.response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var assert = require("assert");
var fs = require("fs");
var saml = require("../lib/index.js");

// Tests Configuration
var validResponse = fs
.readFileSync('./test/assets/saml20.validResponse.xml')
.toString();

var issuerName = 'http://idp.example.com/metadata.php';
var thumbprint = 'e606eced42fa3abd0c5693456384f5931b174707';
var certificate = 'MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==';
var audience = 'http://sp.example.com/demo1/metadata.php';
var inResponseTo = 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685';

describe("lib.saml20.response", function () {
it("Should validate saml 2.0 token using thumbprint", function (done) {
saml.validate(
validResponse,
{
publicKey: certificate,
thumbprint: thumbprint,
bypassExpiration: true,
inResponseTo: inResponseTo,
},
function (err, profile) {
assert.ifError(err);
assert.ok(profile.claims);

assert.strictEqual(issuerName, profile.issuer);
assert.strictEqual(
'_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7',
profile.claims[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
]
);
done();
}
);
});

it("Should validate saml 2.0 token using certificate", function (done) {
saml.validate(
validResponse,
{
publicKey: certificate,
bypassExpiration: true,
inResponseTo: inResponseTo,
},
function (err, profile) {
assert.ifError(err);
assert.strictEqual(issuerName, profile.issuer);
assert.ok(profile.claims);
assert.strictEqual(
'_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7',
profile.claims[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
]
);

done();
}
);
});

it("Should validate saml 2.0 token and check audience", function (done) {
saml.validate(
validResponse,
{
publicKey: certificate,
audience: audience,
bypassExpiration: true,
inResponseTo: inResponseTo,
},
function (err, profile) {
assert.ifError(err);
assert.strictEqual(issuerName, profile.issuer);
assert.ok(profile.claims);
done();
}
);
});

it("Should fail with invalid audience", function (done) {
saml.validate(
validResponse,
{
publicKey: certificate,
audience: 'http://any-other-audience.com/',
bypassExpiration: true,
inResponseTo: inResponseTo,
},
function (err, profile) {
assert.ok(!profile);
assert.ok(err);
assert.strictEqual('Invalid audience.', err.message);
done();
}
);
});

it("Should fail with invalid assertion", function (done) {
saml.validate(
'invalid-assertion',
{
publicKey: certificate,
bypassExpiration: true,
inResponseTo: inResponseTo,
},
function (err, profile) {
assert.ok(!profile);
assert.ok(err);
assert.strictEqual('Invalid assertion.', err.message);
done();
}
);
});

it("Should fail with invalid inResponseTo", function (done) {
saml.validate(
validResponse,
{
publicKey: certificate,
audience: audience,
bypassExpiration: true,
inResponseTo: 'not-the-right-response-to',
},
function (err, profile) {
assert.ok(!profile);
assert.ok(err);
assert.strictEqual('Invalid InResponseTo.', err.message);
done();
}
);
});
});

0 comments on commit f7b5d32

Please # to comment.