diff --git a/.npm/package/npm-shrinkwrap.json b/.npm/package/npm-shrinkwrap.json index 7762aad..700ee0f 100644 --- a/.npm/package/npm-shrinkwrap.json +++ b/.npm/package/npm-shrinkwrap.json @@ -1,10 +1,22 @@ { "lockfileVersion": 1, "dependencies": { + "arraybuffer-to-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer-to-string/-/arraybuffer-to-string-1.0.1.tgz", + "integrity": "sha1-V6vLXkxJSOTtEEoxGQOKNqPU/ZQ=" + }, "async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz", - "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=" + "integrity": "sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=", + "dependencies": { + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + } + } }, "body-parser": { "version": "1.17.1", @@ -16,17 +28,92 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, "debug": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.1.tgz", - "integrity": "sha1-eYVQkLosTjEVzH2HaUkdWPBJE1E=", + "integrity": "sha1-eYVQkLosTjEVzH2HaUkdWPBJE1E=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", "dependencies": { - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" } } + }, + "iconv-lite": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=" + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "raw-body": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", + "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" } } }, @@ -85,9 +172,9 @@ "integrity": "sha1-R5Y2v6P+Ox3r1SCH8KyyBLTxnIg=" }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "escape-html": { "version": "1.0.3", @@ -114,7 +201,24 @@ "http-errors": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", - "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" + "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } }, "iconv-lite": { "version": "0.4.15", @@ -137,14 +241,21 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" }, "mime-types": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dependencies": { + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + } + } }, "ms": { "version": "1.0.0", @@ -159,12 +270,19 @@ "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + } + } }, "parseurl": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, "qs": { "version": "6.4.0", @@ -185,6 +303,11 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" } } }, @@ -206,7 +329,19 @@ "type-is": { "version": "1.6.15", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dependencies": { + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=" + } + } }, "unpipe": { "version": "1.0.0", @@ -227,6 +362,11 @@ "version": "0.1.19", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.19.tgz", "integrity": "sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=" + }, + "xpath.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", + "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==" } } }, @@ -235,6 +375,16 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "dependencies": { + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "xmlbuilder": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", diff --git a/package.js b/package.js index 14e257e..db75f5e 100644 --- a/package.js +++ b/package.js @@ -53,7 +53,8 @@ Npm.depends({ "xpath.js": "1.0.7", "xmldom": "0.1.27", "connect": "3.6.0", - "querystring": "0.2.0" + "querystring": "0.2.0", + "arraybuffer-to-string": "1.0.1" // "xml-encryption": "0.10.0" }); diff --git a/saml_server.js b/saml_server.js index 3ef82aa..46f9250 100755 --- a/saml_server.js +++ b/saml_server.js @@ -100,6 +100,9 @@ Accounts.registerLoginHandler(function(loginRequest) { }; localFindStructure = 'profile.' + Meteor.settings.saml[0].localProfileMatchAttribute; } + if (Meteor.settings.debug) { + console.log("Looking for user with " + localFindStructure + "=" + loginResult.profile.nameID); + } var user = Meteor.users.findOne({ //profile[Meteor.settings.saml[0].localProfileMatchAttribute]: loginResult.profile.nameID [localFindStructure]: loginResult.profile.nameID @@ -109,7 +112,7 @@ Accounts.registerLoginHandler(function(loginRequest) { if (Meteor.settings.saml[0].dynamicProfile) { if (Meteor.settings.debug) { console.log("User not found. Will dynamically create one with '" + Meteor.settings.saml[0].localProfileMatchAttribute + "' = " + loginResult.profile[Meteor.settings.saml[0].localProfileMatchAttribute]); - console.log("Identity handle: " + profileOrEmail + " = " + JSON.stringify(profileOrEmailValue)); + console.log("Identity handle: " + profileOrEmail + " = " + JSON.stringify(profileOrEmailValue) + " || username = " + loginResult.profile.nameID); } Accounts.createUser({ //email: loginResult.profile.email, @@ -119,6 +122,9 @@ Accounts.registerLoginHandler(function(loginRequest) { //[Meteor.settings.saml[0].localProfileMatchAttribute]: loginResult.profile[Meteor.settings.saml[0].localProfileMatchAttribute] }); + if (Meteor.settings.debug) { + console.log("Trying to find user"); + } user = Meteor.users.findOne({ "username": loginResult.profile.nameID }); @@ -275,6 +281,9 @@ middleware = function(req, res, next) { break; case "logout": // This is where we receive SAML LogoutResponse + if (Meteor.settings.debug) { + console.log("Handling call to 'logout' endpoint." + req.query.SAMLResponse); + } _saml = new SAML(service); _saml.validateLogoutResponse(req.query.SAMLResponse, function(err, result) { if (!err) { @@ -318,7 +327,9 @@ middleware = function(req, res, next) { }); res.end(); } else { - // TBD thinking of sth meaning full. + if (Meteor.settings.debug) { + console.log("Couldn't validate SAML Logout Response.."); + } } }) break; diff --git a/saml_utils.js b/saml_utils.js index e4c8a18..d00d03c 100755 --- a/saml_utils.js +++ b/saml_utils.js @@ -10,12 +10,13 @@ const crypto = Npm.require('crypto'); const xmldom = Npm.require('xmldom'); const querystring = Npm.require('querystring'); const xmlbuilder = Npm.require('xmlbuilder'); +const array2string = Npm.require('arraybuffer-to-string'); // var prefixMatch = new RegExp(/(?!xmlns)^.*:/); SAML = function(options) { - this.options = this.initialize(options); + this.options = this.initialize(options); }; // var stripPrefix = function(str) { @@ -23,200 +24,200 @@ SAML = function(options) { // }; SAML.prototype.initialize = function(options) { - if (!options) { - options = {}; - } + if (!options) { + options = {}; + } - if (!options.protocol) { - options.protocol = 'https://'; - } + if (!options.protocol) { + options.protocol = 'https://'; + } - if (!options.path) { - options.path = '/saml/consume'; - } + if (!options.path) { + options.path = '/saml/consume'; + } - if (!options.issuer) { - options.issuer = 'onelogin_saml'; - } + if (!options.issuer) { + options.issuer = 'onelogin_saml'; + } - if (options.identifierFormat === undefined) { - options.identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'; - } + if (options.identifierFormat === undefined) { + options.identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'; + } - if (options.authnContext === undefined) { - options.authnContext = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; - } + if (options.authnContext === undefined) { + options.authnContext = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; + } - return options; + return options; }; SAML.prototype.generateUniqueID = function() { - const chars = 'abcdef0123456789'; - let uniqueID = 'id-'; - for (let i = 0; i < 20; i++) { - uniqueID += chars.substr(Math.floor((Math.random() * 15)), 1); - } - return uniqueID; + const chars = 'abcdef0123456789'; + let uniqueID = 'id-'; + for (let i = 0; i < 20; i++) { + uniqueID += chars.substr(Math.floor((Math.random() * 15)), 1); + } + return uniqueID; }; SAML.prototype.generateInstant = function() { - return new Date().toISOString(); + return new Date().toISOString(); }; SAML.prototype.signRequest = function(xml) { - const signer = crypto.createSign('RSA-SHA1'); - signer.update(xml); - return signer.sign(this.options.privateKey, 'base64'); + const signer = crypto.createSign('RSA-SHA1'); + signer.update(xml); + return signer.sign(this.options.privateKey, 'base64'); }; SAML.prototype.generateAuthorizeRequest = function(req) { - let id = `_${ this.generateUniqueID() }`; - const instant = this.generateInstant(); + let id = `_${ this.generateUniqueID() }`; + const instant = this.generateInstant(); - // Post-auth destination - let callbackUrl; - if (this.options.callbackUrl) { - callbackUrl = this.options.callbackUrl; - } else { - callbackUrl = this.options.protocol + req.headers.host + this.options.path; - } + // Post-auth destination + let callbackUrl; + if (this.options.callbackUrl) { + callbackUrl = this.options.callbackUrl; + } else { + callbackUrl = this.options.protocol + req.headers.host + this.options.path; + } - if (this.options.id) { - id = this.options.id; - } + if (this.options.id) { + id = this.options.id; + } - let request = - `` + - `${ this.options.issuer }\n`; + `${ this.options.issuer }\n`; - if (this.options.identifierFormat) { - request += `\n`; - } + } - request += - '' + - 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\n' + - ''; + request += + '' + + 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\n' + + ''; - return request; + return request; }; SAML.prototype.generateLogoutRequest = function(options) { - // options should be of the form - // nameId: - // sessionIndex: sessionIndex - // --- NO SAMLsettings: + // sessionIndex: sessionIndex + // --- NO SAMLsettings: ` + - `${ this.options.issuer }` + - `${ options.nameID }` + - ''; + `${ this.options.issuer }` + + `${ options.nameID }` + + ''; - request = `${ '' + - `${ this.options.issuer }` + - '${ + 'Version="2.0" ' + + `IssueInstant="${ instant }" ` + + `Destination="${ this.options.idpSLORedirectURL }" ` + + '>' + + `${ this.options.issuer }` + + '${ options.nameID }` + - `${ options.sessionIndex }` + - ''; - if (Meteor.settings.debug) { - console.log('------- SAML Logout request -----------'); - console.log(request); - } - return { - request, - id - }; + `${ options.sessionIndex }` + + ''; + if (Meteor.settings.debug) { + console.log('------- SAML Logout request -----------'); + console.log(request); + } + return { + request, + id + }; }; SAML.prototype.requestToUrl = function(request, operation, callback) { - const self = this; - zlib.deflateRaw(request, function(err, buffer) { - if (err) { - return callback(err); - } + const self = this; + zlib.deflateRaw(request, function(err, buffer) { + if (err) { + return callback(err); + } - const base64 = buffer.toString('base64'); - let target = self.options.entryPoint; + const base64 = buffer.toString('base64'); + let target = self.options.entryPoint; - if (operation === 'logout') { - if (self.options.idpSLORedirectURL) { - target = self.options.idpSLORedirectURL; - } - } + if (operation === 'logout') { + if (self.options.idpSLORedirectURL) { + target = self.options.idpSLORedirectURL; + } + } - if (target.indexOf('?') > 0) { - target += '&'; - } else { - target += '?'; - } + if (target.indexOf('?') > 0) { + target += '&'; + } else { + target += '?'; + } - // TBD. We should really include a proper RelayState here - let relayState; - if (operation === 'logout') { - // in case of logout we want to be redirected back to the Meteor app. - relayState = Meteor.absoluteUrl(); - } else { - relayState = self.options.provider; - } + // TBD. We should really include a proper RelayState here + let relayState; + if (operation === 'logout') { + // in case of logout we want to be redirected back to the Meteor app. + relayState = Meteor.absoluteUrl(); + } else { + relayState = self.options.provider; + } - const samlRequest = { - SAMLRequest: base64, - RelayState: relayState - }; + const samlRequest = { + SAMLRequest: base64, + RelayState: relayState + }; - if (self.options.privateCert) { - samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); - } + if (self.options.privateCert) { + samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); + } - target += querystring.stringify(samlRequest); + target += querystring.stringify(samlRequest); - if (Meteor.settings.debug) { - console.log(`requestToUrl: ${ target }`); - } - if (operation === 'logout') { - // in case of logout we want to be redirected back to the Meteor app. - return callback(null, target); + if (Meteor.settings.debug) { + console.log(`requestToUrl: ${ target }`); + } + if (operation === 'logout') { + // in case of logout we want to be redirected back to the Meteor app. + return callback(null, target); - } else { - callback(null, target); - } - }); + } else { + callback(null, target); + } + }); }; SAML.prototype.getAuthorizeUrl = function(req, callback) { - const request = this.generateAuthorizeRequest(req); + const request = this.generateAuthorizeRequest(req); - this.requestToUrl(request, 'authorize', callback); + this.requestToUrl(request, 'authorize', callback); }; SAML.prototype.getLogoutUrl = function(req, callback) { - const request = this.generateLogoutRequest(req); + const request = this.generateLogoutRequest(req); - this.requestToUrl(request, 'logout', callback); + this.requestToUrl(request, 'logout', callback); }; SAML.prototype.certToPEM = function(cert) { - cert = cert.match(/.{1,64}/g).join('\n'); - cert = `-----BEGIN CERTIFICATE-----\n${ cert }`; - cert = `${ cert }\n-----END CERTIFICATE-----\n`; - return cert; + cert = cert.match(/.{1,64}/g).join('\n'); + cert = `-----BEGIN CERTIFICATE-----\n${ cert }`; + cert = `${ cert }\n-----END CERTIFICATE-----\n`; + return cert; }; // functionfindChilds(node, localName, namespace) { @@ -230,7 +231,7 @@ SAML.prototype.certToPEM = function(cert) { // return res; // } -SAML.prototype.validateStatus = function (doc) { +SAML.prototype.validateStatus = function(doc) { let successStatus = false; let status = ''; let messageText = ''; @@ -251,81 +252,94 @@ SAML.prototype.validateStatus = function (doc) { successStatus = true; } } - return { success :successStatus, message :messageText, statusCode : status}; + return { + success: successStatus, + message: messageText, + statusCode: status + }; }; SAML.prototype.validateSignature = function(xml, cert) { - const self = this; + const self = this; - const doc = new xmldom.DOMParser().parseFromString(xml); - const signature = xmlCrypto.xpath(doc, '//*[local-name(.)=\'Signature\' and namespace-uri(.)=\'http://www.w3.org/2000/09/xmldsig#\']')[0]; + const doc = new xmldom.DOMParser().parseFromString(xml); + const signature = xmlCrypto.xpath(doc, '//*[local-name(.)=\'Signature\' and namespace-uri(.)=\'http://www.w3.org/2000/09/xmldsig#\']')[0]; - const sig = new xmlCrypto.SignedXml(); + const sig = new xmlCrypto.SignedXml(); - sig.keyInfoProvider = { - getKeyInfo(/*key*/) { - return ''; - }, - getKey(/*keyInfo*/) { - return self.certToPEM(cert); - } - }; + sig.keyInfoProvider = { + getKeyInfo( /*key*/ ) { + return ''; + }, + getKey( /*keyInfo*/ ) { + return self.certToPEM(cert); + } + }; - sig.loadSignature(signature); + sig.loadSignature(signature); - return sig.checkSignature(xml); + return sig.checkSignature(xml); }; SAML.prototype.validateLogoutResponse = function(samlResponse, callback) { - const self = this; - - const compressedSAMLResponse = new Buffer(samlResponse, 'base64'); - zlib.inflateRaw(compressedSAMLResponse, function(err, decoded) { - - if (err) { - if (Meteor.settings.debug) { - console.log(err); - } - } else { - const doc = new xmldom.DOMParser().parseFromString(decoded, 'text/xml'); + const self = this; + const compressedSAMLResponse = new Buffer(samlResponse, 'base64'); + zlib.inflateRaw(compressedSAMLResponse, function(err, decoded) { + if (err) { + if (Meteor.settings.debug) { + console.log("Error while inflating." + err); + } + } else { + console.log("construvting new DOM parser: " + Object.prototype.toString.call(decoded)); + console.log(">>>>" + decoded); + const doc = new xmldom.DOMParser().parseFromString(array2string(decoded), 'text/xml'); if (doc) { - const response = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'LogoutResponse'); - if (response) { + const response = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'LogoutResponse')[0]; + if (response) { // TBD. Check if this msg corresponds to one we sent - const inResponseTo = response.getAttribute('InResponseTo'); - if (Meteor.settings.debug) { - console.log(`In Response to: ${ inResponseTo }`); + var inResponseTo; + try { + inResponseTo = response.getAttribute('InResponseTo'); + if (Meteor.settings.debug) { + console.log(`In Response to: ${ inResponseTo }`); + } + } catch (e) { + if (Meteor.settings.debug) { + console.log("Caught error: " + e); + const msg = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusMessage'); + console.log("Unexpected msg from IDP. Does your session still exist at IDP? Idp returned: \n" + msg); + } } + let statusValidateObj = self.validateStatus(doc); - if(statusValidateObj.success) { + if (statusValidateObj.success) { callback(null, inResponseTo); - }else{ + } else { callback('Error. Logout not confirmed by IDP', null); } - } - else { + } else { callback('No Response Found', null); } } } - }); + }); }; SAML.prototype.validateResponse = function(samlResponse, relayState, callback) { - const self = this; - const xml = new Buffer(samlResponse, 'base64').toString('utf8'); - // We currently use RelayState to save SAML provider - if (Meteor.settings.debug) { - console.log(`Validating response with relay state: ${ xml }`); - } - const parser = new xml2js.Parser({ - explicitRoot: true - }); + const self = this; + const xml = new Buffer(samlResponse, 'base64').toString('utf8'); + // We currently use RelayState to save SAML provider + if (Meteor.settings.debug) { + console.log(`Validating response with relay state: ${ xml }`); + } + const parser = new xml2js.Parser({ + explicitRoot: true + }); const doc = new xmldom.DOMParser().parseFromString(xml, 'text/xml'); @@ -408,28 +422,28 @@ SAML.prototype.validateResponse = function(samlResponse, relayState, callback) { const attributeStatement = assertion.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'AttributeStatement')[0]; if (attributeStatement) { - if (Meteor.settings.debug) { - console.log("Attribute Statement found in SAML response: " + attributeStatement); - } + if (Meteor.settings.debug) { + console.log("Attribute Statement found in SAML response: " + attributeStatement); + } const attributes = attributeStatement.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Attribute'); - if (Meteor.settings.debug) { - console.log("Attributes will be processed: " + attributes.length); - } + if (Meteor.settings.debug) { + console.log("Attributes will be processed: " + attributes.length); + } if (attributes) { for (let i = 0; i < attributes.length; i++) { const value = attributes[i].getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'AttributeValue')[0]; - if (Meteor.settings.debug) { - console.log("Name: " + attributes[i]); - console.log(`Adding attrinute from SAML response to profile:` + attributes[i].getAttribute('Name') + " = " + value.textContent); - } + if (Meteor.settings.debug) { + console.log("Name: " + attributes[i]); + console.log(`Adding attrinute from SAML response to profile:` + attributes[i].getAttribute('Name') + " = " + value.textContent); + } profile[attributes[i].getAttribute('Name')] = value.textContent; } } else { - if (Meteor.settings.debug) { - console.log("No Attributes found in SAML attribute statement."); - } - } + if (Meteor.settings.debug) { + console.log("No Attributes found in SAML attribute statement."); + } + } if (!profile.mail && profile['urn:oid:0.9.2342.19200300.100.1.3']) { // See http://www.incommonfederation.org/attributesummary.html for definition of attribute OIDs @@ -440,10 +454,10 @@ SAML.prototype.validateResponse = function(samlResponse, relayState, callback) { profile.email = profile.mail; } } else { - if (Meteor.settings.debug) { - console.log("No Attribute Statement found in SAML response."); - } - } + if (Meteor.settings.debug) { + console.log("No Attribute Statement found in SAML response."); + } + } if (!profile.email && profile.nameID && profile.nameIDFormat && profile.nameIDFormat.indexOf('emailAddress') >= 0) { profile.email = profile.nameID; @@ -463,9 +477,9 @@ SAML.prototype.validateResponse = function(samlResponse, relayState, callback) { } } - }else{ + } else { return callback(new Error(`Status is: ${ statusValidateObj.statusCode }`), null, false); - } + } } }; @@ -473,68 +487,74 @@ SAML.prototype.validateResponse = function(samlResponse, relayState, callback) { let decryptionCert; SAML.prototype.generateServiceProviderMetadata = function(callbackUrl) { - if (!decryptionCert) { - decryptionCert = this.options.privateCert; - } + if (!decryptionCert) { + decryptionCert = this.options.privateCert; + } - if (!this.options.callbackUrl && !callbackUrl) { - throw new Error( - 'Unable to generate service provider metadata when callbackUrl option is not set'); - } + if (!this.options.callbackUrl && !callbackUrl) { + throw new Error( + 'Unable to generate service provider metadata when callbackUrl option is not set'); + } - const metadata = { - 'EntityDescriptor': { - '@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata', - '@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#', - '@entityID': this.options.issuer, - 'SPSSODescriptor': { - '@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol', - 'SingleLogoutService': { - '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - '@Location': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`, - '@ResponseLocation': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/` - }, - 'NameIDFormat': this.options.identifierFormat, - 'AssertionConsumerService': { - '@index': '1', - '@isDefault': 'true', - '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - '@Location': callbackUrl - } - } - } - }; + const metadata = { + 'EntityDescriptor': { + '@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata', + '@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#', + '@entityID': this.options.issuer, + 'SPSSODescriptor': { + '@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol', + 'SingleLogoutService': { + '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + '@Location': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`, + '@ResponseLocation': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/` + }, + 'NameIDFormat': this.options.identifierFormat, + 'AssertionConsumerService': { + '@index': '1', + '@isDefault': 'true', + '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + '@Location': callbackUrl + } + } + } + }; - if (this.options.privateKey) { - if (!decryptionCert) { - throw new Error( - 'Missing decryptionCert while generating metadata for decrypting service provider'); - } + if (this.options.privateKey) { + if (!decryptionCert) { + throw new Error( + 'Missing decryptionCert while generating metadata for decrypting service provider'); + } - decryptionCert = decryptionCert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ''); - decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, ''); - decryptionCert = decryptionCert.replace(/\r\n/g, '\n'); + decryptionCert = decryptionCert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, ''); + decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, ''); + decryptionCert = decryptionCert.replace(/\r\n/g, '\n'); - metadata['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'] = { - 'ds:KeyInfo': { - 'ds:X509Data': { - 'ds:X509Certificate': { - '#text': decryptionCert - } - } - }, - 'EncryptionMethod': [ - // this should be the set that the xmlenc library supports - {'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'}, - {'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'}, - {'@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'} - ] - }; - } + metadata['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'] = { + 'ds:KeyInfo': { + 'ds:X509Data': { + 'ds:X509Certificate': { + '#text': decryptionCert + } + } + }, + 'EncryptionMethod': [ + // this should be the set that the xmlenc library supports + { + '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' + }, + { + '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' + }, + { + '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' + } + ] + }; + } - return xmlbuilder.create(metadata).end({ - pretty: true, - indent: ' ', - newline: '\n' - }); + return xmlbuilder.create(metadata).end({ + pretty: true, + indent: ' ', + newline: '\n' + }); };