Skip to content

Commit

Permalink
Include support for run-time params to be included in the generated URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
cjbarth committed Sep 10, 2018
1 parent 5257486 commit aa29f86
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Config parameter details:
* `decryptionPvk`: optional private key that will be used to attempt to decrypt any encrypted assertions that are received
* `signatureAlgorithm`: optionally set the signature algorithm for signing requests, valid values are 'sha1' (default) or 'sha256'
* Additional SAML behaviors
* `additionalParams`: dictionary of additional query params to add to all requests
* `additionalParams`: dictionary of additional query params to add to all requests; this can also be part of the options that are passed to `authenticate` for params to be added on a per-call basis
* `additionalAuthorizeParams`: dictionary of additional query params to add to 'authorize' requests
* `identifierFormat`: if truthy, name identifier format to request from identity provider (default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`)
* `acceptedClockSkewMs`: Time in milliseconds of skew that is acceptable between client and server when checking `OnBefore` and `NotOnOrAfter` assertion condition validity timestamps. Setting to `-1` will disable checking these conditions entirely. Default is `0`.
Expand Down
22 changes: 15 additions & 7 deletions lib/passport-saml/saml.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ SAML.prototype.requestToUrl = function (request, response, operation, additional
}
};

SAML.prototype.getAdditionalParams = function (req, operation) {
SAML.prototype.getAdditionalParams = function (req, operation, overrideParams) {
var additionalParams = {};

var RelayState = req.query && req.query.RelayState || req.body && req.body.RelayState;
Expand All @@ -344,16 +344,22 @@ SAML.prototype.getAdditionalParams = function (req, operation) {
additionalParams[k] = optionsAdditionalParamsForThisOperation[k];
});

overrideParams = overrideParams || {};
Object.keys(overrideParams).forEach(function(k) {
additionalParams[k] = overrideParams[k];
});

return additionalParams;
};

SAML.prototype.getAuthorizeUrl = function (req, callback) {
SAML.prototype.getAuthorizeUrl = function (req, options, callback) {
var self = this;
self.generateAuthorizeRequest(req, self.options.passive, function(err, request){
if (err)
return callback(err);
var operation = 'authorize';
self.requestToUrl(request, null, operation, self.getAdditionalParams(req, operation), callback);
var overrideParams = options ? options.additionalParams || {} : {};
self.requestToUrl(request, null, operation, self.getAdditionalParams(req, operation, overrideParams), callback);
});
};

Expand Down Expand Up @@ -431,16 +437,18 @@ SAML.prototype.getAuthorizeForm = function (req, callback) {

};

SAML.prototype.getLogoutUrl = function(req, callback) {
SAML.prototype.getLogoutUrl = function(req, options, callback) {
var request = this.generateLogoutRequest(req);
var operation = 'logout';
this.requestToUrl(request, null, operation, this.getAdditionalParams(req, operation), callback);
var overrideParams = options ? options.additionalParams || {} : {};
this.requestToUrl(request, null, operation, this.getAdditionalParams(req, operation, overrideParams), callback);
};

SAML.prototype.getLogoutResponseUrl = function(req, callback) {
SAML.prototype.getLogoutResponseUrl = function(req, options, callback) {
var response = this.generateLogoutResponse(req, req.samlLogoutRequest);
var operation = 'logout';
this.requestToUrl(null, response, operation, this.getAdditionalParams(req, operation), callback);
var overrideParams = options ? options.additionalParams || {} : {};
this.requestToUrl(null, response, operation, this.getAdditionalParams(req, operation, overrideParams), callback);
};

SAML.prototype.certToPEM = function (cert) {
Expand Down
8 changes: 4 additions & 4 deletions lib/passport-saml/strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Strategy.prototype.authenticate = function (req, options) {
req.logout();
if (profile) {
req.samlLogoutRequest = profile;
return self._saml.getLogoutResponseUrl(req, redirectIfSuccess);
return self._saml.getLogoutResponseUrl(req, options, redirectIfSuccess);
}
return self.pass();
}
Expand Down Expand Up @@ -87,11 +87,11 @@ Strategy.prototype.authenticate = function (req, options) {
}
});
} else { // Defaults to HTTP-Redirect
this._saml.getAuthorizeUrl(req, redirectIfSuccess);
this._saml.getAuthorizeUrl(req, options, redirectIfSuccess);
}
}.bind(self),
'logout-request': function() {
this._saml.getLogoutUrl(req, redirectIfSuccess);
this._saml.getLogoutUrl(req, options, redirectIfSuccess);
}.bind(self)
}[options.samlFallback];

Expand All @@ -104,7 +104,7 @@ Strategy.prototype.authenticate = function (req, options) {
};

Strategy.prototype.logout = function(req, callback) {
this._saml.getLogoutUrl(req, callback);
this._saml.getLogoutUrl(req, {}, callback);
};

Strategy.prototype.generateServiceProviderMetadata = function( decryptionCert ) {
Expand Down
177 changes: 144 additions & 33 deletions test/samlTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,160 @@ var SAML = require('../lib/passport-saml/saml.js').SAML;
var should = require('should');
var url = require('url');

describe('SAML.js', function() {
describe('getAuthorizeUrl', function() {
var saml, req;
beforeEach(function() {
describe('SAML.js', function () {
describe('get Urls', function () {
var saml, req, options;
beforeEach(function () {
saml = new SAML({
entryPoint: 'https://exampleidp.com/path?key=value'
entryPoint: 'https://exampleidp.com/path?key=value',
logoutUrl: 'https://exampleidp.com/path?key=value'
});
req = {
protocol: 'https',
headers: {
host: 'examplesp.com'
},
user: {
nameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
nameID: 'nameID'
},
samlLogoutRequest: {
ID: 123
}
}
};
options = {
additionalParams: {
additionalKey: 'additionalValue'
}
};
});
it('calls callback with right host', function(done) {
saml.getAuthorizeUrl(req, function(err, target) {
url.parse(target).host.should.equal('exampleidp.com');
done();

describe('getAuthorizeUrl', function () {
it('calls callback with right host', function (done) {
saml.getAuthorizeUrl(req, {}, function (err, target) {
url.parse(target).host.should.equal('exampleidp.com');
done();
});
});
it('calls callback with right protocol', function (done) {
saml.getAuthorizeUrl(req, {}, function (err, target) {
url.parse(target).protocol.should.equal('https:');
done();
});
});
it('calls callback with right path', function (done) {
saml.getAuthorizeUrl(req, {}, function (err, target) {
url.parse(target).pathname.should.equal('/path');
done();
});
});
it('calls callback with original query string', function (done) {
saml.getAuthorizeUrl(req, {}, function (err, target) {
url.parse(target, true).query['key'].should.equal('value');
done();
});
});
it('calls callback with additional run-time params in query string', function (done) {
saml.getAuthorizeUrl(req, options, function (err, target) {
Object.keys(url.parse(target, true).query).should.have.length(3);
url.parse(target, true).query['key'].should.equal('value');
url.parse(target, true).query['SAMLRequest'].should.not.be.empty();
url.parse(target, true).query['additionalKey'].should.equal('additionalValue');
done();
});
});
// NOTE: This test only tests existence of the assertion, not the correctness
it('calls callback with saml request object', function (done) {
saml.getAuthorizeUrl(req, {}, function (err, target) {
url.parse(target, true).query.should.have.property('SAMLRequest');
done();
});
});
});
it('calls callback with right protocol', function(done) {
saml.getAuthorizeUrl(req, function(err, target) {
url.parse(target).protocol.should.equal('https:');
done();
});
})
it('calls callback with right path', function(done) {
saml.getAuthorizeUrl(req, function(err, target) {
url.parse(target).pathname.should.equal('/path');
done();
});
})
it('calls callback with original query string', function(done) {
saml.getAuthorizeUrl(req, function(err, target) {
url.parse(target, true).query['key'].should.equal('value');
done();
});
})
// NOTE: This test only tests existence of the assertion, not the correctness
it('calls callback with saml request object', function(done) {
saml.getAuthorizeUrl(req, function(err, target) {
url.parse(target, true).query.should.have.property('SAMLRequest');
done();

describe('getLogoutUrl', function () {
it('calls callback with right host', function (done) {
saml.getLogoutUrl(req, {}, function (err, target) {
url.parse(target).host.should.equal('exampleidp.com');
done();
});
});
it('calls callback with right protocol', function (done) {
saml.getLogoutUrl(req, {}, function (err, target) {
url.parse(target).protocol.should.equal('https:');
done();
});
});
it('calls callback with right path', function (done) {
saml.getLogoutUrl(req, {}, function (err, target) {
url.parse(target).pathname.should.equal('/path');
done();
});
});
it('calls callback with original query string', function (done) {
saml.getLogoutUrl(req, {}, function (err, target) {
url.parse(target, true).query['key'].should.equal('value');
done();
});
});
it('calls callback with additional run-time params in query string', function (done) {
saml.getLogoutUrl(req, options, function (err, target) {
Object.keys(url.parse(target, true).query).should.have.length(3);
url.parse(target, true).query['key'].should.equal('value');
url.parse(target, true).query['SAMLRequest'].should.not.be.empty();
url.parse(target, true).query['additionalKey'].should.equal('additionalValue');
done();
});
});
// NOTE: This test only tests existence of the assertion, not the correctness
it('calls callback with saml request object', function (done) {
saml.getLogoutUrl(req, {}, function (err, target) {
url.parse(target, true).query.should.have.property('SAMLRequest');
done();
});
});
});

describe('getLogoutResponseUrl', function () {
it('calls callback with right host', function (done) {
saml.getLogoutResponseUrl(req, {}, function (err, target) {
url.parse(target).host.should.equal('exampleidp.com');
done();
});
});
it('calls callback with right protocol', function (done) {
saml.getLogoutResponseUrl(req, {}, function (err, target) {
url.parse(target).protocol.should.equal('https:');
done();
});
});
it('calls callback with right path', function (done) {
saml.getLogoutResponseUrl(req, {}, function (err, target) {
url.parse(target).pathname.should.equal('/path');
done();
});
});
it('calls callback with original query string', function (done) {
saml.getLogoutResponseUrl(req, {}, function (err, target) {
url.parse(target, true).query['key'].should.equal('value');
done();
});
});
it('calls callback with additional run-time params in query string', function (done) {
saml.getLogoutResponseUrl(req, options, function (err, target) {
Object.keys(url.parse(target, true).query).should.have.length(3);
url.parse(target, true).query['key'].should.equal('value');
url.parse(target, true).query['SAMLResponse'].should.not.be.empty();
url.parse(target, true).query['additionalKey'].should.equal('additionalValue');
done();
});
});
// NOTE: This test only tests existence of the assertion, not the correctness
it('calls callback with saml response object', function (done) {
saml.getLogoutResponseUrl(req, {}, function (err, target) {
url.parse(target, true).query.should.have.property('SAMLResponse');
done();
});
});
});
});
Expand Down
72 changes: 69 additions & 3 deletions test/tests.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit aa29f86

Please sign in to comment.