mirror of https://github.com/MISP/misp-dashboard
chg: [login/diagnostic] Improved login errors feedback and adjusted
diagnosticpull/135/head
parent
d390a169b5
commit
ce4aa82c0a
|
@ -6,7 +6,7 @@ import time
|
||||||
import signal
|
import signal
|
||||||
import functools
|
import functools
|
||||||
import configparser
|
import configparser
|
||||||
from pprint import pprint
|
from urllib.parse import urlparse, parse_qs
|
||||||
import subprocess
|
import subprocess
|
||||||
import diagnostic_util
|
import diagnostic_util
|
||||||
try:
|
try:
|
||||||
|
@ -379,10 +379,22 @@ def check_server_listening(spinner):
|
||||||
r = requests.get(url)
|
r = requests.get(url)
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
return (False, 'Can\'t connect to {}').format(url)
|
return (False, 'Can\'t connect to {}').format(url)
|
||||||
return (
|
|
||||||
r.status_code == 200,
|
if '/error_page' in r.url:
|
||||||
'{} {}reached. Status code [{}]'.format(url, "not " if r.status_code != 200 else "", r.status_code)
|
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
|
@add_spinner
|
||||||
|
@ -394,15 +406,46 @@ def check_server_dynamic_enpoint(spinner):
|
||||||
}
|
}
|
||||||
sleep_max = 15
|
sleep_max = 15
|
||||||
start_time = time.time()
|
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_login = '{}:{}/login'.format(HOST, PORT)
|
||||||
url = '{}:{}/_logs'.format(HOST, PORT)
|
url = '{}:{}/_logs'.format(HOST, PORT)
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
session.verify = False
|
session.verify = configuration_file.getboolean("Auth", "ssl_verify")
|
||||||
r_login = session.post(url_login, data=payload)
|
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:
|
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)
|
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'})
|
r = session.get(url, stream=True, timeout=sleep_max, headers={'Accept': 'text/event-stream'})
|
||||||
return_flag = False
|
return_flag = False
|
||||||
return_text = 'Dynamic endpoint returned data but not in the correct format.'
|
return_text = 'Dynamic endpoint returned data but not in the correct format.'
|
||||||
|
|
50
server.py
50
server.py
|
@ -16,7 +16,7 @@ from time import sleep, strftime
|
||||||
import redis
|
import redis
|
||||||
|
|
||||||
import util
|
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)
|
send_from_directory, stream_with_context, url_for, redirect)
|
||||||
from flask_login import (UserMixin, LoginManager, current_user, login_user, logout_user, login_required)
|
from flask_login import (UserMixin, LoginManager, current_user, login_user, logout_user, login_required)
|
||||||
from helpers import (contributor_helper, geo_helper, live_helper,
|
from helpers import (contributor_helper, geo_helper, live_helper,
|
||||||
|
@ -101,9 +101,11 @@ class User(UserMixin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
post_data = {
|
post_data = {
|
||||||
|
"_method": "POST",
|
||||||
"data[_Token][key]": "",
|
"data[_Token][key]": "",
|
||||||
"data[_Token][fields]": "",
|
"data[_Token][fields]": "",
|
||||||
"data[_Token][unlocked]": "",
|
"data[_Token][unlocked]": "",
|
||||||
|
"data[_Token][debug]": "",
|
||||||
"data[User][email]": self.id,
|
"data[User][email]": self.id,
|
||||||
"data[User][password]": self.password,
|
"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_exp = re.compile(r'name="data\[_Token]\[fields]" value="([^\s]+)"')
|
||||||
token_fields = token_fields_exp.search(login_page.text)
|
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_exp = re.compile(r'name="data\[_Token]\[key]" value="([^\s]+)"')
|
||||||
token_key = token_key_exp.search(login_page.text)
|
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][fields]"] = token_fields.group(1)
|
||||||
post_data["data[_Token][key]"] = token_key.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 request with user credentials + hidden form values.
|
||||||
post_to_login_page = session.post(misp_login_page, data=post_data, allow_redirects=False)
|
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.
|
Redirect unauthorized user to login page.
|
||||||
:return:
|
: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')
|
@app.route('/logout')
|
||||||
|
@ -173,7 +197,7 @@ def logout():
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET','POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
"""
|
"""
|
||||||
Login form route.
|
Login form route.
|
||||||
|
@ -192,14 +216,22 @@ def login():
|
||||||
if request.method == 'POST' and form.validate():
|
if request.method == 'POST' and form.validate():
|
||||||
user = User(form.username.data, form.password.data)
|
user = User(form.username.data, form.password.data)
|
||||||
|
|
||||||
if user.misp_login():
|
error_message = 'Username and Password does not match when connecting to MISP or incorrect MISP permission'
|
||||||
login_user(user)
|
try:
|
||||||
return redirect(url_for('index'))
|
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:
|
else:
|
||||||
auth_error = request.args.get('auth_error', False)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width" />
|
||||||
|
<title>
|
||||||
|
Users - MISP
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<!-- jQuery -->
|
||||||
|
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
|
||||||
|
<!-- Bootstrap Core CSS -->
|
||||||
|
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link href="{{ url_for('static', filename='css/sb-admin-2.css') }}" rel="stylesheet">
|
||||||
|
<!-- Bootstrap Core JavaScript -->
|
||||||
|
<script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="text/css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="flashContainer" style="padding-top:50px; !important;">
|
||||||
|
<div id="main-view-container" class="container-fluid ">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="width:100%;">
|
||||||
|
<table style="margin-left:auto;margin-right:auto;">
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:right;width:250px;padding-right:50px"></td>
|
||||||
|
<td style="width:460px">
|
||||||
|
<div>
|
||||||
|
<img src="{{ url_for('static', filename='pics/misp-logo.png') }}" style="display:block; margin-left: auto; margin-right: auto;"/>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger" style="margin-top: 15px;">
|
||||||
|
{{ error_message }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -36,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if authError %}
|
{% if authError %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
Username and Password does not match when connecting to MISP or incorrect MISP permission
|
{{ authErrorMessage }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue