From 4885473be2704d4d370fdb96e1473bc4eb9368f4 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 15:48:51 +0000
Subject: [PATCH 1/8] user: refactor get_or_create_instance_user() into
 get_or_create_service_actor_by_id()

---
 lib/pleroma/user.ex                   | 13 ++++++-------
 lib/pleroma/web/activity_pub/relay.ex |  3 ++-
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index ffba3f390..463bb9ad4 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1157,19 +1157,18 @@ defmodule Pleroma.User do
     end
   end
 
-  def get_or_create_instance_user do
-    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
-
-    if user = get_cached_by_ap_id(relay_uri) do
+  @doc "Creates an internal service actor by URI if missing.  Optionally takes nickname for addressing."
+  def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
+    if user = get_cached_by_ap_id(uri) do
       user
     else
       changes =
         %User{info: %User.Info{}}
         |> cast(%{}, [:ap_id, :nickname, :local])
-        |> put_change(:ap_id, relay_uri)
-        |> put_change(:nickname, nil)
+        |> put_change(:ap_id, uri)
+        |> put_change(:nickname, nickname)
         |> put_change(:local, true)
-        |> put_change(:follower_address, relay_uri <> "/followers")
+        |> put_change(:follower_address, uri <> "/followers")
 
       {:ok, user} = Repo.insert(changes)
       user
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 93808517b..1ebfcdd86 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,7 +10,8 @@ defmodule Pleroma.Web.ActivityPub.Relay do
   require Logger
 
   def get_actor do
-    User.get_or_create_instance_user()
+    "#{Pleroma.Web.Endpoint.url()}/relay"
+    |> User.get_or_create_service_actor_by_ap_id()
   end
 
   def follow(target_instance) do

From a9d6a12bb3e71bafc11fe7cf5e44ad5bc9981cf9 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 16:22:57 +0000
Subject: [PATCH 2/8] activitypub: controller: rework the way the relay actor
 is presented so the code can be reused

---
 .../web/activity_pub/activity_pub_controller.ex      | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index e2af4ad1a..dc9ef066d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -206,9 +206,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     json(conn, dgettext("errors", "error"))
   end
 
-  def relay(conn, _params) do
-    with %User{} = user <- Relay.get_actor(),
-         {:ok, user} <- User.ensure_keys_present(user) do
+  defp represent_service_actor(%User{} = user, conn) do
+    with {:ok, user} <- User.ensure_keys_present(user) do
       conn
       |> put_resp_header("content-type", "application/activity+json")
       |> json(UserView.render("user.json", %{user: user}))
@@ -217,6 +216,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     end
   end
 
+  defp represent_service_actor(nil, _), do: {:error, :not_found}
+
+  def relay(conn, _params) do
+    Relay.get_actor()
+    |> represent_service_actor(conn)
+  end
+
   def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
     conn
     |> put_resp_header("content-type", "application/activity+json")

From 0a6f6e1b5bf863fc23a90e6ef358129364bcacce Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 16:59:29 +0000
Subject: [PATCH 3/8] webfinger: allow resolution of usernames with dots in
 them (internal actors)

---
 lib/pleroma/web/web_finger/web_finger.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 3fca72de8..fa34c7ced 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.WebFinger do
 
   def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
     host = Pleroma.Web.Endpoint.host()
-    regex = ~r/(acct:)?(?<username>\w+)@#{host}/
+    regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
 
     with %{"username" => username} <- Regex.named_captures(regex, resource),
          %User{} = user <- User.get_cached_by_nickname(username) do

From 62e5ff624e1984b48eecaf2a6c14ad092013a13e Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 17:12:42 +0000
Subject: [PATCH 4/8] user: add is_internal_user? helper function

---
 lib/pleroma/user.ex |  4 ++++
 test/user_test.exs  | 17 +++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 463bb9ad4..c91fbb68a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1410,4 +1410,8 @@ defmodule Pleroma.User do
   end
 
   defp put_password_hash(changeset), do: changeset
+
+  def is_internal_user?(%User{nickname: nil}), do: true
+  def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
+  def is_internal_user?(_), do: false
 end
diff --git a/test/user_test.exs b/test/user_test.exs
index 264b7a40e..908f72a0e 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1310,4 +1310,21 @@ defmodule Pleroma.UserTest do
       assert following == 0
     end
   end
+
+  describe "is_internal_user?/1" do
+    test "non-internal user returns false" do
+      user = insert(:user)
+      refute User.is_internal_user?(user)
+    end
+
+    test "user with no nickname returns true" do
+      user = insert(:user, %{nickname: nil})
+      assert User.is_internal_user?(user)
+    end
+
+    test "user with internal-prefixed nickname returns true" do
+      user = insert(:user, %{nickname: "internal.test"})
+      assert User.is_internal_user?(user)
+    end
+  end
 end

From d930e5d5c322a7c4b3a080dfa3e318d97994aaf1 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 17:14:08 +0000
Subject: [PATCH 5/8] activitypub: introduce internal fetch service actor

---
 lib/pleroma/application.ex                    |  5 +++++
 .../web/activity_pub/internal_fetch_actor.ex  | 20 +++++++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 lib/pleroma/web/activity_pub/internal_fetch_actor.ex

diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index ba4cf8486..035331491 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -140,6 +140,11 @@ defmodule Pleroma.Application do
             id: :federator_init,
             start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
             restart: :temporary
+          },
+          %{
+            id: :internal_fetch_init,
+            start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
+            restart: :temporary
           }
         ] ++
         streamer_child() ++
diff --git a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
new file mode 100644
index 000000000..9213ddde7
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.InternalFetchActor do
+  alias Pleroma.User
+
+  require Logger
+
+  def init do
+    # Wait for everything to settle.
+    Process.sleep(1000 * 5)
+    get_actor()
+  end
+
+  def get_actor do
+    "#{Pleroma.Web.Endpoint.url()}/internal/fetch"
+    |> User.get_or_create_service_actor_by_ap_id("internal.fetch")
+  end
+end

From cf9cb953d5c341bb13e4b86272ff4ef405aeab92 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 17:34:57 +0000
Subject: [PATCH 6/8] activitypub: represent internal fetch actor

---
 .../web/activity_pub/activity_pub_controller.ex     |  6 ++++++
 lib/pleroma/web/activity_pub/views/user_view.ex     | 13 ++++++++++---
 lib/pleroma/web/router.ex                           | 13 +++++++++++--
 3 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index dc9ef066d..133a726c5 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
   alias Pleroma.Object.Fetcher
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.InternalFetchActor
   alias Pleroma.Web.ActivityPub.ObjectView
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -223,6 +224,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     |> represent_service_actor(conn)
   end
 
+  def internal_fetch(conn, _params) do
+    InternalFetchActor.get_actor()
+    |> represent_service_actor(conn)
+  end
+
   def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
     conn
     |> put_resp_header("content-type", "application/activity+json")
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index d9c1bcb2c..639519e0a 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -31,8 +31,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
 
   def render("endpoints.json", _), do: %{}
 
-  # the instance itself is not a Person, but instead an Application
-  def render("user.json", %{user: %{nickname: nil} = user}) do
+  def render("service.json", %{user: user}) do
     {:ok, user} = User.ensure_keys_present(user)
     {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
     public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
@@ -47,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "followers" => "#{user.ap_id}/followers",
       "inbox" => "#{user.ap_id}/inbox",
       "name" => "Pleroma",
-      "summary" => "Virtual actor for Pleroma relay",
+      "summary" =>
+        "An internal service actor for this Pleroma instance.  No user-serviceable parts inside.",
       "url" => user.ap_id,
       "manuallyApprovesFollowers" => false,
       "publicKey" => %{
@@ -60,6 +60,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     |> Map.merge(Utils.make_json_ld_header())
   end
 
+  # the instance itself is not a Person, but instead an Application
+  def render("user.json", %{user: %User{nickname: nil} = user}),
+    do: render("service.json", %{user: user})
+
+  def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
+    do: render("service.json", %{user: user})
+
   def render("user.json", %{user: user}) do
     {:ok, user} = User.ensure_keys_present(user)
     {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 52b8dc0bf..8095ac4b1 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -586,7 +586,7 @@ defmodule Pleroma.Web.Router do
     end
   end
 
-  pipeline :ap_relay do
+  pipeline :ap_service_actor do
     plug(:accepts, ["activity+json", "json"])
   end
 
@@ -663,8 +663,17 @@ defmodule Pleroma.Web.Router do
   end
 
   scope "/relay", Pleroma.Web.ActivityPub do
-    pipe_through(:ap_relay)
+    pipe_through(:ap_service_actor)
+
     get("/", ActivityPubController, :relay)
+    post("/inbox", ActivityPubController, :inbox)
+  end
+
+  scope "/internal/fetch", Pleroma.Web.ActivityPub do
+    pipe_through(:ap_service_actor)
+
+    get("/", ActivityPubController, :internal_fetch)
+    post("/inbox", ActivityPubController, :inbox)
   end
 
   scope "/", Pleroma.Web.ActivityPub do

From 3d23a12d75fc159d3ec25424245847fe703b7bd6 Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 17:48:08 +0000
Subject: [PATCH 7/8] tests: add test for fetching the internal fetch actor

---
 .../web/activity_pub/activity_pub_controller_test.exs | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 452172bb4..40344f17e 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -48,6 +48,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
     end
   end
 
+  describe "/internal/fetch" do
+    test "it returns the internal fetch user", %{conn: conn} do
+      res =
+        conn
+        |> get(activity_pub_path(conn, :internal_fetch))
+        |> json_response(200)
+
+      assert res["id"] =~ "/fetch"
+    end
+  end
+
   describe "/users/:nickname" do
     test "it returns a json representation of the user with accept application/json", %{
       conn: conn

From c1198351022ead54b8de3bc535df32e333eb9b4d Mon Sep 17 00:00:00 2001
From: Ariadne Conill <ariadne@dereferenced.org>
Date: Wed, 17 Jul 2019 17:49:21 +0000
Subject: [PATCH 8/8] update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d91ad817..654d208dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
 - Addressable lists
 - Twitter API: added rate limit for `/api/account/password_reset` endpoint.
+- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
 
 ### Changed
 - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text