From 55a13fc3607c9d753e6fca596010c0a96ba3fba8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 8 Feb 2021 15:32:47 -0600 Subject: [PATCH] MRF NoEmptyPolicy: Deny posts from local users if there is no content or only mentions. Helps prevent accidental button mashes from submitting incomplete posts --- CHANGELOG.md | 1 + .../web/activity_pub/mrf/no_empty_policy.ex | 61 ++++++++ .../web/activity_pub/mrf/no_empty_policy_test.exs | 154 +++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex create mode 100644 test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs 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
API Changes 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 +# 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 +# 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