Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 2 additions & 60 deletions app/meteor-accounts-saml/client/saml_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,70 +43,12 @@ Meteor.logout = function(...args) {
return MeteorLogout.apply(Meteor, args);
};

const openCenteredPopup = function(url, width, height) {
const screenX = typeof window.screenX !== 'undefined' ? window.screenX : window.screenLeft;
const screenY = typeof window.screenY !== 'undefined' ? window.screenY : window.screenTop;
const outerWidth = typeof window.outerWidth !== 'undefined' ? window.outerWidth : document.body.clientWidth;
const outerHeight = typeof window.outerHeight !== 'undefined' ? window.outerHeight : document.body.clientHeight - 22;
// XXX what is the 22?

// Use `outerWidth - width` and `outerHeight - height` for help in
// positioning the popup centered relative to the current window
const left = screenX + (outerWidth - width) / 2;
const top = screenY + (outerHeight - height) / 2;
const features = `width=${ width },height=${ height
},left=${ left },top=${ top },scrollbars=yes`;

const newwindow = window.open(url, 'Login', features);
if (newwindow.focus) {
newwindow.focus();
}

return newwindow;
};

Accounts.saml.initiateLogin = function(options, callback, dimensions) {
// default dimensions that worked well for facebook and google
const popup = openCenteredPopup(
Meteor.absoluteUrl(`_saml/authorize/${ options.provider }/${ options.credentialToken }`), (dimensions && dimensions.width) || 650, (dimensions && dimensions.height) || 500);

const checkPopupOpen = setInterval(function() {
let popupClosed;
try {
// Fix for #328 - added a second test criteria (popup.closed === undefined)
// to humour this Android quirk:
// http://code.google.com/p/android/issues/detail?id=21061
popupClosed = popup.closed || popup.closed === undefined;
} catch (e) {
// For some unknown reason, IE9 (and others?) sometimes (when
// the popup closes too quickly?) throws 'SCRIPT16386: No such
// interface supported' when trying to read 'popup.closed'. Try
// again in 100ms.
return;
}

if (popupClosed) {
clearInterval(checkPopupOpen);
callback(options.credentialToken);
}
}, 100);
};


Meteor.loginWithSaml = function(options, callback) {
Meteor.loginWithSaml = function(options/* , callback*/) {
options = options || {};
const credentialToken = `id-${ Random.id() }`;
options.credentialToken = credentialToken;

Accounts.saml.initiateLogin(options, function(/* error, result*/) {
Accounts.callLoginMethod({
methodArguments: [{
saml: true,
credentialToken,
}],
userCallback: callback,
});
});
window.location.href = `_saml/authorize/${ options.provider }/${ options.credentialToken }`;
};

Meteor.logoutWithSaml = function(options/* , callback*/) {
Expand Down
73 changes: 44 additions & 29 deletions app/meteor-accounts-saml/server/saml_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import bodyParser from 'body-parser';
import fiber from 'fibers';
import _ from 'underscore';
import s from 'underscore.string';

import { SAML } from './saml_utils';
import { Rooms, Subscriptions, CredentialTokens } from '../../models';
Expand Down Expand Up @@ -453,17 +454,6 @@ Accounts.saml.storeCredential = function(credentialToken, loginResult) {
CredentialTokens.create(credentialToken, loginResult);
};

const closePopup = function(res, err) {
res.writeHead(200, {
'Content-Type': 'text/html',
});
let content = '<html><head><script>window.close()</script></head><body><H1>Verified</H1></body></html>';
if (err) {
content = `<html><body><h2>Sorry, an annoying error occured</h2><div>${ err }</div><a onclick="window.close();">Close Window</a></body></html>`;
}
res.end(content, 'utf-8');
};

const samlUrlToObject = function(url) {
// req.url will be '/_saml/<action>/<service name>/<credentialToken>'
if (!url) {
Expand Down Expand Up @@ -512,6 +502,14 @@ const logoutRemoveTokens = function(userId) {
});
};

const showErrorMessage = function(res, err) {
res.writeHead(200, {
'Content-Type': 'text/html',
});
const content = `<html><body><h2>Sorry, an annoying error occured</h2><div>${ s.escapeHTML(err) }</div></body></html>`;
res.end(content, 'utf-8');
};

const middleware = function(req, res, next) {
// Make sure to catch any exceptions because otherwise we'd crash
// the runner
Expand All @@ -536,17 +534,28 @@ const middleware = function(req, res, next) {

// Skip everything if there's no service set by the saml middleware
if (!service) {
if (samlObject.actionName === 'metadata') {
showErrorMessage(res, `Unexpected SAML service ${ samlObject.serviceName }`);
return;
}

throw new Error(`Unexpected SAML service ${ samlObject.serviceName }`);
}

let _saml;
switch (samlObject.actionName) {
case 'metadata':
_saml = new SAML(service);
service.callbackUrl = Meteor.absoluteUrl(`_saml/validate/${ service.provider }`);
try {
_saml = new SAML(service);
service.callbackUrl = Meteor.absoluteUrl(`_saml/validate/${ service.provider }`);
} catch (err) {
showErrorMessage(res, err);
return;
}

res.writeHead(200);
res.write(_saml.generateServiceProviderMetadata(service.callbackUrl));
res.end();
// closePopup(res);
break;
case 'logout':
// This is where we receive SAML LogoutResponse
Expand Down Expand Up @@ -619,9 +628,6 @@ const middleware = function(req, res, next) {
});
res.end();
}
// else {
// // TBD thinking of sth meaning full.
// }
});
}
break;
Expand Down Expand Up @@ -654,31 +660,40 @@ const middleware = function(req, res, next) {
throw new Error(`Unable to validate response url: ${ err }`);
}

const credentialToken = (profile.inResponseToId && profile.inResponseToId.value) || profile.inResponseToId || profile.InResponseTo || samlObject.credentialToken;
let credentialToken = (profile.inResponseToId && profile.inResponseToId.value) || profile.inResponseToId || profile.InResponseTo || samlObject.credentialToken;
const loginResult = {
profile,
};

if (!credentialToken) {
// No credentialToken in IdP-initiated SSO
const saml_idp_credentialToken = Random.id();
Accounts.saml.storeCredential(saml_idp_credentialToken, loginResult);
credentialToken = Random.id();

const url = `${ Meteor.absoluteUrl('home') }?saml_idp_credentialToken=${ saml_idp_credentialToken }`;
res.writeHead(302, {
Location: url,
});
res.end();
} else {
Accounts.saml.storeCredential(credentialToken, loginResult);
closePopup(res);
if (Accounts.saml.settings.debug) {
console.log('[SAML] Using random credentialToken: ', credentialToken);
}
}

Accounts.saml.storeCredential(credentialToken, loginResult);
const url = `${ Meteor.absoluteUrl('home') }?saml_idp_credentialToken=${ credentialToken }`;
res.writeHead(302, {
Location: url,
});
res.end();
});
break;
default:
throw new Error(`Unexpected SAML action ${ samlObject.actionName }`);
}
} catch (err) {
closePopup(res, err);
// #ToDo: Ideally we should send some error message to the client, but there's no way to do it on a redirect right now.
console.log(err);

const url = Meteor.absoluteUrl('home');
res.writeHead(302, {
Location: url,
});
res.end();
}
};

Expand Down