From de2ea4c96a007cd1c6545f0b4a063d3392a1a0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 15 Oct 2015 16:48:38 +0200 Subject: Implemented python login using authenticated SAML credentials. --- scripts/db_update-20151015.sql | 1 + server/handlers.py | 14 +++++++--- server/user_session.py | 58 +++++++++++++++++++++++++++++++++++++----- web/main.js | 9 ++++--- 4 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 scripts/db_update-20151015.sql diff --git a/scripts/db_update-20151015.sql b/scripts/db_update-20151015.sql new file mode 100644 index 0000000..a934725 --- /dev/null +++ b/scripts/db_update-20151015.sql @@ -0,0 +1 @@ +alter table codeq_user alter username type varchar(50), alter password drop not null, add saml_data jsonb; diff --git a/server/handlers.py b/server/handlers.py index 93818f0..5df7161 100644 --- a/server/handlers.py +++ b/server/handlers.py @@ -284,11 +284,19 @@ class GetUserStat(CodeqService): class SamlLogin(CodeqService): def process(self, request): - data = request.data.get('data') - if data is None: + js = request.data + saml_data = js.get('saml_data') + gui_lang = js.get('gui_lang', 'en') + if saml_data is None: request.reply({'code': 1, 'message': 'SAML user data not specified'}) else: - request.reply({'code': 0, 'message': 'OK'}) # TODO: implement login using SAML credentials + session = request.session + try: + name, email, date_joined, last_login = session.saml_login_or_signup(saml_data, gui_lang) + except Exception as e: + request.reply({'code': 2, 'message': 'SAML login failed: ' + str(e)}) + else: + request.reply({'code': 0, 'message': 'OK', 'name': name, 'email': email, 'joined': date_joined.isoformat(), 'last-login': last_login.isoformat(), 'settings': session.get_settings()}) # maps actions to their handlers diff --git a/server/user_session.py b/server/user_session.py index b5cdd61..739da9a 100644 --- a/server/user_session.py +++ b/server/user_session.py @@ -86,7 +86,10 @@ class UserSession(object): finally: cur.close() finally: - conn.commit() + try: + conn.commit() + except: + pass db.return_connection(conn) def signup(self, username, name, email, password, lang): @@ -110,8 +113,45 @@ class UserSession(object): self.settings = {'gui_lang': lang} finally: cur.close() - finally: conn.commit() + finally: + db.return_connection(conn) + + def saml_login_or_signup(self, saml_data, gui_lang): + uuid = saml_data.get('schacUUID') + if uuid is None: + raise AuthenticationFailed('SAML data does not contain schacUUID') + name = saml_data.get('displayName') + email = saml_data.get('mail') + with self._access_lock: + now = datetime.datetime.utcnow() + conn = db.get_connection() + try: + cur = conn.cursor() + try: + cur.execute('update codeq_user set name = %s, email = %s, saml_data = %s, last_login = %s where username = %s and saml_data is not null returning id, gui_lang, date_joined, robot_address', (name, email, psycopg2.extras.Json(saml_data), str(now), uuid)) + row = cur.fetchone() + if row: + self.uid = row[0] + self.username = uuid + self.settings = {'gui_lang': row[1], 'robot_address': row[3]} + return name, email, row[2], now + else: + cur.execute('insert into codeq_user (username, name, email, is_admin, is_active, date_joined, last_login, gui_lang, saml_data) values (%s, %s, %s, %s, %s, %s, %s, %s, %s) returning id', (uuid, name, email, False, True, str(now), str(now), gui_lang, psycopg2.extras.Json(saml_data))) + row = cur.fetchone() + if row is None: + raise SignupFailed('Sign-up failed') + self.uid = row[0] + self.username = uuid + self.settings = {'gui_lang': gui_lang, 'robot_address': None} + return name, email, now, now + finally: + cur.close() + finally: + try: + conn.commit() + except: + pass db.return_connection(conn) def destroy(self): @@ -150,8 +190,8 @@ class UserSession(object): cur.execute("update codeq_user set gui_lang = %s, robot_address = %s where id = %s", (self.settings['gui_lang'], self.settings['robot_address'], self.uid)) finally: cur.close() - finally: conn.commit() + finally: db.return_connection(conn) def load_language_session(self, problem_id): @@ -178,7 +218,10 @@ class UserSession(object): finally: cur.close() finally: - conn.commit() + try: + conn.commit() + except: + pass db.return_connection(conn) def end_language_session(self): @@ -209,7 +252,10 @@ class UserSession(object): finally: cur.close() finally: - conn.commit() + try: + conn.commit() + except: + pass db.return_connection(conn) def update_solution(self, problem_id, trace, solution): @@ -257,8 +303,8 @@ class UserSession(object): raise PasswordChangeFailed('Password change failed') finally: cur.close() - finally: conn.commit() + finally: db.return_connection(conn) def get_stat(self): diff --git a/web/main.js b/web/main.js index 21f639d..67d4274 100644 --- a/web/main.js +++ b/web/main.js @@ -132,7 +132,7 @@ var guiHandlers = { handleResponse = function (res) { var chunks = [], status = res.statusCode, - headers, i, m; + headers, i, m, pythonRequest, keys; if (status !== 200) { guiResponse.code = -2; guiResponse.message = 'Received an error from the identity server: ' + status + ' ' + res.statusMessage; @@ -157,7 +157,7 @@ var guiHandlers = { 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); + logException(guiResponse.message + '\n' + chunks.join(''), e); session.send(guiResponse); return; } @@ -167,14 +167,15 @@ var guiHandlers = { session.send(guiResponse); } else { - sendDataToPython({'action': 'saml_login', 'tid': message.tid, 'sid': message.sid, 'data': m.data && m.data.userData}) + message.saml_data = m.data && m.data.userData; // add the data from SAML authentication, and forward everything to Python + sendDataToPython(message) .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); + logException(guiResponse.message, exc); session.send(guiResponse); }) .done(); -- cgit v1.2.1