aboutsummaryrefslogtreecommitdiff
path: root/test/pleroma/web/activity_pub/mrf
diff options
context:
space:
mode:
Diffstat (limited to 'test/pleroma/web/activity_pub/mrf')
-rw-r--r--test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs84
-rw-r--r--test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs107
-rw-r--r--test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs191
-rw-r--r--test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs141
-rw-r--r--test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs126
-rw-r--r--test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs60
-rw-r--r--test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs259
-rw-r--r--test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs101
-rw-r--r--test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs92
-rw-r--r--test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs356
-rw-r--r--test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs97
-rw-r--r--test/pleroma/web/activity_pub/mrf/mention_policy_test.exs96
-rw-r--r--test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs177
-rw-r--r--test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs78
-rw-r--r--test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs77
-rw-r--r--test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs148
-rw-r--r--test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs100
-rw-r--r--test/pleroma/web/activity_pub/mrf/simple_policy_test.exs579
-rw-r--r--test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs122
-rw-r--r--test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs33
-rw-r--r--test/pleroma/web/activity_pub/mrf/tag_policy_test.exs159
-rw-r--r--test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs31
-rw-r--r--test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs106
23 files changed, 3320 insertions, 0 deletions
diff --git a/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs
new file mode 100644
index 0000000..817eba3
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs
@@ -0,0 +1,84 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
+
+ @id Pleroma.Web.Endpoint.url() <> "/activities/cofe"
+ @local_actor Pleroma.Web.Endpoint.url() <> "/users/cofe"
+
+ test "adds `expires_at` property" do
+ assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "actor" => @local_actor,
+ "type" => "Create",
+ "object" => %{"type" => "Note"}
+ })
+
+ assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364
+ end
+
+ test "keeps existing `expires_at` if it less than the config setting" do
+ expires_at = DateTime.utc_now() |> Timex.shift(days: 1)
+
+ assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "actor" => @local_actor,
+ "type" => "Create",
+ "expires_at" => expires_at,
+ "object" => %{"type" => "Note"}
+ })
+ end
+
+ test "overwrites existing `expires_at` if it greater than the config setting" do
+ too_distant_future = DateTime.utc_now() |> Timex.shift(years: 2)
+
+ assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "actor" => @local_actor,
+ "type" => "Create",
+ "expires_at" => too_distant_future,
+ "object" => %{"type" => "Note"}
+ })
+
+ assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364
+ end
+
+ test "ignores remote activities" do
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "actor" => "https://example.com/users/cofe",
+ "type" => "Create",
+ "object" => %{"type" => "Note"}
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+ end
+
+ test "ignores non-Create/Note activities" do
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "actor" => "https://example.com/users/cofe",
+ "type" => "Follow"
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "actor" => "https://example.com/users/cofe",
+ "type" => "Create",
+ "object" => %{"type" => "Cofe"}
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
new file mode 100644
index 0000000..732a5a7
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do
+ use Pleroma.DataCase, async: true
+ import Pleroma.Factory
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy
+
+ describe "blocking based on attributes" do
+ test "matches followbots by nickname" do
+ actor = insert(:user, %{nickname: "followbot@example.com"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "matches followbots by display name" do
+ actor = insert(:user, %{name: "Federation Bot"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "matches followbots by actor_type" do
+ actor = insert(:user, %{actor_type: "Service"})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
+ end
+ end
+
+ describe "it allows" do
+ test "non-followbots" do
+ actor = insert(:user)
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+
+ test "bots if the target follows the bots" do
+ actor = insert(:user, %{actor_type: "Service"})
+ target = insert(:user)
+
+ User.follow(target, actor)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+ end
+
+ test "it gracefully handles nil display names" do
+ actor = insert(:user, %{name: nil})
+ target = insert(:user)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Follow",
+ "actor" => actor.ap_id,
+ "object" => target.ap_id,
+ "id" => "https://example.com/activities/1234"
+ }
+
+ {:ok, _} = AntiFollowbotPolicy.filter(message)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
new file mode 100644
index 0000000..303d7ca
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
@@ -0,0 +1,191 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ import ExUnit.CaptureLog
+
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy
+
+ @linkless_message %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "hi world!"
+ }
+ }
+
+ @linkful_message %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "<a href='https://example.com'>hi world!</a>"
+ }
+ }
+
+ @response_message %{
+ "type" => "Create",
+ "object" => %{
+ "name" => "yes",
+ "type" => "Answer"
+ }
+ }
+
+ describe "with new user" do
+ test "it allows posts without links" do
+ user = insert(:user, local: false)
+
+ assert user.note_count == 0
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it disallows posts with links" do
+ user = insert(:user, local: false)
+
+ assert user.note_count == 0
+
+ message = %{
+ "type" => "Create",
+ "actor" => user.ap_id,
+ "object" => %{
+ "formerRepresentations" => %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ %{
+ "content" => "<a href='https://example.com'>hi world!</a>"
+ }
+ ]
+ },
+ "content" => "mew"
+ }
+ }
+
+ {:reject, _} = MRF.filter_one(AntiLinkSpamPolicy, message)
+ end
+
+ test "it allows posts with links for local users" do
+ user = insert(:user)
+
+ assert user.note_count == 0
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it disallows posts with links in history" do
+ user = insert(:user, local: false)
+
+ assert user.note_count == 0
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with old user" do
+ test "it allows posts without links" do
+ user = insert(:user, note_count: 1)
+
+ assert user.note_count == 1
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it allows posts with links" do
+ user = insert(:user, note_count: 1)
+
+ assert user.note_count == 1
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with followed new user" do
+ test "it allows posts without links" do
+ user = insert(:user, follower_count: 1)
+
+ assert user.follower_count == 1
+
+ message =
+ @linkless_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+
+ test "it allows posts with links" do
+ user = insert(:user, follower_count: 1)
+
+ assert user.follower_count == 1
+
+ message =
+ @linkful_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+
+ describe "with unknown actors" do
+ setup do
+ Tesla.Mock.mock(fn
+ %{method: :get, url: "http://invalid.actor"} ->
+ %Tesla.Env{status: 500, body: ""}
+ end)
+
+ :ok
+ end
+
+ test "it rejects posts without links" do
+ message =
+ @linkless_message
+ |> Map.put("actor", "http://invalid.actor")
+
+ assert capture_log(fn ->
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end
+
+ test "it rejects posts with links" do
+ message =
+ @linkful_message
+ |> Map.put("actor", "http://invalid.actor")
+
+ assert capture_log(fn ->
+ {:reject, _} = AntiLinkSpamPolicy.filter(message)
+ end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end
+ end
+
+ describe "with contentless-objects" do
+ test "it does not reject them or error out" do
+ user = insert(:user, note_count: 1)
+
+ message =
+ @response_message
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _message} = AntiLinkSpamPolicy.filter(message)
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs
new file mode 100644
index 0000000..859e6f1
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs
@@ -0,0 +1,141 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.EnsureRePrepended
+
+ describe "rewrites summary" do
+ test "it adds `re:` to summary object when child summary and parent summary equal" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res["object"]["summary"] == "re: object-summary"
+ end
+
+ test "it adds `re:` to summary object when child summary containts re-subject of parent summary " do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "re: object-summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res["object"]["summary"] == "re: object-summary"
+ end
+
+ test "it adds `re:` to history" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}},
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}}
+ }
+ ]
+ }
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(EnsureRePrepended, message)
+ assert res["object"]["summary"] == "re: object-summary"
+
+ assert Enum.at(res["object"]["formerRepresentations"]["orderedItems"], 0)["summary"] ==
+ "re: object-summary"
+ end
+
+ test "it accepts Updates" do
+ message = %{
+ "type" => "Update",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}},
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}}
+ }
+ ]
+ }
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(EnsureRePrepended, message)
+ assert res["object"]["summary"] == "re: object-summary"
+
+ assert Enum.at(res["object"]["formerRepresentations"]["orderedItems"], 0)["summary"] ==
+ "re: object-summary"
+ end
+ end
+
+ describe "skip filter" do
+ test "it skip if type isn't 'Create' or 'Update'" do
+ message = %{
+ "type" => "Annotation",
+ "object" => %{"summary" => "object-summary"}
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if summary is empty" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if inReplyTo is empty" do
+ message = %{"type" => "Create", "object" => %{"summary" => "summary"}}
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skip if parent and child summary isn't equal" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "object-summary",
+ "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}}
+ }
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+
+ test "it skips if the object is only a reference" do
+ message = %{
+ "type" => "Create",
+ "object" => "somereference"
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
new file mode 100644
index 0000000..2481900
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
@@ -0,0 +1,126 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicyTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF.FollowBotPolicy
+
+ import Pleroma.Factory
+
+ describe "FollowBotPolicy" do
+ test "follows remote users" do
+ bot = insert(:user, actor_type: "Service")
+ remote_user = insert(:user, local: false)
+ clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [remote_user.follower_address],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "Test post",
+ "type" => "Note",
+ "attributedTo" => remote_user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => remote_user.ap_id
+ }
+
+ refute User.following?(bot, remote_user)
+
+ assert User.get_follow_requests(remote_user) |> length == 0
+
+ FollowBotPolicy.filter(message)
+
+ assert User.get_follow_requests(remote_user) |> length == 1
+ end
+
+ test "does not follow users with #nobot in bio" do
+ bot = insert(:user, actor_type: "Service")
+ remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
+ clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [remote_user.follower_address],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "I don't like follow bots",
+ "type" => "Note",
+ "attributedTo" => remote_user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => remote_user.ap_id
+ }
+
+ refute User.following?(bot, remote_user)
+
+ assert User.get_follow_requests(remote_user) |> length == 0
+
+ FollowBotPolicy.filter(message)
+
+ assert User.get_follow_requests(remote_user) |> length == 0
+ end
+
+ test "does not follow local users" do
+ bot = insert(:user, actor_type: "Service")
+ local_user = insert(:user, local: true)
+ clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [local_user.follower_address],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "Hi I'm a local user",
+ "type" => "Note",
+ "attributedTo" => local_user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => local_user.ap_id
+ }
+
+ refute User.following?(bot, local_user)
+
+ assert User.get_follow_requests(local_user) |> length == 0
+
+ FollowBotPolicy.filter(message)
+
+ assert User.get_follow_requests(local_user) |> length == 0
+ end
+
+ test "does not follow users requiring follower approval" do
+ bot = insert(:user, actor_type: "Service")
+ remote_user = insert(:user, %{local: false, is_locked: true})
+ clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+ message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "to" => [remote_user.follower_address],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "I don't like randos following me",
+ "type" => "Note",
+ "attributedTo" => remote_user.ap_id,
+ "inReplyTo" => nil
+ },
+ "actor" => remote_user.ap_id
+ }
+
+ refute User.following?(bot, remote_user)
+
+ assert User.get_follow_requests(remote_user) |> length == 0
+
+ FollowBotPolicy.filter(message)
+
+ assert User.get_follow_requests(remote_user) |> length == 0
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs b/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs
new file mode 100644
index 0000000..d1e9001
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicyTest do
+ use Pleroma.DataCase, async: true
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
+ defp generate_messages(actor) do
+ {%{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{},
+ "to" => [@public, "f"],
+ "cc" => [actor.follower_address, "d"]
+ },
+ %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]},
+ "to" => ["f", actor.follower_address],
+ "cc" => ["d", @public]
+ }}
+ end
+
+ test "removes from the federated timeline by nickname heuristics 1" do
+ actor = insert(:user, %{nickname: "annoying_ebooks@example.com"})
+
+ {message, except_message} = generate_messages(actor)
+
+ assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message}
+ end
+
+ test "removes from the federated timeline by nickname heuristics 2" do
+ actor = insert(:user, %{nickname: "cirnonewsnetworkbot@meow.cat"})
+
+ {message, except_message} = generate_messages(actor)
+
+ assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message}
+ end
+
+ test "removes from the federated timeline by actor type Application" do
+ actor = insert(:user, %{actor_type: "Application"})
+
+ {message, except_message} = generate_messages(actor)
+
+ assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message}
+ end
+
+ test "removes from the federated timeline by actor type Service" do
+ actor = insert(:user, %{actor_type: "Service"})
+
+ {message, except_message} = generate_messages(actor)
+
+ assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message}
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs
new file mode 100644
index 0000000..b349a4b
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs
@@ -0,0 +1,259 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do
+ use Pleroma.DataCase
+ require Pleroma.Constants
+
+ alias Pleroma.Constants
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "adds mentions to post content" do
+ [lain, coolboymew, dielan, hakui, fence] = [
+ insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false),
+ insert(:user,
+ ap_id: "https://shitposter.club/users/coolboymew",
+ nickname: "coolboymew@shitposter.club",
+ local: false
+ ),
+ insert(:user,
+ ap_id: "https://shitposter.club/users/dielan",
+ nickname: "dielan@shitposter.club",
+ local: false
+ ),
+ insert(:user,
+ ap_id: "https://tuusin.misono-ya.info/users/hakui",
+ nickname: "hakui@tuusin.misono-ya.info",
+ local: false
+ ),
+ insert(:user,
+ ap_id: "https://xyzzy.link/users/fence",
+ nickname: "fence@xyzzy.link",
+ local: false
+ )
+ ]
+
+ object = File.read!("test/fixtures/soapbox_no_mentions_in_content.json") |> Jason.decode!()
+
+ activity = %{
+ "type" => "Create",
+ "actor" => "https://gleasonator.com/users/alex",
+ "object" => object
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+
+ assert filtered ==
+ "<p><span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{dielan.id}\" href=\"https://shitposter.club/users/dielan\" rel=\"ugc\">@<span>dielan</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{coolboymew.id}\" href=\"https://shitposter.club/users/coolboymew\" rel=\"ugc\">@<span>coolboymew</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{fence.id}\" href=\"https://xyzzy.link/users/fence\" rel=\"ugc\">@<span>fence</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{hakui.id}\" href=\"https://tuusin.misono-ya.info/users/hakui\" rel=\"ugc\">@<span>hakui</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{lain.id}\" href=\"https://lain.com/users/lain\" rel=\"ugc\">@<span>lain</span></a></span> </span>Haha yeah, you can control who you reply to.</p>"
+ end
+
+ test "the replied-to user is sorted to the left" do
+ [mario, luigi, wario] = [
+ insert(:user, nickname: "mario"),
+ insert(:user, nickname: "luigi"),
+ insert(:user, nickname: "wario")
+ ]
+
+ {:ok, post1} = CommonAPI.post(mario, %{status: "Letsa go!"})
+
+ {:ok, post2} =
+ CommonAPI.post(luigi, %{status: "Oh yaah", in_reply_to_id: post1.id, to: [mario.ap_id]})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => wario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => wario.ap_id,
+ "content" => "WHA-HA!",
+ "to" => [
+ mario.ap_id,
+ luigi.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => Object.normalize(post2).data["id"]
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+
+ assert filtered ==
+ "<span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{luigi.id}\" href=\"#{luigi.ap_id}\" rel=\"ugc\">@<span>luigi</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{mario.id}\" href=\"#{mario.ap_id}\" rel=\"ugc\">@<span>mario</span></a></span> </span>WHA-HA!"
+ end
+
+ test "don't mention self" do
+ mario = insert(:user, nickname: "mario")
+
+ {:ok, post} = CommonAPI.post(mario, %{status: "Mama mia"})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => mario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => mario.ap_id,
+ "content" => "I'ma tired...",
+ "to" => [
+ mario.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+ assert filtered == "I'ma tired..."
+ end
+
+ test "don't mention in top-level posts" do
+ mario = insert(:user, nickname: "mario")
+ luigi = insert(:user, nickname: "luigi")
+
+ {:ok, post} = CommonAPI.post(mario, %{status: "Letsa go"})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => mario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => mario.ap_id,
+ "content" => "Mama mia!",
+ "to" => [
+ luigi.ap_id,
+ Constants.as_public()
+ ],
+ "quoteUrl" => Object.normalize(post).data["id"]
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+ assert filtered == "Mama mia!"
+ end
+
+ test "with markdown formatting" do
+ mario = insert(:user, nickname: "mario")
+ luigi = insert(:user, nickname: "luigi")
+
+ {:ok, post} = CommonAPI.post(luigi, %{status: "Mama mia"})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => mario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => mario.ap_id,
+ "content" => "<p>I'ma tired...</p>",
+ "to" => [
+ luigi.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+
+ assert filtered ==
+ "<p><span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{luigi.id}\" href=\"#{luigi.ap_id}\" rel=\"ugc\">@<span>luigi</span></a></span> </span>I'ma tired...</p>"
+ end
+
+ test "aware of history" do
+ mario = insert(:user, nickname: "mario")
+ wario = insert(:user, nickname: "wario")
+
+ {:ok, post1} = CommonAPI.post(mario, %{status: "Letsa go!"})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => wario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => wario.ap_id,
+ "content" => "WHA-HA!",
+ "to" => [
+ mario.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => post1.object.data["id"],
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "type" => "Note",
+ "actor" => wario.ap_id,
+ "content" => "WHA-HA!",
+ "to" => [
+ mario.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => post1.object.data["id"]
+ }
+ ]
+ }
+ }
+ }
+
+ expected =
+ "<span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{mario.id}\" href=\"#{mario.ap_id}\" rel=\"ugc\">@<span>mario</span></a></span> </span>WHA-HA!"
+
+ assert {:ok,
+ %{
+ "object" => %{
+ "content" => ^expected,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => ^expected}]}
+ }
+ }} = MRF.filter_one(ForceMentionsInContent, activity)
+ end
+
+ test "works with Updates" do
+ mario = insert(:user, nickname: "mario")
+ wario = insert(:user, nickname: "wario")
+
+ {:ok, post1} = CommonAPI.post(mario, %{status: "Letsa go!"})
+
+ activity = %{
+ "type" => "Update",
+ "actor" => wario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => wario.ap_id,
+ "content" => "WHA-HA!",
+ "to" => [
+ mario.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => post1.object.data["id"],
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "type" => "Note",
+ "actor" => wario.ap_id,
+ "content" => "WHA-HA!",
+ "to" => [
+ mario.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => post1.object.data["id"]
+ }
+ ]
+ }
+ }
+ }
+
+ expected =
+ "<span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{mario.id}\" href=\"#{mario.ap_id}\" rel=\"ugc\">@<span>mario</span></a></span> </span>WHA-HA!"
+
+ assert {:ok,
+ %{
+ "object" => %{
+ "content" => ^expected,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => ^expected}]}
+ }
+ }} = MRF.filter_one(ForceMentionsInContent, activity)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
new file mode 100644
index 0000000..32991c9
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
@@ -0,0 +1,101 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicyTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "it sets the sensitive property with relevant hashtags" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert modified["object"]["sensitive"]
+ end
+
+ test "it is history-aware" do
+ activity = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "hey",
+ "tag" => []
+ }
+ }
+
+ activity_data =
+ activity
+ |> put_in(
+ ["object", "formerRepresentations"],
+ %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ Map.put(
+ activity["object"],
+ "tag",
+ [%{"type" => "Hashtag", "name" => "#nsfw"}]
+ )
+ ]
+ }
+ )
+
+ {:ok, modified} =
+ Pleroma.Web.ActivityPub.MRF.filter_one(
+ Pleroma.Web.ActivityPub.MRF.HashtagPolicy,
+ activity_data
+ )
+
+ refute modified["object"]["sensitive"]
+ assert Enum.at(modified["object"]["formerRepresentations"]["orderedItems"], 0)["sensitive"]
+ end
+
+ test "it works with Update" do
+ activity = %{
+ "type" => "Update",
+ "object" => %{
+ "content" => "hey",
+ "tag" => []
+ }
+ }
+
+ activity_data =
+ activity
+ |> put_in(
+ ["object", "formerRepresentations"],
+ %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ Map.put(
+ activity["object"],
+ "tag",
+ [%{"type" => "Hashtag", "name" => "#nsfw"}]
+ )
+ ]
+ }
+ )
+
+ {:ok, modified} =
+ Pleroma.Web.ActivityPub.MRF.filter_one(
+ Pleroma.Web.ActivityPub.MRF.HashtagPolicy,
+ activity_data
+ )
+
+ refute modified["object"]["sensitive"]
+ assert Enum.at(modified["object"]["formerRepresentations"]["orderedItems"], 0)["sensitive"]
+ end
+
+ test "it doesn't sets the sensitive property with irrelevant hashtags" do
+ user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "#cofe hey"})
+ {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
+
+ refute modified["object"]["sensitive"]
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs
new file mode 100644
index 0000000..3ab7230
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs
@@ -0,0 +1,92 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
+
+ alias Pleroma.Web.CommonAPI
+
+ setup do
+ user = insert(:user)
+
+ message = %{
+ "actor" => user.ap_id,
+ "cc" => [user.follower_address],
+ "type" => "Create",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "https://instance.tld/users/user1",
+ "https://instance.tld/users/user2",
+ "https://instance.tld/users/user3"
+ ],
+ "object" => %{
+ "type" => "Note"
+ }
+ }
+
+ [user: user, message: message]
+ end
+
+ setup do: clear_config(:mrf_hellthread)
+
+ test "doesn't die on chat messages" do
+ clear_config([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
+
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post_chat_message(user, other_user, "moin")
+
+ assert {:ok, _} = filter(activity.data)
+ end
+
+ describe "reject" do
+ test "rejects the message if the recipient count is above reject_threshold", %{
+ message: message
+ } do
+ clear_config([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2})
+
+ assert {:reject, "[HellthreadPolicy] 3 recipients is over the limit of 2"} ==
+ filter(message)
+ end
+
+ test "does not reject the message if the recipient count is below reject_threshold", %{
+ message: message
+ } do
+ clear_config([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3})
+
+ assert {:ok, ^message} = filter(message)
+ end
+ end
+
+ describe "delist" do
+ test "delists the message if the recipient count is above delist_threshold", %{
+ user: user,
+ message: message
+ } do
+ clear_config([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
+
+ {:ok, message} = filter(message)
+ assert user.follower_address in message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in message["cc"]
+ end
+
+ test "does not delist the message if the recipient count is below delist_threshold", %{
+ message: message
+ } do
+ clear_config([:mrf_hellthread], %{delist_threshold: 4, reject_threshold: 0})
+
+ assert {:ok, ^message} = filter(message)
+ end
+ end
+
+ test "excludes follower collection and public URI from threshold count", %{message: message} do
+ clear_config([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3})
+
+ assert {:ok, ^message} = filter(message)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs
new file mode 100644
index 0000000..a0e77d7
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs
@@ -0,0 +1,356 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy
+
+ setup do: clear_config(:mrf_keyword)
+
+ setup do
+ clear_config([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []})
+ end
+
+ describe "rejecting based on keywords" do
+ test "rejects if string matches in content" do
+ clear_config([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ }
+
+ assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
+ KeywordPolicy.filter(message)
+ end
+
+ test "rejects if string matches in summary" do
+ clear_config([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
+ KeywordPolicy.filter(message)
+ end
+
+ test "rejects if regex matches in content" do
+ clear_config([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
+ KeywordPolicy.filter(message)
+ end)
+ end
+
+ test "rejects if regex matches in summary" do
+ clear_config([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
+ KeywordPolicy.filter(message)
+ end)
+ end
+
+ test "rejects if string matches in history" do
+ clear_config([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good",
+ "summary" => "",
+ "formerRepresentations" => %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ ]
+ }
+ }
+ }
+
+ assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
+ KeywordPolicy.filter(message)
+ end
+
+ test "rejects Updates" do
+ clear_config([:mrf_keyword, :reject], ["pun"])
+
+ message = %{
+ "type" => "Update",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good",
+ "summary" => "",
+ "formerRepresentations" => %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ ]
+ }
+ }
+ }
+
+ assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
+ KeywordPolicy.filter(message)
+ end
+ end
+
+ describe "delisting from ftl based on keywords" do
+ test "delists if string matches in content" do
+ clear_config([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+
+ test "delists if string matches in summary" do
+ clear_config([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "summary" => "just a daily reminder that compLAINer is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+
+ test "delists if regex matches in content" do
+ clear_config([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "content" => "just a daily reminder that #{content} is a good pun",
+ "summary" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+
+ ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+ not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+ end)
+ end
+
+ test "delists if regex matches in summary" do
+ clear_config([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+ assert true ==
+ Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "summary" => "just a daily reminder that #{content} is a good pun",
+ "content" => ""
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+
+ ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+ not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+ end)
+ end
+
+ test "delists if string matches in history" do
+ clear_config([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+ message = %{
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create",
+ "object" => %{
+ "content" => "just a daily reminder that compLAINer is a good",
+ "summary" => "",
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "content" => "just a daily reminder that compLAINer is a good pun",
+ "summary" => ""
+ }
+ ]
+ }
+ }
+ }
+
+ {:ok, result} = KeywordPolicy.filter(message)
+ assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+ refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+ end
+ end
+
+ describe "replacing keywords" do
+ test "replaces keyword if string matches in content" do
+ clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"content" => "ZFS is opensource", "summary" => ""}
+ }
+
+ {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
+ assert result == "ZFS is free software"
+ end
+
+ test "replaces keyword if string matches in summary" do
+ clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is opensource", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ assert result == "ZFS is free software"
+ end
+
+ test "replaces keyword if regex matches in content" do
+ clear_config([:mrf_keyword, :replace], [
+ {~r/open(-|\s)?source\s?(software)?/, "free software"}
+ ])
+
+ assert true ==
+ Enum.all?(["opensource", "open-source", "open source"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"content" => "ZFS is #{content}", "summary" => ""}
+ }
+
+ {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
+ result == "ZFS is free software"
+ end)
+ end
+
+ test "replaces keyword if regex matches in summary" do
+ clear_config([:mrf_keyword, :replace], [
+ {~r/open(-|\s)?source\s?(software)?/, "free software"}
+ ])
+
+ assert true ==
+ Enum.all?(["opensource", "open-source", "open source"], fn content ->
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{"summary" => "ZFS is #{content}", "content" => ""}
+ }
+
+ {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+ result == "ZFS is free software"
+ end)
+ end
+
+ test "replaces keyword if string matches in history" do
+ clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "content" => "ZFS is opensource",
+ "summary" => "",
+ "formerRepresentations" => %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ %{"content" => "ZFS is opensource mew mew", "summary" => ""}
+ ]
+ }
+ }
+ }
+
+ {:ok,
+ %{
+ "object" => %{
+ "content" => "ZFS is free software",
+ "formerRepresentations" => %{
+ "orderedItems" => [%{"content" => "ZFS is free software mew mew"}]
+ }
+ }
+ }} = KeywordPolicy.filter(message)
+ end
+
+ test "replaces keyword in Updates" do
+ clear_config([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+ message = %{
+ "type" => "Update",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "object" => %{
+ "content" => "ZFS is opensource",
+ "summary" => "",
+ "formerRepresentations" => %{
+ "type" => "OrderedCollection",
+ "orderedItems" => [
+ %{"content" => "ZFS is opensource mew mew", "summary" => ""}
+ ]
+ }
+ }
+ }
+
+ {:ok,
+ %{
+ "object" => %{
+ "content" => "ZFS is free software",
+ "formerRepresentations" => %{
+ "orderedItems" => [%{"content" => "ZFS is free software mew mew"}]
+ }
+ }
+ }} = KeywordPolicy.filter(message)
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
new file mode 100644
index 0000000..6557c3a
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
@@ -0,0 +1,97 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+
+ alias Pleroma.HTTP
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
+
+ import Mock
+
+ @message %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "content",
+ "attachment" => [
+ %{"url" => [%{"href" => "http://example.com/image.jpg"}]}
+ ]
+ }
+ }
+
+ @message_with_history %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "content",
+ "formerRepresentations" => %{
+ "orderedItems" => [
+ %{
+ "type" => "Note",
+ "content" => "content",
+ "attachment" => [
+ %{"url" => [%{"href" => "http://example.com/image.jpg"}]}
+ ]
+ }
+ ]
+ }
+ }
+ }
+
+ setup do: clear_config([:media_proxy, :enabled], true)
+
+ test "it prefetches media proxy URIs" do
+ Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end)
+
+ with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
+ MediaProxyWarmingPolicy.filter(@message)
+
+ assert called(HTTP.get(:_, :_, :_))
+ end
+ end
+
+ test "it does nothing when no attachments are present" do
+ object =
+ @message["object"]
+ |> Map.delete("attachment")
+
+ message =
+ @message
+ |> Map.put("object", object)
+
+ with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
+ MediaProxyWarmingPolicy.filter(message)
+ refute called(HTTP.get(:_, :_, :_))
+ end
+ end
+
+ test "history-aware" do
+ Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end)
+
+ with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
+ MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history)
+
+ assert called(HTTP.get(:_, :_, :_))
+ end
+ end
+
+ test "works with Updates" do
+ Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
+ {:ok, %Tesla.Env{status: 200, body: ""}}
+ end)
+
+ with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
+ MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history |> Map.put("type", "Update"))
+
+ assert called(HTTP.get(:_, :_, :_))
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/mention_policy_test.exs b/test/pleroma/web/activity_pub/mrf/mention_policy_test.exs
new file mode 100644
index 0000000..6dcae44
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/mention_policy_test.exs
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.MentionPolicy
+
+ setup do: clear_config(:mrf_mention)
+
+ test "pass filter if allow list is empty" do
+ Pleroma.Config.delete([:mrf_mention])
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ describe "allow" do
+ test "empty" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create"
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "to" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "cc" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "cc" => ["https://example.com/ok"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+
+ test "both" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/ok2"]
+ }
+
+ assert MentionPolicy.filter(message) == {:ok, message}
+ end
+ end
+
+ describe "deny" do
+ test "to" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) ==
+ {:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"}
+ end
+
+ test "cc" do
+ clear_config([:mrf_mention], %{actors: ["https://example.com/blocked"]})
+
+ message = %{
+ "type" => "Create",
+ "to" => ["https://example.com/ok"],
+ "cc" => ["https://example.com/blocked"]
+ }
+
+ assert MentionPolicy.filter(message) ==
+ {:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"}
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs
new file mode 100644
index 0000000..386ed39
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs
@@ -0,0 +1,177 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicyTest do
+ use Pleroma.DataCase
+ alias Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy
+
+ setup_all do: clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy])
+
+ test "Notes with content are exempt" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "source" => "this is a test post",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Note"
+ },
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Create"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:ok, message}
+ end
+
+ test "Polls are exempt" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "oneOf" => [
+ %{
+ "name" => "chocolate",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"},
+ "type" => "Note"
+ },
+ %{
+ "name" => "vanilla",
+ "replies" => %{"totalItems" => 0, "type" => "Collection"},
+ "type" => "Note"
+ }
+ ],
+ "source" => "@user2",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Question"
+ },
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Create"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:ok, message}
+ end
+
+ test "Notes with attachments are exempt" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [
+ %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "mediaType" => "image/png",
+ "name" => "",
+ "type" => "Document",
+ "url" => [
+ %{
+ "href" =>
+ "http://localhost:4001/media/68ba231cf12e1382ce458f1979969f8ed5cc07ba198a02e653464abaf39bdb90.png",
+ "mediaType" => "image/png",
+ "type" => "Link"
+ }
+ ]
+ }
+ ],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "source" => "@user2",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Note"
+ },
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Create"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:ok, message}
+ end
+
+ test "Notes with only mentions are denied" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "source" => "@user2",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Note"
+ },
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ "http://localhost:4001/users/user2"
+ ],
+ "type" => "Create"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"}
+ end
+
+ test "Notes with no content are denied" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "source" => "",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" => "Note"
+ },
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" => "Create"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"}
+ end
+
+ test "works with Update" do
+ message = %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "object" => %{
+ "actor" => "http://localhost:4001/users/testuser",
+ "attachment" => [],
+ "cc" => ["http://localhost:4001/users/testuser/followers"],
+ "source" => "",
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" => "Note"
+ },
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type" => "Update"
+ }
+
+ assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"}
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs
new file mode 100644
index 0000000..3533c2b
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicyTest do
+ use Pleroma.DataCase, async: true
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy
+
+ test "it clears content object" do
+ message = %{
+ "type" => "Create",
+ "object" => %{"content" => ".", "attachment" => "image"}
+ }
+
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res["object"]["content"] == ""
+
+ message = put_in(message, ["object", "content"], "<p>.</p>")
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res["object"]["content"] == ""
+ end
+
+ test "history-aware" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => ".",
+ "attachment" => "image",
+ "formerRepresentations" => %{
+ "orderedItems" => [%{"content" => ".", "attachment" => "image"}]
+ }
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(NoPlaceholderTextPolicy, message)
+
+ assert %{
+ "content" => "",
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => ""}]}
+ } = res["object"]
+ end
+
+ test "works with Updates" do
+ message = %{
+ "type" => "Update",
+ "object" => %{
+ "content" => ".",
+ "attachment" => "image",
+ "formerRepresentations" => %{
+ "orderedItems" => [%{"content" => ".", "attachment" => "image"}]
+ }
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(NoPlaceholderTextPolicy, message)
+
+ assert %{
+ "content" => "",
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => ""}]}
+ } = res["object"]
+ end
+
+ @messages [
+ %{
+ "type" => "Create",
+ "object" => %{"content" => "test", "attachment" => "image"}
+ },
+ %{"type" => "Create", "object" => %{"content" => "."}},
+ %{"type" => "Create", "object" => %{"content" => "<p>.</p>"}}
+ ]
+ test "it skips filter" do
+ Enum.each(@messages, fn message ->
+ assert {:ok, res} = NoPlaceholderTextPolicy.filter(message)
+ assert res == message
+ end)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs
new file mode 100644
index 0000000..66a8f4e
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do
+ use Pleroma.DataCase, async: true
+ alias Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Web.ActivityPub.MRF.NormalizeMarkup
+
+ @html_sample """
+ <b>this is in bold</b>
+ <p>this is a paragraph</p>
+ this is a linebreak<br />
+ this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a>
+ this is a link with not allowed "rel" attribute: <a href="http://example.com/" rel="tag noallowed">example.com</a>
+ this is an image: <img src="http://example.com/image.jpg"><br />
+ <script>alert('hacked')</script>
+ """
+
+ @expected """
+ <b>this is in bold</b>
+ <p>this is a paragraph</p>
+ this is a linebreak<br/>
+ this is a link with allowed &quot;rel&quot; attribute: <a href="http://example.com/" rel="tag">example.com</a>
+ this is a link with not allowed &quot;rel&quot; attribute: <a href="http://example.com/">example.com</a>
+ this is an image: <img src="http://example.com/image.jpg"/><br/>
+ alert(&#39;hacked&#39;)
+ """
+
+ test "it filter html tags" do
+ message = %{"type" => "Create", "object" => %{"content" => @html_sample}}
+
+ assert {:ok, res} = NormalizeMarkup.filter(message)
+ assert res["object"]["content"] == @expected
+ end
+
+ test "history-aware" do
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "content" => @html_sample,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => @html_sample}]}
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(NormalizeMarkup, message)
+
+ assert %{
+ "content" => @expected,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => @expected}]}
+ } = res["object"]
+ end
+
+ test "works with Updates" do
+ message = %{
+ "type" => "Update",
+ "object" => %{
+ "content" => @html_sample,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => @html_sample}]}
+ }
+ }
+
+ assert {:ok, res} = MRF.filter_one(NormalizeMarkup, message)
+
+ assert %{
+ "content" => @expected,
+ "formerRepresentations" => %{"orderedItems" => [%{"content" => @expected}]}
+ } = res["object"]
+ end
+
+ test "it skips filter if type isn't `Create` or `Update`" do
+ message = %{"type" => "Note", "object" => %{}}
+
+ assert {:ok, res} = NormalizeMarkup.filter(message)
+ assert res == message
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
new file mode 100644
index 0000000..7a67645
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
@@ -0,0 +1,148 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do
+ use Pleroma.DataCase
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy
+ alias Pleroma.Web.ActivityPub.Visibility
+
+ setup do:
+ clear_config(:mrf_object_age,
+ threshold: 172_800,
+ actions: [:delist, :strip_followers]
+ )
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ defp get_old_message do
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Jason.decode!()
+ |> Map.drop(["published"])
+ end
+
+ defp get_new_message do
+ old_message = get_old_message()
+
+ new_object =
+ old_message
+ |> Map.get("object")
+ |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601())
+
+ old_message
+ |> Map.put("object", new_object)
+ end
+
+ describe "with reject action" do
+ test "works with objects with empty to or cc fields" do
+ clear_config([:mrf_object_age, :actions], [:reject])
+
+ data =
+ get_old_message()
+ |> Map.put("cc", nil)
+ |> Map.put("to", nil)
+
+ assert match?({:reject, _}, ObjectAgePolicy.filter(data))
+ end
+
+ test "it rejects an old post" do
+ clear_config([:mrf_object_age, :actions], [:reject])
+
+ data = get_old_message()
+
+ assert match?({:reject, _}, ObjectAgePolicy.filter(data))
+ end
+
+ test "it allows a new post" do
+ clear_config([:mrf_object_age, :actions], [:reject])
+
+ data = get_new_message()
+
+ assert match?({:ok, _}, ObjectAgePolicy.filter(data))
+ end
+ end
+
+ describe "with delist action" do
+ test "works with objects with empty to or cc fields" do
+ clear_config([:mrf_object_age, :actions], [:delist])
+
+ data =
+ get_old_message()
+ |> Map.put("cc", nil)
+ |> Map.put("to", nil)
+
+ {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ {:ok, data} = ObjectAgePolicy.filter(data)
+
+ assert Visibility.get_visibility(%{data: data}) == "unlisted"
+ end
+
+ test "it delists an old post" do
+ clear_config([:mrf_object_age, :actions], [:delist])
+
+ data = get_old_message()
+
+ {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ {:ok, data} = ObjectAgePolicy.filter(data)
+
+ assert Visibility.get_visibility(%{data: data}) == "unlisted"
+ end
+
+ test "it allows a new post" do
+ clear_config([:mrf_object_age, :actions], [:delist])
+
+ data = get_new_message()
+
+ {:ok, _user} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ assert match?({:ok, ^data}, ObjectAgePolicy.filter(data))
+ end
+ end
+
+ describe "with strip_followers action" do
+ test "works with objects with empty to or cc fields" do
+ clear_config([:mrf_object_age, :actions], [:strip_followers])
+
+ data =
+ get_old_message()
+ |> Map.put("cc", nil)
+ |> Map.put("to", nil)
+
+ {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ {:ok, data} = ObjectAgePolicy.filter(data)
+
+ refute user.follower_address in data["to"]
+ refute user.follower_address in data["cc"]
+ end
+
+ test "it strips followers collections from an old post" do
+ clear_config([:mrf_object_age, :actions], [:strip_followers])
+
+ data = get_old_message()
+
+ {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ {:ok, data} = ObjectAgePolicy.filter(data)
+
+ refute user.follower_address in data["to"]
+ refute user.follower_address in data["cc"]
+ end
+
+ test "it allows a new post" do
+ clear_config([:mrf_object_age, :actions], [:strip_followers])
+
+ data = get_new_message()
+
+ {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"])
+
+ assert match?({:ok, ^data}, ObjectAgePolicy.filter(data))
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs b/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs
new file mode 100644
index 0000000..0ad60a3
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs
@@ -0,0 +1,100 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic
+
+ setup do: clear_config([:mrf_rejectnonpublic])
+
+ describe "public message" do
+ test "it's allowed when address is public" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ assert {:ok, _message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's allowed when cc address contain public address" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ assert {:ok, _message} = RejectNonPublic.filter(message)
+ end
+ end
+
+ describe "followers message" do
+ test "it's allowed when addrer of message in the follower addresses of user and it enabled in config" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["test-address"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ clear_config([:mrf_rejectnonpublic, :allow_followersonly], true)
+ assert {:ok, _message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's rejected when addrer of message in the follower addresses of user and it disabled in config" do
+ actor = insert(:user, follower_address: "test-address")
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["test-address"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ clear_config([:mrf_rejectnonpublic, :allow_followersonly], false)
+ assert {:reject, _} = RejectNonPublic.filter(message)
+ end
+ end
+
+ describe "direct message" do
+ test "it's allows when direct messages are allow" do
+ actor = insert(:user)
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ clear_config([:mrf_rejectnonpublic, :allow_direct], true)
+ assert {:ok, _message} = RejectNonPublic.filter(message)
+ end
+
+ test "it's reject when direct messages aren't allow" do
+ actor = insert(:user)
+
+ message = %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Publid~~~"],
+ "cc" => ["https://www.w3.org/ns/activitystreams#Publid"],
+ "type" => "Create"
+ }
+
+ clear_config([:mrf_rejectnonpublic, :allow_direct], false)
+ assert {:reject, _} = RejectNonPublic.filter(message)
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
new file mode 100644
index 0000000..57fc00a
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -0,0 +1,579 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ alias Pleroma.Web.ActivityPub.MRF.SimplePolicy
+ alias Pleroma.Web.CommonAPI
+
+ setup do:
+ clear_config(:mrf_simple,
+ media_removal: [],
+ media_nsfw: [],
+ federated_timeline_removal: [],
+ report_removal: [],
+ reject: [],
+ followers_only: [],
+ accept: [],
+ avatar_removal: [],
+ banner_removal: [],
+ reject_deletes: []
+ )
+
+ describe "when :media_removal" do
+ test "is empty" do
+ clear_config([:mrf_simple, :media_removal], [])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) == {:ok, media_message}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host" do
+ clear_config([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "match with wildcard domain" do
+ clear_config([:mrf_simple, :media_removal], [{"*.remote.instance", "Whatever reason"}])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "works with Updates" do
+ clear_config([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
+ media_message = build_media_message(type: "Update")
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+ end
+ end
+
+ describe "when :media_nsfw" do
+ test "is empty" do
+ clear_config([:mrf_simple, :media_nsfw], [])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) == {:ok, media_message}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host" do
+ clear_config([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok, put_in(media_message, ["object", "sensitive"], true)}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "match with wildcard domain" do
+ clear_config([:mrf_simple, :media_nsfw], [{"*.remote.instance", "yeah yeah"}])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok, put_in(media_message, ["object", "sensitive"], true)}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "works with Updates" do
+ clear_config([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
+ media_message = build_media_message(type: "Update")
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok, put_in(media_message, ["object", "sensitive"], true)}
+ end
+ end
+
+ defp build_media_message(opts \\ []) do
+ %{
+ "actor" => "https://remote.instance/users/bob",
+ "type" => opts[:type] || "Create",
+ "object" => %{
+ "attachment" => [%{}],
+ "tag" => ["foo"],
+ "sensitive" => false
+ }
+ }
+ end
+
+ describe "when :report_removal" do
+ test "is empty" do
+ clear_config([:mrf_simple, :report_removal], [])
+ report_message = build_report_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(report_message) == {:ok, report_message}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host" do
+ clear_config([:mrf_simple, :report_removal], [{"remote.instance", "muh"}])
+ report_message = build_report_message()
+ local_message = build_local_message()
+
+ assert {:reject, _} = SimplePolicy.filter(report_message)
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "match with wildcard domain" do
+ clear_config([:mrf_simple, :report_removal], [{"*.remote.instance", "suya"}])
+ report_message = build_report_message()
+ local_message = build_local_message()
+
+ assert {:reject, _} = SimplePolicy.filter(report_message)
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+ end
+
+ defp build_report_message do
+ %{
+ "actor" => "https://remote.instance/users/bob",
+ "type" => "Flag"
+ }
+ end
+
+ describe "when :federated_timeline_removal" do
+ test "is empty" do
+ clear_config([:mrf_simple, :federated_timeline_removal], [])
+ {_, ftl_message} = build_ftl_actor_and_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host" do
+ {actor, ftl_message} = build_ftl_actor_and_message()
+
+ ftl_message_actor_host =
+ ftl_message
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ clear_config([:mrf_simple, :federated_timeline_removal], [{ftl_message_actor_host, "uwu"}])
+ local_message = build_local_message()
+
+ assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+ assert actor.follower_address in ftl_message["to"]
+ refute actor.follower_address in ftl_message["cc"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "match with wildcard domain" do
+ {actor, ftl_message} = build_ftl_actor_and_message()
+
+ ftl_message_actor_host =
+ ftl_message
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ clear_config([:mrf_simple, :federated_timeline_removal], [
+ {"*." <> ftl_message_actor_host, "owo"}
+ ])
+
+ local_message = build_local_message()
+
+ assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+ assert actor.follower_address in ftl_message["to"]
+ refute actor.follower_address in ftl_message["cc"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host but only as:Public in to" do
+ {_actor, ftl_message} = build_ftl_actor_and_message()
+
+ ftl_message_actor_host =
+ ftl_message
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ ftl_message = Map.put(ftl_message, "cc", [])
+
+ clear_config([:mrf_simple, :federated_timeline_removal], [
+ {ftl_message_actor_host, "spiderwaifu goes 88w88"}
+ ])
+
+ assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+ refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+ end
+ end
+
+ defp build_ftl_actor_and_message do
+ actor = insert(:user)
+
+ {actor,
+ %{
+ "actor" => actor.ap_id,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
+ "cc" => [actor.follower_address, "http://foo.bar/qux"]
+ }}
+ end
+
+ describe "when :reject" do
+ test "is empty" do
+ clear_config([:mrf_simple, :reject], [])
+
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
+
+ test "activity has a matching host" do
+ clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
+
+ remote_message = build_remote_message()
+
+ assert {:reject, _} = SimplePolicy.filter(remote_message)
+ end
+
+ test "activity matches with wildcard domain" do
+ clear_config([:mrf_simple, :reject], [{"*.remote.instance", ""}])
+
+ remote_message = build_remote_message()
+
+ assert {:reject, _} = SimplePolicy.filter(remote_message)
+ end
+
+ test "actor has a matching host" do
+ clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
+
+ remote_user = build_remote_user()
+
+ assert {:reject, _} = SimplePolicy.filter(remote_user)
+ end
+
+ test "reject Announce when object would be rejected" do
+ clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
+
+ announce = %{
+ "type" => "Announce",
+ "actor" => "https://okay.tld/users/alice",
+ "object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
+ }
+
+ assert {:reject, _} = SimplePolicy.filter(announce)
+ end
+
+ test "reject by URI object" do
+ clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
+
+ announce = %{
+ "type" => "Announce",
+ "actor" => "https://okay.tld/users/alice",
+ "object" => "https://blocked.tld/activities/1"
+ }
+
+ assert {:reject, _} = SimplePolicy.filter(announce)
+ end
+ end
+
+ describe "when :followers_only" do
+ test "is empty" do
+ clear_config([:mrf_simple, :followers_only], [])
+ {_, ftl_message} = build_ftl_actor_and_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
+ test "has a matching host" do
+ actor = insert(:user)
+ following_user = insert(:user)
+ non_following_user = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(following_user, actor)
+
+ activity = %{
+ "actor" => actor.ap_id,
+ "to" => [
+ "https://www.w3.org/ns/activitystreams#Public",
+ following_user.ap_id,
+ non_following_user.ap_id
+ ],
+ "cc" => [actor.follower_address, "http://foo.bar/qux"]
+ }
+
+ dm_activity = %{
+ "actor" => actor.ap_id,
+ "to" => [
+ following_user.ap_id,
+ non_following_user.ap_id
+ ],
+ "cc" => []
+ }
+
+ actor_domain =
+ activity
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ clear_config([:mrf_simple, :followers_only], [{actor_domain, ""}])
+
+ assert {:ok, new_activity} = SimplePolicy.filter(activity)
+ assert actor.follower_address in new_activity["cc"]
+ assert following_user.ap_id in new_activity["to"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["to"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["cc"]
+ refute non_following_user.ap_id in new_activity["to"]
+ refute non_following_user.ap_id in new_activity["cc"]
+
+ assert {:ok, new_dm_activity} = SimplePolicy.filter(dm_activity)
+ assert new_dm_activity["to"] == [following_user.ap_id]
+ assert new_dm_activity["cc"] == []
+ end
+ end
+
+ describe "when :accept" do
+ test "is empty" do
+ clear_config([:mrf_simple, :accept], [])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
+
+ test "is not empty but activity doesn't have a matching host" do
+ clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert {:reject, _} = SimplePolicy.filter(remote_message)
+ end
+
+ test "activity has a matching host" do
+ clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
+
+ test "activity matches with wildcard domain" do
+ clear_config([:mrf_simple, :accept], [{"*.remote.instance", ""}])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
+
+ test "actor has a matching host" do
+ clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
+
+ remote_user = build_remote_user()
+
+ assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ end
+ end
+
+ describe "when :avatar_removal" do
+ test "is empty" do
+ clear_config([:mrf_simple, :avatar_removal], [])
+
+ remote_user = build_remote_user()
+
+ assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ end
+
+ test "is not empty but it doesn't have a matching host" do
+ clear_config([:mrf_simple, :avatar_removal], [{"non.matching.remote", ""}])
+
+ remote_user = build_remote_user()
+
+ assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ end
+
+ test "has a matching host" do
+ clear_config([:mrf_simple, :avatar_removal], [{"remote.instance", ""}])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["icon"]
+ end
+
+ test "match with wildcard domain" do
+ clear_config([:mrf_simple, :avatar_removal], [{"*.remote.instance", ""}])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["icon"]
+ end
+ end
+
+ describe "when :banner_removal" do
+ test "is empty" do
+ clear_config([:mrf_simple, :banner_removal], [])
+
+ remote_user = build_remote_user()
+
+ assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ end
+
+ test "is not empty but it doesn't have a matching host" do
+ clear_config([:mrf_simple, :banner_removal], [{"non.matching.remote", ""}])
+
+ remote_user = build_remote_user()
+
+ assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ end
+
+ test "has a matching host" do
+ clear_config([:mrf_simple, :banner_removal], [{"remote.instance", ""}])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["image"]
+ end
+
+ test "match with wildcard domain" do
+ clear_config([:mrf_simple, :banner_removal], [{"*.remote.instance", ""}])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["image"]
+ end
+ end
+
+ describe "when :reject_deletes is empty" do
+ setup do: clear_config([:mrf_simple, :reject_deletes], [])
+
+ test "it accepts deletions even from rejected servers" do
+ clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
+
+ deletion_message = build_remote_deletion_message()
+
+ assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
+ end
+
+ test "it accepts deletions even from non-whitelisted servers" do
+ clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
+
+ deletion_message = build_remote_deletion_message()
+
+ assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
+ end
+ end
+
+ describe "when :reject_deletes is not empty but it doesn't have a matching host" do
+ setup do: clear_config([:mrf_simple, :reject_deletes], [{"non.matching.remote", ""}])
+
+ test "it accepts deletions even from rejected servers" do
+ clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
+
+ deletion_message = build_remote_deletion_message()
+
+ assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
+ end
+
+ test "it accepts deletions even from non-whitelisted servers" do
+ clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
+
+ deletion_message = build_remote_deletion_message()
+
+ assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
+ end
+ end
+
+ describe "when :reject_deletes has a matching host" do
+ setup do: clear_config([:mrf_simple, :reject_deletes], [{"remote.instance", ""}])
+
+ test "it rejects the deletion" do
+ deletion_message = build_remote_deletion_message()
+
+ assert {:reject, _} = SimplePolicy.filter(deletion_message)
+ end
+ end
+
+ describe "when :reject_deletes match with wildcard domain" do
+ setup do: clear_config([:mrf_simple, :reject_deletes], [{"*.remote.instance", ""}])
+
+ test "it rejects the deletion" do
+ deletion_message = build_remote_deletion_message()
+
+ assert {:reject, _} = SimplePolicy.filter(deletion_message)
+ end
+ end
+
+ defp build_local_message do
+ %{
+ "actor" => "#{Pleroma.Web.Endpoint.url()}/users/alice",
+ "to" => [],
+ "cc" => []
+ }
+ end
+
+ defp build_remote_message do
+ %{"actor" => "https://remote.instance/users/bob"}
+ end
+
+ defp build_remote_user do
+ %{
+ "id" => "https://remote.instance/users/bob",
+ "icon" => %{
+ "url" => "http://example.com/image.jpg",
+ "type" => "Image"
+ },
+ "image" => %{
+ "url" => "http://example.com/image.jpg",
+ "type" => "Image"
+ },
+ "type" => "Person"
+ }
+ end
+
+ defp build_remote_deletion_message do
+ %{
+ "type" => "Delete",
+ "actor" => "https://remote.instance/users/bob"
+ }
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
new file mode 100644
index 0000000..89d3235
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
@@ -0,0 +1,122 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.Emoji
+ alias Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy
+
+ setup do
+ emoji_path = [:instance, :static_dir] |> Config.get() |> Path.join("emoji/stolen")
+
+ Emoji.reload()
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "emoji" => [{"firedfox", "https://example.org/emoji/firedfox.png"}],
+ "actor" => "https://example.org/users/admin"
+ }
+ }
+
+ on_exit(fn ->
+ File.rm_rf!(emoji_path)
+ end)
+
+ [message: message, path: emoji_path]
+ end
+
+ test "does nothing by default", %{message: message} do
+ refute "firedfox" in installed()
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ refute "firedfox" in installed()
+ end
+
+ test "Steals emoji on unknown shortcode from allowed remote host", %{
+ message: message,
+ path: path
+ } do
+ refute "firedfox" in installed()
+ refute File.exists?(path)
+
+ Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
+ end)
+
+ clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ assert "firedfox" in installed()
+ assert File.exists?(path)
+
+ assert path
+ |> Path.join("firedfox.png")
+ |> File.exists?()
+ end
+
+ test "reject regex shortcode", %{message: message} do
+ refute "firedfox" in installed()
+
+ clear_config(:mrf_steal_emoji,
+ hosts: ["example.org"],
+ size_limit: 284_468,
+ rejected_shortcodes: [~r/firedfox/]
+ )
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ refute "firedfox" in installed()
+ end
+
+ test "reject string shortcode", %{message: message} do
+ refute "firedfox" in installed()
+
+ clear_config(:mrf_steal_emoji,
+ hosts: ["example.org"],
+ size_limit: 284_468,
+ rejected_shortcodes: ["firedfox"]
+ )
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ refute "firedfox" in installed()
+ end
+
+ test "reject if size is above the limit", %{message: message} do
+ refute "firedfox" in installed()
+
+ Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
+ end)
+
+ clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 50_000)
+
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+
+ refute "firedfox" in installed()
+ end
+
+ test "reject if host returns error", %{message: message} do
+ refute "firedfox" in installed()
+
+ Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
+ {:ok, %Tesla.Env{status: 404, body: "Not found"}}
+ end)
+
+ clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
+
+ ExUnit.CaptureLog.capture_log(fn ->
+ assert {:ok, _message} = StealEmojiPolicy.filter(message)
+ end) =~ "MRF.StealEmojiPolicy: Failed to fetch https://example.org/emoji/firedfox.png"
+
+ refute "firedfox" in installed()
+ end
+
+ defp installed, do: Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
+end
diff --git a/test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs b/test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs
new file mode 100644
index 0000000..8cbeef9
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.DropPolicy
+ alias Pleroma.Web.ActivityPub.MRF.SubchainPolicy
+
+ @message %{
+ "actor" => "https://banned.com",
+ "type" => "Create",
+ "object" => %{"content" => "hi"}
+ }
+ setup do: clear_config([:mrf_subchain, :match_actor])
+
+ test "it matches and processes subchains when the actor matches a configured target" do
+ clear_config([:mrf_subchain, :match_actor], %{
+ ~r/^https:\/\/banned.com/s => [DropPolicy]
+ })
+
+ {:reject, _} = SubchainPolicy.filter(@message)
+ end
+
+ test "it doesn't match and process subchains when the actor doesn't match a configured target" do
+ clear_config([:mrf_subchain, :match_actor], %{
+ ~r/^https:\/\/borked.com/s => [DropPolicy]
+ })
+
+ {:ok, _message} = SubchainPolicy.filter(@message)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
new file mode 100644
index 0000000..a0db8df
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
@@ -0,0 +1,159 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do
+ use Pleroma.DataCase, async: true
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.TagPolicy
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
+ describe "mrf_tag:disable-any-subscription" do
+ test "rejects message" do
+ actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"])
+ message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => actor.ap_id}
+ assert {:reject, _} = TagPolicy.filter(message)
+ end
+ end
+
+ describe "mrf_tag:disable-remote-subscription" do
+ test "rejects non-local follow requests" do
+ actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"])
+ follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false)
+ message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id}
+ assert {:reject, _} = TagPolicy.filter(message)
+ end
+
+ test "allows non-local follow requests" do
+ actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"])
+ follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: true)
+ message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id}
+ assert {:ok, _message} = TagPolicy.filter(message)
+ end
+ end
+
+ describe "mrf_tag:sandbox" do
+ test "removes from public timelines" do
+ actor = insert(:user, tags: ["mrf_tag:sandbox"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{},
+ "to" => [@public, "f"],
+ "cc" => [@public, "d"]
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d"]},
+ "to" => ["f", actor.follower_address],
+ "cc" => ["d"]
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:force-unlisted" do
+ test "removes from the federated timeline" do
+ actor = insert(:user, tags: ["mrf_tag:force-unlisted"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{},
+ "to" => [@public, "f"],
+ "cc" => [actor.follower_address, "d"]
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]},
+ "to" => ["f", actor.follower_address],
+ "cc" => ["d", @public]
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:media-strip" do
+ test "removes attachments" do
+ actor = insert(:user, tags: ["mrf_tag:media-strip"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+
+ test "removes attachments in Updates" do
+ actor = insert(:user, tags: ["mrf_tag:media-strip"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Update",
+ "object" => %{"attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Update",
+ "object" => %{}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+
+ describe "mrf_tag:media-force-nsfw" do
+ test "Mark as sensitive on presence of attachments" do
+ actor = insert(:user, tags: ["mrf_tag:media-force-nsfw"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"tag" => ["test"], "attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Create",
+ "object" => %{"tag" => ["test"], "attachment" => ["file1"], "sensitive" => true}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+
+ test "Mark as sensitive on presence of attachments in Updates" do
+ actor = insert(:user, tags: ["mrf_tag:media-force-nsfw"])
+
+ message = %{
+ "actor" => actor.ap_id,
+ "type" => "Update",
+ "object" => %{"tag" => ["test"], "attachment" => ["file1"]}
+ }
+
+ except_message = %{
+ "actor" => actor.ap_id,
+ "type" => "Update",
+ "object" => %{"tag" => ["test"], "attachment" => ["file1"], "sensitive" => true}
+ }
+
+ assert TagPolicy.filter(message) == {:ok, except_message}
+ end
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs b/test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs
new file mode 100644
index 0000000..d02af3b
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy
+
+ setup do: clear_config(:mrf_user_allowlist)
+
+ test "pass filter if allow list is empty" do
+ actor = insert(:user)
+ message = %{"actor" => actor.ap_id}
+ assert UserAllowListPolicy.filter(message) == {:ok, message}
+ end
+
+ test "pass filter if allow list isn't empty and user in allow list" do
+ actor = insert(:user)
+ clear_config([:mrf_user_allowlist], %{"localhost" => [actor.ap_id, "test-ap-id"]})
+ message = %{"actor" => actor.ap_id}
+ assert UserAllowListPolicy.filter(message) == {:ok, message}
+ end
+
+ test "rejected if allow list isn't empty and user not in allow list" do
+ actor = insert(:user)
+ clear_config([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]})
+ message = %{"actor" => actor.ap_id}
+ assert {:reject, _} = UserAllowListPolicy.filter(message)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs
new file mode 100644
index 0000000..68c18cf
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy
+
+ describe "accept" do
+ setup do: clear_config([:mrf_vocabulary, :accept])
+
+ test "it accepts based on parent activity type" do
+ clear_config([:mrf_vocabulary, :accept], ["Like"])
+
+ message = %{
+ "type" => "Like",
+ "object" => "whatever"
+ }
+
+ {:ok, ^message} = VocabularyPolicy.filter(message)
+ end
+
+ test "it accepts based on child object type" do
+ clear_config([:mrf_vocabulary, :accept], ["Create", "Note"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "whatever"
+ }
+ }
+
+ {:ok, ^message} = VocabularyPolicy.filter(message)
+ end
+
+ test "it does not accept disallowed child objects" do
+ clear_config([:mrf_vocabulary, :accept], ["Create", "Note"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Article",
+ "content" => "whatever"
+ }
+ }
+
+ {:reject, _} = VocabularyPolicy.filter(message)
+ end
+
+ test "it does not accept disallowed parent types" do
+ clear_config([:mrf_vocabulary, :accept], ["Announce", "Note"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "whatever"
+ }
+ }
+
+ {:reject, _} = VocabularyPolicy.filter(message)
+ end
+ end
+
+ describe "reject" do
+ setup do: clear_config([:mrf_vocabulary, :reject])
+
+ test "it rejects based on parent activity type" do
+ clear_config([:mrf_vocabulary, :reject], ["Like"])
+
+ message = %{
+ "type" => "Like",
+ "object" => "whatever"
+ }
+
+ {:reject, _} = VocabularyPolicy.filter(message)
+ end
+
+ test "it rejects based on child object type" do
+ clear_config([:mrf_vocabulary, :reject], ["Note"])
+
+ message = %{
+ "type" => "Create",
+ "object" => %{
+ "type" => "Note",
+ "content" => "whatever"
+ }
+ }
+
+ {:reject, _} = VocabularyPolicy.filter(message)
+ end
+
+ test "it passes through objects that aren't disallowed" do
+ clear_config([:mrf_vocabulary, :reject], ["Like"])
+
+ message = %{
+ "type" => "Announce",
+ "object" => "whatever"
+ }
+
+ {:ok, ^message} = VocabularyPolicy.filter(message)
+ end
+ end
+end