From f468fb0a07682f8b8e67f90611170cc289702fa7 Mon Sep 17 00:00:00 2001 From: Helge Jung <hej@c3pb.de> Date: Fri, 20 Dec 2024 23:29:25 +0100 Subject: [PATCH] internal_urls: allow arbitrary additional protocols This shall be used for e.g. c3nav to support c3nav://location URLs which are remapped to https://38c3.c3nav.de/l/VALUE (with "VALUE" being the placeholder). See the unittest for additional usage examples. --- src/core/tests/utils.py | 14 +++++++++++++- src/core/utils.py | 12 ++++++++++++ src/hub/settings/base.py | 7 +++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/core/tests/utils.py b/src/core/tests/utils.py index b39677a33..b4d4ca33b 100644 --- a/src/core/tests/utils.py +++ b/src/core/tests/utils.py @@ -2,7 +2,7 @@ import uuid from datetime import timedelta from django.conf import settings -from django.test import TestCase +from django.test import TestCase, override_settings from django.urls import reverse from django.utils.timezone import now @@ -90,6 +90,18 @@ class InternalUrlTests(TestCase): for check in checks: self.assertEqual(check[1], resolve_internal_url(check[0])) + @override_settings(ADDITIONAL_LINK_PROTOCOLS={'c3nav': 'https://test.c3nav.de/l/VALUE', 'ccc': 'https://ccc.de/?goto=VALUE'}) + def test_additional_link_protocols(self): + checks = [ + ('c3nav://unittest', 'https://test.c3nav.de/l/unittest'), + ('c3nav://unittest?a=b', 'https://test.c3nav.de/l/unittest?a=b'), + ('ccc://unittest', 'https://ccc.de/?goto=unittest'), + ('ccc://unittest?foo=bar', 'https://ccc.de/?goto=unittest&foo=bar'), + ] + + for check in checks: + self.assertEqual(check[1], resolve_internal_url(check[0])) + class GitRepoOfflineTests(TestCase): def test_invalid_url_local_path(self): diff --git a/src/core/utils.py b/src/core/utils.py index 3ec4c560b..c445d70d3 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -12,6 +12,7 @@ from urllib.parse import parse_qs, urlparse, urlunparse import requests +from django.conf import settings from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.core.validators import validate_slug @@ -189,6 +190,17 @@ def resolve_internal_url(url: str, fallback_as_is: bool = True) -> str | None: if protocol == 'wiki': return hub_absolute('plainui:static_page', query_string=query_string, page_slug=remainder) + if link := settings.ADDITIONAL_LINK_PROTOCOLS.get(protocol): + link = link.replace('VALUE', remainder) + if query_string: + result = urlparse(link) + if q := result.query: + q += '&' + query_string + else: + q = query_string + link = urlunparse((result[0], result[1], result[2], result[3], q, result[5])) + return link + except NoReverseMatch: # we matched a protocol but the remainder was something bogus return None diff --git a/src/hub/settings/base.py b/src/hub/settings/base.py index edfd10e2b..087ef7e96 100644 --- a/src/hub/settings/base.py +++ b/src/hub/settings/base.py @@ -115,6 +115,7 @@ env = environ.FileAwareEnv( CSP_FORM_ACTION=(list, ["'self'"]), CSP_BASE_URI=(list, ["'self'"]), CSP_INCLUDE_NONCE_IN=(list, ['script-src']), + ADDITIONAL_LINK_PROTOCOLS=(dict, {}), ) @@ -536,6 +537,12 @@ PRETIX_SECRET_KEY = env('PRETIX_SECRET') # the JWT shared secret with Pretix METRICS_SERVER_IPS = env('METRICS_SERVER_IPS') +# ---------------------------------- +# additional protocols supported by core.utils.resolve_internal_url() and thus, all markdown in the hub +# ---------------------------------- + +ADDITIONAL_LINK_PROTOCOLS = env.dict('ADDITIONAL_LINK_PROTOCOLS', cast={'value': str}) + # ---------------------------------- # External Schedule Support # ---------------------------------- -- GitLab