diff --git a/web/__init__.py b/web/__init__.py index f1286b7..d1797c7 100644 --- a/web/__init__.py +++ b/web/__init__.py @@ -50,6 +50,9 @@ def create_app(test_config=None): from . import config app.register_blueprint(config.blueprint) + from . import rules + app.register_blueprint(rules.blueprint) + from . import vpn app.register_blueprint(vpn.blueprint) diff --git a/web/auth.py b/web/auth.py index 8f3e93d..fe04671 100644 --- a/web/auth.py +++ b/web/auth.py @@ -11,7 +11,7 @@ class User(flask_login.UserMixin): self.dn = dn self.username = username self.data = data - self.groups = data.get('memberOf', []) + self.groups = set(data.get('memberOf', ())) try: self.is_admin = db.load('settings').get('ldap_admin') in self.groups except: diff --git a/web/rules.py b/web/rules.py new file mode 100644 index 0000000..abc87cf --- /dev/null +++ b/web/rules.py @@ -0,0 +1,88 @@ +import flask +import flask_login + +from . import db +from . import system + +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 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 Exception as e: + return flask.Response(f'something went catastrophically wrong: {e}', + status=400, mimetype='text/plain') + +@blueprint.route('/edit/', methods=('GET', 'POST')) +@flask_login.login_required +def edit(index): + try: + 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(): + form = flask.request.form + rules = db.read('rules') + rules[index]['name'] = form.get('name') + rules[index]['text'] = form.get('text') + rules[index]['managers'] = [m for m in form.getlist('manager') if m] + db.write('rules', rules) + system.run(system.save_config) + + 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 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', ())) + +@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 Exception as e: + return flask.Response(f'something went catastrophically wrong: {e}', + status=400, mimetype='text/plain') + +@blueprint.route('/toggle//') +@flask_login.login_required +def toggle(index, enable): + try: + with db.locked(): + rules = db.read('rules') + if not can_toggle(flask_login.current_user, rules[index]): + return flask.Response('forbidden', status=403, mimetype='text/plain') + rules[index]['enabled'] = (enable == 'true') + db.write('rules', rules) + system.run(system.save_config) + 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 Exception as e: + return flask.Response(f'something went catastrophically wrong: {e}', + status=400, mimetype='text/plain') diff --git a/web/system.py b/web/system.py index ff829dd..253c231 100644 --- a/web/system.py +++ b/web/system.py @@ -117,8 +117,12 @@ map {name} {{ # Print forwarding rules. with open(f'{output}/etc/nftables.d/forward.nft', 'w', encoding='utf-8') as f: - for forward in db.read('forwards'): - print(forward, file=f) + for index, rule in enumerate(db.read('rules')): + if rule.get('enabled') and rule.get('text'): + if 'name' in rule: + print(f'# {index}. {rule["name"]}', file=f) + print(rule['text'], file=f) + print(file=f) # Print wireguard config. with open(f'{output}/etc/wireguard/wg.conf', 'w', encoding='utf-8') as f: diff --git a/web/templates/index.html b/web/templates/index.html index 9a8cd91..790441f 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -8,7 +8,7 @@
nastavitve aplikacije FRIwall
Omrežja
definicije obsegov IP -
Luknje +
Urejanje pravil
pravila za posredovanje prometa
NAT
javni naslovi za pisarniška omrežja @@ -22,6 +22,8 @@
VPN
urejanje ključev za WireGuard VPN +
Pravila +
vklop / izklop pravil za požarni zid
{% endblock %} diff --git a/web/templates/rules/edit.html b/web/templates/rules/edit.html new file mode 100644 index 0000000..1554447 --- /dev/null +++ b/web/templates/rules/edit.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block content %} +

+Urejate pravilo #{{ index }}. + +

+

+
+ + +

+Uporabniki, ki lahko o(ne)mogočijo pravilo
+{% for manager in rule.managers %} +
+{% endfor %} + + +

+ + +

+

+ +{% endblock %} diff --git a/web/templates/rules/index.html b/web/templates/rules/index.html new file mode 100644 index 0000000..93fafa3 --- /dev/null +++ b/web/templates/rules/index.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block content %} +

+Urejate prioritete pravil za požarni zid. Pravilo odstranite tako, da izbrišete pripadajočo številko. V zadnji vrstici lahko dodate novo pravilo. + +

+ + +{% for rule in rules %} + + +
+ +{{ rule.name }} +{% endfor %} +
+ +
+

+

+ +{% endblock %} diff --git a/web/templates/rules/manage.html b/web/templates/rules/manage.html new file mode 100644 index 0000000..1b7936a --- /dev/null +++ b/web/templates/rules/manage.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block content %} +

+Tu lahko vklopite in izklopite posamezna pravila za požarni zid. + +

+{% for rule in rules %} +
+{% if rule.enabled %} + {{ rule.name }} onemogoči +{% else %} + {{ rule.name }} omogoči +{% endif %} +
+
{{ rule.text }}
+{% endfor %} +
+ +{% endblock %}