Newer
Older
/**
* This class is used for the logout page.
*
* It allows the user to start logout from all the services where a session exists (if any). Logout will be
* triggered by loading an iframe where we send a SAML logout request to the SingleLogoutService endpoint of the
* given SP. After successful response back from the SP, we will load a small template in the iframe that loads
* this class again (IFrameLogoutHandler branch of the constructor), and sends a message to the main page
* (core:logout-iframe branch).
*
* The iframes communicate the logout status for their corresponding association via an event message, for which the
* main page is listening (the clearAssociation() method). Upon reception of a message, we'll check if there was an
* error or not, and call the appropriate method (either completed() or failed()).
*/
class SimpleSAMLLogout {
if (page === 'core:logout-iframe') { // main page
this.populateData();
if (Object.keys(this.sps).length === 0) {
// all SPs completed logout, this was a reload
this.btncontinue.click();
}
this.btnall.onclick(function () {
this.initLogout.bind(this);
return true;
});
window.addEventListener('message', this.clearAssociation.bind(this), false);
} else if (page === 'IFrameLogoutHandler') { // iframe
var data = document.querySelector('i[id="data"]');
var message = {
spId: data.getAttribute('data-spid')
if (data.hasAttribute('data-error')) {
message.error = data.getAttribute('data-error');
}
window.parent.postMessage(JSON.stringify(message), SimpleSAMLLogout.getOrigin());
}
}
/**
* Clear an association when it is signaled from an iframe (either failed or completed).
*
* @param event The event containing the message from the iframe.
*/
if (event.origin !== SimpleSAMLLogout.getOrigin()) {
// we don't accept events from other origins
return;
}
if (typeof data.error === 'undefined') {
this.completed(data.spId);
} else {
this.failed(data.spId, data.error);
}
if (Object.keys(this.sps).length === 0) {
if (this.nfailed === 0) {
// all SPs successfully logged out, continue w/o user interaction
this.btncontinue.click();
}
}
}
/**
* Mark logout as completed for a given SP.
*
* This method will be called by the SimpleSAML\IdP\IFrameLogoutHandler class upon successful logout from the SP.
*
* @param id The ID of the SP that completed logout successfully.
*/
if (typeof this.sps[id] === 'undefined') {
return;
}
this.sps[id].icon.removeClass('fa-spin');
this.sps[id].icon.removeClass('fa-circle-o-notch');
this.sps[id].icon.addClass('fa-check-circle');
this.sps[id].element.toggle();
delete this.sps[id];
this.finish();
}
/**
* Mark logout as failed for a given SP.
*
* This method will be called by the SimpleSAML\IdP\IFrameLogoutHandler class upon logout failure from the SP.
*
* @param id The ID of the SP that failed to complete logout.
* @param reason The reason why logout failed.
*/
if (typeof this.sps[id] === 'undefined') {
return;
}
this.sps[id].element.addClass('error');
//var icon = document.getElementById(this.sps[id].icon);
this.sps[id].icon.removeClass('fa-spin fa-circle-o-notch');
this.sps[id].icon.addClass('fa-exclamation-circle');
if (this.errmsg.hasClass('hidden')) {
this.errmsg.removeClass('hidden');
}
if (this.errfrm.hasClass('hidden')) {
this.errfrm.removeClass('hidden');
}
delete this.sps[id];
this.nfailed++;
this.finish();
}
/**
* Finish the logout process, acting according to the current situation:
*
* - If there were failures, an error message is shown telling the user to close the browser.
* - If everything went ok, then we just continue back to the service that started logout.
*
* Note: this method won't do anything if there are SPs pending logout (e.g. waiting for the timeout).
*/
if (Object.keys(this.sps).length > 0) { // pending services
return;
}
if (typeof this.timeout !== 'undefined') {
clearTimeout(this.timeout);
}
if (this.nfailed > 0) { // some services failed to log out
this.errmsg.removeClass('hidden');
this.errfrm.removeClass('hidden');
this.actions.addClass('hidden');
} else { // all services done
this.btncontinue.click();
}
}
/**
* Get the origin of the current page.
*/
if (!origin) {
// IE < 11 does not support window.location.origin
origin = window.location.protocol + "//" + window.location.hostname +
}
return origin;
}
/**
* This method starts logout on all SPs where we are currently logged in.
*
* @param event The click event on the "Yes, all services" button.
*/
event.preventDefault();
this.btnall.prop('disabled', true);
this.btncancel.prop('disabled', true);
Object.keys(this.sps).forEach((function (id) {
this.sps[id].status = 'inprogress';
this.sps[id].startTime = (new Date()).getTime();
this.sps[id].iframe.attr('src', this.sps[id].iframe.data('url'));
this.sps[id].icon.addClass('fa-spin');
}).bind(this));
this.initTimeout();
}
/**
* Set timeouts for all logout operations.
*
* If an SP didn't reply by the timeout, we'll mark it as failed.
*/
for (const id in this.sps) {
if (typeof id === 'undefined') {
continue;
}
if (!this.sps.hasOwnProperty(id)) {
continue;
}
if (this.sps[id].status !== 'inprogress') {
continue;
}
var now = ((new Date()).getTime() - this.sps[id].startTime) / 1000;
if (this.sps[id].timeout <= now) {
this.failed(id, 'Timed out', window.document);
} else {
// get the lowest timeout we have
if ((this.sps[id].timeout - now) < timeout) {
timeout = this.sps[id].timeout - now;
}
}
}
if (Object.keys(this.sps).length > 0) {
// we have associations left, check them again as soon as one expires
this.timeout = setTimeout(this.initTimeout.bind(this), timeout * 1000);
} else {
this.finish();
}
}
/**
* This method populates the data we need from data-* properties in the page.
*/
this.btnall = document.querySelector('button[id="btn-all"]');
this.btncancel = document.querySelector('button[id="btn-cancel"]');
this.btncontinue = document.querySelector('button[id="btn-continue"]');
this.actions = document.querySelector('div[id="original-actions"]');
this.errmsg = document.querySelector('div[id="error-message"]');
this.errfrm = document.querySelector('form[id="error-form"]');
// initialise SP status and timeout arrays
document.querySelectorAll('li[id^="sp-"]').forEach(function (currentValue, index, arr) {
var id = currentValue.getAttribute('data-id');
var iframe = document.querySelector('iframe[id="iframe-' + id + '"]');
var status = currentValue.getAttribute('data-status');
switch (status) {
case 'failed':
that.nfailed++;
case 'completed':
return;
}
that.sps[id] = {
status: status,
timeout: currentValue.getAttribute('data-timeout'),
element: currentValue,
icon: document.querySelector('i[id="icon-' + id + '"]'),