diff --git a/config.example.py b/config.example.py index 6831062..1682dd4 100644 --- a/config.example.py +++ b/config.example.py @@ -10,11 +10,6 @@ class Config: FLOWSPEC6_MAX_RULES = 9000 RTBH_MAX_RULES = 100000 - # Flask debugging - DEBUG = True - # Flask testing - TESTING = False - # Choose your authentication method and set it to True here or # the production / development config # SSO auth enabled @@ -104,6 +99,8 @@ class DevelopmentConfig(Config): SQLALCHEMY_DATABASE_URI = "Your Local Database URI" LOCAL_IP = "127.0.0.1" LOCAL_IP6 = "::ffff:127.0.0.1" + + # Debug and Devel mode enabled DEBUG = True DEVEL = True diff --git a/flowapp/__about__.py b/flowapp/__about__.py index 1ef1e64..da7547d 100755 --- a/flowapp/__about__.py +++ b/flowapp/__about__.py @@ -1,4 +1,4 @@ -__version__ = "1.1.9" +__version__ = "1.2.0" __title__ = "ExaFS" __description__ = "Tool for creation, validation, and execution of ExaBGP messages." __author__ = "CESNET / Jiri Vrany, Petr Adamec, Josef Verich, Jakub Man" diff --git a/flowapp/__init__.py b/flowapp/__init__.py index d1ea49d..5bed6f8 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import os -from flask import Flask, redirect, render_template, session, url_for +from flask import Flask, redirect, render_template, session, url_for, flash from flask_sso import SSO from flask_sqlalchemy import SQLAlchemy @@ -128,11 +127,15 @@ def select_org(org_id=None): user = db.session.query(models.User).filter_by(uuid=uuid).first() if user is None: - return render_template("errors/404.html"), 404 # Handle missing user gracefully + return render_template("errors/404.html"), 404 orgs = user.organization if org_id: - org = db.session.query(models.Organization).filter_by(id=org_id).first() + # Verify user belongs to this organization + org = user.organization.filter_by(id=org_id).first() + if not org: + flash("You don't have access to this organization", "alert-danger") + return redirect(url_for("index")) session["user_org_id"] = org.id session["user_org"] = org.name return redirect("/") diff --git a/flowapp/auth.py b/flowapp/auth.py index a26628b..f484386 100644 --- a/flowapp/auth.py +++ b/flowapp/auth.py @@ -48,11 +48,13 @@ def decorated(*args, **kwargs): def user_or_admin_required(f): """ decorator for admin/user endpoints + Allows access if the user has at least one role with ID > 1 (user or admin) + Role IDs: 1=view (read-only), 2=user (can create/edit), 3=admin """ @wraps(f) def decorated(*args, **kwargs): - if not all(i > 1 for i in session["user_role_ids"]): + if not any(i > 1 for i in session["user_role_ids"]): return redirect(url_for("index")) return f(*args, **kwargs) diff --git a/flowapp/constants.py b/flowapp/constants.py index 819f685..b37c8a0 100644 --- a/flowapp/constants.py +++ b/flowapp/constants.py @@ -35,9 +35,9 @@ MAX_PORT = 65535 MAX_PACKET = 9216 -IPV6_NEXT_HEADER = {"tcp": "tcp", "udp": "udp", "icmp": "58", "all": ""} +IPV6_NEXT_HEADER = {"tcp": "tcp", "udp": "udp", "icmp": "58", "gre": "gre", "all": ""} -IPV4_PROTOCOL = {"tcp": "tcp", "udp": "udp", "icmp": "icmp", "all": ""} +IPV4_PROTOCOL = {"tcp": "tcp", "udp": "udp", "icmp": "icmp", "gre": "gre", "all": ""} IPV4_FRAGMENT = { "dont": "dont-fragment", diff --git a/flowapp/instance_config.py b/flowapp/instance_config.py index 4136530..bded1ca 100644 --- a/flowapp/instance_config.py +++ b/flowapp/instance_config.py @@ -102,8 +102,14 @@ class InstanceConfig: "divide_before": True, }, {"name": "Add action", "url": "admin.action"}, - {"name": "RTBH Communities", "url": "admin.communities"}, + { + "name": "RTBH Communities", + "url": "admin.communities", + "divide_before": True, + }, {"name": "Add RTBH Comm.", "url": "admin.community"}, + {"name": "AS Paths", "url": "admin.as_paths"}, + {"name": "Add AS Path", "url": "admin.as_path"}, ], } DASHBOARD = { diff --git a/flowapp/templates/macros.html b/flowapp/templates/macros.html index 661be2f..a8495e8 100644 --- a/flowapp/templates/macros.html +++ b/flowapp/templates/macros.html @@ -52,9 +52,12 @@ - - - +
+ + +
{% endif %} {% if rule.comment %} + + {% if rule.community.id in allowed_communities %} +
+ + +
+ {% endif %} {% endif %} {% if rule.comment %} + {% endif %} {% if rule.comment %} + {% endfor %} diff --git a/flowapp/templates/pages/as_paths.html b/flowapp/templates/pages/as_paths.html index 369fda0..0df9eb9 100644 --- a/flowapp/templates/pages/as_paths.html +++ b/flowapp/templates/pages/as_paths.html @@ -17,9 +17,12 @@ - - - +
+ + +
{% endfor %} diff --git a/flowapp/templates/pages/communities.html b/flowapp/templates/pages/communities.html index b0005bb..45e4426 100644 --- a/flowapp/templates/pages/communities.html +++ b/flowapp/templates/pages/communities.html @@ -31,9 +31,12 @@ - - - +
+ + +
{% endfor %} diff --git a/flowapp/templates/pages/machine_api_key.html b/flowapp/templates/pages/machine_api_key.html index c2ced22..5c6a4a1 100644 --- a/flowapp/templates/pages/machine_api_key.html +++ b/flowapp/templates/pages/machine_api_key.html @@ -42,9 +42,12 @@

