From 3c90f7f7156889a1f74950ab976819faa281df43 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 28 Jul 2020 18:55:29 -0500 Subject: [PATCH 1/6] SimpleMRF: Let instances be silenced --- config/description.exs | 6 +++ docs/configuration/cheatsheet.md | 1 + .../web/activity_pub/mrf/simple_policy.ex | 28 +++++++++++ .../activity_pub/mrf/simple_policy_test.exs | 47 +++++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/config/description.exs b/config/description.exs index 91261c1e1..9ffe6f93d 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1524,6 +1524,12 @@ description: "List of instances to only accept activities from (except deletes)", suggestions: ["example.com", "*.example.com"] }, + %{ + key: :silence, + type: {:list, :string}, + description: "Force posts from the given instances to be visible by followers only", + suggestions: ["example.com", "*.example.com"] + }, %{ key: :report_removal, type: {:list, :string}, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 2a25a024a..9a7f4f369 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -122,6 +122,7 @@ To add configuration to your config file, you can copy it from the base config. * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline. * `reject`: List of instances to reject any activities from. * `accept`: List of instances to accept any activities from. +* `silence`: List of instances to force posts as followers-only. * `report_removal`: List of instances to reject reports from. * `avatar_removal`: List of instances to strip avatars from. * `banner_removal`: List of instances to strip banners from. diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index b77b8c7b4..e168a943e 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do @behaviour Pleroma.Web.ActivityPub.MRF alias Pleroma.Config + alias Pleroma.FollowingRelationship alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF @@ -108,6 +109,32 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do {:ok, object} end + defp check_silence(%{host: actor_host} = _actor_info, object) do + silence = + Config.get([:mrf_simple, :silence]) + |> MRF.subdomains_regex() + + object = + with true <- MRF.subdomain_match?(silence, actor_host), + user <- User.get_cached_by_ap_id(object["actor"]) do + to = + FollowingRelationship.followers_ap_ids(user, Map.get(object, "to", [])) ++ + [user.follower_address] + + cc = FollowingRelationship.followers_ap_ids(user, Map.get(object, "cc", [])) + + object + |> Map.put("to", to) + |> Map.put("cc", cc) + else + _ -> object + end + + {:ok, object} + end + + defp check_silence(_actor_info, object), do: {:ok, object} + defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do report_removal = Config.get([:mrf_simple, :report_removal]) @@ -174,6 +201,7 @@ def filter(%{"actor" => actor} = object) do {:ok, object} <- check_media_removal(actor_info, object), {:ok, object} <- check_media_nsfw(actor_info, object), {:ok, object} <- check_ftl_removal(actor_info, object), + {:ok, object} <- check_silence(actor_info, object), {:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} else diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index e842d8d8d..510a31d80 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do import Pleroma.Factory alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF.SimplePolicy + alias Pleroma.Web.CommonAPI setup do: clear_config(:mrf_simple, @@ -15,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do federated_timeline_removal: [], report_removal: [], reject: [], + silence: [], accept: [], avatar_removal: [], banner_removal: [], @@ -261,6 +263,51 @@ test "actor has a matching host" do end end + describe "when :silence" do + test "is empty" do + Config.put([:mrf_simple, :silence], []) + {_, 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"] + } + + actor_domain = + activity + |> Map.fetch!("actor") + |> URI.parse() + |> Map.fetch!(:host) + + Config.put([:mrf_simple, :silence], [actor_domain]) + + assert {:ok, new_activity} = SimplePolicy.filter(activity) + assert actor.follower_address in new_activity["to"] + 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"] + end + end + describe "when :accept" do test "is empty" do Config.put([:mrf_simple, :accept], []) From 2a99e7df8e3c5c5c6cdf15bff56d0258c9a5287e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 28 Jul 2020 20:17:18 -0500 Subject: [PATCH 2/6] SimpleMRF silence: optimize, work okay with nil values in addressing --- lib/pleroma/following_relationship.ex | 6 +++++- lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 13 ++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index c2020d30a..83b366dd4 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -95,7 +95,11 @@ def followers_query(%User{} = user) do |> where([r], r.state == ^:follow_accept) end - def followers_ap_ids(%User{} = user, from_ap_ids \\ nil) do + def followers_ap_ids(user, from_ap_ids \\ nil) + + def followers_ap_ids(_, []), do: [] + + def followers_ap_ids(%User{} = user, from_ap_ids) do query = user |> followers_query() diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index e168a943e..4dce22cfa 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -117,14 +117,15 @@ defp check_silence(%{host: actor_host} = _actor_info, object) do object = with true <- MRF.subdomain_match?(silence, actor_host), user <- User.get_cached_by_ap_id(object["actor"]) do - to = - FollowingRelationship.followers_ap_ids(user, Map.get(object, "to", [])) ++ - [user.follower_address] + # Don't use Map.get/3 intentionally, these must not be nil + fixed_to = object["to"] || [] + fixed_cc = object["cc"] || [] - cc = FollowingRelationship.followers_ap_ids(user, Map.get(object, "cc", [])) + to = FollowingRelationship.followers_ap_ids(user, fixed_to) + cc = FollowingRelationship.followers_ap_ids(user, fixed_cc) object - |> Map.put("to", to) + |> Map.put("to", [user.follower_address] ++ to) |> Map.put("cc", cc) else _ -> object @@ -133,8 +134,6 @@ defp check_silence(%{host: actor_host} = _actor_info, object) do {:ok, object} end - defp check_silence(_actor_info, object), do: {:ok, object} - defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do report_removal = Config.get([:mrf_simple, :report_removal]) From 93638935d783c092dabac51982426ebd98a21e0e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 29 Jul 2020 12:58:08 -0500 Subject: [PATCH 3/6] SimpleMRF: :silence --> :followers_only --- config/description.exs | 2 +- docs/configuration/cheatsheet.md | 2 +- lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 10 +++++----- test/web/activity_pub/mrf/simple_policy_test.exs | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/description.exs b/config/description.exs index 9ffe6f93d..dc6e2a76e 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1525,7 +1525,7 @@ suggestions: ["example.com", "*.example.com"] }, %{ - key: :silence, + key: :followers_only, type: {:list, :string}, description: "Force posts from the given instances to be visible by followers only", suggestions: ["example.com", "*.example.com"] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 9a7f4f369..b195b6f17 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -122,7 +122,7 @@ To add configuration to your config file, you can copy it from the base config. * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline. * `reject`: List of instances to reject any activities from. * `accept`: List of instances to accept any activities from. -* `silence`: List of instances to force posts as followers-only. +* `followers_only`: List of instances to force posts as followers-only. * `report_removal`: List of instances to reject reports from. * `avatar_removal`: List of instances to strip avatars from. * `banner_removal`: List of instances to strip banners from. diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 4dce22cfa..ffaac767e 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -109,13 +109,13 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do {:ok, object} end - defp check_silence(%{host: actor_host} = _actor_info, object) do - silence = - Config.get([:mrf_simple, :silence]) + defp check_followers_only(%{host: actor_host} = _actor_info, object) do + followers_only = + Config.get([:mrf_simple, :followers_only]) |> MRF.subdomains_regex() object = - with true <- MRF.subdomain_match?(silence, actor_host), + with true <- MRF.subdomain_match?(followers_only, actor_host), user <- User.get_cached_by_ap_id(object["actor"]) do # Don't use Map.get/3 intentionally, these must not be nil fixed_to = object["to"] || [] @@ -200,7 +200,7 @@ def filter(%{"actor" => actor} = object) do {:ok, object} <- check_media_removal(actor_info, object), {:ok, object} <- check_media_nsfw(actor_info, object), {:ok, object} <- check_ftl_removal(actor_info, object), - {:ok, object} <- check_silence(actor_info, object), + {:ok, object} <- check_followers_only(actor_info, object), {:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} else diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 510a31d80..c0e82731b 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do federated_timeline_removal: [], report_removal: [], reject: [], - silence: [], + followers_only: [], accept: [], avatar_removal: [], banner_removal: [], @@ -263,9 +263,9 @@ test "actor has a matching host" do end end - describe "when :silence" do + describe "when :followers_only" do test "is empty" do - Config.put([:mrf_simple, :silence], []) + Config.put([:mrf_simple, :followers_only], []) {_, ftl_message} = build_ftl_actor_and_message() local_message = build_local_message() @@ -296,7 +296,7 @@ test "has a matching host" do |> URI.parse() |> Map.fetch!(:host) - Config.put([:mrf_simple, :silence], [actor_domain]) + Config.put([:mrf_simple, :followers_only], [actor_domain]) assert {:ok, new_activity} = SimplePolicy.filter(activity) assert actor.follower_address in new_activity["to"] From e2e66e50d3066d48d8ef9200e7d221f5aeec4c44 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Jul 2020 14:29:00 +0200 Subject: [PATCH 4/6] SimplePolicyTest: Add test for leaking DMs. --- test/web/activity_pub/mrf/simple_policy_test.exs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index c0e82731b..9a1a7bdc8 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -290,6 +290,15 @@ test "has a matching host" do "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") @@ -305,6 +314,10 @@ test "has a matching host" do 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 From 1dd162a5f75e6c2ef1813cd477e6d938127220d9 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 31 Jul 2020 09:57:30 +0200 Subject: [PATCH 5/6] SimplePolicy: Fix problem with DM leaks. --- lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 8 ++++++-- test/web/activity_pub/mrf/simple_policy_test.exs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index ffaac767e..bb193475a 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -109,6 +109,10 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do {:ok, object} end + defp intersection(list1, list2) do + list1 -- list1 -- list2 + end + defp check_followers_only(%{host: actor_host} = _actor_info, object) do followers_only = Config.get([:mrf_simple, :followers_only]) @@ -125,8 +129,8 @@ defp check_followers_only(%{host: actor_host} = _actor_info, object) do cc = FollowingRelationship.followers_ap_ids(user, fixed_cc) object - |> Map.put("to", [user.follower_address] ++ to) - |> Map.put("cc", cc) + |> Map.put("to", intersection([user.follower_address | to], fixed_to)) + |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc)) else _ -> object end diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 9a1a7bdc8..d7dde62c4 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -308,7 +308,7 @@ test "has a matching host" do Config.put([:mrf_simple, :followers_only], [actor_domain]) assert {:ok, new_activity} = SimplePolicy.filter(activity) - assert actor.follower_address in new_activity["to"] + 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"] From 37b9e5e1384a6dae103773e29b386ac9843ecf5e Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 31 Jul 2020 10:29:16 +0000 Subject: [PATCH 6/6] Apply 1 suggestion(s) to 1 file(s) --- docs/configuration/cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index d18d638b9..65cccda30 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline. * `reject`: List of instances to reject any activities from. * `accept`: List of instances to accept any activities from. -* `followers_only`: List of instances to force posts as followers-only. +* `followers_only`: List of instances to decrease post visibility to only the followers, including for DM mentions. * `report_removal`: List of instances to reject reports from. * `avatar_removal`: List of instances to strip avatars from. * `banner_removal`: List of instances to strip banners from.