From 54d534385b0703a0844a478e1427fb692b8dc2d2 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 10:35:06 +0200 Subject: [PATCH 01/10] add debug print for rule ids in session --- flowapp/views/rules.py | 52 +++++++++++------------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/flowapp/views/rules.py b/flowapp/views/rules.py index b2e2956..d5840eb 100644 --- a/flowapp/views/rules.py +++ b/flowapp/views/rules.py @@ -73,9 +73,7 @@ def reactivate_rule(rule_type, rule_id): form.net_ranges = get_user_nets(session["user_id"]) if rule_type > 2: - form.action.choices = [ - (g.id, g.name) for g in db.session.query(Action).order_by("name") - ] + form.action.choices = [(g.id, g.name) for g in db.session.query(Action).order_by("name")] form.action.data = model.action_id if rule_type == 1: @@ -188,6 +186,7 @@ def delete_rule(rule_type, rule_id): flash("Rule deleted", "alert-success") else: + print("in session": [str(x) for x in session[constants.RULES_KEY]]) flash("You can not delete this rule", "alert-warning") return redirect( @@ -256,9 +255,7 @@ def group_delete(): "{} / {}".format(session["user_email"], session["user_orgs"]), ) - db.session.query(model_name).filter(model_name.id.in_(to_delete)).delete( - synchronize_session=False - ) + db.session.query(model_name).filter(model_name.id.in_(to_delete)).delete(synchronize_session=False) db.session.commit() flash("Rules {} deleted".format(to_delete), "alert-success") @@ -309,9 +306,7 @@ def group_update(): form = form_name(request.form) form.net_ranges = get_user_nets(session["user_id"]) if rule_type_int > 2: - form.action.choices = [ - (g.id, g.name) for g in db.session.query(Action).order_by("name") - ] + form.action.choices = [(g.id, g.name) for g in db.session.query(Action).order_by("name")] if rule_type_int == 1: form.community.choices = get_user_communities(session["user_role_ids"]) @@ -429,9 +424,7 @@ def ipv4_rule(): if model: model.expires = round_to_ten_minutes(form.expires.data) - flash_message = ( - "Existing IPv4 Rule found. Expiration time was updated to new value." - ) + flash_message = "Existing IPv4 Rule found. Expiration time was updated to new value." else: model = Flowspec4( source=form.source.data, @@ -473,17 +466,12 @@ def ipv4_rule(): else: for field, errors in form.errors.items(): for error in errors: - print( - "Error in the %s field - %s" - % (getattr(form, field).label.text, error) - ) + print("Error in the %s field - %s" % (getattr(form, field).label.text, error)) default_expires = datetime.now() + timedelta(days=7) form.expires.data = default_expires - return render_template( - "forms/ipv4_rule.html", form=form, action_url=url_for("rules.ipv4_rule") - ) + return render_template("forms/ipv4_rule.html", form=form, action_url=url_for("rules.ipv4_rule")) @rules.route("/add_ipv6_rule", methods=["GET", "POST"]) @@ -507,9 +495,7 @@ def ipv6_rule(): if model: model.expires = round_to_ten_minutes(form.expires.data) - flash_message = ( - "Existing IPv4 Rule found. Expiration time was updated to new value." - ) + flash_message = "Existing IPv4 Rule found. Expiration time was updated to new value." else: model = Flowspec6( source=form.source.data, @@ -550,17 +536,12 @@ def ipv6_rule(): else: for field, errors in form.errors.items(): for error in errors: - print( - "Error in the %s field - %s" - % (getattr(form, field).label.text, error) - ) + print("Error in the %s field - %s" % (getattr(form, field).label.text, error)) default_expires = datetime.now() + timedelta(days=7) form.expires.data = default_expires - return render_template( - "forms/ipv6_rule.html", form=form, action_url=url_for("rules.ipv6_rule") - ) + return render_template("forms/ipv6_rule.html", form=form, action_url=url_for("rules.ipv6_rule")) @rules.route("/add_rtbh_rule", methods=["GET", "POST"]) @@ -586,9 +567,7 @@ def rtbh_rule(): if model: model.expires = round_to_ten_minutes(form.expires.data) - flash_message = ( - "Existing RTBH Rule found. Expiration time was updated to new value." - ) + flash_message = "Existing RTBH Rule found. Expiration time was updated to new value." else: model = RTBH( ipv4=form.ipv4.data, @@ -622,17 +601,12 @@ def rtbh_rule(): else: for field, errors in form.errors.items(): for error in errors: - print( - "Error in the %s field - %s" - % (getattr(form, field).label.text, error) - ) + print("Error in the %s field - %s" % (getattr(form, field).label.text, error)) default_expires = datetime.now() + timedelta(days=7) form.expires.data = default_expires - return render_template( - "forms/rtbh_rule.html", form=form, action_url=url_for("rules.rtbh_rule") - ) + return render_template("forms/rtbh_rule.html", form=form, action_url=url_for("rules.rtbh_rule")) @rules.route("/export") From 3868408ef1778328283cc4807abb36c06fc4362a Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 12:16:00 +0200 Subject: [PATCH 02/10] session ready to test on server --- flowapp/__init__.py | 31 ++++++++++++------------------- flowapp/views/rules.py | 1 - requirements.txt | 4 ++-- run.example.py | 37 +++++++++++++++++-------------------- 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index f92aeea..61b196f 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -6,6 +6,7 @@ from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect from flask_migrate import Migrate +from flask_session import Session from .__about__ import __version__ from .instance_config import InstanceConfig @@ -14,16 +15,12 @@ db = SQLAlchemy() migrate = Migrate() csrf = CSRFProtect() +ext = SSO() +sess = Session() -def create_app(): +def create_app(config_object=None): app = Flask(__name__) - # Map SSO attributes from ADFS to session keys under session['user'] - #: Default attribute map - SSO_ATTRIBUTE_MAP = { - "eppn": (True, "eppn"), - "cn": (False, "cn"), - } # db.init_app(app) migrate.init_app(app, db) @@ -31,13 +28,13 @@ def create_app(): # Load the default configuration for dashboard and main menu app.config.from_object(InstanceConfig) + if config_object: + app.config.from_object(config_object) app.config.setdefault("VERSION", __version__) - app.config.setdefault("SSO_ATTRIBUTE_MAP", SSO_ATTRIBUTE_MAP) - app.config.setdefault("SSO_LOGIN_URL", "/login") - # This attaches the *flask_sso* login handler to the SSO_LOGIN_URL, - ext = SSO(app=app) + # Init SSO + ext.init_app(app) from flowapp import models, constants, validators from .views.admin import admin @@ -85,7 +82,7 @@ def logout(): @app.route("/ext-login") def ext_login(): - header_name = app.config.get("AUTH_HEADER_NAME", 'X-Authenticated-User') + header_name = app.config.get("AUTH_HEADER_NAME", "X-Authenticated-User") if header_name not in request.headers: return render_template("errors/401.html") @@ -148,9 +145,7 @@ def internal_error(exception): def utility_processor(): def editable_rule(rule): if rule: - validators.editable_range( - rule, models.get_user_nets(session["user_id"]) - ) + validators.editable_range(rule, models.get_user_nets(session["user_id"])) return True return False @@ -176,7 +171,7 @@ def inject_dashboard(): def format_datetime(value): if value is None: return app.config.get("MISSING_DATETIME_MESSAGE", "Never") - + format = "y/MM/dd HH:mm" return babel.dates.format_datetime(value, format) @@ -187,9 +182,7 @@ def _register_user_to_session(uuid: str): session["user_name"] = user.name session["user_id"] = user.id session["user_roles"] = [role.name for role in user.role.all()] - session["user_orgs"] = ", ".join( - org.name for org in user.organization.all() - ) + session["user_orgs"] = ", ".join(org.name for org in user.organization.all()) session["user_role_ids"] = [role.id for role in user.role.all()] session["user_org_ids"] = [org.id for org in user.organization.all()] roles = [i > 1 for i in session["user_role_ids"]] diff --git a/flowapp/views/rules.py b/flowapp/views/rules.py index d5840eb..714f622 100644 --- a/flowapp/views/rules.py +++ b/flowapp/views/rules.py @@ -186,7 +186,6 @@ def delete_rule(rule_type, rule_id): flash("Rule deleted", "alert-success") else: - print("in session": [str(x) for x in session[constants.RULES_KEY]]) flash("You can not delete this rule", "alert-warning") return redirect( diff --git a/requirements.txt b/requirements.txt index c929dbd..48c625d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ -Flask>=2.0.2 +Flask<3 Flask-SQLAlchemy>=2.2 Flask-SSO>=0.4.0 Flask-WTF>=1.0.0 Flask-Migrate>=3.0.0 Flask-Script>=2.0.0 +Flask-Session PyJWT>=2.4.0 PyMySQL>=1.0.0 pytest>=7.0.0 requests>=2.20.0 babel>=2.7.0 -mysqlclient>=2.0.0 email_validator>=1.1 pika>=1.3.0 diff --git a/run.example.py b/run.example.py index 2911b5b..21f5667 100644 --- a/run.example.py +++ b/run.example.py @@ -2,40 +2,37 @@ This is an example of how to run the application. First copy the file as run.py (or whatever you want) Then edit the file to match your needs. + In general you should not need to edit this example file. Only if you want to configure the application main menu and -dashboard. Or in case that you want to add extensions etc. +dashboard. + +Or in case that you want to add extensions etc. """ from os import environ -from flowapp import create_app, db +from flowapp import create_app, db, sess import config -# Call app factory -app = create_app() - # Configurations -env = environ.get('EXAFS_ENV', 'Production') +env = environ.get("EXAFS_ENV", "Production") -if env == 'devel': - app.config.from_object(config.DevelopmentConfig) - app.config.update( - DEVEL=True - ) +# Call app factory +if env == "devel": + app = create_app(config.DevelopmentConfig) else: - app.config.from_object(config.ProductionConfig) - app.config.update( - SESSION_COOKIE_SECURE=True, - SESSION_COOKIE_HTTPONLY=True, - SESSION_COOKIE_SAMESITE='Lax', - DEVEL=False - ) + app = create_app(config.ProductionConfig) # init database object db.init_app(app) +# session on server +app.config.update(SESSION_TYPE="sqlalchemy") +app.config.update(SESSION_SQLALCHEMY=db) +sess.init_app(app) + # run app -if __name__ == '__main__': - app.run(host='::', port=8080, debug=True) +if __name__ == "__main__": + app.run(host="::", port=8080, debug=True) From a354eb2fe115b2ca0eae286499344009c483be43 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 13:04:38 +0200 Subject: [PATCH 03/10] debug session init --- flowapp/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index 61b196f..7384f1c 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -176,6 +176,7 @@ def format_datetime(value): return babel.dates.format_datetime(value, format) def _register_user_to_session(uuid: str): + print(f"registering user {uuid} to session") user = db.session.query(models.User).filter_by(uuid=uuid).first() session["user_uuid"] = user.uuid session["user_email"] = user.uuid @@ -188,4 +189,6 @@ def _register_user_to_session(uuid: str): roles = [i > 1 for i in session["user_role_ids"]] session["can_edit"] = True if all(roles) and roles else [] + print("session", session) + return app From 7ec5a15d2f457ad11999daee6aac636efc143ca7 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 13:16:20 +0200 Subject: [PATCH 04/10] update run example.py --- run.example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.example.py b/run.example.py index 21f5667..b7372b1 100644 --- a/run.example.py +++ b/run.example.py @@ -35,4 +35,4 @@ # run app if __name__ == "__main__": - app.run(host="::", port=8080, debug=True) + app.run(host="127.0.0.1", port=8000, debug=True) From 3002dfad1096cff5c42326748c67b3951f7fb9fb Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 13:25:54 +0200 Subject: [PATCH 05/10] lets try filesystem cachelib --- flowapp/__init__.py | 7 +++++++ run.example.py | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index 7384f1c..ff28978 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -7,6 +7,7 @@ from flask_wtf.csrf import CSRFProtect from flask_migrate import Migrate from flask_session import Session +from cachelib.file import FileSystemCache from .__about__ import __version__ from .instance_config import InstanceConfig @@ -36,6 +37,12 @@ def create_app(config_object=None): # Init SSO ext.init_app(app) + # Init session + app.config.update(SESSION_TYPE="cachelib") + app.config.update(SESSION_SERIALIZATION_FORMAT="json") + app.config.update(SESSION_CACHELIB=FileSystemCache(threshold=500, cache_dir="/sessions")) + sess.init_app(app) + from flowapp import models, constants, validators from .views.admin import admin from .views.rules import rules diff --git a/run.example.py b/run.example.py index b7372b1..9e268c3 100644 --- a/run.example.py +++ b/run.example.py @@ -12,7 +12,7 @@ from os import environ -from flowapp import create_app, db, sess +from flowapp import create_app, db import config @@ -28,10 +28,6 @@ # init database object db.init_app(app) -# session on server -app.config.update(SESSION_TYPE="sqlalchemy") -app.config.update(SESSION_SQLALCHEMY=db) -sess.init_app(app) # run app if __name__ == "__main__": From 372ca44f07ca404e29e32e45405f56ae574aa1c9 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 13:38:59 +0200 Subject: [PATCH 06/10] typo --- flowapp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index ff28978..a44fe1c 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -183,7 +183,7 @@ def format_datetime(value): return babel.dates.format_datetime(value, format) def _register_user_to_session(uuid: str): - print(f"registering user {uuid} to session") + print(f"Registering user {uuid} to session") user = db.session.query(models.User).filter_by(uuid=uuid).first() session["user_uuid"] = user.uuid session["user_email"] = user.uuid From 78ca1c3a4c28f83943f9b53b78c1e6790cf413fc Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 13:51:42 +0200 Subject: [PATCH 07/10] sso map! --- flowapp/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index a44fe1c..a44f40f 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -23,6 +23,12 @@ def create_app(config_object=None): app = Flask(__name__) + SSO_ATTRIBUTE_MAP = { + "eppn": (True, "eppn"), + "cn": (False, "cn"), + } + app.config.setdefault("SSO_ATTRIBUTE_MAP", SSO_ATTRIBUTE_MAP) + app.config.setdefault("SSO_LOGIN_URL", "/login") # db.init_app(app) migrate.init_app(app, db) csrf.init_app(app) From 59edcb304f84f8a4db112f29a68a02ab31dcb0a8 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Fri, 20 Sep 2024 14:06:08 +0200 Subject: [PATCH 08/10] back to SQLAlchemy session --- flowapp/__init__.py | 13 +++---------- run.example.py | 7 ++++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/flowapp/__init__.py b/flowapp/__init__.py index a44f40f..fb96b09 100644 --- a/flowapp/__init__.py +++ b/flowapp/__init__.py @@ -7,7 +7,6 @@ from flask_wtf.csrf import CSRFProtect from flask_migrate import Migrate from flask_session import Session -from cachelib.file import FileSystemCache from .__about__ import __version__ from .instance_config import InstanceConfig @@ -23,13 +22,15 @@ def create_app(config_object=None): app = Flask(__name__) + # SSO configuration SSO_ATTRIBUTE_MAP = { "eppn": (True, "eppn"), "cn": (False, "cn"), } app.config.setdefault("SSO_ATTRIBUTE_MAP", SSO_ATTRIBUTE_MAP) app.config.setdefault("SSO_LOGIN_URL", "/login") - # db.init_app(app) + + # extension init migrate.init_app(app, db) csrf.init_app(app) @@ -43,12 +44,6 @@ def create_app(config_object=None): # Init SSO ext.init_app(app) - # Init session - app.config.update(SESSION_TYPE="cachelib") - app.config.update(SESSION_SERIALIZATION_FORMAT="json") - app.config.update(SESSION_CACHELIB=FileSystemCache(threshold=500, cache_dir="/sessions")) - sess.init_app(app) - from flowapp import models, constants, validators from .views.admin import admin from .views.rules import rules @@ -202,6 +197,4 @@ def _register_user_to_session(uuid: str): roles = [i > 1 for i in session["user_role_ids"]] session["can_edit"] = True if all(roles) and roles else [] - print("session", session) - return app diff --git a/run.example.py b/run.example.py index 9e268c3..782ff16 100644 --- a/run.example.py +++ b/run.example.py @@ -12,7 +12,7 @@ from os import environ -from flowapp import create_app, db +from flowapp import create_app, db, sess import config @@ -28,6 +28,11 @@ # init database object db.init_app(app) +# init session +app.config.update(SESSION_TYPE="sqlalchemy") +app.config.update(SESSION_SQLALCHEMY=db) +sess.init_app(app) + # run app if __name__ == "__main__": From 831f7085aec01d2ee36f85a94252a95faeec7f47 Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Mon, 23 Sep 2024 09:21:45 +0200 Subject: [PATCH 09/10] version 0.8.1 with server side session --- README.md | 4 +++- flowapp/__about__.py | 2 +- run.example.py | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba8b1f7..269047c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,9 @@ Last part of the system is Guarda service. This systemctl service is running in * [Local database instalation notes](./docs/DB_LOCAL.md) ## Change Log -- 0.8.0 - API keys update. **Run migration scripts to update your DB**. Keys can now have expiration date and readonly flag. Admin can create special keys for certain machinnes. +- 0.8.1 application is using Flask-Session stored in DB using SQL Alchemy driver. This can be configured for other +drivers, however server side session is required for the application proper function. +- 0.8.0 - API keys update. **Run migration scripts to update your DB**. Keys can now have expiration date and readonly flag. Admin can create special keys for certain machines. - 0.7.3 - New possibility of external auth proxy. - 0.7.2 - Dashboard and Main menu are now customizable in config. App is ready to be packaged using setup.py. - 0.7.0 - ExaAPI now have two options - HTTP or RabbitMQ. ExaAPI process has been renamed, update of ExaBGP process value is needed for this version. diff --git a/flowapp/__about__.py b/flowapp/__about__.py index 59b7f43..6ed043c 100755 --- a/flowapp/__about__.py +++ b/flowapp/__about__.py @@ -1 +1 @@ -__version__ = "0.8.0" +__version__ = "0.8.1" diff --git a/run.example.py b/run.example.py index 782ff16..e19c804 100644 --- a/run.example.py +++ b/run.example.py @@ -3,6 +3,10 @@ First copy the file as run.py (or whatever you want) Then edit the file to match your needs. +From version 0.8.1 the application is using Flask-Session +stored in DB using SQL Alchemy driver. This can be configured for other +drivers, however server side session is required for the application. + In general you should not need to edit this example file. Only if you want to configure the application main menu and dashboard. From 1da865d32f24581e50456f701b7f445159855c5a Mon Sep 17 00:00:00 2001 From: Jiri Vrany Date: Mon, 23 Sep 2024 09:45:50 +0200 Subject: [PATCH 10/10] version 0.8.1 with server side session --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 48c625d..876a5c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ requests>=2.20.0 babel>=2.7.0 email_validator>=1.1 pika>=1.3.0 +mysqlclient>=2.0.0