mirror of
https://github.com/netzbegruenung/meteor-accounts-saml.git
synced 2024-05-01 11:24:52 +02:00
Merge branch 'feature/PR_#27'
# Conflicts: # saml_utils.js
This commit is contained in:
commit
9c6e12af5f
78
.versions
78
.versions
|
@ -1,70 +1,72 @@
|
|||
accounts-base@1.3.1
|
||||
allow-deny@1.0.6
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.19.4
|
||||
babel-runtime@1.0.1
|
||||
accounts-base@1.4.2
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.4.0
|
||||
babel-compiler@7.0.4
|
||||
babel-runtime@1.2.0
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
blaze@2.3.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.1.1
|
||||
boilerplate-generator@1.4.0
|
||||
caching-compiler@1.1.9
|
||||
caching-html-compiler@1.0.7
|
||||
callback-hook@1.0.10
|
||||
check@1.2.5
|
||||
callback-hook@1.1.0
|
||||
check@1.3.0
|
||||
coffeescript@1.0.17
|
||||
ddp@1.3.0
|
||||
ddp-client@2.0.0
|
||||
ddp-common@1.2.9
|
||||
ddp@1.4.0
|
||||
ddp-client@2.3.1
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.0.0
|
||||
ddp-server@2.1.2
|
||||
deps@1.0.12
|
||||
diff-sequence@1.0.7
|
||||
ecmascript@0.8.1
|
||||
ecmascript-runtime@0.4.1
|
||||
ecmascript-runtime-client@0.4.3
|
||||
ecmascript-runtime-server@0.4.1
|
||||
ejson@1.0.13
|
||||
diff-sequence@1.1.0
|
||||
dynamic-import@0.3.0
|
||||
ecmascript@0.10.0
|
||||
ecmascript-runtime@0.5.0
|
||||
ecmascript-runtime-client@0.6.0
|
||||
ecmascript-runtime-server@0.5.0
|
||||
ejson@1.1.0
|
||||
geojson-utils@1.0.10
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.2.12
|
||||
id-map@1.0.9
|
||||
http@1.4.0
|
||||
id-map@1.1.0
|
||||
jquery@1.11.10
|
||||
local-test:steffo:meteor-accounts-saml@0.0.13
|
||||
localstorage@1.1.0
|
||||
logging@1.1.17
|
||||
meteor@1.7.0
|
||||
minimongo@1.2.1
|
||||
modules@0.9.2
|
||||
modules-runtime@0.8.0
|
||||
mongo@1.1.19
|
||||
local-test:steffo:meteor-accounts-saml@0.0.14
|
||||
localstorage@1.2.0
|
||||
logging@1.1.19
|
||||
meteor@1.8.2
|
||||
minimongo@1.4.3
|
||||
modules@0.11.3
|
||||
modules-runtime@0.9.1
|
||||
mongo@1.4.2
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.6
|
||||
npm-mongo@2.2.24
|
||||
npm-mongo@2.2.33
|
||||
observe-sequence@1.0.16
|
||||
ordered-dict@1.0.9
|
||||
ordered-dict@1.1.0
|
||||
practicalmeteor:chai@2.1.0_1
|
||||
practicalmeteor:loglevel@1.2.0_2
|
||||
practicalmeteor:mocha@2.4.5_2
|
||||
practicalmeteor:mocha-core@0.1.4
|
||||
practicalmeteor:sinon@1.14.1_2
|
||||
promise@0.8.9
|
||||
random@1.0.10
|
||||
promise@0.10.1
|
||||
random@1.1.0
|
||||
rate-limit@1.0.8
|
||||
reactive-var@1.0.11
|
||||
reload@1.1.11
|
||||
retry@1.0.9
|
||||
reload@1.2.0
|
||||
retry@1.1.0
|
||||
routepolicy@1.0.12
|
||||
service-configuration@1.0.11
|
||||
socket-stream-client@0.1.0
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.2
|
||||
steffo:meteor-accounts-saml@0.0.13
|
||||
steffo:meteor-accounts-saml@0.0.14
|
||||
templating@1.1.14
|
||||
templating-tools@1.1.1
|
||||
tmeasday:test-reporter-helpers@0.2.1
|
||||
tracker@1.1.3
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.1.0
|
||||
webapp@1.3.17
|
||||
url@1.2.0
|
||||
webapp@1.5.0
|
||||
webapp-hashing@1.0.9
|
||||
|
|
12
README.md
12
README.md
|
@ -33,7 +33,7 @@ settings = {"saml":[{
|
|||
"dynamicProfile": true // set to true if we want to create a user in Meteor.users dynamically if SAML assertion is valid
|
||||
"identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", // Defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
||||
"localProfileMatchAttribute": "telephoneNumber" // CAUTION: this will be mapped to profile.<localProfileMatchAttribute> attribute in Mongo if identifierFormat (see above) differs from urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress,
|
||||
"attributesSAML": {[telephoneNumber, sn, givenName, mail]}, // attrs from SAML attr statement, which will be used for local Meteor profile creation. Currently no real attribute mapping. If required use mapping on IdP side.
|
||||
"attributesSAML": [telephoneNumber, sn, givenName, mail], // attrs from SAML attr statement, which will be used for local Meteor profile creation. Currently no real attribute mapping. If required use mapping on IdP side.
|
||||
|
||||
}]}
|
||||
|
||||
|
@ -49,13 +49,13 @@ in some template
|
|||
in helper function
|
||||
|
||||
```
|
||||
'click .saml-login': function(event, template){
|
||||
'click .saml-login' (event) {
|
||||
event.preventDefault();
|
||||
var provider = $(event.target).data('provider');
|
||||
var provider = event.target.getAttribute('data-provider');
|
||||
Meteor.loginWithSaml({
|
||||
provider:provider
|
||||
}, function(error, result){
|
||||
//handle errors and result
|
||||
provider
|
||||
}, function(error, result) {
|
||||
//handle errors and result
|
||||
});
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Package.describe({
|
||||
name:"steffo:meteor-accounts-saml",
|
||||
summary: "SAML Login (SP) for Meteor. Works with OpenAM, OpenIDP and provides Single Logout.",
|
||||
version: "0.0.13",
|
||||
version: "0.0.14",
|
||||
git: "https://github.com/steffow/meteor-accounts-saml.git"
|
||||
});
|
||||
|
||||
|
@ -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"
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -108,7 +111,8 @@ Accounts.registerLoginHandler(function(loginRequest) {
|
|||
if (!user) {
|
||||
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("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) + " || username = " + loginResult.profile.nameID);
|
||||
}
|
||||
Accounts.createUser({
|
||||
//email: loginResult.profile.email,
|
||||
|
@ -118,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
|
||||
});
|
||||
|
@ -274,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) {
|
||||
|
@ -317,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;
|
||||
|
|
772
saml_utils.js
772
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 =
|
||||
`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="${ id }" Version="2.0" IssueInstant="${ instant
|
||||
let request =
|
||||
`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="${ id }" Version="2.0" IssueInstant="${ instant
|
||||
}" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="${ callbackUrl }" Destination="${
|
||||
this.options.entryPoint }">` +
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>\n`;
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>\n`;
|
||||
|
||||
if (this.options.identifierFormat) {
|
||||
request += `<samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Format="${ this.options.identifierFormat
|
||||
if (this.options.identifierFormat) {
|
||||
request += `<samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Format="${ this.options.identifierFormat
|
||||
}" AllowCreate="true"></samlp:NameIDPolicy>\n`;
|
||||
}
|
||||
}
|
||||
|
||||
request +=
|
||||
'<samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact">' +
|
||||
'<saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n' +
|
||||
'</samlp:AuthnRequest>';
|
||||
request +=
|
||||
'<samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact">' +
|
||||
'<saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>\n' +
|
||||
'</samlp:AuthnRequest>';
|
||||
|
||||
return request;
|
||||
return request;
|
||||
};
|
||||
|
||||
SAML.prototype.generateLogoutRequest = function(options) {
|
||||
// options should be of the form
|
||||
// nameId: <nameId as submitted during SAML SSO>
|
||||
// sessionIndex: sessionIndex
|
||||
// --- NO SAMLsettings: <Meteor.setting.saml entry for the provider you want to SLO from
|
||||
// options should be of the form
|
||||
// nameId: <nameId as submitted during SAML SSO>
|
||||
// sessionIndex: sessionIndex
|
||||
// --- NO SAMLsettings: <Meteor.setting.saml entry for the provider you want to SLO from
|
||||
|
||||
const id = `_${ this.generateUniqueID() }`;
|
||||
const instant = this.generateInstant();
|
||||
const id = `_${ this.generateUniqueID() }`;
|
||||
const instant = this.generateInstant();
|
||||
|
||||
let request = `${ '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ' +
|
||||
let request = `${ '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ' +
|
||||
'xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="' }${ id }" Version="2.0" IssueInstant="${ instant
|
||||
}" Destination="${ this.options.idpSLORedirectURL }">` +
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>` +
|
||||
`<saml:NameID Format="${ this.options.identifierFormat }">${ options.nameID }</saml:NameID>` +
|
||||
'</samlp:LogoutRequest>';
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>` +
|
||||
`<saml:NameID Format="${ this.options.identifierFormat }">${ options.nameID }</saml:NameID>` +
|
||||
'</samlp:LogoutRequest>';
|
||||
|
||||
request = `${ '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ' +
|
||||
request = `${ '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ' +
|
||||
'ID="' }${ id }" ` +
|
||||
'Version="2.0" ' +
|
||||
`IssueInstant="${ instant }" ` +
|
||||
`Destination="${ this.options.idpSLORedirectURL }" ` +
|
||||
'>' +
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>` +
|
||||
'<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ' +
|
||||
'NameQualifier="http://id.init8.net:8080/openam" ' +
|
||||
`SPNameQualifier="${ this.options.issuer }" ` +
|
||||
`Format="${ this.options.identifierFormat }">${
|
||||
'Version="2.0" ' +
|
||||
`IssueInstant="${ instant }" ` +
|
||||
`Destination="${ this.options.idpSLORedirectURL }" ` +
|
||||
'>' +
|
||||
`<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${ this.options.issuer }</saml:Issuer>` +
|
||||
'<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ' +
|
||||
'NameQualifier="http://id.init8.net:8080/openam" ' +
|
||||
`SPNameQualifier="${ this.options.issuer }" ` +
|
||||
`Format="${ this.options.identifierFormat }">${
|
||||
options.nameID }</saml:NameID>` +
|
||||
`<samlp:SessionIndex xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">${ options.sessionIndex }</samlp:SessionIndex>` +
|
||||
'</samlp:LogoutRequest>';
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('------- SAML Logout request -----------');
|
||||
console.log(request);
|
||||
}
|
||||
return {
|
||||
request,
|
||||
id
|
||||
};
|
||||
`<samlp:SessionIndex xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">${ options.sessionIndex }</samlp:SessionIndex>` +
|
||||
'</samlp:LogoutRequest>';
|
||||
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,273 +231,330 @@ SAML.prototype.certToPEM = function(cert) {
|
|||
// return res;
|
||||
// }
|
||||
|
||||
SAML.prototype.validateSignature = function(xml, cert) {
|
||||
const self = this;
|
||||
SAML.prototype.validateStatus = function(doc) {
|
||||
let successStatus = false;
|
||||
let status = '';
|
||||
let messageText = '';
|
||||
const statusNodes = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusCode');
|
||||
|
||||
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];
|
||||
if (statusNodes.length) {
|
||||
|
||||
const sig = new xmlCrypto.SignedXml();
|
||||
const statusNode = statusNodes[0];
|
||||
const statusMessage = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'StatusMessage')[0];
|
||||
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo(/*key*/) {
|
||||
return '<X509Data></X509Data>';
|
||||
},
|
||||
getKey(/*keyInfo*/) {
|
||||
return self.certToPEM(cert);
|
||||
}
|
||||
};
|
||||
if (statusMessage) {
|
||||
messageText = statusMessage.firstChild.textContent;
|
||||
}
|
||||
|
||||
sig.loadSignature(signature);
|
||||
status = statusNode.getAttribute('Value');
|
||||
|
||||
return sig.checkSignature(xml);
|
||||
if (status === 'urn:oasis:names:tc:SAML:2.0:status:Success') {
|
||||
successStatus = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: successStatus,
|
||||
message: messageText,
|
||||
statusCode: status
|
||||
};
|
||||
};
|
||||
|
||||
SAML.prototype.getElement = function(parentElement, elementName) {
|
||||
if (parentElement[`saml:${ elementName }`]) {
|
||||
return parentElement[`saml:${ elementName }`];
|
||||
} else if (parentElement[`samlp:${ elementName }`]) {
|
||||
return parentElement[`samlp:${ elementName }`];
|
||||
} else if (parentElement[`saml2p:${ elementName }`]) {
|
||||
return parentElement[`saml2p:${ elementName }`];
|
||||
} else if (parentElement[`saml2:${ elementName }`]) {
|
||||
return parentElement[`saml2:${ elementName }`];
|
||||
}
|
||||
return parentElement[elementName];
|
||||
SAML.prototype.validateSignature = function(xml, cert) {
|
||||
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 sig = new xmlCrypto.SignedXml();
|
||||
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo( /*key*/ ) {
|
||||
return '<X509Data></X509Data>';
|
||||
},
|
||||
getKey( /*keyInfo*/ ) {
|
||||
return self.certToPEM(cert);
|
||||
}
|
||||
};
|
||||
|
||||
sig.loadSignature(signature);
|
||||
|
||||
return sig.checkSignature(xml);
|
||||
};
|
||||
|
||||
SAML.prototype.validateLogoutResponse = function(samlResponse, callback) {
|
||||
const self = this;
|
||||
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')[0];
|
||||
if (response) {
|
||||
|
||||
const compressedSAMLResponse = new Buffer(samlResponse, 'base64');
|
||||
zlib.inflateRaw(compressedSAMLResponse, function(err, decoded) {
|
||||
// TBD. Check if this msg corresponds to one we sent
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(err);
|
||||
}
|
||||
} else {
|
||||
const parser = new xml2js.Parser({
|
||||
explicitRoot: true
|
||||
});
|
||||
parser.parseString(decoded, function(err, doc) {
|
||||
const response = self.getElement(doc, 'LogoutResponse');
|
||||
|
||||
if (response) {
|
||||
// TBD. Check if this msg corresponds to one we sent
|
||||
const inResponseTo = response.$.InResponseTo;
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`In Response to: ${ inResponseTo }`);
|
||||
}
|
||||
const status = self.getElement(response, 'Status');
|
||||
const statusCode = self.getElement(status[0], 'StatusCode')[0].$.Value;
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`StatusCode: ${ JSON.stringify(statusCode) }`);
|
||||
}
|
||||
if (statusCode === 'urn:oasis:names:tc:SAML:2.0:status:Success') {
|
||||
// In case of a successful logout at IDP we return inResponseTo value.
|
||||
// This is the only way how we can identify the Meteor user (as we don't use Session Cookies)
|
||||
callback(null, inResponseTo);
|
||||
} else {
|
||||
callback('Error. Logout not confirmed by IDP', null);
|
||||
}
|
||||
} else {
|
||||
callback('No Response Found', null);
|
||||
}
|
||||
});
|
||||
}
|
||||
let statusValidateObj = self.validateStatus(doc);
|
||||
|
||||
});
|
||||
if (statusValidateObj.success) {
|
||||
callback(null, inResponseTo);
|
||||
} else {
|
||||
callback('Error. Logout not confirmed by IDP', null);
|
||||
|
||||
}
|
||||
} 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
|
||||
});
|
||||
|
||||
parser.parseString(xml, function(err, doc) {
|
||||
// Verify signature
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Verify signature');
|
||||
}
|
||||
if (self.options.cert && !self.validateSignature(xml, self.options.cert)) {
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Signature WRONG');
|
||||
}
|
||||
return callback(new Error('Invalid signature'), null, false);
|
||||
}
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Signature OK');
|
||||
}
|
||||
const response = self.getElement(doc, 'Response');
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Got response');
|
||||
}
|
||||
if (response) {
|
||||
const assertion = self.getElement(response, 'Assertion');
|
||||
if (!assertion) {
|
||||
return callback(new Error('Missing SAML assertion'), null, false);
|
||||
}
|
||||
const doc = new xmldom.DOMParser().parseFromString(xml, 'text/xml');
|
||||
|
||||
const profile = {};
|
||||
if (doc) {
|
||||
|
||||
if (response.$ && response.$.InResponseTo) {
|
||||
profile.inResponseToId = response.$.InResponseTo;
|
||||
}
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Verify status');
|
||||
}
|
||||
let statusValidateObj = self.validateStatus(doc);
|
||||
|
||||
const issuer = self.getElement(assertion[0], 'Issuer');
|
||||
if (issuer) {
|
||||
profile.issuer = issuer[0]._;
|
||||
}
|
||||
if (statusValidateObj.success) {
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Status ok');
|
||||
}
|
||||
// Verify signature
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Verify signature');
|
||||
}
|
||||
if (self.options.cert && !self.validateSignature(xml, self.options.cert)) {
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Signature WRONG');
|
||||
}
|
||||
return callback(new Error('Invalid signature'), null, false);
|
||||
}
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Signature OK');
|
||||
}
|
||||
const response = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'Response')[0];
|
||||
if (response) {
|
||||
if (Meteor.settings.debug) {
|
||||
console.log('Got response');
|
||||
}
|
||||
|
||||
const subject = self.getElement(assertion[0], 'Subject');
|
||||
const assertion = response.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Assertion')[0];
|
||||
if (!assertion) {
|
||||
return callback(new Error('Missing SAML assertion'), null, false);
|
||||
}
|
||||
|
||||
if (subject) {
|
||||
const nameID = self.getElement(subject[0], 'NameID');
|
||||
if (nameID) {
|
||||
profile.nameID = nameID[0]._;
|
||||
const profile = {};
|
||||
|
||||
if (nameID[0].$.Format) {
|
||||
profile.nameIDFormat = nameID[0].$.Format;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.hasAttribute('InResponseTo')) {
|
||||
profile.inResponseToId = response.getAttribute('InResponseTo');
|
||||
}
|
||||
|
||||
const authnStatement = self.getElement(assertion[0], 'AuthnStatement');
|
||||
const issuer = assertion.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Issuer')[0];
|
||||
if (issuer) {
|
||||
profile.issuer = issuer.textContent;
|
||||
}
|
||||
|
||||
if (authnStatement) {
|
||||
if (authnStatement[0].$.SessionIndex) {
|
||||
const subject = assertion.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Subject')[0];
|
||||
|
||||
profile.sessionIndex = authnStatement[0].$.SessionIndex;
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`Session Index: ${ profile.sessionIndex }`);
|
||||
}
|
||||
} else if (Meteor.settings.debug) {
|
||||
console.log('No Session Index Found');
|
||||
}
|
||||
if (subject) {
|
||||
const nameID = subject.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'NameID')[0];
|
||||
if (nameID) {
|
||||
profile.nameID = nameID.textContent;
|
||||
|
||||
if (nameID.hasAttribute('Format')) {
|
||||
profile.nameIDFormat = nameID.getAttribute('Format');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const authnStatement = assertion.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'AuthnStatement')[0];
|
||||
|
||||
if (authnStatement) {
|
||||
if (authnStatement.hasAttribute('SessionIndex')) {
|
||||
|
||||
profile.sessionIndex = authnStatement.getAttribute('SessionIndex');
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`Session Index: ${ profile.sessionIndex }`);
|
||||
}
|
||||
} else if (Meteor.settings.debug) {
|
||||
console.log('No Session Index Found');
|
||||
}
|
||||
|
||||
|
||||
} else if (Meteor.settings.debug) {
|
||||
console.log('No AuthN Statement found');
|
||||
}
|
||||
} else if (Meteor.settings.debug) {
|
||||
console.log('No AuthN Statement found');
|
||||
}
|
||||
|
||||
const attributeStatement = self.getElement(assertion[0], 'AttributeStatement');
|
||||
if (attributeStatement) {
|
||||
const attributes = self.getElement(attributeStatement[0], 'Attribute');
|
||||
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);
|
||||
}
|
||||
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 (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);
|
||||
}
|
||||
profile[attributes[i].getAttribute('Name')] = value.textContent;
|
||||
|
||||
if (attributes) {
|
||||
attributes.forEach(function(attribute) {
|
||||
const value = self.getElement(attribute, 'AttributeValue');
|
||||
if (typeof value[0] === 'string') {
|
||||
profile[attribute.$.Name] = value[0];
|
||||
} else {
|
||||
profile[attribute.$.Name] = value[0]._;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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
|
||||
profile.mail = profile['urn:oid:0.9.2342.19200300.100.1.3'];
|
||||
}
|
||||
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
|
||||
profile.mail = profile['urn:oid:0.9.2342.19200300.100.1.3'];
|
||||
}
|
||||
|
||||
if (!profile.email && profile.mail) {
|
||||
profile.email = profile.mail;
|
||||
}
|
||||
}
|
||||
if (!profile.email && profile.mail) {
|
||||
profile.email = profile.mail;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`NameID: ${ JSON.stringify(profile) }`);
|
||||
}
|
||||
if (!profile.email && profile.nameID && profile.nameIDFormat && profile.nameIDFormat.indexOf('emailAddress') >= 0) {
|
||||
profile.email = profile.nameID;
|
||||
}
|
||||
if (Meteor.settings.debug) {
|
||||
console.log(`NameID: ${ JSON.stringify(profile) }`);
|
||||
}
|
||||
|
||||
callback(null, profile, false);
|
||||
} else {
|
||||
const logoutResponse = self.getElement(doc, 'LogoutResponse');
|
||||
callback(null, profile, false);
|
||||
} else {
|
||||
const logoutResponse = doc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:protocol', 'LogoutResponse');
|
||||
|
||||
if (logoutResponse) {
|
||||
callback(null, null, true);
|
||||
} else {
|
||||
return callback(new Error('Unknown SAML response message'), null, false);
|
||||
}
|
||||
if (logoutResponse) {
|
||||
callback(null, null, true);
|
||||
} else {
|
||||
return callback(new Error('Unknown SAML response message'), null, false);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
return callback(new Error(`Status is: ${ statusValidateObj.statusCode }`), null, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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'
|
||||
});
|
||||
};
|
Loading…
Reference in a new issue