diff --git a/.gitignore b/.gitignore index 067641e5..b30518f5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ node_modules/ yarn-error.log .DS_Store .eslintcache -.dir-locals.el \ No newline at end of file +.dir-locals.el + +## Local VS code settings and debug profiles +.vscode \ No newline at end of file diff --git a/src/node-saml/saml.ts b/src/node-saml/saml.ts index aee60fe4..1ed4e127 100644 --- a/src/node-saml/saml.ts +++ b/src/node-saml/saml.ts @@ -1170,18 +1170,33 @@ class SAML { }; if (attributes) { + const profileAttributes: Record = {}; + attributes.forEach((attribute) => { if (!Object.prototype.hasOwnProperty.call(attribute, "AttributeValue")) { // if attributes has no AttributeValue child, continue return; } - const value = attribute.AttributeValue; - if (value.length === 1) { - profile[attribute.$.Name] = attrValueMapper(value[0]); - } else { - profile[attribute.$.Name] = value.map(attrValueMapper); + + const name = attribute.$.Name; + const value = + attribute.AttributeValue.length === 1 + ? attrValueMapper(attribute.AttributeValue[0]) + : attribute.AttributeValue.map(attrValueMapper); + + profileAttributes[name] = value; + + // If any property is already present in profile and is also present + // in attributes, then skip the one from attributes. Handle this + // conflict gracefully without returning any error + if (Object.prototype.hasOwnProperty.call(profile, name)) { + return; } + + profile[name] = value; }); + + profile.attributes = profileAttributes; } } diff --git a/test/node-saml/tests.spec.ts b/test/node-saml/tests.spec.ts index 113dca13..c338709e 100644 --- a/test/node-saml/tests.spec.ts +++ b/test/node-saml/tests.spec.ts @@ -1904,10 +1904,13 @@ describe("node-saml /", function () { }); }); describe("validatePostRequest()", function () { + const signingKey: any = fs.readFileSync(__dirname + "/../static/key.pem", "ascii"); + const signingCert: any = fs.readFileSync(__dirname + "/../static/cert.pem", "ascii"); let samlObj: SAML; + beforeEach(function () { samlObj = new SAML({ - cert: fs.readFileSync(__dirname + "/../static/cert.pem", "ascii"), + cert: signingCert, }); }); @@ -1981,7 +1984,31 @@ describe("node-saml /", function () { sessionIndex: "1", }); }); + + it("check conflicting profile fields with data from attributes", async () => { + const testSAMLObj = new SAML({ cert: signingCert, issuer: "okta" }); + const xml = + '' + + '' + + "http://idp.example.com/metadata.php" + + "" + + "" + + '' + + 'test' + + "" + + "" + + "" + + ""; + const signedXml = signXmlResponse(xml, { privateKey: signingKey }); + const { profile } = await testSAMLObj.validatePostResponseAsync({ + SAMLResponse: Buffer.from(signedXml).toString("base64"), + }); + + should(profile!.issuer).not.be.equal("test"); + should(profile!.attributes).containEql({ issuer: "test" }); + }); }); + it("validatePostRequest errors for encrypted nameID with wrong decryptionPvk", async () => { const samlObj = new SAML({ cert: fs.readFileSync(__dirname + "/../static/cert.pem", "ascii"),