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'
+ });
};