diff --git a/CHANGES.rst b/CHANGES.rst index dd8daae8075104cd8a1b795428151a2ce328f3fc..1dcd0c3569ef2a4d0cadf6b4dda08975e78d1deb 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 0e0113e1397dffca74c8b82c04f9eb1da2668034..75f9ca052be94c4a3bb6e4475d3b37d845a9ee56 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") diff --git a/invenio_config_tuw/ext.py b/invenio_config_tuw/ext.py index d21bbc9bc9fa979270d72611d5f3d138a56c03c3..1f8d636afba2a194137f8d8e8cb0d5b5d3a3359f 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. @@ -63,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.""" diff --git a/invenio_config_tuw/startup.py b/invenio_config_tuw/startup.py index 4fa84a41b32b3397ebfcb06abe4fc40abbab406f..73a1b717d4e3cfd7101d62ed08ca9d8b79b4c02f 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 e3e12dabc32de84686bda4e7f85270dbc9a43c7b..dc5cb12ef25c1ca256ed00b950d4ea2ca4960456 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"]