From ce4aa82c0a7be42341148cc0bc716e639b6c0a56 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Tue, 29 Oct 2019 14:52:10 +0100 Subject: [PATCH] chg: [login/diagnostic] Improved login errors feedback and adjusted diagnostic --- diagnostic.py | 57 ++++++++++++++++++++++++++++++++++----- server.py | 50 +++++++++++++++++++++++++++------- templates/error_page.html | 43 +++++++++++++++++++++++++++++ templates/login.html | 2 +- 4 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 templates/error_page.html diff --git a/diagnostic.py b/diagnostic.py index 008915c..c735f1d 100755 --- a/diagnostic.py +++ b/diagnostic.py @@ -6,7 +6,7 @@ import time import signal import functools import configparser -from pprint import pprint +from urllib.parse import urlparse, parse_qs import subprocess import diagnostic_util try: @@ -379,10 +379,22 @@ def check_server_listening(spinner): r = requests.get(url) except requests.exceptions.ConnectionError: return (False, 'Can\'t connect to {}').format(url) - return ( - r.status_code == 200, - '{} {}reached. Status code [{}]'.format(url, "not " if r.status_code != 200 else "", r.status_code) - ) + + if '/error_page' in r.url: + o = urlparse(r.url) + query = parse_qs(o.query) + error_code = query.get('error_code', '') + if error_code[0] == '1': + return (False, 'To many redirects. Server may not be properly configured\n\t➥ Try to correctly setup an HTTPS server or change the cookie policy in the configuration') + else: + error_message = query.get('error_message', '')[0] + return (False, 'Unkown error: {}\n{}'.format(error_code, error_message)) + else: + return ( + r.status_code == 200, + '{} {}reached. Status code [{}]'.format(url, "not " if r.status_code != 200 else "", r.status_code) + ) + @add_spinner @@ -394,15 +406,46 @@ def check_server_dynamic_enpoint(spinner): } sleep_max = 15 start_time = time.time() + + # Check MISP connectivity + url_misp = configuration_file.get("Auth", "misp_fqdn") + try: + r = requests.get(url_misp, verify=configuration_file.getboolean("Auth", "ssl_verify")) + except requests.exceptions.SSLError as e: + if 'CERTIFICATE_VERIFY_FAILED' in str(e): + return (False, 'SSL connection error certificate verify failed.\n\t➥ Review your configuration'.format(e)) + else: + return (False, 'SSL connection error `{}`.\n\t➥ Review your configuration'.format(e)) + + except requests.exceptions.ConnectionError: + return (False, 'MISP `{}` cannot be reached.\n\t➥ Review your configuration'.format(url_misp)) + url_login = '{}:{}/login'.format(HOST, PORT) url = '{}:{}/_logs'.format(HOST, PORT) session = requests.Session() - session.verify = False + session.verify = configuration_file.getboolean("Auth", "ssl_verify") r_login = session.post(url_login, data=payload) + + # Check if we ended up on the error page + if '/error_page' in r_login.url: + o = urlparse(r_login.url) + query = parse_qs(o.query) + error_code = query.get('error_code', '') + if error_code[0] == '2': + return (False, 'MISP cannot be reached for authentication\n\t➥ Review MISP fully qualified name and SSL settings') + else: + error_message = query.get('error_message', '')[0] + return (False, 'Unkown error: {}\n{}'.format(error_code, error_message)) + + # Recover error message from the url if '/login' in r_login.url: - return_text = 'Invalid credential. Use valid credential to proceed.' + o = urlparse(r_login.url) + query = parse_qs(o.query) + error_message = query.get('auth_error_message', 'Unknown error')[0] + return_text = error_message return (False, return_text) + # Connection seems to be successful, checking if we receive data from event-stream r = session.get(url, stream=True, timeout=sleep_max, headers={'Accept': 'text/event-stream'}) return_flag = False return_text = 'Dynamic endpoint returned data but not in the correct format.' diff --git a/server.py b/server.py index 833d407..9685b16 100755 --- a/server.py +++ b/server.py @@ -16,7 +16,7 @@ from time import sleep, strftime import redis import util -from flask import (Flask, Response, jsonify, render_template, request, +from flask import (Flask, Response, jsonify, render_template, request, make_response, send_from_directory, stream_with_context, url_for, redirect) from flask_login import (UserMixin, LoginManager, current_user, login_user, logout_user, login_required) from helpers import (contributor_helper, geo_helper, live_helper, @@ -101,9 +101,11 @@ class User(UserMixin): :return: """ post_data = { + "_method": "POST", "data[_Token][key]": "", "data[_Token][fields]": "", "data[_Token][unlocked]": "", + "data[_Token][debug]": "", "data[User][email]": self.id, "data[User][password]": self.password, } @@ -120,12 +122,17 @@ class User(UserMixin): token_fields_exp = re.compile(r'name="data\[_Token]\[fields]" value="([^\s]+)"') token_fields = token_fields_exp.search(login_page.text) - # This regex matches the "data[_Token][fields]" value needed to make a POST request on the MISP login page. + # This regex matches the "data[_Token][key]" value needed to make a POST request on the MISP login page. token_key_exp = re.compile(r'name="data\[_Token]\[key]" value="([^\s]+)"') token_key = token_key_exp.search(login_page.text) + # This regex matches the "data[_Token][debug]" value needed to make a POST request on the MISP login page. + token_key_exp = re.compile(r'name="data\[_Token]\[debug]" value="([^\s]+)"') + token_debug = token_key_exp.search(login_page.text) + post_data["data[_Token][fields]"] = token_fields.group(1) post_data["data[_Token][key]"] = token_key.group(1) + post_data["data[_Token][debug]"] = token_debug.group(1) # POST request with user credentials + hidden form values. post_to_login_page = session.post(misp_login_page, data=post_data, allow_redirects=False) @@ -159,7 +166,24 @@ def unauthorized(): Redirect unauthorized user to login page. :return: """ - return redirect(url_for('login')) + redirectCount = int(request.cookies.get('redirectCount', '0')) + if redirectCount > 5: + response = make_response(redirect(url_for( + 'error_page', + error_message='Too many redirects. This can be due to your brower not accepting cookies or the misp-dashboard website is badly configured', + error_code='1' + ))) + response.set_cookie('redirectCount', '0', secure=False, httponly=True) + else: + response = make_response(redirect(url_for('login'))) + response.set_cookie('redirectCount', str(redirectCount+1), secure=False, httponly=True) + return response + + +@app.route('/error_page') +def error_page(): + error_message = request.args.get('error_message', False) + return render_template('error_page.html', error_message=error_message) @app.route('/logout') @@ -173,7 +197,7 @@ def logout(): return redirect(url_for('login')) -@app.route('/login', methods=['GET','POST']) +@app.route('/login', methods=['GET', 'POST']) def login(): """ Login form route. @@ -192,14 +216,22 @@ def login(): if request.method == 'POST' and form.validate(): user = User(form.username.data, form.password.data) - if user.misp_login(): - login_user(user) - return redirect(url_for('index')) + error_message = 'Username and Password does not match when connecting to MISP or incorrect MISP permission' + try: + is_logged_in, misp_error_message = user.misp_login() + if len(misp_error_message) > 0: + error_message = misp_error_message + if is_logged_in: + login_user(user) + return redirect(url_for('index')) + except requests.exceptions.SSLError: + return redirect(url_for('login', auth_error=True, auth_error_message='MISP cannot be reached for authentication')) - return redirect(url_for('login', auth_error=True)) + return redirect(url_for('login', auth_error=True, auth_error_message=error_message)) else: auth_error = request.args.get('auth_error', False) - return render_template('login.html', title='Login', form=form, authError=auth_error) + auth_error_message = request.args.get('auth_error_message', '') + return render_template('login.html', title='Login', form=form, authError=auth_error, authErrorMessage=auth_error_message) diff --git a/templates/error_page.html b/templates/error_page.html new file mode 100644 index 0000000..56f6364 --- /dev/null +++ b/templates/error_page.html @@ -0,0 +1,43 @@ + + + + + + + Users - MISP + + + + + + + + + + + + + + +
+
+
+
+ +
+ + + + + +
+
+ +
+
+ {{ error_message }} +
+
+
+ + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html index 4e128ec..c26ddbb 100644 --- a/templates/login.html +++ b/templates/login.html @@ -36,7 +36,7 @@ {% if authError %}
- Username and Password does not match when connecting to MISP or incorrect MISP permission + {{ authErrorMessage }}
{% endif %}