changeset 3977:6fa4ca0c047e

component AP gateway: HTML redirection: when a request is done on AP endpoint and `Accept` header is not set to `application/json`, the request can now be redirected to a configurable URL.
author Goffi <goffi@goffi.org>
date Fri, 11 Nov 2022 13:51:20 +0100
parents db45d49518f6
children 97fbe11c4476
files sat/plugins/plugin_comp_ap_gateway/__init__.py sat/plugins/plugin_comp_ap_gateway/http_server.py
diffstat 2 files changed, 61 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/__init__.py	Thu Nov 10 15:16:43 2022 +0100
+++ b/sat/plugins/plugin_comp_ap_gateway/__init__.py	Fri Nov 11 13:51:20 2022 +0100
@@ -20,7 +20,6 @@
 import calendar
 import hashlib
 import json
-from os import access
 from pathlib import Path
 from pprint import pformat
 import re
@@ -33,7 +32,6 @@
     Optional,
     Set,
     Tuple,
-    Type,
     Union,
     overload,
 )
@@ -266,6 +264,27 @@
             self.host.memory.getConfig(CONF_SECTION, "auto_mentions", C.BOOL_TRUE)
         )
 
+        html_redirect: Dict[str, Union[str, dict]] = self.host.memory.getConfig(
+            CONF_SECTION, 'html_redirect_dict', {}
+        )
+        self.html_redirect: Dict[str, List[dict]] = {}
+        for url_type, target in html_redirect.items():
+            if isinstance(target, str):
+                target = {"url": target}
+            elif not isinstance(target, dict):
+                raise exceptions.ConfigError(
+                    f"html_redirect target must be a URL or a dict, not {target!r}"
+                )
+            filters = target.setdefault("filters", {})
+            if "url" not in target:
+                log.warning(f"invalid HTML redirection, missing target URL: {target}")
+                continue
+            # a slash in the url_type is a syntactic shortcut to have a node filter
+            if "/" in url_type:
+                url_type, node_filter = url_type.split("/", 1)
+                filters["node"] = node_filter
+            self.html_redirect.setdefault(url_type, []).append(target)
+
         # HTTP server launch
         self.server = HTTPServer(self)
         if connection_type == 'http':
@@ -361,7 +380,7 @@
             if resp.code == 404:
                 raise exceptions.NotFound(f"Can't find resource at {url}")
             else:
-                msg = f"HTTP error {resp.code}: {text}"
+                msg = f"HTTP error {resp.code} (url: {url}): {text}"
                 raise exceptions.ExternalRequestError(msg)
         try:
             return await treq.json_content(resp)
--- a/sat/plugins/plugin_comp_ap_gateway/http_server.py	Thu Nov 10 15:16:43 2022 +0100
+++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py	Fri Nov 11 13:51:20 2022 +0100
@@ -26,6 +26,7 @@
 
 from twisted.web import http, resource as web_resource, server
 from twisted.web import static
+from twisted.web import util as web_util
 from twisted.python import failure
 from twisted.internet import defer
 from twisted.words.protocols.jabber import jid, error
@@ -40,8 +41,8 @@
 from sat.memory.sqla_mapping import SubscriptionState
 
 from .constants import (
-    NS_AP, CONTENT_TYPE_AP, TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX,
-    TYPE_EVENT, AP_REQUEST_TYPES, PAGE_SIZE, ACTIVITY_TYPES_LOWER,
+    NS_AP, MEDIA_TYPE_AP, CONTENT_TYPE_AP, TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX,
+    TYPE_OUTBOX, TYPE_EVENT, AP_REQUEST_TYPES, PAGE_SIZE, ACTIVITY_TYPES_LOWER,
     ACTIVIY_NO_ACCOUNT_ALLOWED, SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS,
     TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE, TYPE_REACTION, ST_AP_CACHE
 )
@@ -949,6 +950,42 @@
             path
         )
         request_type, extra_args = self.apg.parseAPURL(ap_url)
+        if ((request.getHeader("accept") != MEDIA_TYPE_AP
+             and request_type in self.apg.html_redirect)):
+            # this is not a AP request, and we have a redirections for it
+            kw = {}
+            if extra_args:
+                kw["jid"], kw["node"] = await self.apg.getJIDAndNode(extra_args[0])
+                kw["jid_user"] = kw["jid"].user
+                if kw["node"] is None:
+                    kw["node"] = self.apg._m.namespace
+                if len(extra_args) > 1:
+                    kw["item"] = extra_args[1]
+                else:
+                    kw["item"] = ""
+            else:
+                kw["jid"], kw["jid_user"], kw["node"], kw["item"] = "", "", "", ""
+
+            redirections = self.apg.html_redirect[request_type]
+            for redirection in redirections:
+                filters = redirection["filters"]
+                if not filters:
+                    break
+                # if we have filter, they must all match
+                elif all(v in kw[k] for k,v in filters.items()):
+                    break
+            else:
+                # no redirection is matching
+                redirection = None
+
+            if redirection is not None:
+                kw = {k: parse.quote(str(v), safe="") for k,v in kw.items()}
+                target_url = redirection["url"].format(**kw)
+                content = web_util.redirectTo(target_url.encode(), request)
+                request.write(content)
+                request.finish()
+                return
+
         if len(extra_args) == 0:
             if request_type != "shared_inbox":
                 raise exceptions.DataError(f"Invalid request type: {request_type!r}")