summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/incoming-blocks.fix1
-rw-r--r--changelog.d/remote-report-policy.add1
-rw-r--r--config/config.exs5
-rw-r--r--lib/pleroma/constants.ex5
-rw-r--r--lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex118
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex12
-rw-r--r--test/pleroma/web/activity_pub/activity_pub_controller_test.exs21
-rw-r--r--test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs155
8 files changed, 318 insertions, 0 deletions
diff --git a/changelog.d/incoming-blocks.fix b/changelog.d/incoming-blocks.fix
new file mode 100644
index 000000000..3228d7318
--- /dev/null
+++ b/changelog.d/incoming-blocks.fix
@@ -0,0 +1 @@
+Fix incoming Block activities being rejected
diff --git a/changelog.d/remote-report-policy.add b/changelog.d/remote-report-policy.add
new file mode 100644
index 000000000..1cf25b1a8
--- /dev/null
+++ b/changelog.d/remote-report-policy.add
@@ -0,0 +1 @@
+Added RemoteReportPolicy from Rebased for handling bogus federated reports
diff --git a/config/config.exs b/config/config.exs
index 47ddfac5a..07e98011d 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -434,6 +434,11 @@ config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
+config :pleroma, :mrf_remote_report,
+ reject_all: false,
+ reject_anonymous: true,
+ reject_empty_message: true
+
config :pleroma, :mrf_force_mention,
mention_parent: true,
mention_quoted: true
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index 5268ebe7a..2828c79a9 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -87,6 +87,7 @@ defmodule Pleroma.Constants do
const(activity_types,
do: [
+ "Block",
"Create",
"Update",
"Delete",
@@ -115,6 +116,10 @@ defmodule Pleroma.Constants do
]
)
+ const(object_types,
+ do: ~w[Event Question Answer Audio Video Image Article Note Page ChatMessage]
+ )
+
# basic regex, just there to weed out potential mistakes
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
const(mime_regex,
diff --git a/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex b/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex
new file mode 100644
index 000000000..fa0610bf1
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex
@@ -0,0 +1,118 @@
+defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
+ @moduledoc "Drop remote reports if they don't contain enough information."
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ alias Pleroma.Config
+
+ @impl true
+ def filter(%{"type" => "Flag"} = object) do
+ with {_, false} <- {:local, local?(object)},
+ {:ok, _} <- maybe_reject_all(object),
+ {:ok, _} <- maybe_reject_anonymous(object),
+ {:ok, _} <- maybe_reject_third_party(object),
+ {:ok, _} <- maybe_reject_empty_message(object) do
+ {:ok, object}
+ else
+ {:local, true} -> {:ok, object}
+ {:reject, message} -> {:reject, message}
+ error -> {:reject, error}
+ end
+ end
+
+ def filter(object), do: {:ok, object}
+
+ defp maybe_reject_all(object) do
+ if Config.get([:mrf_remote_report, :reject_all]) do
+ {:reject, "[RemoteReportPolicy] Remote report"}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp maybe_reject_anonymous(%{"actor" => actor} = object) do
+ with true <- Config.get([:mrf_remote_report, :reject_anonymous]),
+ %URI{path: "/actor"} <- URI.parse(actor) do
+ {:reject, "[RemoteReportPolicy] Anonymous: #{actor}"}
+ else
+ _ -> {:ok, object}
+ end
+ end
+
+ defp maybe_reject_third_party(%{"object" => objects} = object) do
+ {_, to} =
+ case objects do
+ [head | tail] when is_binary(head) -> {tail, head}
+ s when is_binary(s) -> {[], s}
+ _ -> {[], ""}
+ end
+
+ with true <- Config.get([:mrf_remote_report, :reject_third_party]),
+ false <- String.starts_with?(to, Pleroma.Web.Endpoint.url()) do
+ {:reject, "[RemoteReportPolicy] Third-party: #{to}"}
+ else
+ _ -> {:ok, object}
+ end
+ end
+
+ defp maybe_reject_empty_message(%{"content" => content} = object)
+ when is_binary(content) and content != "" do
+ {:ok, object}
+ end
+
+ defp maybe_reject_empty_message(object) do
+ if Config.get([:mrf_remote_report, :reject_empty_message]) do
+ {:reject, ["RemoteReportPolicy] No content"]}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp local?(%{"actor" => actor}) do
+ String.starts_with?(actor, Pleroma.Web.Endpoint.url())
+ end
+
+ @impl true
+ def describe do
+ mrf_remote_report =
+ Config.get(:mrf_remote_report)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_remote_report: mrf_remote_report}}
+ end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_remote_report,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy",
+ label: "MRF Remote Report",
+ description: "Drop remote reports if they don't contain enough information.",
+ children: [
+ %{
+ key: :reject_all,
+ type: :boolean,
+ description: "Reject all remote reports? (this option takes precedence)",
+ suggestions: [false]
+ },
+ %{
+ key: :reject_anonymous,
+ type: :boolean,
+ description: "Reject anonymous remote reports?",
+ suggestions: [true]
+ },
+ %{
+ key: :reject_third_party,
+ type: :boolean,
+ description: "Reject reports on users from third-party instances?",
+ suggestions: [true]
+ },
+ %{
+ key: :reject_empty_message,
+ type: :boolean,
+ description: "Reject remote reports with no message?",
+ suggestions: [true]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index b3043b93a..35774d410 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
+ import Pleroma.Constants, only: [activity_types: 0, object_types: 0]
+
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
@@ -38,6 +40,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@impl true
def validate(object, meta)
+ # This overload works together with the InboxGuardPlug
+ # and ensures that we are not accepting any activity type
+ # that cannot pass InboxGuardPlug.
+ # If we want to support any more activity types, make sure to
+ # add it in Pleroma.Constants's activity_types or object_types,
+ # and, if applicable, allowed_activity_types_from_strangers.
+ def validate(%{"type" => type}, _meta)
+ when type not in activity_types() and type not in object_types(),
+ do: {:error, :not_allowed_object_type}
+
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 3bd589f49..d4175b56f 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1320,6 +1320,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
html_body: ~r/#{note.data["object"]}/i
)
end
+
+ test "it accepts an incoming Block", %{conn: conn, data: data} do
+ user = insert(:user)
+
+ data =
+ data
+ |> Map.put("type", "Block")
+ |> Map.put("to", [user.ap_id])
+ |> Map.put("cc", [])
+ |> Map.put("object", user.ap_id)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+ ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+ assert Activity.get_by_ap_id(data["id"])
+ end
end
describe "GET /users/:nickname/outbox" do
diff --git a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs
new file mode 100644
index 000000000..8d2a6b4fa
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs
@@ -0,0 +1,155 @@
+defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicyTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy
+
+ setup do
+ clear_config([:mrf_remote_report, :reject_all], false)
+ end
+
+ test "doesn't impact local report" do
+ clear_config([:mrf_remote_report, :reject_anonymous], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "http://localhost:4001/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects anonymous report if `reject_anonymous: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves anonymous report if `reject_anonymous: false`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects report on third party if `reject_third_party: true`" do
+ clear_config([:mrf_remote_report, :reject_third_party], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves report on first party if `reject_third_party: true`" do
+ clear_config([:mrf_remote_report, :reject_third_party], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["http://localhost:4001/actor"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves report on third party if `reject_third_party: false`" do
+ clear_config([:mrf_remote_report, :reject_third_party], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects empty message report if `reject_empty_message: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects empty message report (\"\") if `reject_empty_message: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"],
+ "content" => ""
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves empty message report if `reject_empty_message: false`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves anonymous, empty message report with all settings disabled" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "reject remote report if `reject_all: true`" do
+ clear_config([:mrf_remote_report, :reject_all], true)
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "content" => "Transphobia",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+end