Machines and ApiKeys

{% endif %} - - - +
+ + +
{% endfor %} diff --git a/flowapp/templates/pages/orgs.html b/flowapp/templates/pages/orgs.html index 50ed1a1..3533bfe 100644 --- a/flowapp/templates/pages/orgs.html +++ b/flowapp/templates/pages/orgs.html @@ -42,9 +42,12 @@ - - - +
+ + +
{% endfor %} diff --git a/flowapp/templates/pages/users.html b/flowapp/templates/pages/users.html index f230abd..fd910fc 100644 --- a/flowapp/templates/pages/users.html +++ b/flowapp/templates/pages/users.html @@ -33,9 +33,12 @@ - - - +
+ + +
{% endfor %} diff --git a/flowapp/utils/app_factory.py b/flowapp/utils/app_factory.py index 6a4cae3..9e034aa 100644 --- a/flowapp/utils/app_factory.py +++ b/flowapp/utils/app_factory.py @@ -169,17 +169,13 @@ def ext_login(): @app.route("/local-login") def local_login(): - print("Local login started") if not app.config.get("LOCAL_AUTH", False): - print("Local auth not enabled") return render_template("errors/401.html") uuid = app.config.get("LOCAL_USER_UUID", False) if not uuid: - print("Local user not set") return render_template("errors/401.html") - print(f"Local login with {uuid}") return _handle_login(uuid, app) return app @@ -211,9 +207,7 @@ def _register_user_to_session(uuid, db): """Register user information to session.""" from flowapp.models import User - print(f"Registering user {uuid} to session") user = db.session.query(User).filter_by(uuid=uuid).first() - print(f"Got user {user} from DB") session["user_uuid"] = user.uuid session["user_email"] = user.uuid session["user_name"] = user.name diff --git a/flowapp/views/admin.py b/flowapp/views/admin.py index add1b67..cf5241d 100644 --- a/flowapp/views/admin.py +++ b/flowapp/views/admin.py @@ -104,7 +104,7 @@ def add_machine_key(): return render_template("forms/machine_api_key.html", form=form, generated_key=generated) -@admin.route("/delete_machine_key/", methods=["GET"]) +@admin.route("/delete_machine_key/", methods=["POST"]) @auth_required @admin_required def delete_machine_key(key_id): @@ -113,6 +113,9 @@ def delete_machine_key(key_id): :param key_id: integer """ model = db.session.get(MachineApiKey, key_id) + if not model: + flash("Key not found", "alert-danger") + return redirect(url_for("admin.machine_keys")) # delete from db db.session.delete(model) db.session.commit() @@ -181,7 +184,7 @@ def edit_user(user_id): ) -@admin.route("/user/delete/", methods=["GET"]) +@admin.route("/user/delete/", methods=["POST"]) @auth_required @admin_required def delete_user(user_id): @@ -387,11 +390,14 @@ def edit_organization(org_id): ) -@admin.route("/organization/delete/", methods=["GET"]) +@admin.route("/organization/delete/", methods=["POST"]) @auth_required @admin_required def delete_organization(org_id): org = db.session.get(Organization, org_id) + if not org: + flash("Organization not found", "alert-danger") + return redirect(url_for("admin.organizations")) aname = org.name db.session.delete(org) message = "Organization {} deleted".format(aname) @@ -465,11 +471,14 @@ def edit_as_path(path_id): ) -@admin.route("/as-path/delete/", methods=["GET"]) +@admin.route("/as-path/delete/", methods=["POST"]) @auth_required @admin_required def delete_as_path(path_id): pth = db.session.get(ASPath, path_id) + if not pth: + flash("AS path not found", "alert-danger") + return redirect(url_for("admin.as_paths")) db.session.delete(pth) message = f"AS path {pth.prefix} : {pth.as_path} deleted" alert_type = "alert-success" @@ -544,11 +553,14 @@ def edit_action(action_id): ) -@admin.route("/action/delete/", methods=["GET"]) +@admin.route("/action/delete/", methods=["POST"]) @auth_required @admin_required def delete_action(action_id): action = db.session.get(Action, action_id) + if not action: + flash("Action not found", "alert-danger") + return redirect(url_for("admin.actions")) aname = action.name db.session.delete(action) @@ -628,11 +640,14 @@ def edit_community(community_id): ) -@admin.route("/community/delete/", methods=["GET"]) +@admin.route("/community/delete/", methods=["POST"]) @auth_required @admin_required def delete_community(community_id): community = db.session.get(Community, community_id) + if not community: + flash("Community not found", "alert-danger") + return redirect(url_for("admin.communities")) aname = community.name db.session.delete(community) message = "Community {} deleted".format(aname) diff --git a/flowapp/views/rules.py b/flowapp/views/rules.py index b871eec..d746271 100644 --- a/flowapp/views/rules.py +++ b/flowapp/views/rules.py @@ -148,7 +148,7 @@ def reactivate_rule(rule_type, rule_id): ) -@rules.route("/delete//", methods=["GET"]) +@rules.route("/delete//", methods=["POST"]) @auth_required @user_or_admin_required def delete_rule(rule_type, rule_id): @@ -205,7 +205,7 @@ def delete_rule(rule_type, rule_id): ) -@rules.route("/delete_and_whitelist//", methods=["GET"]) +@rules.route("/delete_and_whitelist//", methods=["POST"]) @auth_required @user_or_admin_required def delete_and_whitelist(rule_type, rule_id): @@ -301,7 +301,7 @@ def group_delete(): to_delete = request.form.getlist("delete-id") # Check if user has permission to delete these rules - if set(to_delete).issubset(set(allowed_rules_str)) or is_admin(session["user_roles"]): + if set(to_delete).issubset(set(allowed_rules_str)) or is_admin(session["user_role_ids"]): for rule_id in to_delete: # withdraw route model = db.session.get(model_name, rule_id) @@ -357,7 +357,7 @@ def group_update(): allowed_rules_str = [str(x) for x in allowed_rule_ids] # redirect bad request - if not set(to_update).issubset(set(allowed_rules_str)) and not is_admin(session["user_roles"]): + if not set(to_update).issubset(set(allowed_rules_str)) and not is_admin(session["user_role_ids"]): flash("You can't edit these rules!", "alert-danger") return redirect( url_for( diff --git a/flowapp/views/whitelist.py b/flowapp/views/whitelist.py index 743174c..400aa94 100644 --- a/flowapp/views/whitelist.py +++ b/flowapp/views/whitelist.py @@ -100,7 +100,7 @@ def reactivate(wl_id): ) -@whitelist.route("/delete/", methods=["GET"]) +@whitelist.route("/delete/", methods=["POST"]) @auth_required @user_or_admin_required def delete(wl_id): diff --git a/flowapp/tests/__init__.py b/tests/__init__.py similarity index 100% rename from flowapp/tests/__init__.py rename to tests/__init__.py diff --git a/flowapp/tests/conftest.py b/tests/conftest.py similarity index 100% rename from flowapp/tests/conftest.py rename to tests/conftest.py diff --git a/flowapp/tests/rule_service_integration.py b/tests/rule_service_integration.py similarity index 100% rename from flowapp/tests/rule_service_integration.py rename to tests/rule_service_integration.py diff --git a/flowapp/tests/test_api_auth.py b/tests/test_api_auth.py similarity index 100% rename from flowapp/tests/test_api_auth.py rename to tests/test_api_auth.py diff --git a/flowapp/tests/test_api_deprecated.py b/tests/test_api_deprecated.py similarity index 100% rename from flowapp/tests/test_api_deprecated.py rename to tests/test_api_deprecated.py diff --git a/flowapp/tests/test_api_v3.py b/tests/test_api_v3.py similarity index 100% rename from flowapp/tests/test_api_v3.py rename to tests/test_api_v3.py diff --git a/flowapp/tests/test_api_whitelist_integration.py b/tests/test_api_whitelist_integration.py similarity index 100% rename from flowapp/tests/test_api_whitelist_integration.py rename to tests/test_api_whitelist_integration.py diff --git a/flowapp/tests/test_flowapp.py b/tests/test_flowapp.py similarity index 100% rename from flowapp/tests/test_flowapp.py rename to tests/test_flowapp.py diff --git a/flowapp/tests/test_flowspec.py b/tests/test_flowspec.py similarity index 100% rename from flowapp/tests/test_flowspec.py rename to tests/test_flowspec.py diff --git a/flowapp/tests/test_forms.py b/tests/test_forms.py similarity index 100% rename from flowapp/tests/test_forms.py rename to tests/test_forms.py diff --git a/flowapp/tests/test_forms_cl.py b/tests/test_forms_cl.py similarity index 100% rename from flowapp/tests/test_forms_cl.py rename to tests/test_forms_cl.py diff --git a/flowapp/tests/test_login.py b/tests/test_login.py similarity index 100% rename from flowapp/tests/test_login.py rename to tests/test_login.py diff --git a/flowapp/tests/test_models.py b/tests/test_models.py similarity index 100% rename from flowapp/tests/test_models.py rename to tests/test_models.py diff --git a/flowapp/tests/test_rule_service.py b/tests/test_rule_service.py similarity index 100% rename from flowapp/tests/test_rule_service.py rename to tests/test_rule_service.py diff --git a/flowapp/tests/test_rule_service_reactivate_delete.py b/tests/test_rule_service_reactivate_delete.py similarity index 100% rename from flowapp/tests/test_rule_service_reactivate_delete.py rename to tests/test_rule_service_reactivate_delete.py diff --git a/flowapp/tests/test_utils.py b/tests/test_utils.py similarity index 100% rename from flowapp/tests/test_utils.py rename to tests/test_utils.py diff --git a/flowapp/tests/test_validators.py b/tests/test_validators.py similarity index 100% rename from flowapp/tests/test_validators.py rename to tests/test_validators.py diff --git a/flowapp/tests/test_whitelist_common.py b/tests/test_whitelist_common.py similarity index 100% rename from flowapp/tests/test_whitelist_common.py rename to tests/test_whitelist_common.py diff --git a/flowapp/tests/test_whitelist_service.py b/tests/test_whitelist_service.py similarity index 100% rename from flowapp/tests/test_whitelist_service.py rename to tests/test_whitelist_service.py diff --git a/flowapp/tests/test_zzz_api_rtbh_expired_bug.py b/tests/test_zzz_api_rtbh_expired_bug.py similarity index 100% rename from flowapp/tests/test_zzz_api_rtbh_expired_bug.py rename to tests/test_zzz_api_rtbh_expired_bug.py