From 54a6855ddfb4b47b91b8fe2c184bbca3dbc2884d Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 14:00:21 +0200 Subject: [PATCH 01/13] Transmogrifier Tests: Extract Accept handling --- .../transmogrifier/accept_handling_test.exs | 113 ++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 102 +--------------- 2 files changed, 114 insertions(+), 101 deletions(-) create mode 100644 test/web/activity_pub/transmogrifier/accept_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/web/activity_pub/transmogrifier/accept_handling_test.exs new file mode 100644 index 000000000..3c4e134ff --- /dev/null +++ b/test/web/activity_pub/transmogrifier/accept_handling_test.exs @@ -0,0 +1,113 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.AcceptHandlingTest do + use Pleroma.DataCase + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming accepts which were pre-accepted" do + follower = insert(:user) + followed = insert(:user) + + {:ok, follower} = User.follow(follower, followed) + assert User.following?(follower, followed) == true + + {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) + + accept_data = + File.read!("test/fixtures/mastodon-accept-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + + object = + accept_data["object"] + |> Map.put("actor", follower.ap_id) + |> Map.put("id", follow_activity.data["id"]) + + accept_data = Map.put(accept_data, "object", object) + + {:ok, activity} = Transmogrifier.handle_incoming(accept_data) + refute activity.local + + assert activity.data["object"] == follow_activity.data["id"] + + assert activity.data["id"] == accept_data["id"] + + follower = User.get_cached_by_id(follower.id) + + assert User.following?(follower, followed) == true + end + + test "it works for incoming accepts which were orphaned" do + follower = insert(:user) + followed = insert(:user, locked: true) + + {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) + + accept_data = + File.read!("test/fixtures/mastodon-accept-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + + accept_data = + Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) + + {:ok, activity} = Transmogrifier.handle_incoming(accept_data) + assert activity.data["object"] == follow_activity.data["id"] + + follower = User.get_cached_by_id(follower.id) + + assert User.following?(follower, followed) == true + end + + test "it works for incoming accepts which are referenced by IRI only" do + follower = insert(:user) + followed = insert(:user, locked: true) + + {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) + + accept_data = + File.read!("test/fixtures/mastodon-accept-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + |> Map.put("object", follow_activity.data["id"]) + + {:ok, activity} = Transmogrifier.handle_incoming(accept_data) + assert activity.data["object"] == follow_activity.data["id"] + + follower = User.get_cached_by_id(follower.id) + + assert User.following?(follower, followed) == true + + follower = User.get_by_id(follower.id) + assert follower.following_count == 1 + + followed = User.get_by_id(followed.id) + assert followed.follower_count == 1 + end + + test "it fails for incoming accepts which cannot be correlated" do + follower = insert(:user) + followed = insert(:user, locked: true) + + accept_data = + File.read!("test/fixtures/mastodon-accept-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + + accept_data = + Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) + + :error = Transmogrifier.handle_incoming(accept_data) + + follower = User.get_cached_by_id(follower.id) + + refute User.following?(follower, followed) == true + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6dd9a3fec..52b4178bf 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -359,7 +359,7 @@ test "it strips internal reactions" do refute Map.has_key?(object_data, "reaction_count") end - test "it works for incomming unfollows with an existing follow" do + test "it works for incoming unfollows with an existing follow" do user = insert(:user) follow_data = @@ -403,106 +403,6 @@ test "it works for incoming follows to locked account" do assert [^pending_follower] = User.get_follow_requests(user) end - test "it works for incoming accepts which were pre-accepted" do - follower = insert(:user) - followed = insert(:user) - - {:ok, follower} = User.follow(follower, followed) - assert User.following?(follower, followed) == true - - {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) - - accept_data = - File.read!("test/fixtures/mastodon-accept-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - object = - accept_data["object"] - |> Map.put("actor", follower.ap_id) - |> Map.put("id", follow_activity.data["id"]) - - accept_data = Map.put(accept_data, "object", object) - - {:ok, activity} = Transmogrifier.handle_incoming(accept_data) - refute activity.local - - assert activity.data["object"] == follow_activity.data["id"] - - assert activity.data["id"] == accept_data["id"] - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == true - end - - test "it works for incoming accepts which were orphaned" do - follower = insert(:user) - followed = insert(:user, locked: true) - - {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) - - accept_data = - File.read!("test/fixtures/mastodon-accept-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - accept_data = - Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - - {:ok, activity} = Transmogrifier.handle_incoming(accept_data) - assert activity.data["object"] == follow_activity.data["id"] - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == true - end - - test "it works for incoming accepts which are referenced by IRI only" do - follower = insert(:user) - followed = insert(:user, locked: true) - - {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) - - accept_data = - File.read!("test/fixtures/mastodon-accept-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - |> Map.put("object", follow_activity.data["id"]) - - {:ok, activity} = Transmogrifier.handle_incoming(accept_data) - assert activity.data["object"] == follow_activity.data["id"] - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == true - - follower = User.get_by_id(follower.id) - assert follower.following_count == 1 - - followed = User.get_by_id(followed.id) - assert followed.follower_count == 1 - end - - test "it fails for incoming accepts which cannot be correlated" do - follower = insert(:user) - followed = insert(:user, locked: true) - - accept_data = - File.read!("test/fixtures/mastodon-accept-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - accept_data = - Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - - :error = Transmogrifier.handle_incoming(accept_data) - - follower = User.get_cached_by_id(follower.id) - - refute User.following?(follower, followed) == true - end - test "it fails for incoming rejects which cannot be correlated" do follower = insert(:user) followed = insert(:user, locked: true) From 8f9fbc86c0dcf307b87b38d218b36df2f9f35a7f Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 14:02:09 +0200 Subject: [PATCH 02/13] Transmogrifier: Small readability changes. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 6 ++++-- test/web/activity_pub/transmogrifier/undo_handling_test.exs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 2f04cc6ff..fe016e720 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -661,7 +661,8 @@ def handle_incoming( ) when type in ~w{Update Block Follow} do with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), - {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do + {:ok, activity, _} <- + Pipeline.common_pipeline(data, local: false) do {:ok, activity} end end @@ -670,7 +671,8 @@ def handle_incoming( %{"type" => "Delete"} = data, _options ) do - with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do + with {:ok, activity, _} <- + Pipeline.common_pipeline(data, local: false) do {:ok, activity} else {:error, {:validate_object, _}} = e -> diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs index 01dd6c370..8683f7135 100644 --- a/test/web/activity_pub/transmogrifier/undo_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -130,7 +130,7 @@ test "it works for incoming unannounces with an existing notice" do "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" end - test "it works for incomming unfollows with an existing follow" do + test "it works for incoming unfollows with an existing follow" do user = insert(:user) follow_data = From f1a0c10b17ff20a5ebbd070dc38aaedf82f8fe2e Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 15:13:07 +0200 Subject: [PATCH 03/13] AcceptValidator: Add basic validator with tests. --- lib/pleroma/web/activity_pub/builder.ex | 13 ++++++ .../web/activity_pub/object_validator.ex | 11 +++++ .../object_validators/accept_validator.ex | 42 ++++++++++++++++++ .../accept_validation_test.exs | 44 +++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/object_validators/accept_validator.ex create mode 100644 test/web/activity_pub/object_validators/accept_validation_test.exs diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 1b4c421b8..e1f88e6cc 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do require Pleroma.Constants + @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()} + def accept(actor, accepted_activity) do + data = %{ + "id" => Utils.generate_activity_id(), + "actor" => actor.ap_id, + "type" => "Accept", + "object" => accepted_activity.data["id"], + "to" => [accepted_activity.actor] + } + + {:ok, data, []} + end + @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()} def follow(follower, followed) do data = %{ diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index e1114a44d..d9dd2bc30 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator @@ -30,6 +31,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) + def validate(%{"type" => "Accept"} = object, meta) do + with {:ok, object} <- + object + |> AcceptValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + def validate(%{"type" => "Follow"} = object, meta) do with {:ok, object} <- object diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex new file mode 100644 index 000000000..b81e078e3 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator do + use Ecto.Schema + + alias Pleroma.EctoType.ActivityPub.ObjectValidators + + import Ecto.Changeset + import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + + @primary_key false + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:type, :string) + field(:object, ObjectValidators.ObjectID) + field(:actor, ObjectValidators.ObjectID) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, __schema__(:fields)) + end + + def validate_data(cng) do + cng + |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_inclusion(:type, ["Accept"]) + |> validate_actor_presence() + |> validate_object_presence() + end + + def cast_and_validate(data) do + data + |> cast_data + |> validate_data + end +end diff --git a/test/web/activity_pub/object_validators/accept_validation_test.exs b/test/web/activity_pub/object_validators/accept_validation_test.exs new file mode 100644 index 000000000..7f5dc14af --- /dev/null +++ b/test/web/activity_pub/object_validators/accept_validation_test.exs @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidationTest do + use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Pipeline + alias Pleroma.Web.ActivityPub.ObjectValidator + + import Pleroma.Factory + + setup do + follower = insert(:user) + followed = insert(:user, local: false) + + {:ok, follow_data, _} = Builder.follow(follower, followed) + {:ok, follow_activity, _} = Pipeline.common_pipeline(follow_data, local: true) + + {:ok, accept_data, _} = Builder.accept(followed, follow_activity) + + %{accept_data: accept_data, followed: followed} + end + + test "it validates a basic 'accept'", %{accept_data: accept_data} do + assert {:ok, _, _} = ObjectValidator.validate(accept_data, []) + end + + test "it fails when the actor doesn't exist", %{accept_data: accept_data} do + accept_data = + accept_data + |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + + assert {:error, _} = ObjectValidator.validate(accept_data, []) + end + + test "it fails when the accepted activity doesn't exist", %{accept_data: accept_data} do + accept_data = + accept_data + |> Map.put("object", "https://gensokyo.2hu/users/raymoo/follows/1") + + assert {:error, _} = ObjectValidator.validate(accept_data, []) + end +end From 8b1e8bec2ffcb3a73eea93015d73b44c4996baff Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 15:32:00 +0200 Subject: [PATCH 04/13] AcceptValidation: Codify accept rules. --- .../object_validators/accept_validator.ex | 16 +++++++++++++++- .../object_validators/accept_validation_test.exs | 11 +++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex index b81e078e3..6d0fa669a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator do use Ecto.Schema alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Activity import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -31,7 +32,8 @@ def validate_data(cng) do |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Accept"]) |> validate_actor_presence() - |> validate_object_presence() + |> validate_object_presence(allowed_types: ["Follow"]) + |> validate_accept_rights() end def cast_and_validate(data) do @@ -39,4 +41,16 @@ def cast_and_validate(data) do |> cast_data |> validate_data end + + def validate_accept_rights(cng) do + with object_id when is_binary(object_id) <- get_field(cng, :object), + %Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id), + true <- followed_actor == get_field(cng, :actor) do + cng + else + _e -> + cng + |> add_error(:actor, "can't accept the given activity") + end + end end diff --git a/test/web/activity_pub/object_validators/accept_validation_test.exs b/test/web/activity_pub/object_validators/accept_validation_test.exs index 7f5dc14af..2d5d18046 100644 --- a/test/web/activity_pub/object_validators/accept_validation_test.exs +++ b/test/web/activity_pub/object_validators/accept_validation_test.exs @@ -41,4 +41,15 @@ test "it fails when the accepted activity doesn't exist", %{accept_data: accept_ assert {:error, _} = ObjectValidator.validate(accept_data, []) end + + test "for an accepted follow, it only validates if the actor of the accept is the followed actor", + %{accept_data: accept_data} do + stranger = insert(:user) + + accept_data = + accept_data + |> Map.put("actor", stranger.ap_id) + + assert {:error, _} = ObjectValidator.validate(accept_data, []) + end end From da3f9b9988d2cee4baa6018e6450b2d6027e1ce3 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 15:41:19 +0200 Subject: [PATCH 05/13] Transmogrifier: Remove handling of orphaned accepts This was a Mastodon 2.3 issue and has been fixed for a long time. According to fediverse.networks, less than one percent of servers still run a version this old or older. --- .../web/activity_pub/transmogrifier.ex | 18 +-------------- .../transmogrifier/accept_handling_test.exs | 22 ------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index fe016e720..5ea97e9b7 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -391,27 +391,11 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = objec defp fix_content(object), do: object - defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do - with true <- id =~ "follows", - %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id), - %Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do - {:ok, activity} - else - _ -> {:error, nil} - end - end - - defp mastodon_follow_hack(_, _), do: {:error, nil} - - defp get_follow_activity(follow_object, followed) do + defp get_follow_activity(follow_object, _followed) do with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object), {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do {:ok, activity} else - # Can't find the activity. This might a Mastodon 2.3 "Accept" - {:activity, nil} -> - mastodon_follow_hack(follow_object, followed) - _ -> {:error, nil} end diff --git a/test/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/web/activity_pub/transmogrifier/accept_handling_test.exs index 3c4e134ff..bc4cc227d 100644 --- a/test/web/activity_pub/transmogrifier/accept_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/accept_handling_test.exs @@ -44,28 +44,6 @@ test "it works for incoming accepts which were pre-accepted" do assert User.following?(follower, followed) == true end - test "it works for incoming accepts which were orphaned" do - follower = insert(:user) - followed = insert(:user, locked: true) - - {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) - - accept_data = - File.read!("test/fixtures/mastodon-accept-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - accept_data = - Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - - {:ok, activity} = Transmogrifier.handle_incoming(accept_data) - assert activity.data["object"] == follow_activity.data["id"] - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == true - end - test "it works for incoming accepts which are referenced by IRI only" do follower = insert(:user) followed = insert(:user, locked: true) From 3f6d50111e57a942ecc24d4aa7cdbec23b95dfec Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 16:07:42 +0200 Subject: [PATCH 06/13] Linter fixes. --- .../web/activity_pub/object_validators/accept_validator.ex | 2 +- .../activity_pub/object_validators/accept_validation_test.exs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex index 6d0fa669a..fd75f4b6e 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator do use Ecto.Schema - alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Activity + alias Pleroma.EctoType.ActivityPub.ObjectValidators import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations diff --git a/test/web/activity_pub/object_validators/accept_validation_test.exs b/test/web/activity_pub/object_validators/accept_validation_test.exs index 2d5d18046..d6111ba41 100644 --- a/test/web/activity_pub/object_validators/accept_validation_test.exs +++ b/test/web/activity_pub/object_validators/accept_validation_test.exs @@ -4,9 +4,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidationTest do use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.Builder - alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.Pipeline import Pleroma.Factory From 9dda13bfa193be8ab5d9b2c117f7a50aaba451e1 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 16:22:15 +0200 Subject: [PATCH 07/13] Transmogrifier Test: Remove mastodon hack test. --- test/web/activity_pub/transmogrifier_test.exs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 52b4178bf..13da864d1 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -422,32 +422,6 @@ test "it fails for incoming rejects which cannot be correlated" do refute User.following?(follower, followed) == true end - test "it works for incoming rejects which are orphaned" do - follower = insert(:user) - followed = insert(:user, locked: true) - - {:ok, follower} = User.follow(follower, followed) - {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed) - - assert User.following?(follower, followed) == true - - reject_data = - File.read!("test/fixtures/mastodon-reject-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - reject_data = - Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id)) - - {:ok, activity} = Transmogrifier.handle_incoming(reject_data) - refute activity.local - assert activity.data["id"] == reject_data["id"] - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == false - end - test "it works for incoming rejects which are referenced by IRI only" do follower = insert(:user) followed = insert(:user, locked: true) From f988d82e463d2c08fa2cc22dc6ee733ee8668671 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 17:26:01 +0200 Subject: [PATCH 08/13] Transmogrifier: Handle accepts with the pipeline --- lib/pleroma/web/activity_pub/side_effects.ex | 28 ++++++++++++++++ .../web/activity_pub/transmogrifier.ex | 32 +------------------ .../transmogrifier/accept_handling_test.exs | 2 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5104d38ee..3ba7eaf9e 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -21,9 +21,37 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Web.Push alias Pleroma.Web.Streamer alias Pleroma.Workers.BackgroundWorker + alias Pleroma.FollowingRelationship def handle(object, meta \\ []) + # Task this handles + # - Follows + # - Sends a notification + def handle( + %{ + data: %{ + "actor" => actor, + "type" => "Accept", + "object" => follow_activity_id + } + } = object, + meta + ) do + with %Activity{actor: follower_id} = follow_activity <- + Activity.get_by_ap_id(follow_activity_id), + %User{} = followed <- User.get_cached_by_ap_id(actor), + %User{} = follower <- User.get_cached_by_ap_id(follower_id), + {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), + {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do + Notification.update_notification_type(followed, follow_activity) + User.update_follower_count(followed) + User.update_following_count(follower) + end + + {:ok, object, meta} + end + # Tasks this handle # - Follows if possible # - Sends a notification diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 5ea97e9b7..24da1ef9c 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.FollowingRelationship alias Pleroma.Maps - alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Repo @@ -535,35 +534,6 @@ def handle_incoming( end end - def handle_incoming( - %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data, - _options - ) do - with actor <- Containment.get_actor(data), - {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), - {:ok, follow_activity} <- get_follow_activity(follow_object, followed), - {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do - User.update_follower_count(followed) - User.update_following_count(follower) - - Notification.update_notification_type(followed, follow_activity) - - ActivityPub.accept(%{ - to: follow_activity.data["to"], - type: "Accept", - actor: followed, - object: follow_activity.data["id"], - local: false, - activity_id: id - }) - else - _e -> - :error - end - end - def handle_incoming( %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data, _options @@ -643,7 +613,7 @@ def handle_incoming( %{"type" => type} = data, _options ) - when type in ~w{Update Block Follow} do + when type in ~w{Update Block Follow Accept} do with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do diff --git a/test/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/web/activity_pub/transmogrifier/accept_handling_test.exs index bc4cc227d..77d468f5c 100644 --- a/test/web/activity_pub/transmogrifier/accept_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/accept_handling_test.exs @@ -82,7 +82,7 @@ test "it fails for incoming accepts which cannot be correlated" do accept_data = Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - :error = Transmogrifier.handle_incoming(accept_data) + {:error, _} = Transmogrifier.handle_incoming(accept_data) follower = User.get_cached_by_id(follower.id) From 25bfee0d12d6ee096bba169089cc57c91efd7bc3 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 17:43:16 +0200 Subject: [PATCH 09/13] ActivityPub: Remove ActivityPub.accept Switch to the pipeline in CommonAPI and SideEffects --- lib/pleroma/web/activity_pub/activity_pub.ex | 5 ----- lib/pleroma/web/activity_pub/side_effects.ex | 15 +++------------ lib/pleroma/web/common_api/common_api.ex | 13 ++----------- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index fe62673dc..6dd94119b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -285,11 +285,6 @@ def listen(%{to: to, actor: actor, context: context, object: object} = params) d end end - @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()} - def accept(params) do - accept_or_reject("Accept", params) - end - @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()} def reject(params) do accept_or_reject("Reject", params) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 3ba7eaf9e..4228041e7 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -16,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Push @@ -72,18 +73,8 @@ def handle( {_, {:ok, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do if followed.local && !followed.locked do - Utils.update_follow_state_for_all(object, "accept") - FollowingRelationship.update(follower, followed, :follow_accept) - User.update_follower_count(followed) - User.update_following_count(follower) - - %{ - to: [following_user], - actor: followed, - object: follow_id, - local: true - } - |> ActivityPub.accept() + {:ok, accept_data, _} = Builder.accept(followed, object) + {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) end else {:following, {:error, _}, follower, followed} -> diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index c08e0ffeb..7b08c19a8 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -122,17 +122,8 @@ def unfollow(follower, unfollowed) do def accept_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follower} <- User.follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept), - {:ok, _activity} <- - ActivityPub.accept(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Accept" - }) do - Notification.update_notification_type(followed, follow_activity) + {:ok, accept_data, _} <- Builder.accept(followed, follow_activity), + {:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do {:ok, follower} end end From 500576dcb623bdc29193e3b372837c581e151755 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 11 Aug 2020 19:22:14 +0200 Subject: [PATCH 10/13] Linting fixes. --- lib/pleroma/web/activity_pub/side_effects.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4228041e7..e1fa75e1c 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -22,7 +22,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Web.Push alias Pleroma.Web.Streamer alias Pleroma.Workers.BackgroundWorker - alias Pleroma.FollowingRelationship def handle(object, meta \\ []) From 62f7cca9a1e3f6c6685094eb3618876d4b6ca3a7 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 12 Aug 2020 13:39:54 +0200 Subject: [PATCH 11/13] Transmogrifier Tests: Extract rejections. --- .../transmogrifier/follow_handling_test.exs | 19 +++++ .../transmogrifier/reject_handling_test.exs | 67 +++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 72 ------------------- 3 files changed, 86 insertions(+), 72 deletions(-) create mode 100644 test/web/activity_pub/transmogrifier/reject_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs index 17e764ca1..757d90941 100644 --- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -185,5 +185,24 @@ test "it works for incoming follow requests from hubzilla" do assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2" assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) end + + test "it works for incoming follows to locked account" do + pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + user = insert(:user, locked: true) + + data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Follow" + assert data["object"] == user.ap_id + assert data["state"] == "pending" + assert data["actor"] == "http://mastodon.example.org/users/admin" + + assert [^pending_follower] = User.get_follow_requests(user) + end end end diff --git a/test/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/web/activity_pub/transmogrifier/reject_handling_test.exs new file mode 100644 index 000000000..5e5248641 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/reject_handling_test.exs @@ -0,0 +1,67 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it fails for incoming rejects which cannot be correlated" do + follower = insert(:user) + followed = insert(:user, locked: true) + + accept_data = + File.read!("test/fixtures/mastodon-reject-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + + accept_data = + Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) + + :error = Transmogrifier.handle_incoming(accept_data) + + follower = User.get_cached_by_id(follower.id) + + refute User.following?(follower, followed) == true + end + + test "it works for incoming rejects which are referenced by IRI only" do + follower = insert(:user) + followed = insert(:user, locked: true) + + {:ok, follower} = User.follow(follower, followed) + {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) + + assert User.following?(follower, followed) == true + + reject_data = + File.read!("test/fixtures/mastodon-reject-activity.json") + |> Poison.decode!() + |> Map.put("actor", followed.ap_id) + |> Map.put("object", follow_activity.data["id"]) + + {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) + + follower = User.get_cached_by_id(follower.id) + + assert User.following?(follower, followed) == false + end + + test "it rejects activities without a valid ID" do + user = insert(:user) + + data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + |> Map.put("id", "") + + :error = Transmogrifier.handle_incoming(data) + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 13da864d1..0dd4e6e47 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -384,78 +384,6 @@ test "it works for incoming unfollows with an existing follow" do refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) end - test "it works for incoming follows to locked account" do - pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") - user = insert(:user, locked: true) - - data = - File.read!("test/fixtures/mastodon-follow-activity.json") - |> Poison.decode!() - |> Map.put("object", user.ap_id) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["type"] == "Follow" - assert data["object"] == user.ap_id - assert data["state"] == "pending" - assert data["actor"] == "http://mastodon.example.org/users/admin" - - assert [^pending_follower] = User.get_follow_requests(user) - end - - test "it fails for incoming rejects which cannot be correlated" do - follower = insert(:user) - followed = insert(:user, locked: true) - - accept_data = - File.read!("test/fixtures/mastodon-reject-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - - accept_data = - Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - - :error = Transmogrifier.handle_incoming(accept_data) - - follower = User.get_cached_by_id(follower.id) - - refute User.following?(follower, followed) == true - end - - test "it works for incoming rejects which are referenced by IRI only" do - follower = insert(:user) - followed = insert(:user, locked: true) - - {:ok, follower} = User.follow(follower, followed) - {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) - - assert User.following?(follower, followed) == true - - reject_data = - File.read!("test/fixtures/mastodon-reject-activity.json") - |> Poison.decode!() - |> Map.put("actor", followed.ap_id) - |> Map.put("object", follow_activity.data["id"]) - - {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) - - follower = User.get_cached_by_id(follower.id) - - assert User.following?(follower, followed) == false - end - - test "it rejects activities without a valid ID" do - user = insert(:user) - - data = - File.read!("test/fixtures/mastodon-follow-activity.json") - |> Poison.decode!() - |> Map.put("object", user.ap_id) - |> Map.put("id", "") - - :error = Transmogrifier.handle_incoming(data) - end - test "skip converting the content when it is nil" do object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" From 7224bf309ef38a80898d7e560e96fbc2895737be Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 12 Aug 2020 14:48:51 +0200 Subject: [PATCH 12/13] Transmogrifier: Move Rejects to the Pipeline --- lib/pleroma/web/activity_pub/builder.ex | 19 +++++-- .../web/activity_pub/object_validator.ex | 7 ++- ...alidator.ex => accept_reject_validator.ex} | 10 ++-- lib/pleroma/web/activity_pub/side_effects.ex | 24 ++++++++ .../web/activity_pub/transmogrifier.ex | 38 +------------ .../reject_validation_test.exs | 56 +++++++++++++++++++ .../transmogrifier/reject_handling_test.exs | 2 +- 7 files changed, 105 insertions(+), 51 deletions(-) rename lib/pleroma/web/activity_pub/object_validators/{accept_validator.ex => accept_reject_validator.ex} (82%) create mode 100644 test/web/activity_pub/object_validators/reject_validation_test.exs diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index e1f88e6cc..f2392ce79 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -14,19 +14,28 @@ defmodule Pleroma.Web.ActivityPub.Builder do require Pleroma.Constants - @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()} - def accept(actor, accepted_activity) do + def accept_or_reject(actor, activity, type) do data = %{ "id" => Utils.generate_activity_id(), "actor" => actor.ap_id, - "type" => "Accept", - "object" => accepted_activity.data["id"], - "to" => [accepted_activity.actor] + "type" => type, + "object" => activity.data["id"], + "to" => [activity.actor] } {:ok, data, []} end + @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()} + def reject(actor, rejected_activity) do + accept_or_reject(actor, rejected_activity, "Reject") + end + + @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()} + def accept(actor, accepted_activity) do + accept_or_reject(actor, accepted_activity, "Accept") + end + @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()} def follow(follower, followed) do data = %{ diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index d9dd2bc30..3f1dffe2b 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator @@ -31,10 +31,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) - def validate(%{"type" => "Accept"} = object, meta) do + def validate(%{"type" => type} = object, meta) + when type in ~w[Accept Reject] do with {:ok, object} <- object - |> AcceptValidator.cast_and_validate() + |> AcceptRejectValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex similarity index 82% rename from lib/pleroma/web/activity_pub/object_validators/accept_validator.ex rename to lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex index fd75f4b6e..179beda58 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptValidator do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do use Ecto.Schema alias Pleroma.Activity @@ -30,10 +30,10 @@ def cast_data(data) do def validate_data(cng) do cng |> validate_required([:id, :type, :actor, :to, :cc, :object]) - |> validate_inclusion(:type, ["Accept"]) + |> validate_inclusion(:type, ["Accept", "Reject"]) |> validate_actor_presence() |> validate_object_presence(allowed_types: ["Follow"]) - |> validate_accept_rights() + |> validate_accept_reject_rights() end def cast_and_validate(data) do @@ -42,7 +42,7 @@ def cast_and_validate(data) do |> validate_data end - def validate_accept_rights(cng) do + def validate_accept_reject_rights(cng) do with object_id when is_binary(object_id) <- get_field(cng, :object), %Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id), true <- followed_actor == get_field(cng, :actor) do @@ -50,7 +50,7 @@ def validate_accept_rights(cng) do else _e -> cng - |> add_error(:actor, "can't accept the given activity") + |> add_error(:actor, "can't accept or reject the given activity") end end end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index e1fa75e1c..a4ad12d53 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -52,6 +52,30 @@ def handle( {:ok, object, meta} end + # Task this handles + # - Rejects all existing follow activities for this person + # - Updates the follow state + def handle( + %{ + data: %{ + "actor" => actor, + "type" => "Reject", + "object" => follow_activity_id + } + } = object, + meta + ) do + with %Activity{actor: follower_id} = follow_activity <- + Activity.get_by_ap_id(follow_activity_id), + %User{} = followed <- User.get_cached_by_ap_id(actor), + %User{} = follower <- User.get_cached_by_ap_id(follower_id), + {:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do + FollowingRelationship.update(follower, followed, :follow_reject) + end + + {:ok, object, meta} + end + # Tasks this handle # - Follows if possible # - Sends a notification diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 24da1ef9c..544f3f3b6 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Activity alias Pleroma.EarmarkRenderer alias Pleroma.EctoType.ActivityPub.ObjectValidators - alias Pleroma.FollowingRelationship alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment @@ -390,16 +389,6 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = objec defp fix_content(object), do: object - defp get_follow_activity(follow_object, _followed) do - with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object), - {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do - {:ok, activity} - else - _ -> - {:error, nil} - end - end - # Reduce the object list to find the reported user. defp get_reported(objects) do Enum.reduce_while(objects, nil, fn ap_id, _ -> @@ -534,31 +523,6 @@ def handle_incoming( end end - def handle_incoming( - %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data, - _options - ) do - with actor <- Containment.get_actor(data), - {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), - {:ok, follow_activity} <- get_follow_activity(follow_object, followed), - {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), - %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), - {:ok, activity} <- - ActivityPub.reject(%{ - to: follow_activity.data["to"], - type: "Reject", - actor: followed, - object: follow_activity.data["id"], - local: false, - activity_id: id - }) do - {:ok, activity} - else - _e -> :error - end - end - @misskey_reactions %{ "like" => "👍", "love" => "❤️", @@ -613,7 +577,7 @@ def handle_incoming( %{"type" => type} = data, _options ) - when type in ~w{Update Block Follow Accept} do + when type in ~w{Update Block Follow Accept Reject} do with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do diff --git a/test/web/activity_pub/object_validators/reject_validation_test.exs b/test/web/activity_pub/object_validators/reject_validation_test.exs new file mode 100644 index 000000000..370bb6e5c --- /dev/null +++ b/test/web/activity_pub/object_validators/reject_validation_test.exs @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.RejectValidationTest do + use Pleroma.DataCase + + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.Pipeline + + import Pleroma.Factory + + setup do + follower = insert(:user) + followed = insert(:user, local: false) + + {:ok, follow_data, _} = Builder.follow(follower, followed) + {:ok, follow_activity, _} = Pipeline.common_pipeline(follow_data, local: true) + + {:ok, reject_data, _} = Builder.reject(followed, follow_activity) + + %{reject_data: reject_data, followed: followed} + end + + test "it validates a basic 'reject'", %{reject_data: reject_data} do + assert {:ok, _, _} = ObjectValidator.validate(reject_data, []) + end + + test "it fails when the actor doesn't exist", %{reject_data: reject_data} do + reject_data = + reject_data + |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + + assert {:error, _} = ObjectValidator.validate(reject_data, []) + end + + test "it fails when the rejected activity doesn't exist", %{reject_data: reject_data} do + reject_data = + reject_data + |> Map.put("object", "https://gensokyo.2hu/users/raymoo/follows/1") + + assert {:error, _} = ObjectValidator.validate(reject_data, []) + end + + test "for an rejected follow, it only validates if the actor of the reject is the followed actor", + %{reject_data: reject_data} do + stranger = insert(:user) + + reject_data = + reject_data + |> Map.put("actor", stranger.ap_id) + + assert {:error, _} = ObjectValidator.validate(reject_data, []) + end +end diff --git a/test/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/web/activity_pub/transmogrifier/reject_handling_test.exs index 5e5248641..7592fbe1c 100644 --- a/test/web/activity_pub/transmogrifier/reject_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/reject_handling_test.exs @@ -24,7 +24,7 @@ test "it fails for incoming rejects which cannot be correlated" do accept_data = Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id)) - :error = Transmogrifier.handle_incoming(accept_data) + {:error, _} = Transmogrifier.handle_incoming(accept_data) follower = User.get_cached_by_id(follower.id) From 2e347e8286fab13075e6e39e64e56cb3ba14e7e8 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 12 Aug 2020 15:07:46 +0200 Subject: [PATCH 13/13] ActivityPub: Remove `reject`, move everything to the Pipeline. --- lib/pleroma/web/activity_pub/activity_pub.ex | 21 -------------------- lib/pleroma/web/activity_pub/side_effects.ex | 18 +++++------------ lib/pleroma/web/common_api/common_api.ex | 14 ++----------- 3 files changed, 7 insertions(+), 46 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 6dd94119b..bde1fe708 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -285,27 +285,6 @@ def listen(%{to: to, actor: actor, context: context, object: object} = params) d end end - @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()} - def reject(params) do - accept_or_reject("Reject", params) - end - - @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()} - defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do - local = Map.get(params, :local, true) - activity_id = Map.get(params, :activity_id, nil) - - data = - %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object} - |> Maps.put_if_present("id", activity_id) - - with {:ok, activity} <- insert(data, local), - _ <- notify_and_stream(activity), - :ok <- maybe_federate(activity) do - {:ok, activity} - end - end - @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) :: {:ok, Activity.t()} | nil | {:error, any()} def unfollow(follower, followed, activity_id \\ nil, local \\ true) do diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a4ad12d53..14a1da0c1 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -55,6 +55,7 @@ def handle( # Task this handles # - Rejects all existing follow activities for this person # - Updates the follow state + # - Dismisses notificatios def handle( %{ data: %{ @@ -71,6 +72,7 @@ def handle( %User{} = follower <- User.get_cached_by_ap_id(follower_id), {:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do FollowingRelationship.update(follower, followed, :follow_reject) + Notification.dismiss(follow_activity) end {:ok, object, meta} @@ -100,19 +102,9 @@ def handle( {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) end else - {:following, {:error, _}, follower, followed} -> - Utils.update_follow_state_for_all(object, "reject") - FollowingRelationship.update(follower, followed, :follow_reject) - - if followed.local do - %{ - to: [follower.ap_id], - actor: followed, - object: follow_id, - local: true - } - |> ActivityPub.reject() - end + {:following, {:error, _}, _follower, followed} -> + {:ok, reject_data, _} = Builder.reject(followed, object) + {:ok, _activity, _} = Pipeline.common_pipeline(reject_data, local: true) _ -> nil diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 7b08c19a8..a8141b28f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -6,9 +6,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation - alias Pleroma.FollowingRelationship alias Pleroma.Formatter - alias Pleroma.Notification alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -130,16 +128,8 @@ def accept_follow_request(follower, followed) do def reject_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), - {:ok, _notifications} <- Notification.dismiss(follow_activity), - {:ok, _activity} <- - ActivityPub.reject(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Reject" - }) do + {:ok, reject_data, _} <- Builder.reject(followed, follow_activity), + {:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do {:ok, follower} end end