Skip to content

Commit

Permalink
Implment devMode
Browse files Browse the repository at this point in the history
  • Loading branch information
Megapixel99 committed Jan 23, 2021
1 parent d6a8632 commit db8bc52
Showing 1 changed file with 109 additions and 89 deletions.
198 changes: 109 additions & 89 deletions lib/passport-cas.js
Original file line number Diff line number Diff line change
@@ -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');


/**
Expand All @@ -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.)
Expand Down Expand Up @@ -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);
Expand All @@ -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,
});

}

/**
Expand All @@ -114,53 +115,83 @@ 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) {
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
Expand All @@ -169,30 +200,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)) {
Expand All @@ -202,35 +233,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<emails.length; i++) {
} else {
for (let i = 0; i < emails.length; i++) {
profile.emails.push({
'value': emails[i],
'type': 'default'
value: emails[i],
type: 'default',
});
}
}
}
else {
} else {
profile.emails = [emails];
}
delete attributes[mappedKey];
}
else {
} else {
var mappedKey = self.casPropertyMap[key] || key;
var value = attributes[mappedKey];
if (Array.isArray(value)) {
profile[key] = value[0];
}
else if (value) {
} else if (value) {
profile[key] = value;
}
delete attributes[mappedKey];
Expand All @@ -240,14 +266,13 @@ 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);
}
};
Expand All @@ -261,7 +286,7 @@ CasStrategy.prototype.authenticate = function(req, options) {
* @param {String} returnUrl
* @api public
*/
CasStrategy.prototype.logout = function(req, res, returnUrl) {
CasStrategy.prototype.logout = function (req, res, returnUrl) {
req.logout();
if (returnUrl) {
this.cas.logout(req, res, returnUrl, true);
Expand Down Expand Up @@ -293,31 +318,28 @@ CasStrategy.prototype.logout = function(req, res, returnUrl) {
* Completion callback with signature `fn(err, ticket)`
* @api public
*/
CasStrategy.prototype.getProxyTicket = function(req, targetService, done) {
var err, pgtiou;
CasStrategy.prototype.getProxyTicket = function (req, targetService, done) {
let err; let
pgtiou;
if (!req.session) {
err = new Error('Session is not found');
}
else if (!req.session[this.casSessionKey]) {
} else if (!req.session[this.casSessionKey]) {
err = new Error('User is not authenticated with CAS');
}
else {
} else {
pgtiou = req.session[this.casSessionKey].PGTIOU;
if (!pgtiou) {
err = new Error('PGTIOU token not found. Make sure pgtURL option is correct, and the CAS server allows proxies.');
}
}

if (err) {
return done(err);
}
else {
this.cas.getProxyTicket(pgtiou, targetService, function(err, PT) {
done(err, PT);
});
}
}

this.cas.getProxyTicket(pgtiou, targetService, (err, PT) => {
done(err, PT);
});
};


/**
Expand All @@ -326,14 +348,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
Expand All @@ -349,16 +370,16 @@ 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,
pgt_host: parsedURL.hostname,
pgt_port: parsedURL.port,
ssl_key: serverKey,
ssl_cert: serverCertificate,
ssl_ca: serverCA || null
ssl_ca: serverCA || null,
});
}

Expand All @@ -367,4 +388,3 @@ function PgtServer(casURL, pgtURL, serverCertificate, serverKey, serverCA) {
* Expose `PgtServer`.
*/
module.exports.PgtServer = PgtServer;

0 comments on commit db8bc52

Please sign in to comment.