changeset 3994:69d970f974ff

component AP gateway: don't percent-encode `@`: Mastodon doesn't like when "@" is percent-encoded (because it doesn't do it, and it checks that the URL matches the ID without taking care of percent-encoding).
author Goffi <goffi@goffi.org>
date Fri, 10 Feb 2023 17:20:02 +0100
parents 722c25818778
children 27e5294649c2
files sat/plugins/plugin_comp_ap_gateway/__init__.py sat/plugins/plugin_comp_ap_gateway/http_server.py tests/unit/test_ap-gateway.py
diffstat 3 files changed, 21 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/__init__.py	Mon Dec 05 11:54:47 2022 +0100
+++ b/sat/plugins/plugin_comp_ap_gateway/__init__.py	Fri Feb 10 17:20:02 2023 +0100
@@ -838,7 +838,7 @@
         """
         return parse.urljoin(
             self.base_ap_url,
-            str(Path(type_).joinpath(*(parse.quote_plus(a) for a in args)))
+            str(Path(type_).joinpath(*(parse.quote_plus(a, safe="@") for a in args)))
         )
 
     def isLocalURL(self, url: str) -> bool:
--- a/sat/plugins/plugin_comp_ap_gateway/http_server.py	Mon Dec 05 11:54:47 2022 +0100
+++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py	Fri Feb 10 17:20:02 2023 +0100
@@ -592,7 +592,9 @@
                 "https://w3id.org/security/v1"
             ],
 
-            "id": ap_url,
+            # XXX: Mastodon doesn't like percent-encode arobas, so we have to unescape it
+            #   if it is escaped
+            "id": ap_url.replace("%40", "@"),
             "type": "Person",
             "preferredUsername": preferred_username,
             "inbox": inbox,
@@ -637,7 +639,8 @@
         return parse.urljoin(
             f"https://{self.apg.public_url}",
             request.path.decode().rstrip("/")
-        )
+        # we unescape "@" for the same reason as in [APActorRequest]
+        ).replace("%40", "@")
 
     def queryData2RSMRequest(
         self,
--- a/tests/unit/test_ap-gateway.py	Mon Dec 05 11:54:47 2022 +0100
+++ b/tests/unit/test_ap-gateway.py	Fri Feb 10 17:20:02 2023 +0100
@@ -255,7 +255,7 @@
         "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1",
         "orderedItems": [
             f"{TEST_BASE_URL}/users/ext_user",
-            f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}",
+            f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user@{PUBLIC_URL}",
         ],
         "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/following",
         "totalItems": 2,
@@ -273,7 +273,7 @@
         "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1",
         "orderedItems": [
             f"{TEST_BASE_URL}/users/ext_user",
-            f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}",
+            f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user@{PUBLIC_URL}",
         ],
         "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/followers",
         "totalItems": 2,
@@ -643,7 +643,7 @@
         """
         assert type_ and url is None or url and type_ is None
         if type_ is not None:
-            path = f"_ap/{type_}/some_user%40test.example"
+            path = f"_ap/{type_}/some_user@test.example"
         else:
             url_parsed = parse.urlparse(url)
             path = url_parsed.path.lstrip("/")
@@ -685,7 +685,7 @@
             **self.ap_request_params(ap_gateway, "outbox")
         )
         assert outbox["@context"] == ["https://www.w3.org/ns/activitystreams"]
-        assert outbox["id"] == "https://test.example/_ap/outbox/some_user%40test.example"
+        assert outbox["id"] == "https://test.example/_ap/outbox/some_user@test.example"
         assert outbox["totalItems"] == len(XMPP_ITEMS)
         assert outbox["type"] == "OrderedCollection"
         assert outbox["first"]
@@ -697,7 +697,7 @@
         assert first_page["@context"] == ["https://www.w3.org/ns/activitystreams"]
         assert (
             first_page["id"]
-            == "https://test.example/_ap/outbox/some_user%40test.example?page=first"
+            == "https://test.example/_ap/outbox/some_user@test.example?page=first"
         )
         assert first_page["type"] == "OrderedCollectionPage"
         assert first_page["partOf"] == outbox["id"]
@@ -705,11 +705,11 @@
         first_item = first_page["orderedItems"][0]
         assert first_item["@context"] == ["https://www.w3.org/ns/activitystreams"]
         assert (
-            first_item["id"] == "https://test.example/_ap/item/some_user%40test.example/4"
+            first_item["id"] == "https://test.example/_ap/item/some_user@test.example/4"
         )
         assert (
             first_item["actor"]
-            == "https://test.example/_ap/actor/some_user%40test.example"
+            == "https://test.example/_ap/actor/some_user@test.example"
         )
         assert first_item["type"] == "Create"
         first_item_obj = first_item["object"]
@@ -794,7 +794,7 @@
         assert following["@context"] == ["https://www.w3.org/ns/activitystreams"]
         assert (
             following["id"]
-            == "https://test.example/_ap/following/some_user%40test.example"
+            == "https://test.example/_ap/following/some_user@test.example"
         )
         assert following["totalItems"] == len(subscriptions)
         assert following["type"] == "OrderedCollection"
@@ -832,7 +832,7 @@
         assert followers["@context"] == ["https://www.w3.org/ns/activitystreams"]
         assert (
             followers["id"]
-            == "https://test.example/_ap/followers/some_user%40test.example"
+            == "https://test.example/_ap/followers/some_user@test.example"
         )
         assert followers["totalItems"] == len(subscribers)
         assert followers["type"] == "OrderedCollection"
@@ -861,14 +861,14 @@
             await ap_gateway.onMessage(ap_gateway.client, mess_data)
             url, actor_id, doc = signAndPost.call_args[0]
         assert url == "https://example.org/users/test_user/inbox"
-        assert actor_id == "https://test.example/_ap/actor/some_user%40test.example"
+        assert actor_id == "https://test.example/_ap/actor/some_user@test.example"
         obj = doc["object"]
         assert doc["@context"] == ["https://www.w3.org/ns/activitystreams"]
-        assert doc["actor"] == "https://test.example/_ap/actor/some_user%40test.example"
+        assert doc["actor"] == "https://test.example/_ap/actor/some_user@test.example"
         assert obj["type"] == "Note"
         assert obj["content"] == "This is a test message."
         assert obj["attributedTo"] == (
-            "https://test.example/_ap/actor/some_user%40test.example"
+            "https://test.example/_ap/actor/some_user@test.example"
         )
         # we must have a direct message, thus the item must be only addressed to destinee
         # ("to" attribute of the message), and the "Public" namespace must not be set
@@ -1669,11 +1669,11 @@
         ap_object = ap_item["object"]
         actor_id = (
             "https://test.example/_ap/actor/"
-            "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example"
+            "___urn.3Axmpp.3Aevents.3A0---some_user@test.example"
         )
         event_id = (
             "https://test.example/_ap/item/"
-            "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example/event_123"
+            "___urn.3Axmpp.3Aevents.3A0---some_user@test.example/event_123"
         )
         assert ap_object["name"] == "test event"
         assert ap_object["actor"] == actor_id
@@ -1700,7 +1700,7 @@
     @ed
     async def test_ap_event_2_xmpp_event(self, ap_gateway):
         """AP events are converted to XMPP events"""
-        test_actor = "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example"
+        test_actor = "___urn.3Axmpp.3Aevents.3A0---some_user@test.example"
         ap_object = {
             "actor": f"https://test.example/_ap/actor/{test_actor}",
             "attachment": [