From 943324b66158d1bd2449894ec41bc544c56058a7 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 16 Jan 2019 15:13:09 +0100
Subject: [PATCH] MastoAPI: Don't break on missing users.

---
 lib/pleroma/user.ex                           | 10 +++++++
 .../web/mastodon_api/views/status_view.ex     | 19 ++++++++++--
 .../web/twitter_api/views/activity_view.ex    | 12 +-------
 test/web/mastodon_api/status_view_test.exs    | 30 +++++++++++++++++++
 4 files changed, 57 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 681280539..a52e536d3 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1040,4 +1040,14 @@ defp local_nickname_regex() do
       @strict_local_nickname_regex
     end
   end
+
+  def error_user(ap_id) do
+    %User{
+      name: ap_id,
+      ap_id: ap_id,
+      info: %User.Info{},
+      nickname: "erroruser@example.com",
+      inserted_at: NaiveDateTime.utc_now()
+    }
+  end
 end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index db543ffe5..7f5a52ea3 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -32,6 +32,19 @@ defp get_replied_to_activities(activities) do
     end)
   end
 
+  defp get_user(ap_id) do
+    cond do
+      user = User.get_cached_by_ap_id(ap_id) ->
+        user
+
+      user = User.get_by_guessed_nickname(ap_id) ->
+        user
+
+      true ->
+        User.error_user(ap_id)
+    end
+  end
+
   def render("index.json", opts) do
     replied_to_activities = get_replied_to_activities(opts.activities)
 
@@ -48,7 +61,7 @@ def render(
         "status.json",
         %{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
       ) do
-    user = User.get_cached_by_ap_id(activity.data["actor"])
+    user = get_user(activity.data["actor"])
     created_at = Utils.to_masto_date(activity.data["published"])
 
     reblogged = Activity.get_create_activity_by_object_ap_id(object)
@@ -93,7 +106,7 @@ def render(
   end
 
   def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
-    user = User.get_cached_by_ap_id(activity.data["actor"])
+    user = get_user(activity.data["actor"])
 
     like_count = object["like_count"] || 0
     announcement_count = object["announcement_count"] || 0
@@ -116,7 +129,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
     created_at = Utils.to_masto_date(object["published"])
 
     reply_to = get_reply_to(activity, opts)
-    reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
+    reply_to_user = reply_to && get_user(reply_to.data["actor"])
 
     content =
       object
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index 108e7bfc5..03708d84c 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -101,20 +101,10 @@ defp get_user(ap_id, opts) do
         user
 
       true ->
-        error_user(ap_id)
+        User.error_user(ap_id)
     end
   end
 
-  defp error_user(ap_id) do
-    %User{
-      name: ap_id,
-      ap_id: ap_id,
-      info: %User.Info{},
-      nickname: "erroruser@example.com",
-      inserted_at: NaiveDateTime.utc_now()
-    }
-  end
-
   def render("index.json", opts) do
     context_ids = collect_context_ids(opts.activities)
     users = collect_users(opts.activities)
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs
index 1076b5002..d30ae6149 100644
--- a/test/web/mastodon_api/status_view_test.exs
+++ b/test/web/mastodon_api/status_view_test.exs
@@ -19,6 +19,36 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
     :ok
   end
 
+  test "returns a temporary ap_id based user for activities missing db users" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+    Repo.delete(user)
+    Cachex.clear(:user_cache)
+
+    %{account: ms_user} = StatusView.render("status.json", activity: activity)
+
+    assert ms_user.acct == "erroruser@example.com"
+  end
+
+  test "tries to get a user by nickname if fetching by ap_id doesn't work" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+
+    {:ok, user} =
+      user
+      |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
+      |> Repo.update()
+
+    Cachex.clear(:user_cache)
+
+    result = StatusView.render("status.json", activity: activity)
+
+    assert result[:account][:id] == to_string(user.id)
+  end
+
   test "a note with null content" do
     note = insert(:note_activity)