Skip to content
Snippets Groups Projects
Commit 56a2ff8e authored by iaibrys's avatar iaibrys
Browse files

Fix CAS login issue

parent 3a171b12
No related branches found
No related tags found
No related merge requests found
Pipeline #539 passed
......@@ -5,8 +5,11 @@ RUN mkdir ${APP_DIR}
WORKDIR ${APP_DIR}
COPY . ${APP_DIR}
COPY package.json ${APP_DIR}
COPY package-lock.json ${APP_DIR}
RUN npm install
COPY . ${APP_DIR}
ENTRYPOINT ["sh", "-c", "./scripts/run-server.sh"]
/**
* Cas
*/
var _ = require('underscore'),
url = require('url'),
http = require('http'),
https = require('https'),
parseString = require('xml2js').parseString,
processors = require('xml2js/lib/processors'),
passport = require('passport'),
uuid = require('node-uuid'),
util = require('util');
function Strategy(options, verify) {
if (typeof options == 'function') {
verify = options;
options = {};
}
if (!verify) {
throw new Error('cas authentication strategy requires a verify function');
}
this.version = options.version || "CAS1.0";
this.ssoBase = options.ssoBaseURL;
this.serverBaseURL = options.serverBaseURL;
this.validateURL = options.validateURL;
this.serviceURL = options.serviceURL;
this.useSaml = options.useSaml || false;
this.parsed = url.parse(this.ssoBase);
if (this.parsed.protocol === 'http:') {
this.client = http;
} else {
this.client = https;
}
passport.Strategy.call(this);
this.name = 'cas';
this._verify = verify;
this._passReqToCallback = options.passReqToCallback;
var xmlParseOpts = {
'trim': true,
'normalize': true,
'explicitArray': false,
'tagNameProcessors': [processors.normalize, processors.stripPrefix]
};
var self = this;
switch (this.version) {
case "CAS1.0":
this._validateUri = "/validate";
this._validate = function (req, body, verified) {
var lines = body.split('\n');
if (lines.length >= 1) {
if (lines[0] === 'no') {
return verified(new Error('Authentication failed'));
} else if (lines[0] === 'yes' && lines.length >= 2) {
if (self._passReqToCallback) {
self._verify(req, lines[1], verified);
} else {
self._verify(lines[1], verified);
}
return;
}
}
return verified(new Error('The response from the server was bad'));
};
break;
case "CAS3.0":
if (this.useSaml) {
this._validateUri = "/samlValidate";
this._validate = function (req, body, verified) {
parseString(body, xmlParseOpts, function (err, result) {
if (err) {
console.log('ERROR');
console.log(JSON.stringify(err, null, 2));
console.log(JSON.stringify(result, null, 2));
return verified(new Error('The response from the server was bad'));
}
try {
var response = result.envelope.body.response;
var success = response.status.statuscode['$'].Value.match(/Success$/);
if (success) {
var attributes = {};
_.each(response.assertion.attributestatement.attribute, function(attribute) {
attributes[attribute['$'].AttributeName.toLowerCase()] = attribute.attributevalue;
});
var profile = {
'user': response.assertion.authenticationstatement.subject.nameidentifier,
'attributes': attributes
};
if (self._passReqToCallback) {
self._verify(req, profile, verified);
} else {
self._verify(profile, verified);
}
return;
}
return verified(new Error('Authentication failed'));
} catch (e) {
return verified(new Error('Authentication failed'));
}
});
};
} else {
this._validateUri = "/p3/serviceValidate";
this._validate = function (req, body, verified) {
parseString(body, xmlParseOpts, function (err, result) {
if (err) {
console.log('ERROR');
console.log(JSON.stringify(err, null, 2));
return verified(new Error('The response from the server was bad'));
}
try {
if (result.serviceresponse.authenticationfailure) {
return verified(new Error('Authentication failed ' + result.serviceresponse.authenticationfailure.$.code));
}
var success = result.serviceresponse.authenticationsuccess;
if (success) {
if (self._passReqToCallback) {
self._verify(req, success, verified);
} else {
self._verify(success, verified);
}
return;
}
return verified(new Error('Authentication failed'));
} catch (e) {
return verified(new Error('Authentication failed'));
}
});
};
}
break;
default:
throw new Error('unsupported version ' + this.version);
}
}
Strategy.prototype.service = function(req) {
var serviceURL = this.serviceURL || req.originalUrl;
var resolvedURL = url.resolve(this.serverBaseURL, serviceURL);
var parsedURL = url.parse(resolvedURL, true);
delete parsedURL.query.ticket;
delete parsedURL.search;
return url.format(parsedURL);
};
Strategy.prototype.authenticate = function (req, options) {
options = options || {};
// CAS Logout flow as described in
// https://wiki.jasig.org/display/CAS/Proposal%3A+Front-Channel+Single+Sign-Out var relayState = req.query.RelayState;
var relayState = req.query.RelayState;
if (relayState) {
// logout locally
req.logout();
return this.redirect(this.ssoBase + '/logout?_eventId=next&RelayState=' +
relayState);
}
var service = this.service(req);
var ticket = req.param('ticket');
if (!ticket) {
var redirectURL = url.parse(this.ssoBase + '/login', true);
redirectURL.query.service = service;
// copy loginParams in login query
for (var property in options.loginParams ) {
var loginParam = options.loginParams[property];
if (loginParam) {
redirectURL.query[property] = loginParam;
}
}
return this.redirect(url.format(redirectURL));
}
var self = this;
var verified = function (err, user, info) {
if (err) {
return self.error(err);
}
if (!user) {
return self.fail(info);
}
self.success(user, info);
};
var _validateUri = this.validateURL || this._validateUri;
var _handleResponse = function (response) {
response.setEncoding('utf8');
var body = '';
response.on('data', function (chunk) {
return body += chunk;
});
return response.on('end', function () {
return self._validate(req, body, verified);
});
};
if (this.useSaml) {
var requestId = uuid.v4();
var issueInstant = new Date().toISOString();
var soapEnvelope = util.format('<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" MajorVersion="1" MinorVersion="1" RequestID="%s" IssueInstant="%s"><samlp:AssertionArtifact>%s</samlp:AssertionArtifact></samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>', requestId, issueInstant, ticket);
var path = url.format({
pathname: this.parsed.pathname + _validateUri,
query: {
'TARGET': service
}});
console.log('path %o content %o', path, soapEnvelope);
var request = this.client.request({
host: this.parsed.hostname,
port: this.parsed.port,
method: 'POST',
path: path
}, _handleResponse);
request.on('error', function (e) {
return self.fail(new Error(e));
});
request.write(soapEnvelope);
request.end();
} else {
var path = url.format({
pathname: this.parsed.pathname + _validateUri,
query: {
ticket: ticket,
service: service
}
});
console.log('path %o content %o:%o', path, this.parsed.hostname, this.parsed.port);
var get = this.client.get({
host: this.parsed.hostname,
port: this.parsed.port,
path: path
}, _handleResponse);
get.on('error', function (e) {
return self.fail(new Error(e));
});
}
};
exports.Strategy = Strategy;
......@@ -4,6 +4,7 @@ const path = require('path');
const url = require('url');
const express = require('express');
const passport = require('passport');
const passportcas = require('./index-passport-cas.js');
const session = require('express-session');
const redis = require('redis');
let RedisStore = require('connect-redis')(session);
......@@ -50,7 +51,7 @@ function authPath(targetPath, req, isFile, access) {
}
function authError(req, res, paths) {
if (typeof paths === 'String') { paths = [paths] }
if (typeof paths === 'string') { paths = [paths] }
console.error(`user ${req.user.profile.user} not authorized for path(s) %o`, paths);
console.error(JSON.stringify(req.user, null, 2));
res.status(403);
......@@ -74,9 +75,9 @@ const authFilemanager = (req, res, next) => {
return passport.authenticate('cas', function (err, user, info) {
console.log(`Auth callback for ${req.originalUrl} ${req.url}`);
console.dir(err);
console.dir(user);
console.dir(info);
console.log(`err: ${err}`);
console.log(`user: ${user}`);
console.log(`info: ${info}`);
if (err) {
return next(err);
......@@ -113,9 +114,9 @@ const authRequest = (req, res, next) => {
console.log(`Authorizing ${req.originalUrl} ${req.url} ${req.user}`);
return passport.authenticate('cas', function (err, user, info) {
console.log(`Auth callback for ${req.originalUrl} ${req.url}`);
console.dir(err);
console.dir(user);
console.dir(info);
console.log(`err: ${err}`);
console.log(`user: ${user}`);
console.log(`info: ${info}`);
if (err) {
return next(err);
......@@ -152,12 +153,17 @@ passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new (require('passport-cas').Strategy)({
const passportArgs = {
version: 'CAS3.0',
ssoBaseURL: 'https://auth.dtu.dk/dtu',
serverBaseURL,
// validateURL: '/serviceValidate'
}, function(profile, done) {
validateURL: '/serviceValidate'
};
console.log('PASSPORT CONFIG');
console.log(JSON.stringify(passportArgs, null, 2));
passport.use(new (passportcas.Strategy)(passportArgs, function(profile, done) {
const userName = profile.user;
const userKey = `dtu_enote:user$${userName}`;
......
......@@ -1643,7 +1643,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
......@@ -1664,12 +1665,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
......@@ -1684,17 +1687,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
......@@ -1811,7 +1817,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
......@@ -1823,6 +1830,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
......@@ -1837,6 +1845,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
......@@ -1844,12 +1853,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
......@@ -1868,6 +1879,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
......@@ -1948,7 +1960,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
......@@ -1960,6 +1973,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
......@@ -2045,7 +2059,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
......@@ -2081,6 +2096,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
......@@ -2100,6 +2116,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
......@@ -2143,12 +2160,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment