Handle "Move" activity
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 30 Oct 2019 11:21:49 +0000 (18:21 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 30 Oct 2019 11:21:49 +0000 (18:21 +0700)
lib/pleroma/following_relationship.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/workers/background_worker.ex
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier_test.exs

index 2ffac17ee135667774c0cd64f110d6425fc81706..2f89eb4cf6ccdbe5fa945f888e18cc59780c6c42 100644 (file)
@@ -107,4 +107,28 @@ defmodule Pleroma.FollowingRelationship do
       [user.follower_address | following]
     end
   end
+
+  def move_following(origin, target) do
+    following_relationships =
+      __MODULE__
+      |> where(following_id: ^origin.id)
+      |> preload([:follower])
+      |> limit(50)
+      |> Repo.all()
+
+    case following_relationships do
+      [] ->
+        :ok
+
+      following_relationships ->
+        Enum.each(following_relationships, fn following_relationship ->
+          Repo.transaction(fn ->
+            Repo.delete(following_relationship)
+            User.follow(following_relationship.follower, target)
+          end)
+        end)
+
+        move_following(origin, target)
+    end
+  end
 end
index 6adadacdc35f544cb3017927a588000fbd4fefc9..67c2cb659eb0ed77d13d4518f052305a6d898f6d 100644 (file)
@@ -514,6 +514,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  def move(%User{} = origin, %User{} = target, local \\ true) do
+    params = %{
+      "type" => "Move",
+      "actor" => origin.ap_id,
+      "object" => origin.ap_id,
+      "target" => target.ap_id
+    }
+
+    with true <- origin.ap_id in target.also_known_as,
+         {:ok, activity} <- insert(params, local) do
+      maybe_federate(activity)
+
+      BackgroundWorker.enqueue("move_following", %{
+        "origin_id" => origin.id,
+        "target_id" => target.id
+      })
+
+      {:ok, activity}
+    else
+      false -> {:error, "Target account must have the origin in `alsoKnownAs`"}
+      err -> err
+    end
+  end
+
   defp fetch_activities_for_context_query(context, opts) do
     public = [Pleroma.Constants.as_public()]
 
index c23f2dcd04c277630964df09b3c3a13d992e5548..78ee6192ac42763555aab0b30d6970ede6ffd9ae 100644 (file)
@@ -785,6 +785,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  def handle_incoming(
+        %{
+          "type" => "Move",
+          "actor" => origin_actor,
+          "object" => origin_actor,
+          "target" => target_actor
+        },
+        _options
+      ) do
+    with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
+         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
+         true <- origin_actor in target_user.also_known_as do
+      ActivityPub.move(origin_user, target_user, false)
+    else
+      _e -> :error
+    end
+  end
+
   def handle_incoming(_, _), do: :error
 
   @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
index cd409749348678e01a6ad3f14fd912ab0dc381ad..e172f6d3f2b6196338900b6448e80e17083b9b83 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
   def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
   def is_public?(%Object{data: data}), do: is_public?(data)
+  def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
   def is_public?(%Activity{data: data}), do: is_public?(data)
   def is_public?(%{"directMessage" => true}), do: false
   def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
index 7ffc8eabec217ae19db9ec72c5465353526b4eeb..323a4da1ea2d73ae1e2e56af42cd92dcbacb0a76 100644 (file)
@@ -71,4 +71,11 @@ defmodule Pleroma.Workers.BackgroundWorker do
     activity = Activity.get_by_id(activity_id)
     Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
   end
+
+  def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
+    origin = User.get_cached_by_id(origin_id)
+    target = User.get_cached_by_id(target_id)
+
+    Pleroma.FollowingRelationship.move_following(origin, target)
+  end
 end
index 0e5eb0c50c81d7d05cc75fcecc84ca60117500ce..3f79593e5548fe53b339f0e8caa85439fa7bda1f 100644 (file)
@@ -4,6 +4,8 @@
 
 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
   use Pleroma.DataCase
+  use Oban.Testing, repo: Pleroma.Repo
+
   alias Pleroma.Activity
   alias Pleroma.Builders.ActivityBuilder
   alias Pleroma.Object
@@ -1420,4 +1422,50 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert follow_info.hide_follows == true
     end
   end
+
+  describe "Move activity" do
+    test "create" do
+      %{ap_id: old_ap_id} = old_user = insert(:user)
+      %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
+      follower = insert(:user)
+
+      User.follow(follower, old_user)
+
+      assert User.following?(follower, old_user)
+
+      assert {:ok, activity} = ActivityPub.move(old_user, new_user)
+
+      assert %Activity{
+               actor: ^old_ap_id,
+               data: %{
+                 "actor" => ^old_ap_id,
+                 "object" => ^old_ap_id,
+                 "target" => ^new_ap_id,
+                 "type" => "Move"
+               },
+               local: true
+             } = activity
+
+      params = %{
+        "op" => "move_following",
+        "origin_id" => old_user.id,
+        "target_id" => new_user.id
+      }
+
+      assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
+
+      Pleroma.Workers.BackgroundWorker.perform(params, nil)
+
+      refute User.following?(follower, old_user)
+      assert User.following?(follower, new_user)
+    end
+
+    test "old user must be in the new user's `also_known_as` list" do
+      old_user = insert(:user)
+      new_user = insert(:user)
+
+      assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
+               ActivityPub.move(old_user, new_user)
+    end
+  end
 end
index b36adcf9b8d7352fcdb9515eba7b498640a285b9..76e7ad8ea2036eade1c6f1e2a5ec5278e8201ec1 100644 (file)
@@ -1181,6 +1181,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
       assert [user.follower_address] == activity.data["to"]
     end
+
+    test "it accepts Move activities" do
+      old_user = insert(:user)
+      new_user = insert(:user)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "type" => "Move",
+        "actor" => old_user.ap_id,
+        "object" => old_user.ap_id,
+        "target" => new_user.ap_id
+      }
+
+      assert :error = Transmogrifier.handle_incoming(message)
+
+      {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
+
+      assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
+      assert activity.actor == old_user.ap_id
+      assert activity.data["actor"] == old_user.ap_id
+      assert activity.data["object"] == old_user.ap_id
+      assert activity.data["target"] == new_user.ap_id
+      assert activity.data["type"] == "Move"
+    end
   end
 
   describe "prepare outgoing" do