Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_js:
- 4
- 6
- 8
- 10
- 12
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
4 changes: 4 additions & 0 deletions lib/wsfed.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ module.exports = function(options) {
var profileMap = options.profileMapper(user);
var claims = profileMap.getClaims(options);
var ni = profileMap.getNameIdentifier(options);
if (!ni || !ni.nameIdentifier) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In saml11.create both the nameIdentifier and nameIdentifierFormat are optional.

https://github.com/auth0/node-saml/blob/a08d250d02938acde2c53d72858a78d33f4dd684/lib/saml11.js#L41-L42

I realise this is the same approach adopted in auth0/node-samlp#102 but is there a reason why it's preferable to fail here rather than not pass the options?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question @luuuis! I feel like the responsibility of the middleware should be to guarantee that meaningful response is returned. Otherwise, it's going to return the empty nameIdentifier without alert to the client and the client has to deal with it somehow. While empty nameIdentifier be a clear indication of the problem within used profile mapper.

On the other hand, somebody might say that it's the job of the profile mapper to guarantee the existence of nameIdentifier and fail otherwise, but it's not the obvious assumption. And I feel like failing fast is more robust and predictable option. WDYT?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Even though the <NameIdentifier> is optional, for our purposes we always want to include one in the SAML assertions so it is appropriate to fail fast.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, for our purposes this would work just fine. I'm thinking: could this potentially be a breaking change for consumers of this lib? In other words, do you know if the name identifier is strictly required in the assertion and hence not finding one should result in us throwing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it's a good question. So it might be a breaking change for those consumers, that:

  • use custom profile mapper that returns name identifier object without nameIdentifier claim inside
  • used profile mapper (default or custom) maps empty name identifier
    and expect an assertion to be successfully formed.

Reading SAML v1.1 spec, I don't see that <NameIdentifier> has to be a non-empty string. So it might be a valid case for some flow?

On the other hand, since a similar change has been done in samlp it seems nobody explicitly requested a possibility to support this scenario. What if this change will be released as MINOR or MAJOR change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After talking with @fadymak, given that we don't know if any consumer of the library really needs to explicitly pass empty nameIdentifier, it was decided not to introduce an extra complexity (of supporting requireNameIdentifier config option) for now and to go with this breaking change, releasing a major version.

This new config option requireNameIdentifier might be added in the future if the corresponding feedback will be received.

return next(new Error('No attribute was found to generate the nameIdentifier'));
}

saml11.create({
signatureAlgorithm: options.signatureAlgorithm,
digestAlgorithm: options.digestAlgorithm,
Expand Down
21 changes: 16 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "WSFed server middleware",
"main": "lib/index.js",
"scripts": {
"test": "mocha"
"test": "mocha",
"release": "standard-version"
},
"repository": {
"type": "git",
Expand All @@ -24,15 +25,25 @@
"saml": "^0.12.1"
},
"devDependencies": {
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2",
"chai": "~1.5.0",
"cheerio": "0.22.0",
"debug": "~3.2.6",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this a missing dependency or perhaps when testing locally? I can't seem to find where it's used

Copy link
Copy Markdown
Contributor Author

@forrest-ua forrest-ua Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry, forgot to mention this. This dev dependency is used by mocha. Automatically mocha picks the latest debug lib verison 4.x.x which uses features from ES2015 (like let keywords) and this is not compatible with node v4 (supported by this library). By pinpointing the debug version to 3.x.x. (3.2.6 is the latest) we can still successfully run tests for nodejs v4. Otherwise, tests are failing because of the usage of let by debug package for node v4 basically.

"express": "~3.1.0",
"husky": "^4.3.0",
"mocha": "~1.8.1",
"request": "~2.14.0",
"xmldom": "=0.1.15",
"cheerio": "0.22.0",
"standard-version": "^9.0.0",
"xml-crypto": "~0.10.1",
"xml-encryption": "0.11.0",
"xmldom": "=0.1.15",
"xpath": "0.0.5",
"xtend": "~2.0.3",
"xml-encryption": "0.11.0"
"xtend": "~2.0.3"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
25 changes: 17 additions & 8 deletions test/fixture/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ var credentials = {
pkcs7: fs.readFileSync(path.join(__dirname, 'wsfed.test-cert.pb7'))
};

module.exports.options = {};

module.exports.start = function(options, callback){
module.exports.options = options;
if (typeof options === 'function') {
callback = options;
options = {};
module.exports.options = {};
}

var app = express();
Expand Down Expand Up @@ -64,13 +67,19 @@ module.exports.start = function(options, callback){
}

//configure wsfed middleware
app.get('/wsfed',
wsfed.auth(xtend({}, {
issuer: 'fixture-test',
getPostURL: getPostURL,
cert: credentials.cert,
key: credentials.key
}, options)));
app.get('/wsfed', function(req, res, next) {
wsfed.auth(xtend({}, {
issuer: 'fixture-test',
getPostURL: getPostURL,
cert: credentials.cert,
key: credentials.key
}, module.exports.options))(req, res, function(err){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this why not pass in a profileMapper stub to start and have that behave differently in each test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current implementation, if we pass stubbed profile mapper, we wouldn't be able to test the behavior of picking default profile mapper PassportProfileMapper (existing tests basically), since the corresponding evaluation of which mapper to chose happens on server start (at the very beginning).

Another viable option is to leave the current test suite as it is, testing the default profile mapper, and have a separate test suite where we start a server with a stubbed profile mapper.

There are different options in the end, but honestly, I find the proposed approach of runtime building wsfed auth for testing the most flexible and easier to use. I saw it basically in node-samlp https://github.com/auth0/node-samlp/blob/master/test/fixture/server.js#L58-L70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current approach is fine. 👍

if (err) {
return res.send(400, err.message);
}
next();
})
});

var server = http.createServer(app).listen(5050, callback);
module.exports.close = server.close.bind(server);
Expand Down
34 changes: 34 additions & 0 deletions test/wsfed.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,39 @@ describe('wsfed', function () {
});
});
});

describe('using custom profile mapper', function() {
describe('when NameIdentifier is not found', function(){
function ProfileMapper(user) {
this.user = user;
}
ProfileMapper.prototype.getClaims = function () {
return this.user;
}
ProfileMapper.prototype.getNameIdentifier = function () {
return null;
}

before(function () {
server.options = {
profileMapper: function createProfileMapper(user) {
return new ProfileMapper(user)
}
};
});

it('should return an error', function(done){
request.get({
jar: request.jar(),
uri: 'http://localhost:5050/wsfed?wa=wsignin1.0&wctx=123&wtrealm=urn:the-super-client-id'
}, function (err, response){
if(err) return done(err);
expect(response.statusCode).to.equal(400);
expect(response.body).to.equal('No attribute was found to generate the nameIdentifier');
done();
});
});
});
});
});