From a99e0e444ff35f57e89df40030c5b035832497ca Mon Sep 17 00:00:00 2001
From: Maximilian Moser <maximilian.moser@tuwien.ac.at>
Date: Wed, 5 Feb 2025 00:15:59 +0100
Subject: [PATCH 1/3] Move Flask config override from ext to startup

* apparently it was a leftover `before_first_request` handler
* reworked it as `finalize_app` entrypoint
* also, added a check if the app config is already a TUW config to avoid
  unnecessary nesting
---
 invenio_config_tuw/ext.py     | 21 ---------------------
 invenio_config_tuw/startup.py | 33 +++++++++++++++++++++++++++++++++
 pyproject.toml                |  2 ++
 3 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/invenio_config_tuw/ext.py b/invenio_config_tuw/ext.py
index d21bbc9..341e041 100644
--- a/invenio_config_tuw/ext.py
+++ b/invenio_config_tuw/ext.py
@@ -10,7 +10,6 @@
 from typing import List
 
 from flask import current_app
-from flask.config import Config
 from flask_minify import Minify
 from flask_security.signals import user_registered
 from invenio_base.utils import obj_or_import_string
@@ -19,26 +18,6 @@ from . import config
 from .auth.utils import auto_trust_user
 
 
-class TUWConfig(Config):
-    """Override for the Flask config that evaluates the SITE_{API,UI}_URL proxies."""
-
-    @classmethod
-    def from_flask_config(cls, config):
-        """Create a clone of the given config."""
-        return cls(config.root_path, config)
-
-    def __getitem__(self, key):
-        """Return config[key], or str(config[key]) if key is 'SITE_{UI,API}_URL'."""
-        value = super().__getitem__(key)
-
-        # give special treatment to the URL configuration items:
-        # enforce their evaluation as strings
-        if key in ("SITE_UI_URL", "SITE_API_URL"):
-            value = str(value)
-
-        return value
-
-
 @user_registered.connect
 def auto_trust_new_user(sender, user, **kwargs):
     """Execute `auto_trust_user()` on newly created users.
diff --git a/invenio_config_tuw/startup.py b/invenio_config_tuw/startup.py
index 4fa84a4..73a1b71 100644
--- a/invenio_config_tuw/startup.py
+++ b/invenio_config_tuw/startup.py
@@ -17,6 +17,7 @@ from logging import ERROR
 from logging.handlers import SMTPHandler
 
 import importlib_metadata
+from flask.config import Config
 from invenio_rdm_records.services.search_params import MyDraftsParam
 from invenio_requests.proxies import current_request_type_registry
 
@@ -24,6 +25,29 @@ from .curations import TUWCurationRequest
 from .logs import DetailedFormatter
 
 
+class TUWConfig(Config):
+    """Override for the Flask config that evaluates the SITE_{API,UI}_URL proxies."""
+
+    @classmethod
+    def from_flask_config(cls, config):
+        """Create a clone of the given config."""
+        if isinstance(config, TUWConfig):
+            return config
+
+        return cls(config.root_path, config)
+
+    def __getitem__(self, key):
+        """Return config[key], or str(config[key]) if key is 'SITE_{UI,API}_URL'."""
+        value = super().__getitem__(key)
+
+        # give special treatment to the URL configuration items:
+        # enforce their evaluation as strings
+        if key in ("SITE_UI_URL", "SITE_API_URL"):
+            value = str(value)
+
+        return value
+
+
 def register_smtp_error_handler(app):
     """Register email error handler to the application."""
     handler_name = "invenio-config-tuw-smtp-error-handler"
@@ -109,6 +133,15 @@ def customize_curation_request_type(app):
     current_request_type_registry.register_type(TUWCurationRequest(), force=True)
 
 
+def override_flask_config(app):
+    """Replace the app's config with our own override.
+
+    This evaluates the ``LocalProxy`` objects used for ``SITE_{API,UI}_URL`` by
+    casting them into strings (which is their expected type).
+    """
+    app.config = TUWConfig.from_flask_config(app.config)
+
+
 def patch_flask_create_url_adapter(app):
     """Patch Flask's {host,subdomain}_matching with 3.1 behavior.
 
diff --git a/pyproject.toml b/pyproject.toml
index e3e12da..dc5cb12 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -81,12 +81,14 @@ invenio_config_tuw_mail_handler = "invenio_config_tuw.startup:register_smtp_erro
 invenio_config_tuw_search_drafts = "invenio_config_tuw.startup:override_search_drafts_options"
 invenio_config_tuw_curation_settings = "invenio_config_tuw.startup:register_menu_entries"
 invenio_config_tuw_curation_request = "invenio_config_tuw.startup:customize_curation_request_type"
+invenio_config_tuw_flask_config = "invenio_config_tuw.startup:override_flask_config"
 invenio_config_tuw_patch_flask = "invenio_config_tuw.startup:patch_flask_create_url_adapter"
 
 [project.entry-points."invenio_base.api_finalize_app"]
 invenio_config_tuw_mail_handler = "invenio_config_tuw.startup:register_smtp_error_handler"
 invenio_config_tuw_search_drafts = "invenio_config_tuw.startup:override_search_drafts_options"
 invenio_config_tuw_curation_request = "invenio_config_tuw.startup:customize_curation_request_type"
