diff --git a/lib/passport-cas.js b/lib/passport-cas.js index 2252037..4951b15 100644 --- a/lib/passport-cas.js +++ b/lib/passport-cas.js @@ -15,10 +15,10 @@ var url = require('url') * * Authentication is done by redirecting the user to the CAS login page. The * user will return with a ticket in the querystring. This ticket is then - * validated by the application against the CAS server to obtain the username + * validated by the application against the CAS server to obtain the username * and profile. * - * (CAS optionally allows the application to obtain tickets for 3rd party + * (CAS optionally allows the application to obtain tickets for 3rd party * services on behalf of the user. This requires the use of a PGT callback * server, which can be run with the PgtServer() function also from this * module.) @@ -46,19 +46,20 @@ var url = require('url') * - `propertyMap` Optional. A basic key-value object for mapping extended user attributes from CAS to passport's profile format. * - `passReqToCallback` Optional. When `true`, `req` is the first argument to the verify callback (default: `false`) * - `sslCA` Optional. SSL CA bundle to use to validate the PGT server. + * - `devMode` Optional. Useful when testing locally, assumes a CAS session was successfully created * * Example: * * var CasStrategy = require('passport-cas2').Strategy; * var cas = new CasStrategy({ * casURL: 'https://signin.example.com/cas', - * propertyMap: { + * propertyMap: { * id: 'guid', * givenName: 'givenname', * familyName: 'surname', * emails: 'defaultmail' * } - * }, + * }, * function(username, profile, done) { * User.findOrCreate(..., function(err, user) { * done(err, user); @@ -77,20 +78,21 @@ function CasStrategy(options, verify) { options = undefined; } options = options || {}; - + if (!verify) { throw new TypeError('CasStrategy requires a verify callback'); } if (!options.casURL) { throw new TypeError('CasStrategy requires a casURL option'); } - + Strategy.call(this); this.name = 'cas'; this._verify = verify; this._passReqToCallback = options.passReqToCallback; - + this.casBaseUrl = options.casURL; + this.devMode = options.devMode; this.casPgtUrl = options.pgtURL || undefined; this.casPropertyMap = options.propertyMap || {}; this.casSessionKey = options.sessionKey || 'cas'; - + this.cas = new CAS({ base_url: this.casBaseUrl, version: 2, @@ -99,7 +101,7 @@ function CasStrategy(options, verify) { ssl_key: options.sslKey, ssl_ca: options.sslCA }); - + } /** @@ -117,14 +119,44 @@ util.inherits(CasStrategy, Strategy); CasStrategy.prototype.authenticate = function(req, options) { if (!req._passport) { return this.error(new Error('passport.initialize() middleware not in use')); } options = options || {}; - + var self = this; var reqURL = url.parse(req.originalUrl || req.url, true); var service; - + + // The provided `verify` callback will call this on completion + function verified(err, user, info) { + if (!self.devMode) { + if (err) { return self.error(err); } + if (!user) { return self.fail(info); } + } + self.success(user, info); + } + + if (self.devMode) { + var username = 'test_user'; + var profile = { + provider: 'CAS', + id: username, + displayName: username, + name: { + familyName: null, + givenName: null, + middleName: null, + }, + emails: [], + }; + if (self._passReqToCallback) { + self._verify(req, username, profile, verified); + } else { + self._verify(username, profile, verified); + } + return; + } + // `ticket` is present if user is already authenticated/authorized by CAS var ticket = reqURL.query['ticket']; - + // The `service` string is the current URL, minus the ticket delete reqURL.query['ticket']; service = url.format({ @@ -133,7 +165,7 @@ CasStrategy.prototype.authenticate = function(req, options) { pathname: req.headers['x-proxied-request-uri'] || reqURL.pathname, query: reqURL.query }); - + if (!ticket) { // Redirect to CAS server for authentication self.redirect(self.casBaseUrl + '/login?service=' + encodeURIComponent(service), 307); @@ -141,7 +173,7 @@ CasStrategy.prototype.authenticate = function(req, options) { else { // User has returned from CAS site with a ticket self.cas.validate(ticket, function(err, status, username, extended) { - + // Ticket validation failed if (err) { var date = new Date(); @@ -160,7 +192,7 @@ CasStrategy.prototype.authenticate = function(req, options) { self.fail(err); } } - + // Validation successful else { // The provided `verify` callback will call this on completion @@ -169,13 +201,13 @@ CasStrategy.prototype.authenticate = function(req, options) { if (!user) { return self.fail(info); } self.success(user, info); } - + req.session[self.casSessionKey] = {}; - + if (self.casPgtUrl) { req.session[self.casSessionKey].PGTIOU = extended.PGTIOU; } - + var attributes = extended.attributes; var profile = { provider: 'CAS', @@ -188,7 +220,7 @@ CasStrategy.prototype.authenticate = function(req, options) { }, emails: [] }; - + // Map relevant extended attributes returned by CAS into the profile for (var key in profile) { if (key == 'name') { @@ -202,7 +234,7 @@ CasStrategy.prototype.authenticate = function(req, options) { } delete attributes[mappedKey]; } - } + } else if (key == 'emails') { var mappedKey = self.casPropertyMap.emails || 'emails'; var emails = attributes[mappedKey]; @@ -229,7 +261,7 @@ CasStrategy.prototype.authenticate = function(req, options) { var value = attributes[mappedKey]; if (Array.isArray(value)) { profile[key] = value[0]; - } + } else if (value) { profile[key] = value; } @@ -240,14 +272,14 @@ CasStrategy.prototype.authenticate = function(req, options) { for (var key in attributes) { profile[key] = attributes[key]; } - + if (self._passReqToCallback) { self._verify(req, username, profile, verified); } else { self._verify(username, profile, verified); } } - + }, service); } }; @@ -307,7 +339,7 @@ CasStrategy.prototype.getProxyTicket = function(req, targetService, done) { err = new Error('PGTIOU token not found. Make sure pgtURL option is correct, and the CAS server allows proxies.'); } } - + if (err) { return done(err); } @@ -333,7 +365,7 @@ module.exports.Strategy = CasStrategy; * This is the server needed to obtain CAS tickets for 3rd party services on * behalf of the user. It is typically run as a separate process from the * application. Multiple applications may share the same PGT callback server. - * + * * @param {String} casURL * The URL of the CAS server. * @param {String} pgtURL @@ -367,4 +399,3 @@ function PgtServer(casURL, pgtURL, serverCertificate, serverKey, serverCA) { * Expose `PgtServer`. */ module.exports.PgtServer = PgtServer; -