From e1e0d5d75922c0e6738d97963f0df5ed4327d253 Mon Sep 17 00:00:00 2001
From: floatingghost <hannah@coffee-and-dreams.uk>
Date: Fri, 18 Nov 2022 11:14:35 +0000
Subject: [PATCH] microblogpub federation fixes (#288)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/288
---
 lib/pleroma/http/adapter_helper.ex            |  1 +
 lib/pleroma/user.ex                           |  3 +-
 lib/pleroma/web/activity_pub/activity_pub.ex  | 14 ++++-
 .../user_with_invalid_also_known_as.json      | 57 +++++++++++++++++++
 test/pleroma/object/fetcher_test.exs          |  2 +-
 test/pleroma/user_test.exs                    | 19 +++++++
 6 files changed, 93 insertions(+), 3 deletions(-)
 create mode 100644 test/fixtures/microblogpub/user_with_invalid_also_known_as.json

diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index 4949dd727..e837ac8d4 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -99,6 +99,7 @@ defp proxy_type(_), do: {:error, :unknown}
           | {:error, atom()}
           | nil
   def parse_proxy(nil), do: nil
+  def parse_proxy(""), do: nil
 
   def parse_proxy(proxy) when is_binary(proxy) do
     with %URI{} = uri <- URI.parse(proxy),
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 700cab2b5..eb907a2d8 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1910,7 +1910,8 @@ def get_or_fetch_by_ap_id(ap_id) do
       {%User{} = user, _} ->
         {:ok, user}
 
-      _ ->
+      e ->
+        Logger.error("Could not fetch user, #{inspect(e)}")
         {:error, :not_found}
     end
   end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index dcdc7085f..254d91a7e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1530,6 +1530,18 @@ defp object_to_user_data(data, additional) do
     # we request WebFinger here
     nickname = additional[:nickname_from_acct] || generate_nickname(data)
 
+    # also_known_as must be a URL
+    also_known_as =
+      data
+      |> Map.get("alsoKnownAs", [])
+      |> Enum.filter(fn url ->
+        case URI.parse(url) do
+          %URI{scheme: "http"} -> true
+          %URI{scheme: "https"} -> true
+          _ -> false
+        end
+      end)
+
     %{
       ap_id: data["id"],
       uri: get_actor_url(data["url"]),
@@ -1547,7 +1559,7 @@ defp object_to_user_data(data, additional) do
       featured_address: featured_address,
       bio: data["summary"] || "",
       actor_type: actor_type,
-      also_known_as: Map.get(data, "alsoKnownAs", []),
+      also_known_as: also_known_as,
       public_key: public_key,
       inbox: data["inbox"],
       shared_inbox: shared_inbox,
diff --git a/test/fixtures/microblogpub/user_with_invalid_also_known_as.json b/test/fixtures/microblogpub/user_with_invalid_also_known_as.json
new file mode 100644
index 000000000..a03076226
--- /dev/null
+++ b/test/fixtures/microblogpub/user_with_invalid_also_known_as.json
@@ -0,0 +1,57 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://w3id.org/security/v1",
+    {
+      "Hashtag": "as:Hashtag",
+      "sensitive": "as:sensitive",
+      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+      "alsoKnownAs": {
+        "@id": "as:alsoKnownAs",
+        "@type": "@id"
+      },
+      "movedTo": {
+        "@id": "as:movedTo",
+        "@type": "@id"
+      },
+      "toot": "http://joinmastodon.org/ns#",
+      "featured": {
+        "@id": "toot:featured",
+        "@type": "@id"
+      },
+      "Emoji": "toot:Emoji",
+      "blurhash": "toot:blurhash",
+      "votersCount": "toot:votersCount",
+      "schema": "http://schema.org#",
+      "PropertyValue": "schema:PropertyValue",
+      "value": "schema:value",
+      "ostatus": "http://ostatus.org#",
+      "conversation": "ostatus:conversation"
+    }
+  ],
+  "type": "Person",
+  "id": "https://mbp.example.com",
+  "following": "https://mbp.example.com/following",
+  "followers": "https://mbp.example.com/followers",
+  "featured": "https://mbp.example.com/featured",
+  "inbox": "https://mbp.example.com/inbox",
+  "outbox": "https://mbp.example.com/outbox",
+  "preferredUsername": "MBP",
+  "name": "MBP",
+  "summary": "wowee",
+  "endpoints": {
+    "sharedInbox": "https://mbp.example.com/inbox"
+  },
+  "url": "https://mbp.example.com/",
+  "manuallyApprovesFollowers": false,
+  "attachment": [],
+  "icon": {
+    "mediaType": "image/jpeg",
+    "type": "Image",
+    "url": "https://beta.4201337.xyz/static/denise.jpg"
+  },
+  "tag": [],
+  "alsoKnownAs": [
+    "example@elsewhere.com"
+  ]
+}
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
index cd5437617..71306cdfe 100644
--- a/test/pleroma/object/fetcher_test.exs
+++ b/test/pleroma/object/fetcher_test.exs
@@ -166,7 +166,7 @@ test "it resets instance reachability on successful fetch" do
       Instances.set_consistently_unreachable(id)
       refute Instances.reachable?(id)
 
-      {:ok, object} =
+      {:ok, _object} =
         Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
 
       assert Instances.reachable?(id)
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 195df2a03..44763daf7 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -968,6 +968,25 @@ test "it returns the old user if stale, but unfetchable" do
 
       assert user.last_refreshed_at == orig_user.last_refreshed_at
     end
+
+    test "it doesn't fail on invalid alsoKnownAs entries" do
+      Tesla.Mock.mock(fn
+        %{url: "https://mbp.example.com/"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/microblogpub/user_with_invalid_also_known_as.json"
+              |> File.read!(),
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        _ ->
+          %Tesla.Env{status: 404}
+      end)
+
+      assert {:ok, %User{also_known_as: []}} =
+               User.get_or_fetch_by_ap_id("https://mbp.example.com/")
+    end
   end
 
   test "returns an ap_id for a user" do