diff --git a/README.md b/README.md index af883dab..0a330068 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ type Profile = { * `skipRequestCompression`: if set to true, the SAML request from the service provider won't be compressed. * `authnRequestBinding`: if set to `HTTP-POST`, will request authentication from IDP via HTTP POST binding, otherwise defaults to HTTP Redirect * `disableRequestACSUrl`: if truthy, SAML AuthnRequest from the service provider will not include the optional AssertionConsumerServiceURL. Default is falsy so it is automatically included. + * `useFriendlyNames`: if truthy `FriendlyName` from assertion will be used instead of `Name` for profile property name * **InResponseTo Validation** * `validateInResponseTo`: if truthy, then InResponseTo will be validated from incoming SAML responses * `requestIdExpirationPeriodMs`: Defines the expiration time when a Request ID generated for a SAML request will not be valid if seen in a SAML response in the `InResponseTo` field. Default is 8 hours. diff --git a/lib/passport-saml/saml.js b/lib/passport-saml/saml.js index 9e5c4e5e..95a665b2 100644 --- a/lib/passport-saml/saml.js +++ b/lib/passport-saml/saml.js @@ -1031,10 +1031,11 @@ SAML.prototype.processValidlySignedAssertion = function(xml, samlResponseXml, in return; } var value = attribute.AttributeValue; + var name = self.options.useFriendlyNames && attribute.$.FriendlyName ? attribute.$.FriendlyName : attribute.$.Name; if (value.length === 1) { - profile[attribute.$.Name] = attrValueMapper(value[0]); + profile[name] = attrValueMapper(value[0]); } else { - profile[attribute.$.Name] = value.map(attrValueMapper); + profile[name] = value.map(attrValueMapper); } }); } diff --git a/test/tests.js b/test/tests.js index 6bbe761d..ae9456f3 100644 --- a/test/tests.js +++ b/test/tests.js @@ -211,8 +211,60 @@ describe( 'passport-saml /', function() { fakeClock.restore(); server.close(done); }); - }); + describe('FriendlyName', function () { + var app, profile; + + beforeEach(function () { + var check = capturedChecks.find(c => c.name === 'Testshib -- valid encrypted response should succeed') + var pp = new passport.Authenticator(); + app = express(); + app.use(bodyParser.urlencoded({extended: false})); + app.use(pp.initialize()); + var config = check.config; + config.callbackUrl = 'http://localhost:3033/login'; + config.useFriendlyNames = true; + config.passReqToCallback = false; + pp.use(new SamlStrategy(config, function (_profile, done) { + profile = _profile; + done(null, { id: profile.nameID }); + }) + ); + + fakeClock = sinon.useFakeTimers(Date.parse(check.mockDate)); + + app.post('/login', + pp.authenticate("saml"), + function (req, res) { + res.status(200).send("200 OK"); + }); + + app.use(function (err, req, res, next) { + // console.log( err.stack ); + res.status(500).send('500 Internal Server Error'); + }); + }); + + it('should use FriendlyName when useFriendlyNames set in config', function (done) { + server = app.listen(3033, function () { + var requestOpts = { + url: 'http://localhost:3033/login', + method: 'POST', + form: check.samlResponse + }; + + request(requestOpts, function (err, response, body) { + should.not.exist(err); + profile.should.not.have.property('urn:oid:1.3.6.1.4.1.5923.1.1.1.6') + profile.eduPersonPrincipalName.should.equal('myself@testshib.org') + profile.should.not.have.property('urn:oid:1.3.6.1.4.1.5923.1.1.1.7') + profile.eduPersonEntitlement.should.equal('urn:mace:dir:entitlement:common-lib-terms') + done() + }); + }); + }); + }) + }); describe( 'captured SAML requests /', function() { var capturedChecks = [