From a5df435931def693856727c3f5ba414b172c86a6 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 12 Jul 2023 14:18:31 +0200 Subject: [PATCH] Consolidate error handling Do or do not; there is no try. With some exceptions. --- web/__init__.py | 9 +++++ web/config.py | 56 ++++++++++---------------- web/nat.py | 37 +++++++---------- web/rules.py | 58 +++++++++----------------- web/vpn.py | 105 ++++++++++++++++++++---------------------------- 5 files changed, 106 insertions(+), 159 deletions(-) diff --git a/web/__init__.py b/web/__init__.py index ac9aac3..9afeecc 100644 --- a/web/__init__.py +++ b/web/__init__.py @@ -81,6 +81,15 @@ def create_app(test_config=None): def unauth_handler(): return flask.redirect(flask.url_for('auth.login', next=flask.request.endpoint)) + @app.errorhandler(TimeoutError) + def timeout_error(e): + return flask.render_template('busy.html') + + @app.errorhandler(Exception) + def internal_server_error(e): + return flask.Response(f'something went catastrophically wrong: {e}', + status=500, mimetype='text/plain') + @app.route('/') @flask_login.login_required def home(): diff --git a/web/config.py b/web/config.py index 6e19f9b..d0be979 100644 --- a/web/config.py +++ b/web/config.py @@ -11,43 +11,29 @@ blueprint = flask.Blueprint('config', __name__, url_prefix='/config') @blueprint.route('/', methods=('GET', 'POST')) @flask_login.login_required def index(): - try: - if not flask_login.current_user.is_admin: - return flask.Response('forbidden', status=403, mimetype='text/plain') + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') - with db.locked(): - settings = db.read('settings') - - if flask.request.method == 'POST': - form = flask.request.form - for name, value in form.items(): - if name in settings: - settings[name] = value - db.write('settings', settings) - system.run(system.save_config) - return flask.redirect(flask.url_for('config.index')) - - return flask.render_template('config/index.html', settings=settings) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + with db.locked(): + settings = db.read('settings') + if flask.request.method == 'POST': + form = flask.request.form + for name, value in form.items(): + if name in settings: + settings[name] = value + db.write('settings', settings) + system.run(system.save_config) + return flask.redirect(flask.url_for('config.index')) + return flask.render_template('config/index.html', settings=settings) @blueprint.route('/edit/', methods=('GET', 'POST')) @flask_login.login_required def edit(name): - try: - if not flask_login.current_user.is_admin: - return flask.Response('forbidden', status=403, mimetype='text/plain') - if flask.request.method == 'POST': - form = flask.request.form - db.save(name, json.loads(form.get('text').replace('\r\n', '\n'))) - system.run(system.save_config) - content = json.dumps(db.load(name), indent=2) - return flask.render_template('config/edit.html', **locals()) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') + if flask.request.method == 'POST': + form = flask.request.form + db.save(name, json.loads(form.get('text').replace('\r\n', '\n'))) + system.run(system.save_config) + content = json.dumps(db.load(name), indent=2) + return flask.render_template('config/edit.html', **locals()) diff --git a/web/nat.py b/web/nat.py index daa5b20..98d05d8 100644 --- a/web/nat.py +++ b/web/nat.py @@ -9,27 +9,18 @@ blueprint = flask.Blueprint('nat', __name__, url_prefix='/nat') @blueprint.route('/', methods=('GET', 'POST')) @flask_login.login_required def index(): - try: - if not flask_login.current_user.is_admin: - return flask.Response('forbidden', status=403, mimetype='text/plain') - - with db.locked(): - nat = { office: "" for office in db.read('networks') } - nat |= db.read('nat') - - if flask.request.method == 'POST': - form = flask.request.form - for office, address in form.items(): - if office in nat: - nat[office] = address - db.write('nat', nat) - system.run(system.save_config) - return flask.redirect(flask.url_for('nat.index')) - - return flask.render_template('nat/index.html', nat=nat) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') + with db.locked(): + nat = { office: "" for office in db.read('networks') } + nat |= db.read('nat') + if flask.request.method == 'POST': + form = flask.request.form + for office, address in form.items(): + if office in nat: + nat[office] = address + db.write('nat', nat) + system.run(system.save_config) + return flask.redirect(flask.url_for('nat.index')) + return flask.render_template('nat/index.html', nat=nat) diff --git a/web/rules.py b/web/rules.py index b9b843b..f1d9422 100644 --- a/web/rules.py +++ b/web/rules.py @@ -9,29 +9,23 @@ blueprint = flask.Blueprint('rules', __name__, url_prefix='/rules') @blueprint.route('/', methods=('GET', 'POST')) @flask_login.login_required def index(): - try: - if not flask_login.current_user.is_admin: - return flask.Response('forbidden', status=403, mimetype='text/plain') + if not flask_login.current_user.is_admin: + return flask.Response('forbidden', status=403, mimetype='text/plain') - if flask.request.method == 'POST': - with db.locked(): - rules = db.read('rules') - form = flask.request.form - oldrules = {rule['name']: rule for rule in rules} - rules = [] - for index, name in sorted( - zip(form.getlist('index'), form.getlist('name')), key=lambda e: int(e[0] or 0)): - if index and name: - rules.append(oldrules.get(name, {'name': name})) - db.write('rules', rules) - system.run(system.save_config) + if flask.request.method == 'POST': + with db.locked(): + rules = db.read('rules') + form = flask.request.form + oldrules = {rule['name']: rule for rule in rules} + rules = [] + for index, name in sorted( + zip(form.getlist('index'), form.getlist('name')), key=lambda e: int(e[0] or 0)): + if index and name: + rules.append(oldrules.get(name, {'name': name})) + db.write('rules', rules) + system.run(system.save_config) - return flask.render_template('rules/index.html', rules=db.load('rules')) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + return flask.render_template('rules/index.html', rules=db.load('rules')) @blueprint.route('/edit/', methods=('GET', 'POST')) @flask_login.login_required @@ -53,11 +47,6 @@ def edit(index): return flask.render_template('rules/edit.html', index=index, rule=db.load('rules')[index]) except IndexError as e: return flask.Response(f'invalid rule: {index}', status=400, mimetype='text/plain') - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') def can_toggle(user, rule): return user.is_admin or not user.groups.isdisjoint(rule.get('managers', ())) @@ -65,15 +54,9 @@ def can_toggle(user, rule): @blueprint.route('/manage') @flask_login.login_required def manage(): - try: - rules = [rule|{'index': index} for index, rule in enumerate(db.load('rules')) - if can_toggle(flask_login.current_user, rule)] - return flask.render_template('rules/manage.html', rules=rules) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + rules = [rule|{'index': index} for index, rule in enumerate(db.load('rules')) + if can_toggle(flask_login.current_user, rule)] + return flask.render_template('rules/manage.html', rules=rules) @blueprint.route('/toggle//') @flask_login.login_required @@ -89,8 +72,3 @@ def toggle(index, enable): return flask.redirect(flask.url_for('rules.manage')) except IndexError as e: return flask.Response(f'invalid rule: {index}', status=400, mimetype='text/plain') - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') diff --git a/web/vpn.py b/web/vpn.py index 0f3cbd6..b1bc0d2 100644 --- a/web/vpn.py +++ b/web/vpn.py @@ -21,13 +21,8 @@ def index(): @blueprint.route('/list') @flask_login.login_required def list(): - try: - user = flask_login.current_user.get_id() - return flask.jsonify({k: v for k, v in db.load('wireguard').items() if v.get('user') == user}) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'failed: {e}', status=500, mimetype='text/plain') + user = flask_login.current_user.get_id() + return flask.jsonify({k: v for k, v in db.load('wireguard').items() if v.get('user') == user}) @blueprint.route('/new', methods=('POST',)) @flask_login.login_required @@ -36,52 +31,46 @@ def new(): if not re.match(wgkey_regex, pubkey): return flask.Response('invalid key', status=400, mimetype='text/plain') - try: - settings = db.load('settings') - server_pubkey = subprocess.run([f'wg pubkey'], input=settings.get('wg_key'), - text=True, capture_output=True, shell=True).stdout.strip() + settings = db.load('settings') + server_pubkey = subprocess.run([f'wg pubkey'], input=settings.get('wg_key'), + text=True, capture_output=True, shell=True).stdout.strip() - host = ipaddress.ip_interface(settings.get('wg_net', '10.0.0.1/24')) - with db.locked(): - # Find a free address for the new key. - ips = db.read('wireguard') - for ip in host.network.hosts(): - if ip != host.ip and str(ip) not in ips: - break - else: - return flask.Response('no more available IP addresses', status=500, mimetype='text/plain') - now = datetime.datetime.utcnow() - name = re.sub('[^\w ]', '', flask.request.json.get('name', '')) + host = ipaddress.ip_interface(settings.get('wg_net', '10.0.0.1/24')) + with db.locked(): + # Find a free address for the new key. + ips = db.read('wireguard') + for ip in host.network.hosts(): + if ip != host.ip and str(ip) not in ips: + break + else: + return flask.Response('no more available IP addresses', status=500, mimetype='text/plain') + now = datetime.datetime.utcnow() + name = re.sub('[^\w ]', '', flask.request.json.get('name', '')) - ips[str(ip)] = { - 'key': pubkey, - 'time': now.timestamp(), - 'user': flask_login.current_user.get_id(), - 'name': name, - } - db.write('wireguard', ips) - - # Generate a new config archive for firewall nodes. - system.run(system.save_config) - - # Template arguments. - args = { - 'server': f'{settings.get("wg_endpoint")}', - 'port': f'{settings.get("wg_port", 51820)}', - 'server_key': server_pubkey, - 'pubkey': pubkey, - 'ip': str(ip), - 'timestamp': now, + ips[str(ip)] = { + 'key': pubkey, + 'time': now.timestamp(), + 'user': flask_login.current_user.get_id(), 'name': name, - 'add_default': flask.request.json.get('add_default', False), - 'use_dns': flask.request.json.get('use_dns', True), } - return flask.render_template('vpn/wg-fri.conf', **args) - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + db.write('wireguard', ips) + + # Generate a new config archive for firewall nodes. + system.run(system.save_config) + + # Template arguments. + args = { + 'server': f'{settings.get("wg_endpoint")}', + 'port': f'{settings.get("wg_port", 51820)}', + 'server_key': server_pubkey, + 'pubkey': pubkey, + 'ip': str(ip), + 'timestamp': now, + 'name': name, + 'add_default': flask.request.json.get('add_default', False), + 'use_dns': flask.request.json.get('use_dns', True), + } + return flask.render_template('vpn/wg-fri.conf', **args) @blueprint.route('/del', methods=('POST',)) @flask_login.login_required @@ -90,17 +79,11 @@ def delete(): if not wgkey_regex.match(pubkey): return flask.Response('invalid key', status=400, mimetype='text/plain') - try: - with db.locked(): - user = flask_login.current_user.get_id() - ips = {k: v for k, v in db.read('wireguard').items() if v.get('user') != user or v.get('key') != pubkey} - db.write('wireguard', ips) + with db.locked(): + user = flask_login.current_user.get_id() + ips = {k: v for k, v in db.read('wireguard').items() if v.get('user') != user or v.get('key') != pubkey} + db.write('wireguard', ips) - system.run(system.save_config) + system.run(system.save_config) - return flask.Response(f'deleted key {pubkey}', status=200, mimetype='text/plain') - except TimeoutError: - return flask.render_template('busy.html') - except Exception as e: - return flask.Response(f'something went catastrophically wrong: {e}', - status=400, mimetype='text/plain') + return flask.Response(f'deleted key {pubkey}', status=200, mimetype='text/plain')