diff --git a/lib/passport-cas.js b/lib/passport-cas.js index 2252037..4f096e7 100644 --- a/lib/passport-cas.js +++ b/lib/passport-cas.js @@ -1,10 +1,10 @@ /** * Module dependencies. */ -var url = require('url') - , util = require('util') - , Strategy = require('passport-strategy') - , CAS = require('cas'); +const url = require('url'); +const util = require('util'); +const Strategy = require('passport-strategy'); +const CAS = require('cas'); /** @@ -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); @@ -72,34 +73,34 @@ var url = require('url') * @api public */ function CasStrategy(options, verify) { - if (typeof options == 'function') { + if (typeof options === 'function') { verify = options; 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, external_pgt_url: this.casPgtUrl, ssl_cert: options.sslCert, ssl_key: options.sslKey, - ssl_ca: options.sslCA + ssl_ca: options.sslCA, }); - } /** @@ -114,53 +115,84 @@ util.inherits(CasStrategy, Strategy); * @param {Object} options * @api protected */ -CasStrategy.prototype.authenticate = function(req, options) { +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']; - + const { ticket } = reqURL.query; + // The `service` string is the current URL, minus the ticket - delete reqURL.query['ticket']; + delete reqURL.query.ticket; service = url.format({ protocol: req.headers['x-forwarded-proto'] || req.headers['x-proxied-protocol'] || req.protocol || 'http', host: req.headers['x-forwarded-host'] || req.headers.host || reqURL.host, pathname: req.headers['x-proxied-request-uri'] || reqURL.pathname, - query: reqURL.query + query: reqURL.query, }); - - if (!ticket) { + console.log(req.session.passport); + + if (req.session.passport !== undefined && req.session.passport.user !== undefined) { + console.log('k'); + self._verify(username, profile, verified); + } else if (!ticket) { // Redirect to CAS server for authentication - self.redirect(self.casBaseUrl + '/login?service=' + encodeURIComponent(service), 307); - } - else { + self.redirect('/', 307); + } else { // User has returned from CAS site with a ticket - self.cas.validate(ticket, function(err, status, username, extended) { - + self.cas.validate(ticket, (err, status, username, extended) => { // Ticket validation failed if (err) { - var date = new Date(); - var token = Math.round(date.getTime() / 60000); - if (req.query['_cas_retry'] != token) { - // There was a CAS error. A common cause is when an old - // `ticket` portion of the querystring remains after the - // session times out and the user refreshes the page. - // So remove the `ticket` and try again. - var url = (req.originalUrl || req.url) - .replace(/_cas_retry=\d+&?/, '') - .replace(/([?&])ticket=[\w.-]+/, '$1_cas_retry='+token); - self.redirect(url, 307); + const date = new Date(); + const token = Math.round(date.getTime() / 60000); + if (req.query._cas_retry != token) { + // There was a CAS error. A common cause is when an old + // `ticket` portion of the querystring remains after the + // session times out and the user refreshes the page. + // So remove the `ticket` and try again. + const url = (req.originalUrl || req.url) + .replace(/_cas_retry=\d+&?/, '') + .replace(/([?&])ticket=[\w.-]+/, `$1_cas_retry=${token}`); + self.redirect(url, 307); } else { - // Already retried. There is no way to recover from this. - self.fail(err); + // Already retried. There is no way to recover from this. + self.fail(err); } } - + // Validation successful else { // The provided `verify` callback will call this on completion @@ -169,30 +201,30 @@ 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 = { + + const { attributes } = extended; + const profile = { provider: 'CAS', id: extended.id || username, displayName: attributes.displayName || username, name: { familyName: null, givenName: null, - middleName: null + middleName: null, }, - emails: [] + emails: [], }; - + // Map relevant extended attributes returned by CAS into the profile for (var key in profile) { if (key == 'name') { - for (var subKey in profile[key]) { + for (const subKey in profile[key]) { var mappedKey = self.casPropertyMap[subKey] || subKey; var value = attributes[mappedKey]; if (Array.isArray(value)) { @@ -202,35 +234,30 @@ CasStrategy.prototype.authenticate = function(req, options) { } delete attributes[mappedKey]; } - } - else if (key == 'emails') { + } else if (key == 'emails') { var mappedKey = self.casPropertyMap.emails || 'emails'; - var emails = attributes[mappedKey]; + const emails = attributes[mappedKey]; if (Array.isArray(emails)) { - if (typeof emails[0] == 'object') { + if (typeof emails[0] === 'object') { profile.emails = emails; - } - else { - for (var i=0; i { + done(err, PT); + }); +}; /** @@ -326,14 +349,13 @@ CasStrategy.prototype.getProxyTicket = function(req, targetService, done) { module.exports.Strategy = CasStrategy; - /** * Start a CAS PGT callback server. PGT stands for proxy granting ticket. * * 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 @@ -349,8 +371,8 @@ module.exports.Strategy = CasStrategy; * @api public */ function PgtServer(casURL, pgtURL, serverCertificate, serverKey, serverCA) { - var parsedURL = url.parse(pgtURL); - var cas = new CAS({ + const parsedURL = url.parse(pgtURL); + const cas = new CAS({ base_url: casURL, version: 2.0, pgt_server: true, @@ -358,7 +380,7 @@ function PgtServer(casURL, pgtURL, serverCertificate, serverKey, serverCA) { pgt_port: parsedURL.port, ssl_key: serverKey, ssl_cert: serverCertificate, - ssl_ca: serverCA || null + ssl_ca: serverCA || null, }); } @@ -367,4 +389,3 @@ function PgtServer(casURL, pgtURL, serverCertificate, serverKey, serverCA) { * Expose `PgtServer`. */ module.exports.PgtServer = PgtServer; -