routes/admin.py: refactor DetailedHistory

- Run HTML through the template engine, preventing XSS from various
  vectors
- Fix uncaught exception when a history entry about domain template
  deletion is processed
- Adapt indentation to 4 space characters per level
This commit is contained in:
zoeller-freinet 2021-12-04 15:36:05 +01:00
parent 6f12b783a8
commit f0008ce401

View File

@ -4,7 +4,7 @@ import traceback
import re import re
from base64 import b64encode from base64 import b64encode
from ast import literal_eval from ast import literal_eval
from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort, flash, session from flask import Blueprint, render_template, render_template_string, make_response, url_for, current_app, request, redirect, jsonify, abort, flash, session
from flask_login import login_required, current_user from flask_login import login_required, current_user
from ..decorators import operator_role_required, admin_role_required, history_access_required from ..decorators import operator_role_required, admin_role_required, history_access_required
@ -752,15 +752,7 @@ def manage_account():
'There is something wrong, please contact Administrator.' 'There is something wrong, please contact Administrator.'
}), 400) }), 400)
# check for lower key as well for old databases
def get_key_val(dict, k):
key_lowercase = k.lower()
if k in dict.keys():
return str(dict[k])
elif key_lowercase in dict.keys():
return str(dict[key_lowercase])
else:
return ""
class DetailedHistory(): class DetailedHistory():
def __init__(self, history, change_set): def __init__(self, history, change_set):
self.history = history self.history = history
@ -770,101 +762,132 @@ class DetailedHistory():
if not history.detail: if not history.detail:
self.detailed_msg = "" self.detailed_msg = ""
return return
if 'add_rrest' not in history.detail:
detail_dict = json.loads(history.detail.replace("'", '"')) if 'add_rrest' in history.detail:
else:
detail_dict = json.loads(history.detail.replace("\'", '')) detail_dict = json.loads(history.detail.replace("\'", ''))
key_array = list(detail_dict.keys()) else:
if 'domain_type' in detail_dict.keys() and 'account_id' in detail_dict.keys(): # this is a domain creation detail_dict = json.loads(history.detail.replace("'", '"'))
self.detailed_msg = """
<table class="table table-bordered table-striped"><tr><td>Domain type:</td><td>{0}</td></tr> <tr><td>Account:</td><td>{1}</td></tr></table> if 'domain_type' in detail_dict and 'account_id' in detail_dict: # this is a domain creation
""".format(detail_dict['domain_type'], self.detailed_msg = render_template_string("""
Account.get_name_by_id(self=None, account_id=detail_dict['account_id']) if detail_dict['account_id'] != "0" else "None") <table class="table table-bordered table-striped">
elif 'authenticator' in detail_dict.keys(): # this is a user authentication <tr><td>Domain type:</td><td>{{ domaintype }}</td></tr>
self.detailed_msg = """ <tr><td>Account:</td><td>{{ account }}</td></tr>
</table>
""",
domaintype=detail_dict['domain_type'],
account=Account.get_name_by_id(self=None, account_id=detail_dict['account_id']) if detail_dict['account_id'] != "0" else "None")
elif 'authenticator' in detail_dict: # this is a user authentication
self.detailed_msg = render_template_string("""
<table class="table table-bordered table-striped" style="width:565px;"> <table class="table table-bordered table-striped" style="width:565px;">
<thead> <thead>
<tr> <tr>
<th colspan="3" style="background: <th colspan="3" style="background: rgba({{ background_rgba }});">
""" <p style="color:white;">User {{ username }} authentication {{ auth_result }}</p>
# Change table header background colour depending on auth success or failure
if detail_dict['success'] == 1:
self.detailed_msg+= """
rgba(68,157,68);"> <p style="color:white;">
User {0} authentication success
</p></th>
""".format(detail_dict['username'])
else:
self.detailed_msg+= """
rgba(201,48,44);"> <p style="color:white;">
User {0} authentication failure
</th> </th>
""".format(detail_dict['username'])
self.detailed_msg+= """
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>Authenticator Type:</td> <td>Authenticator Type:</td>
<td colspan="2">{0}</td> <td colspan="2">{{ authenticator }}</td>
</tr> </tr>
<tr> <tr>
<td>IP Address</td> <td>IP Address</td>
<td colspan="2">{1}</td> <td colspan="2">{{ ip_address }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
""".format(detail_dict['authenticator'], detail_dict['ip_address']) """,
elif 'add_rrests' in detail_dict.keys(): # this is a domain record change background_rgba="68,157,68" if detail_dict['success'] == 1 else "201,48,44",
username=detail_dict['username'],
auth_result="success" if detail_dict['success'] == 1 else "failure",
authenticator=detail_dict['authenticator'],
ip_address=detail_dict['ip_address'])
elif 'add_rrests' in detail_dict: # this is a domain record change
# changes_set = [] # changes_set = []
self.detailed_msg = "" self.detailed_msg = ""
# extract_changelogs_from_a_history_entry(changes_set, history, 0) # extract_changelogs_from_a_history_entry(changes_set, history, 0)
elif 'name' in detail_dict.keys() and 'template' in history.msg: # template creation
self.detailed_msg = """ elif 'name' in detail_dict and 'template' in history.msg: # template creation / deletion
<table class="table table-bordered table-striped"><tr><td>Template name:</td><td>{0}</td></tr> <tr><td>Description:</td><td>{1}</td></tr></table> self.detailed_msg = render_template_string("""
""".format(get_key_val(detail_dict, key_array[0]), get_key_val(detail_dict, key_array[1])) <table class="table table-bordered table-striped">
<tr><td>Template name:</td><td>{{ template_name }}</td></tr>
<tr><td>Description:</td><td>{{ description }}</td></tr>
</table>
""",
template_name=DetailedHistory.get_key_val(detail_dict, "name"),
description=DetailedHistory.get_key_val(detail_dict, "description"))
elif 'Change domain' in history.msg and 'access control' in history.msg: # added or removed a user from a domain elif 'Change domain' in history.msg and 'access control' in history.msg: # added or removed a user from a domain
self.detailed_msg = """ users_with_access = DetailedHistory.get_key_val(detail_dict, "user_has_access")
<table class="table table-bordered table-striped"><tr><td>Users with access to this domain</td><td>{0}</td></tr><tr><td>Number of users:</td><td>{1}</td><tr></table> self.detailed_msg = render_template_string("""
""".format(get_key_val(detail_dict, key_array[0]), len((detail_dict[key_array[0]]))) <table class="table table-bordered table-striped">
<tr><td>Users with access to this domain</td><td>{{ users_with_access }}</td></tr>
<tr><td>Number of users:</td><td>{{ users_with_access | length }}</td><tr>
</table>
""",
users_with_access=users_with_access)
elif 'Created API key' in history.msg or 'Updated API key' in history.msg: elif 'Created API key' in history.msg or 'Updated API key' in history.msg:
self.detailed_msg = """ self.detailed_msg = render_template_string("""
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<tr><td>Key: </td><td>{0}</td></tr> <tr><td>Key: </td><td>{{ keyname }}</td></tr>
<tr><td>Role:</td><td>{1}</td></tr> <tr><td>Role:</td><td>{{ rolename }}</td></tr>
<tr><td>Description:</td><td>{2}</td></tr> <tr><td>Description:</td><td>{{ description }}</td></tr>
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr> <tr><td>Accessible domains with this API key:</td><td>{{ linked_domains }}</td></tr>
<tr><td>Accessible accounts with this API key:</td><td>{{ linked_accounts }}</td></tr>
</table> </table>
""".format(get_key_val(detail_dict, key_array[0]), get_key_val(detail_dict, key_array[1]), """,
get_key_val(detail_dict, key_array[2]), get_key_val(detail_dict, key_array[3])) keyname=DetailedHistory.get_key_val(detail_dict, "key"),
elif 'Update type for domain' in history.msg: rolename=DetailedHistory.get_key_val(detail_dict, "role"),
self.detailed_msg = """ description=DetailedHistory.get_key_val(detail_dict, "description"),
<table class="table table-bordered table-striped"> linked_domains=DetailedHistory.get_key_val(detail_dict, "domains"),
<tr><td>Domain: </td><td>{0}</td></tr> linked_accounts=DetailedHistory.get_key_val(detail_dict, "accounts"))
<tr><td>Domain type:</td><td>{1}</td></tr>
<tr><td>Masters:</td><td>{2}</td></tr>
</table>
""".format(get_key_val(detail_dict, key_array[0]), get_key_val(detail_dict, key_array[1]), get_key_val(detail_dict, key_array[2]))
elif 'Delete API key' in history.msg: elif 'Delete API key' in history.msg:
self.detailed_msg = """ self.detailed_msg = render_template_string("""
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<tr><td>Key: </td><td>{0}</td></tr> <tr><td>Key: </td><td>{{ keyname }}</td></tr>
<tr><td>Role:</td><td>{1}</td></tr> <tr><td>Role:</td><td>{{ rolename }}</td></tr>
<tr><td>Description:</td><td>{2}</td></tr> <tr><td>Description:</td><td>{{ description }}</td></tr>
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr> <tr><td>Accessible domains with this API key:</td><td>{{ linked_domains }}</td></tr>
</table> </table>
""".format(get_key_val(detail_dict, key_array[0]), get_key_val(detail_dict, key_array[1]), """,
get_key_val(detail_dict, key_array[2]), get_key_val(detail_dict, key_array[3])) keyname=DetailedHistory.get_key_val(detail_dict, "key"),
rolename=DetailedHistory.get_key_val(detail_dict, "role"),
description=DetailedHistory.get_key_val(detail_dict, "description"),
linked_domains=DetailedHistory.get_key_val(detail_dict, "domains"))
elif 'Update type for domain' in history.msg:
self.detailed_msg = render_template_string("""
<table class="table table-bordered table-striped">
<tr><td>Domain: </td><td>{{ domain }}</td></tr>
<tr><td>Domain type:</td><td>{{ domain_type }}</td></tr>
<tr><td>Masters:</td><td>{{ masters }}</td></tr>
</table>
""",
domain=DetailedHistory.get_key_val(detail_dict, "domain"),
domain_type=DetailedHistory.get_key_val(detail_dict, "type"),
masters=DetailedHistory.get_key_val(detail_dict, "masters"))
elif 'reverse' in history.msg: elif 'reverse' in history.msg:
self.detailed_msg = """ self.detailed_msg = render_template_string("""
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<tr><td>Domain Type: </td><td>{0}</td></tr> <tr><td>Domain Type: </td><td>{{ domain_type }}</td></tr>
<tr><td>Domain Master IPs:</td><td>{1}</td></tr> <tr><td>Domain Master IPs:</td><td>{{ domain_master_ips }}</td></tr>
</table> </table>
""".format(get_key_val(detail_dict, key_array[0]), get_key_val(detail_dict, key_array[1])) """,
domain_type=DetailedHistory.get_key_val(detail_dict, "domain_type"),
domain_master_ips=DetailedHistory.get_key_val(detail_dict, "domain_master_ips"))
# check for lower key as well for old databases
@staticmethod
def get_key_val(_dict, key):
return _dict.get(key, _dict.get(key.lower(), ''))
# convert a list of History objects into DetailedHistory objects # convert a list of History objects into DetailedHistory objects
def convert_histories(histories): def convert_histories(histories):