From cbc096f9cb44a7d26b4fa01a40dbba594ab339ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 15 Oct 2015 15:09:00 +0200 Subject: Implemented minimum support for authentication via SAML with an addition of a new daemon. TODO: python login with SAML credentials. --- web/main.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) (limited to 'web') diff --git a/web/main.js b/web/main.js index c07e12e..90d4cd1 100644 --- a/web/main.js +++ b/web/main.js @@ -1,11 +1,19 @@ var engine = require('engine.io'), // web sockets communication handler, sitting on the low-level HTTP handler - http_server = require('http').createServer(), // the low-level HTTP handler + http = require('http'), // HTTP library + https = require('https'), // HTTPS library + http_server = http.createServer(), // the low-level HTTP handler net = require('net'), // TCP sockets library Promise = require('bluebird'), // the promises library log4js = require('log4js'), // the logger + url = require('url'), // URL parser express = require('express'), // library providing the Express web framework http_app = express(); // web framework engine, sitting on the low-level HTTP handler +var SAML_LOGINWAIT_URL = process.env.CODEQ_SAML_LOGINWAIT_URL || 'https://codeq.si/Shibboleth.sso/WaitLogin'; + +var samlLoginwaitUrlParsed = url.parse(SAML_LOGINWAIT_URL), + samlLoginwaitIsHttps = samlLoginwaitUrlParsed.protocol === 'https:'; + // ================================================================================ // The logger // ================================================================================ @@ -111,6 +119,89 @@ var guiHandlers = { }).done(); }, + 'saml_login': function samlLogin(session, message) { + var guiResponse = {'tid': message.tid, 'sid': message.sid}, + aborted = false, + options = { + hostname: samlLoginwaitUrlParsed.hostname, + port: samlLoginwaitUrlParsed.port || (samlLoginwaitIsHttps ? 443 : 80), + path: samlLoginwaitUrlParsed.pathname + '?sid=' + session.sid, + method: 'GET', + rejectUnauthorized: false // allow server certificates we don't know + }, + handleResponse = function (res) { + var chunks = [], + status = res.statusCode, + headers, i, m; + if (status !== 200) { + guiResponse.code = -2; + guiResponse.message = 'Received an error from the identity server: ' + status + ' ' + res.statusMessage; + chunks.push(guiResponse.message, '\n'); + headers = res.rawHeaders; + for (i = 0; i < headers.length; i++) chunks.push(headers[i], '\n'); + chunks.push('\n'); + } + res.on('data', function (d) { + chunks.push(d); + }); + res.on('end', function () { + if (aborted) return; // already handled + if (status !== 200) { + logger.error(chunks.join('')); + session.send(guiResponse); + return; + } + try { + m = JSON.parse(chunks.join('')); + } + catch (e) { + guiResponse.code = -3; + guiResponse.message = 'Response from the identity server is not a JSON: ' + e; + logger.error(guiResponse.message + '\n' + chunks.join(''), e); + session.send(guiResponse); + return; + } + if (m.code !== 0) { + guiResponse.code = m.code || -5; + guiResponse.message = m.message || 'Status message from the identity server is not set'; + session.send(guiResponse); + } + else { + sendDataToPython({'action': 'saml_login', 'tid': message.tid, 'sid': message.sid, 'data': m.data && m.data.userData}) + .then(function (jsonReply) { + session.samlData = m.data; // perhaps we'll need the SAML data in the future + session.send(jsonReply); + }, function (exc) { + guiResponse.code = -6; + guiResponse.message = 'Python processing of SAML login generated an error: ' + exc; + logger.error(guiResponse.message, exc); + session.send(guiResponse); + }) + .done(); + } + }); + }, + req; + if (samlLoginwaitIsHttps) req = https.request(options, handleResponse); + else req = http.request(options, handleResponse); + req.on('error', function (e) { + if (aborted) return; // already handled + guiResponse.code = -1; + guiResponse.message = 'Error when trying to contact the identity server: ' + e; + logException('Error when trying to contact the identity server: ' + e, e); + session.send(guiResponse); + }); + req.setTimeout(600000, function () { // 10 minutes timeout + aborted = true; + req.abort(); + guiResponse.code = -4; + guiResponse.message = 'Timeout waiting for a reply from the identity server'; + logger.error(guiResponse.message); + session.send(guiResponse); + }); + req.end(); + }, + // actions to use default handling should define truthy values that are not functions // (this is to filter out unnecessary traffic before it hits Python) 'login': true, -- cgit v1.2.1