mirror of https://github.com/MISP/misp-dashboard
Merge pull request #135 from mokaddem/improvements-login-diagnostic
Improvements on login and diagnosticpull/139/head
commit
5967a9d34a
|
@ -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:
|
||||
|
@ -15,6 +15,8 @@ try:
|
|||
import json
|
||||
import flask
|
||||
import requests
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
from halo import Halo
|
||||
except ModuleNotFoundError as e:
|
||||
print('Dependency not met. Either not in a virtualenv or dependency not installed.')
|
||||
|
@ -379,10 +381,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 +408,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', ['Redirected to `loging` caused by an unknown error'])[0]
|
||||
return_text = 'Redirected to `loging` caused by: {}'.format(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.'
|
||||
|
|
58
server.py
58
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)
|
||||
|
@ -136,9 +143,11 @@ class User(UserMixin):
|
|||
# Logged in, check if logged in user can access the dashboard
|
||||
me_json = session.get(misp_user_me_page).json()
|
||||
dashboard_access = me_json.get('UserSetting', {}).get('dashboard_access', False)
|
||||
if dashboard_access is not False:
|
||||
return dashboard_access is True or dashboard_access == 1
|
||||
return None
|
||||
if dashboard_access is True or dashboard_access == 1:
|
||||
return (True, '')
|
||||
else:
|
||||
return (None, 'User does not have dashboard access')
|
||||
return (None, '')
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
|
@ -159,7 +168,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', auth_error=True, auth_error_message='Unauthorized. Review your cookie settings')))
|
||||
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 +199,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 +218,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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
{% if authError %}
|
||||
<div class="alert alert-danger">
|
||||
Username and Password does not match when connecting to MISP or incorrect MISP permission
|
||||
{{ authErrorMessage }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
Loading…
Reference in New Issue