passport-saml
Advanced tools
Comparing version 0.0.4 to 0.1.0
@@ -7,3 +7,11 @@ var zlib = require('zlib'); | ||
var querystring = require('querystring'); | ||
var xmlbuilder = require('xmlbuilder'); | ||
// Patch the xml-crypto envelope transform, which should remove our specific signature, but is | ||
// currently removing the first signature it finds in the whole doc. TODO: submit a pull | ||
// request to xml-crypto. | ||
var patchedEnvelopedSignature = require('./patched-enveloped-signature.js'); | ||
xmlCrypto.SignedXml.CanonicalizationAlgorithms['http://www.w3.org/2000/09/xmldsig#enveloped-signature'] = | ||
patchedEnvelopedSignature.EnvelopedSignature; | ||
var SAML = function (options) { | ||
@@ -48,3 +56,3 @@ this.options = this.initialize(options); | ||
var date = new Date(); | ||
return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth()+1)).slice(-2) + '-' + ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + (date.getUTCHours()+2)).slice(-2) + ":" + ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z"; | ||
return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth()+1)).slice(-2) + '-' + ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + date.getUTCHours()).slice(-2) + ":" + ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z"; | ||
}; | ||
@@ -56,7 +64,8 @@ | ||
return signer.sign(this.options.privateCert, 'base64'); | ||
} | ||
}; | ||
SAML.prototype.generateAuthorizeRequest = function (req) { | ||
SAML.prototype.generateAuthorizeRequest = function (req, isPassive) { | ||
var id = "_" + this.generateUniqueID(); | ||
var instant = this.generateInstant(); | ||
var callbackUrl; | ||
@@ -67,22 +76,41 @@ // Post-auth destination | ||
} else { | ||
var callbackUrl = this.options.protocol + req.headers.host + this.options.path; | ||
callbackUrl = this.options.protocol + req.headers.host + this.options.path; | ||
} | ||
var 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"; | ||
var 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', | ||
'#text': this.options.issuer | ||
}, | ||
'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', | ||
'#text': 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' | ||
} | ||
} | ||
} | ||
}; | ||
if (isPassive) | ||
request['samlp:AuthnRequest']['@IsPassive'] = true; | ||
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:AuthnRequest']['samlp:NameIDPolicy'] = { | ||
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol', | ||
'@Format': this.options.identifierFormat, | ||
'@AllowCreate': 'true' | ||
}; | ||
} | ||
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 xmlbuilder.create(request).end(); | ||
}; | ||
@@ -94,20 +122,53 @@ | ||
//samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" | ||
// ID="_135ad2fd-b275-4428-b5d6-3ac3361c3a7f" Version="2.0" Destination="https://idphost/adfs/ls/" | ||
//IssueInstant="2008-06-03T12:59:57Z"><saml:Issuer>myhost</saml:Issuer><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" | ||
//NameQualifier="https://idphost/adfs/ls/">myemail@mydomain.com</NameID<samlp:SessionIndex>_0628125f-7f95-42cc-ad8e-fde86ae90bbe | ||
//</samlp:SessionIndex></samlp:LogoutRequest> | ||
var 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.entryPoint, | ||
'saml:Issuer' : { | ||
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion', | ||
'#text': this.options.issuer | ||
}, | ||
'saml:NameID' : { | ||
'@Format': req.user.nameIDFormat, | ||
'#text': req.user.nameID | ||
} | ||
} | ||
}; | ||
var 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.entryPoint + "\">" + | ||
"<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">" + this.options.issuer + "</saml:Issuer>"+ | ||
"<saml:NameID Format=\""+req.user.nameIDFormat+"\">"+req.user.nameID+"</saml:NameID>"+ | ||
"</samlp:LogoutRequest>"; | ||
return request; | ||
} | ||
return xmlbuilder.create(request).end(); | ||
}; | ||
SAML.prototype.requestToUrl = function (request, operation, callback) { | ||
SAML.prototype.generateLogoutResponse = function (req, logoutRequest) { | ||
var id = "_" + this.generateUniqueID(); | ||
var instant = this.generateInstant(); | ||
var request = { | ||
'samlp:LogoutResponse' : { | ||
'@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, | ||
'@InResponseTo': logoutRequest.ID, | ||
'saml:Issuer' : { | ||
'#text': this.options.issuer | ||
}, | ||
'samlp:Status': { | ||
'samlp:StatusCode': { | ||
'@Value': 'urn:oasis:names:tc:SAML:2.0:status:Success' | ||
} | ||
} | ||
} | ||
}; | ||
return xmlbuilder.create(request).end(); | ||
}; | ||
SAML.prototype.requestToUrl = function (request, response, operation, additionalParameters, callback) { | ||
var self = this; | ||
zlib.deflateRaw(request, function(err, buffer) { | ||
zlib.deflateRaw(request || response, function(err, buffer) { | ||
if (err) { | ||
@@ -123,23 +184,30 @@ return callback(err); | ||
target = self.options.logoutUrl + '?'; | ||
} | ||
} | ||
} else if (operation !== 'authorize') { | ||
return callback(new Error("Unknown operation: "+operation)); | ||
} | ||
var samlRequest = { | ||
var samlMessage = request ? { | ||
SAMLRequest: base64 | ||
} : { | ||
SAMLResponse: base64 | ||
}; | ||
Object.keys(additionalParameters).forEach(function(k) { | ||
samlMessage[k] = additionalParameters[k]; | ||
}); | ||
if (self.options.privateCert) { | ||
samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; | ||
samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); | ||
samlMessage.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; | ||
samlMessage.Signature = self.signRequest(querystring.stringify(samlMessage)); | ||
} | ||
target += querystring.stringify(samlRequest); | ||
target += querystring.stringify(samlMessage); | ||
callback(null, target); | ||
}); | ||
} | ||
}; | ||
SAML.prototype.getAuthorizeUrl = function (req, callback) { | ||
var request = this.generateAuthorizeRequest(req); | ||
this.requestToUrl(request, 'authorize', callback); | ||
var request = this.generateAuthorizeRequest(req, this.options.passive); | ||
var RelayState = req.query && req.query.RelayState || req.body && req.body.RelayState; | ||
this.requestToUrl(request, null, 'authorize', RelayState ? { RelayState: RelayState } : {}, callback); | ||
}; | ||
@@ -149,5 +217,11 @@ | ||
var request = this.generateLogoutRequest(req); | ||
var RelayState = req.query && req.query.RelayState || req.body && req.body.RelayState; | ||
this.requestToUrl(request, null, 'logout', RelayState ? { RelayState: RelayState } : {}, callback); | ||
}; | ||
this.requestToUrl(request, 'logout', callback); | ||
} | ||
SAML.prototype.getLogoutResponseUrl = function(req, callback) { | ||
var response = this.generateLogoutResponse(req, req.samlLogoutRequest); | ||
var RelayState = req.query && req.query.RelayState || req.body && req.body.RelayState; | ||
this.requestToUrl(null, response, 'logout', RelayState ? { RelayState: RelayState } : {}, callback); | ||
}; | ||
@@ -161,10 +235,21 @@ SAML.prototype.certToPEM = function (cert) { | ||
SAML.prototype.validateSignature = function (xml, cert) { | ||
// This function checks that the |currentNode| in the |fullXml| document contains exactly 1 valid | ||
// signature of the |currentNode|. | ||
// | ||
// See https://github.com/bergie/passport-saml/issues/19 for references to some of the attack | ||
// vectors against SAML signature verification. | ||
SAML.prototype.validateSignature = function (fullXml, currentNode, cert) { | ||
var self = this; | ||
var doc = new xmldom.DOMParser().parseFromString(xml); | ||
var signature = xmlCrypto.xpath.SelectNodes(doc, "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0]; | ||
var xpathSigQuery = ".//*[local-name(.)='Signature' and " + | ||
"namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']"; | ||
var signatures = xmlCrypto.xpath(currentNode, xpathSigQuery); | ||
// This function is expecting to validate exactly one signature, so if we find more or fewer | ||
// than that, reject. | ||
if (signatures.length != 1) | ||
return false; | ||
var signature = signatures[0].toString(); | ||
var sig = new xmlCrypto.SignedXml(); | ||
sig.keyInfoProvider = { | ||
getKeyInfo: function (key) { | ||
return "<X509Data></X509Data>" | ||
return "<X509Data></X509Data>"; | ||
}, | ||
@@ -175,94 +260,205 @@ getKey: function (keyInfo) { | ||
}; | ||
sig.loadSignature(signature.toString()); | ||
return sig.checkSignature(xml); | ||
sig.loadSignature(signature); | ||
// We expect each signature to contain exactly one reference to the top level of the xml we | ||
// are validating, so if we see anything else, reject. | ||
if (sig.references.length != 1 ) | ||
return false; | ||
var refUri = sig.references[0].uri; | ||
var refId = (refUri[0] === '#') ? refUri.substring(1) : refUri; | ||
// If we can't find the reference at the top level, reject | ||
if (currentNode.getAttribute('ID') != refId) | ||
return false; | ||
// If we find any extra referenced nodes, reject. (xml-crypto only verifies one digest, so | ||
// multiple candidate references is bad news) | ||
var totalReferencedNodes = xmlCrypto.xpath(currentNode.ownerDocument, | ||
"//*[@ID='" + refId + "']"); | ||
if (totalReferencedNodes.length > 1) | ||
return false; | ||
return sig.checkSignature(fullXml); | ||
}; | ||
SAML.prototype.getElement = function (parentElement, elementName) { | ||
if (parentElement['saml:' + elementName]) { | ||
return parentElement['saml:' + elementName]; | ||
} else if (parentElement['samlp:'+elementName]) { | ||
return parentElement['samlp:'+elementName]; | ||
} | ||
return parentElement[elementName]; | ||
} | ||
SAML.prototype.validatePostResponse = function (container, callback) { | ||
var self = this; | ||
var xml = new Buffer(container.SAMLResponse, 'base64').toString('ascii'); | ||
var doc = new xmldom.DOMParser().parseFromString(xml); | ||
SAML.prototype.validateResponse = function (samlResponse, callback) { | ||
var self = this; | ||
var xml = new Buffer(samlResponse, 'base64').toString('ascii'); | ||
var parser = new xml2js.Parser({explicitRoot:true}); | ||
parser.parseString(xml, function (err, doc) { | ||
// Verify signature | ||
if (self.options.cert && !self.validateSignature(xml, self.options.cert)) { | ||
// Check if this document has a valid top-level signature | ||
var validSignature = false; | ||
if (self.options.cert && self.validateSignature(xml, doc.documentElement, self.options.cert)) { | ||
validSignature = true; | ||
} | ||
var assertions = xmlCrypto.xpath(doc, "/*[local-name()='Response']/*[local-name()='Assertion']"); | ||
if (assertions.length > 1) { | ||
// There's no reason I know of that we want to handle multiple assertions, and it seems like a | ||
// potential risk vector for signature scope issues, so treat this as an invalid signature | ||
return callback(new Error('Invalid signature'), null, false); | ||
} else if (assertions.length == 1) { | ||
if (self.options.cert && | ||
!validSignature && | ||
!self.validateSignature(xml, assertions[0], self.options.cert)) { | ||
return callback(new Error('Invalid signature'), null, false); | ||
} | ||
return processValidlySignedAssertion(assertions[0].toString(), callback); | ||
} | ||
var response = self.getElement(doc, 'Response'); | ||
// If there's no assertion, and there is a top-level signature, fall back on xml2js response | ||
// parsing for the passive status & LogoutResponse code. | ||
if (self.options.cert && !validSignature) { | ||
return callback(new Error('Invalid signature'), null, false); | ||
} | ||
var parserConfig = { | ||
explicitRoot: true, | ||
tagNameProcessors: [xml2js.processors.stripPrefix] | ||
}; | ||
var parser = new xml2js.Parser(parserConfig); | ||
parser.parseString(xml, function (err, doc) { | ||
if (err) { | ||
return callback(err, null, false); | ||
} | ||
var response = doc.Response; | ||
if (response) { | ||
var assertion = self.getElement(response, 'Assertion'); | ||
var assertion = response.Assertion; | ||
if (!assertion) { | ||
var status = response.Status; | ||
if (status) { | ||
status = status[0].StatusCode; | ||
if (status && status[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:Responder") { | ||
status = status[0].StatusCode; | ||
if (status && status[0].$.Value === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") { | ||
return callback(null, null, false); | ||
} | ||
} | ||
} | ||
return callback(new Error('Missing SAML assertion'), null, false); | ||
} | ||
profile = {}; | ||
var issuer = self.getElement(assertion[0], 'Issuer'); | ||
if (issuer) { | ||
profile.issuer = issuer[0]; | ||
} else { | ||
var logoutResponse = doc.LogoutResponse; | ||
if (logoutResponse){ | ||
callback(null, null, true); | ||
} else { | ||
return callback(new Error('Unknown SAML response message'), null, false); | ||
} | ||
} | ||
}); | ||
}; | ||
var subject = self.getElement(assertion[0], 'Subject'); | ||
if (subject) { | ||
var nameID = self.getElement(subject[0], 'NameID'); | ||
if (nameID) { | ||
profile.nameID = nameID[0]["_"]; | ||
function processValidlySignedAssertion (xml, callback) { | ||
var parserConfig = { | ||
explicitRoot: true, | ||
tagNameProcessors: [xml2js.processors.stripPrefix] | ||
}; | ||
var parser = new xml2js.Parser(parserConfig); | ||
parser.parseString(xml, function (err, doc) { | ||
if (err) { | ||
return callback(err, null, false); | ||
} | ||
if (nameID[0]['$'].Format) { | ||
profile.nameIDFormat = nameID[0]['$'].Format; | ||
} | ||
var assertion = doc.Assertion; | ||
var profile = {}; | ||
var issuer = assertion.Issuer; | ||
if (issuer) { | ||
profile.issuer = issuer[0]; | ||
} | ||
var subject = assertion.Subject; | ||
if (subject) { | ||
var nameID = subject[0].NameID; | ||
if (nameID) { | ||
profile.nameID = nameID[0]._; | ||
if (nameID[0].$.Format) { | ||
profile.nameIDFormat = nameID[0].$.Format; | ||
} | ||
} | ||
} | ||
var attributeStatement = self.getElement(assertion[0], 'AttributeStatement'); | ||
if (!attributeStatement) { | ||
return callback(new Error('Missing AttributeStatement'), null, false); | ||
} | ||
var attributeStatement = assertion.AttributeStatement; | ||
if (attributeStatement) { | ||
var attributes = attributeStatement[0].Attribute; | ||
var attributes = self.getElement(attributeStatement[0], 'Attribute'); | ||
var attrValueMapper = function(value) { | ||
return typeof value === 'string' ? value : value._; | ||
}; | ||
if (attributes) { | ||
attributes.forEach(function (attribute) { | ||
var value = self.getElement(attribute, 'AttributeValue'); | ||
if (typeof value[0] === 'string') { | ||
profile[attribute['$'].Name] = value[0]; | ||
var value = attribute.AttributeValue; | ||
if (value.length === 1) { | ||
profile[attribute.$.Name] = attrValueMapper(value[0]); | ||
} else { | ||
profile[attribute['$'].Name] = value[0]['_']; | ||
profile[attribute.$.Name] = value.map(attrValueMapper); | ||
} | ||
}); | ||
} | ||
} | ||
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; | ||
} | ||
callback(null, profile, false); | ||
} else { | ||
var logoutResponse = self.getElement(doc, 'LogoutResponse'); | ||
callback(null, profile, false); | ||
}); | ||
} | ||
if (logoutResponse){ | ||
callback(null, null, true); | ||
} else { | ||
return callback(new Error('Unknown SAML response message'), null, false); | ||
} | ||
SAML.prototype.validatePostRequest = function (container, callback) { | ||
var self = this; | ||
var xml = new Buffer(container.SAMLRequest, 'base64').toString('ascii'); | ||
var parserConfig = { | ||
explicitRoot: true, | ||
tagNameProcessors: [xml2js.processors.stripPrefix] | ||
}; | ||
var parser = new xml2js.Parser(parserConfig); | ||
parser.parseString(xml, function (err, doc) { | ||
if (err) { | ||
return callback(err, null, false); | ||
} | ||
// Check if this document has a valid top-level signature | ||
if (self.options.cert && !self.validateSignature(xml, self.options.cert)) { | ||
return callback(new Error('Invalid signature'), null, false); | ||
} | ||
processValidlySignedPostRequest(self, doc, callback); | ||
}); | ||
}; | ||
function processValidlySignedPostRequest(self, doc, callback) { | ||
var request = doc.LogoutRequest; | ||
if (request) { | ||
var profile = {}; | ||
if (request.$.ID) { | ||
profile.ID = request.$.ID; | ||
} else { | ||
return callback(new Error('Missing SAML LogoutRequest ID'), null, false); | ||
} | ||
var issuer = request.Issuer; | ||
if (issuer) { | ||
profile.issuer = issuer[0]; | ||
} else { | ||
return callback(new Error('Missing SAML issuer'), null, false); | ||
} | ||
var nameID = request.NameID; | ||
if (nameID) { | ||
profile.nameID = nameID[0]._; | ||
if (nameID[0].$.Format) { | ||
profile.nameIDFormat = nameID[0].$.Format; | ||
} | ||
} else { | ||
return callback(new Error('Missing SAML NameID'), null, false); | ||
} | ||
callback(null, profile, true); | ||
} else { | ||
return callback(new Error('Unknown SAML request message'), null, false); | ||
} | ||
} | ||
exports.SAML = SAML; |
@@ -27,7 +27,4 @@ var passport = require('passport'); | ||
var self = this; | ||
if (req.body && req.body.SAMLResponse) { | ||
// We have a response, get the user identity out of it | ||
var response = req.body.SAMLResponse; | ||
this._saml.validateResponse(response, function (err, profile, loggedOut) { | ||
function validateCallback(err, profile, loggedOut) { | ||
if (err) { | ||
@@ -38,9 +35,8 @@ return self.error(err); | ||
if (loggedOut) { | ||
if (self._saml.options.logoutRedirect) { | ||
self.redirect(self._saml.options.logoutRedirect); | ||
return; | ||
} else { | ||
self.redirect("/"); | ||
req.logout(); | ||
if (profile) { | ||
req.samlLogoutRequest = profile; | ||
return self._saml.getLogoutResponseUrl(req, redirectIfSuccess); | ||
} | ||
return self.pass(); | ||
} | ||
@@ -61,14 +57,30 @@ | ||
self._verify(profile, verified); | ||
}); | ||
} else { | ||
// Initiate new SAML authentication request | ||
} | ||
this._saml.getAuthorizeUrl(req, function (err, url) { | ||
if (err) { | ||
return self.fail(); | ||
} | ||
function redirectIfSuccess(err, url) { | ||
if (err) { | ||
self.fail(); | ||
} else { | ||
self.redirect(url); | ||
}); | ||
} | ||
} | ||
if (req.body && req.body.SAMLResponse) { | ||
this._saml.validatePostResponse(req.body, validateCallback); | ||
} else if (req.body && req.body.SAMLRequest) { | ||
this._saml.validatePostRequest(req.body, validateCallback); | ||
} else if (options.samlFallback) { | ||
// Initiate fallback redirection | ||
var operation = { | ||
'login-request': 'getAuthorizeUrl', | ||
'logout-request': 'getLogoutUrl' | ||
}[options.samlFallback]; | ||
if (!operation) { | ||
return self.fail(); | ||
} | ||
this._saml[operation](req, redirectIfSuccess); | ||
} else { | ||
return self.fail(); | ||
} | ||
}; | ||
@@ -75,0 +87,0 @@ |
{ | ||
"name": "passport-saml", | ||
"version": "0.0.4", | ||
"version": "0.1.0", | ||
"licenses": [{ | ||
@@ -16,19 +16,34 @@ "type": "MIT", | ||
"contributors" : [ | ||
"Michael Bosworth" | ||
"Michael Bosworth", | ||
"Herbert Vojčík", | ||
"Peter Loer" | ||
], | ||
"repository" : { | ||
"type" : "git", | ||
"url" : "https://github.com/bergie/passport-saml.git" | ||
}, | ||
"main": "./lib/passport-saml", | ||
"dependencies": { | ||
"passport": "0.1.x", | ||
"zlib": "1.0.x", | ||
"xml2js": "0.2.0", | ||
"passport": "0.2.x", | ||
"xml2js": "0.4.x", | ||
"xml-crypto": "0.0.x", | ||
"xmldom": "0.1.x" | ||
"xmldom": "0.1.x", | ||
"xmlbuilder": "~2.2" | ||
}, | ||
"devDependencies": { | ||
"express": "2.5.x", | ||
"ejs": "0.7.x" | ||
"express": "3.x", | ||
"body-parser": "*", | ||
"ejs": "0.7.x", | ||
"jshint": "*", | ||
"request": "*", | ||
"should": "*", | ||
"mocha": "*" | ||
}, | ||
"engines": { | ||
"node": ">= 0.6.0" | ||
"node": ">= 0.8.0" | ||
}, | ||
"scripts" : { | ||
"test": "mocha", | ||
"jshint": "./node_modules/.bin/jshint lib" | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
92780
15
1126
7
14
2
+ Addedxmlbuilder@~2.2
+ Addedlodash-node@2.4.1(transitive)
+ Addedpassport@0.2.2(transitive)
+ Addedpassport-strategy@1.0.0(transitive)
+ Addedxml2js@0.4.23(transitive)
+ Addedxmlbuilder@11.0.12.2.1(transitive)
- Removedzlib@1.0.x
- Removedpassport@0.1.18(transitive)
- Removedpkginfo@0.2.3(transitive)
- Removedxml2js@0.2.0(transitive)
- Removedzlib@1.0.5(transitive)
Updatedpassport@0.2.x
Updatedxml2js@0.4.x