summaryrefslogtreecommitdiff
path: root/saml
diff options
context:
space:
mode:
authorAleš Smodiš <aless@guru.si>2015-10-15 18:46:54 +0200
committerAleš Smodiš <aless@guru.si>2015-10-15 18:46:54 +0200
commit4a781b21db10f82e35b9945109b5f4d41ad0e8c3 (patch)
tree3907cb657aeb9bf4ba27dcc630935329bac1a5b6 /saml
parentde2ea4c96a007cd1c6545f0b4a063d3392a1a0d3 (diff)
Server-side support for SAML logout, sessions are destroyed only using an AJAX call.
Diffstat (limited to 'saml')
-rw-r--r--saml/saml.js156
1 files changed, 103 insertions, 53 deletions
diff --git a/saml/saml.js b/saml/saml.js
index a363c1e..43844f2 100644
--- a/saml/saml.js
+++ b/saml/saml.js
@@ -118,6 +118,15 @@ var makeWaiter = function (sid) {
return waiter;
};
+var makeDoubleWaiter = function (sid) {
+ var waiter = makeWaiter(sid);
+ waiter.responsePromise = new Promise(function (resolve, reject) {
+ waiter.responseResolve = resolve;
+ waiter.responseReject = reject;
+ });
+ return waiter;
+};
+
// ================================================================================
// XML DOM data extraction functions for SAML data
// ================================================================================
@@ -169,7 +178,7 @@ var parseAttributes = function (attributeStatementNode) {
}
}
if (i < 0) {
- console.log('ERROR: an attribute without a name');
+ logger.error('ERROR: an attribute without a name');
}
else {
result[name] = parseAttributeValue(node);
@@ -258,7 +267,7 @@ var handleLogoutResponse = function (xml, form, res) {
var errors = [],
issuer = null,
status = null,
- session = null,
+ waiter = null,
doc, rootNode, attributes, i, inResponseTo, node;
doc = new xmldom.DOMParser().parseFromString(xml);
rootNode = doc.documentElement;
@@ -275,8 +284,12 @@ var handleLogoutResponse = function (xml, form, res) {
}
if (!inResponseTo) errors.push('request ID that this message is a response to is missing');
else {
- session = waitingLogouts[inResponseTo];
- if (!session) errors.push('there is no session waiting on request ID ' + inResponseTo);
+ waiter = parkedLogoutRequests[inResponseTo];
+ if (!waiter) errors.push('there is no session waiting on request ID ' + inResponseTo);
+ else {
+ waiter.done = Date.now();
+ delete parkedLogoutRequests[inResponseTo];
+ }
}
node = rootNode.firstChild;
while (node) {
@@ -302,11 +315,11 @@ var handleLogoutResponse = function (xml, form, res) {
if (errors.length > 0) {
if (errors.length > 1) res.send('Multiple errors:\n ' + errors.join('\n '));
else res.send('Error: ' + errors[0]);
- session.samlLogout(errors.join('\n'));
+ if (waiter) waiter.responseReject(new Error(errors.join(', ')));
}
else {
res.send('OK');
- session.samlLogout(null);
+ if (waiter) waiter.responseResolve();
}
};
@@ -378,7 +391,7 @@ var handleLogoutRequest = function (xml, form, res) {
};
makeSamlRedirectUrl(SLOServiceURL, 'SAMLRequest', responseObj, form.RelayState ? {RelayState: form.RelayState} : null, function (err, svcUrl) {
if (err) {
- console.log('Error while assembling the Logout SAML response: ' + err);
+ logException('Error while assembling the Logout SAML response: ' + err, err);
res.send('Failed: ' + err);
return;
}
@@ -422,9 +435,9 @@ http_app.get('/Shibboleth.sso/Login', function (req, res) {
makeSamlRedirectUrl(SSOServiceURL, 'SAMLRequest', request, null, function (err, svcUrl) {
if (err) {
logException('Error while assembling the Login SAML request: ' + err, err);
- res.send('Failed: ' + err);
waiter.done = Date.now();
waiter.reject(err instanceof Error ? err : new Error(err));
+ res.send('Failed: ' + err);
return;
}
res.redirect(svcUrl); // redirect to the IdP
@@ -446,51 +459,56 @@ http_app.get('/Shibboleth.sso/Login', function (req, res) {
// redirect to logout on Arnes AAI, https://codeq.si/Shibboleth.sso/Logout
http_app.get('/Shibboleth.sso/Logout', function (req, res) {
var sid = req.query.sid,
- session, samlData, requestId, request;
+ waiter, requestId, request;
if (!sid) {
res.send('SID not set');
return;
}
- session = sessions[sid];
- if (!session) {
- res.send('No such session: ' + sid);
- return;
- }
- samlData = session.saml;
- if (!samlData) {
- res.send('No SAML session associated with session ' + sid);
- return;
- }
- if (!samlData.NameID) {
- res.send('The SAML data for session ' + sid + ' does not contain the NameID');
- return;
- }
- requestId = makeRequestId();
- request = {
- 'samlp:LogoutRequest': {
- '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
- '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
- '@Version': '2.0',
- '@Destination': SLOServiceURL,
- '@ID': requestId,
- '@IssueInstant': new Date().toISOString(),
- 'saml:Issuer' : {
- '#text': SPIssuer
- },
- 'saml:NameID': samlData.NameID
- }
- };
- if (samlData.SessionIndex) requestObj['samlp:LogoutRequest']['samlp:SessionIndex'] = {'#text': samlData.SessionIndex};
- makeSamlRedirectUrl(SLOServiceURL, 'SAMLRequest', request, null, function (err, svcUrl) {
- if (err) {
- console.log('Error while assembling the Logout SAML request: ' + err);
- res.send('Failed: ' + err);
- return;
- }
- res.redirect(svcUrl); // redirect to the IdP
- parkedLogoutRequests[requestId] = session;
- });
+ waiter = waitingLogouts[sid];
+ if (!waiter) waitingLogouts[sid] = waiter = makeDoubleWaiter(sid);
+
+ waiter.promise
+ .then(function (samlData) {
+ requestId = makeRequestId();
+ request = {
+ 'samlp:LogoutRequest': {
+ '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
+ '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
+ '@Version': '2.0',
+ '@Destination': SLOServiceURL,
+ '@ID': requestId,
+ '@IssueInstant': new Date().toISOString(),
+ 'saml:Issuer' : {
+ '#text': SPIssuer
+ },
+ 'saml:NameID': samlData.NameID
+ }
+ };
+ if (samlData.SessionIndex) request['samlp:LogoutRequest']['samlp:SessionIndex'] = {'#text': samlData.SessionIndex};
+ makeSamlRedirectUrl(SLOServiceURL, 'SAMLRequest', request, null, function (err, svcUrl) {
+ if (err) {
+ logException('Error while assembling the Logout SAML request: ' + err, err);
+ waiter.done = Date.now();
+ waiter.responseReject(err instanceof Error ? err : new Error(err));
+ res.send('Failed: ' + err);
+ return;
+ }
+ res.redirect(svcUrl); // redirect to the IdP
+ parkedLogoutRequests[requestId] = waiter;
+ waiter.parked = Date.now();
+ waiter.requestId = requestId;
+ });
+ })
+ .catch(function (e) {
+ logException('Exception while processing login request: ' + e, e);
+ res.send('Error: ' + e);
+ if (waiter) {
+ waiter.done = Date.now();
+ waiter.responseReject(e);
+ }
+ })
+ .done();
});
// user logged in, read the credentials
@@ -606,7 +624,7 @@ http_app.get('/Shibboleth.sso/SLO/Redirect', function (req, res) {
var form = req.query,
delegateToHandler = function (err, buffer, handler) {
if (err) {
- console.log('Error while deflating request: ' + err);
+ logException('Error while deflating request: ' + err, err);
res.send('Failed: ' + err);
return;
}
@@ -618,25 +636,57 @@ http_app.get('/Shibboleth.sso/SLO/Redirect', function (req, res) {
});
// ================================================================================
-// Web API handler
+// Web API handlers
// ================================================================================
http_app.get('/Shibboleth.sso/WaitLogin', function (req, res) {
var sid = req.query.sid,
waiter;
if (!sid) {
- res.set('Content-Type', 'text/plain');
- res.send('Provide sid as a query parameter');
+ res.json({'code': -1, 'message': 'sid query parameter not set'});
return;
}
waiter = waitingLogins[sid];
if (!waiter) waitingLogins[sid] = waiter = makeWaiter(sid);
waiter.listeners = waiter.listeners + 1;
waiter.promise.then(function (jsonResponse) {
+ delete waitingLogins[sid];
res.json({'code': 0, 'message': 'OK', 'data': jsonResponse});
+ }, function (err) {
delete waitingLogins[sid];
+ res.json({'code': 1, 'message': '' + err});
+ }).done();
+});
+
+http_app.get('/Shibboleth.sso/WaitLogout', function (req, res) {
+ var sid = req.query.sid,
+ saml = req.query.saml,
+ waiter, samlData;
+ if (!sid) {
+ res.json({'code': -1, 'message': 'sid query parameter not set'});
+ return;
+ }
+ if (!saml) {
+ res.json({'code': -2, 'message': 'saml query parameter not set'});
+ return;
+ }
+ try {
+ samlData = JSON.parse(saml);
+ }
+ catch (e) {
+ res.json({'code': -3, 'message': 'saml query parameter is not proper JSON: ' + e});
+ return;
+ }
+
+ waiter = waitingLogouts[sid];
+ if (!waiter) waitingLogouts[sid] = waiter = makeDoubleWaiter(sid);
+ waiter.listeners = waiter.listeners + 1;
+ waiter.resolve(samlData);
+ waiter.responsePromise.then(function () {
+ delete waitingLogouts[sid];
+ res.json({'code': 0, 'message': 'OK'});
}, function (err) {
+ delete waitingLogouts[sid];
res.json({'code': 1, 'message': '' + err});
- delete waitingLogins[sid];
}).done();
});