+invenio_config_tuw_flask_config = "invenio_config_tuw.startup:override_flask_config"
 invenio_config_tuw_patch_flask = "invenio_config_tuw.startup:patch_flask_create_url_adapter"
 
 [project.entry-points."invenio_celery.tasks"]
-- 
GitLab


From e2cd5b79345197fc19a65067be256a8563d490e8 Mon Sep 17 00:00:00 2001
From: Maximilian Moser <maximilian.moser@tuwien.ac.at>
Date: Wed, 5 Feb 2025 00:18:12 +0100
Subject: [PATCH 2/3] Remove `SERVER_NAME` config for the duration of requests

* because otherwise, `url_for()` will prefer that value over the HTTP
  Host header value
* this causes issues for us with our multi-domain setup, in conjunction
  with the OIDC redirects - given that `SERVER_NAME` can only be a
  scalar value, trying to login on a secondary domain will redirect to
  the primary one and typically fail (because sessions are different, etc.)
* also make sure that the `APP_ALLOWED_HOSTS` config contains the
  `SERVER_NAME` if set
---
 invenio_config_tuw/ext.py | 38 ++++++++++++++++++++++++++++++++------
 1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/invenio_config_tuw/ext.py b/invenio_config_tuw/ext.py
index 341e041..1f8d636 100644
--- a/invenio_config_tuw/ext.py
+++ b/invenio_config_tuw/ext.py
@@ -42,14 +42,40 @@ class InvenioConfigTUW(object):
         """Flask application initialization."""
         self.init_config(app)
         self.init_minify(app)
+        self.handle_server_name(app)
         app.extensions["invenio-config-tuw"] = self
 
-        @app.before_first_request
-        def hack_app_config():
-            # replace the app's config with our own override that evaluates the
-            # LocalProxy objects used for SITE_{API,UI}_URL by casting them into strings
-            # (which is their expected type)
-            app.config = TUWConfig.from_flask_config(app.config)
+    def handle_server_name(self, app):
+        """Pop the `SERVER_NAME` configuration item between requests.
+
+        This can be useful in multi-domain setups where for some reason, absolute
+        URLs with the currently requested hostname need to be generated inside an
+        active request context (e.g. OIDC redirect URIs).
+        It seems like if `SERVER_NAME` is set, it will take precedence over
+        HTTP `Host` when calling `url_for()`.
+        """
+        self.server_name = app.config.get("SERVER_NAME", None)
+
+        # since allowing the client to set arbitrary vlaues of the HTTP Host header
+        # field can lead to arbitrary redirects, it's important to keep track of
+        # allowed values
+        allowed_hosts = app.config.get("APP_ALLOWED_HOSTS", [])
+        if self.server_name and self.server_name not in allowed_hosts:
+            allowed_hosts.append(self.server_name)
+
+        app.config["APP_ALLOWED_HOSTS"] = app.config["ALLOWED_HOSTS"] = allowed_hosts
+
+        @app.before_request
+        def pop_server_name():
+            """Unset `SERVER_NAME` to prefer the HOST HTTP header value."""
+            self.server_name = app.config.get("SERVER_NAME", None)
+            app.config["SERVER_NAME"] = None
+
+        @app.after_request
+        def restore_server_name(response):
+            """Restore `SERVER_NAME` enable creating URLs outside of requests."""
+            app.config.setdefault("SERVER_NAME", self.server_name)
+            return response
 
     def init_config(self, app):
         """Initialize configuration."""
-- 
GitLab


From c5ac5a72221b372336b0c525d81433641a6210bc Mon Sep 17 00:00:00 2001
From: Maximilian Moser <maximilian.moser@tuwien.ac.at>
Date: Wed, 5 Feb 2025 00:31:05 +0100
Subject: [PATCH 3/3] Bump version to v2025.1.2

---
 CHANGES.rst                    | 6 ++++++
 invenio_config_tuw/__init__.py | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index dd8daae..1dcd0c3 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,6 +9,12 @@ Changes
 =======
 
 
+Version 2025.1.2 (released 2025-02-05)
+
+- Rework the Flask config class override as a `finalize_app` handler
+- Temporarily unset `SERVER_NAME` during requests, to avoid forcing it for `url_for()`
+
+
 Version 2025.1.1 (released 2025-02-04)
 
 - Override `Flask.create_url_adapter()` to match v3.1 regarding `SERVER_NAME`
diff --git a/invenio_config_tuw/__init__.py b/invenio_config_tuw/__init__.py
index 0e0113e..75f9ca0 100644
--- a/invenio_config_tuw/__init__.py
+++ b/invenio_config_tuw/__init__.py
@@ -9,6 +9,6 @@
 
 from .ext import InvenioConfigTUW
 
-__version__ = "2025.1.1"
+__version__ = "2025.1.2"
 
 __all__ = ("__version__", "InvenioConfigTUW")
-- 
GitLab