summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Felder <feld@feld.me>2021-02-08 15:32:47 -0600
committerMark Felder <feld@feld.me>2021-02-08 15:32:47 -0600
commit55a13fc3607c9d753e6fca596010c0a96ba3fba8 (patch)
treed0556e556083e437d9b7482b1805eaef071a393f
parentd7262f7d2232343e47c2206b134a7d794e35c505 (diff)
MRF NoEmptyPolicy: Deny posts from local users if there is no content or only mentions.
Helps prevent accidental button mashes from submitting incomplete posts
-rw-r--r--CHANGELOG.md1
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex61
-rw-r--r--test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs154
3 files changed, 216 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1dbdb3f4e..d4acbc9a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ability to set ActivityPub aliases for follower migration.
- Configurable background job limits for RichMedia (link previews) and MediaProxyWarmingPolicy
- Ability to define custom HTTP headers per each frontend
+- MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
<details>
<summary>API Changes</summary>
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
new file mode 100644
index 000000000..32bb1b645
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -0,0 +1,61 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
+ @moduledoc "Filter local activities which have no content"
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ alias Pleroma.Web
+
+ @impl true
+ def filter(%{"actor" => actor} = object) do
+ with true <- is_local?(actor),
+ true <- is_note?(object),
+ false <- has_attachment?(object),
+ true <- only_mentions?(object) do
+ {:reject, "[NoEmptyPolicy]"}
+ else
+ _ ->
+ {:ok, object}
+ end
+ end
+
+ def filter(object), do: {:ok, object}
+
+ defp is_local?(actor) do
+ if actor |> String.starts_with?("#{Web.base_url()}") do
+ true
+ else
+ false
+ end
+ end
+
+ defp has_attachment?(%{
+ "type" => "Create",
+ "object" => %{"type" => "Note", "attachment" => attachments}
+ })
+ when length(attachments) > 0,
+ do: true
+
+ defp has_attachment?(_), do: false
+
+ defp only_mentions?(%{"type" => "Create", "object" => %{"type" => "Note", "source" => source}}) do
+ non_mentions =
+ source |> String.split() |> Enum.filter(&(not String.starts_with?(&1, "@"))) |> length
+
+ if non_mentions > 0 do
+ false
+ else
+ true
+ end
+ end
+
+ defp only_mentions?(_), do: false
+
+ defp is_note?(%{"type" => "Create", "object" => %{"type" => "Note"}}), do: true
+ defp is_note?(_), do: false
+
+ @impl true
+ def describe, do: {:ok, %{}}
+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 000000000..fbcf68414
--- /dev/null
+++ b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs
@@ -0,0 +1,154 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 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
+end