Improve MRF returns

Update MRFs to return more specific information about the actions taken. This will help to simplify hooking into the MRF framework for telemetry and logging purposes.

Backwards compatibility exists for MRFs that only return {:ok, _} and {:reject, _}
This commit is contained in:
Mark Felder 2024-08-26 11:48:59 -04:00
parent 7839fb6db3
commit fe130b80cb
35 changed files with 337 additions and 219 deletions

View file

@ -8,14 +8,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
@impl true
def filter(activity) do
activity =
if note?(activity) and local?(activity) do
maybe_add_expiration(activity)
else
activity
end
{:ok, activity}
if note?(activity) and local?(activity) do
maybe_add_expiration(activity)
else
{:pass, activity}
end
end
@impl true
@ -35,9 +32,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
with %{"expires_at" => existing_expires_at} <- activity,
:lt <- DateTime.compare(existing_expires_at, expires_at) do
activity
{:pass, activity}
else
_ -> Map.put(activity, "expires_at", expires_at)
_ ->
{:filter, Map.put(activity, "expires_at", expires_at)}
end
end

View file

@ -69,14 +69,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
score = determine_if_followbot(actor)
if score < 0.8 || bot_allowed?(activity, actor) do
{:ok, activity}
{:pass, activity}
else
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
reason = "Scored #{actor_id} as #{score}"
{:reject, %{activity: activity, reason: reason}}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -7,8 +7,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Logger
@impl true
def history_awareness, do: :auto
@ -33,27 +31,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)},
{:old_user, true} <- {:old_user, old_user?(u)} do
{:ok, activity}
{:pass, activity}
else
{:ok, %User{local: true}} ->
{:ok, activity}
{:pass, activity}
{:contains_links, false} ->
{:ok, activity}
{:pass, activity}
{:old_user, false} ->
{:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
reason = "User has no posts nor followers"
{:reject, %{activity: activity, reason: reason}}
{:error, _} ->
{:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
reason = "Failed to get or fetch user by ap_id"
{:reject, %{activity: activity, reason: reason}}
e ->
{:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
reason = "Unhandled error"
{:reject, %{activity: activity, reason: reason, error: e}}
end
end
# in all other cases, pass through
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -60,27 +60,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
{:has_mentions, true} <- {:has_mentions, object_has_recipients?(activity)},
{:good_reputation, true} <- {:good_reputation, good_reputation?(u)} do
{:ok, activity}
{:pass, activity}
else
{:ok, %User{local: true}} ->
{:ok, activity}
{:pass, activity}
{:has_mentions, false} ->
{:ok, activity}
{:pass, activity}
{:good_reputation, false} ->
{:reject, "[AntiMentionSpamPolicy] User rejected"}
reason = "Bad reputation"
{:reject, %{activity: activity, reason: reason}}
{:error, _} ->
{:reject, "[AntiMentionSpamPolicy] Failed to get or fetch user by ap_id"}
reason = "Failed to get or fetch user by ap_id"
{:reject, %{activity: activity, reason: reason}}
e ->
{:reject, "[AntiMentionSpamPolicy] Unhandled error #{inspect(e)}"}
reason = "Unhandled error"
{:reject, %{activity: activity, reason: reason, error: e}}
end
end
# in all other cases, pass through
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -32,8 +32,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
alias Pleroma.Config
require Logger
@query_retries 1
@query_timeout 500
@ -42,14 +40,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
actor_info = URI.parse(actor)
with {:ok, activity} <- check_rbl(actor_info, activity) do
{:ok, activity}
{:pass, activity}
else
_ -> {:reject, "[DNSRBLPolicy]"}
{:error, reason} ->
{:reject, %{activity: activity, reason: reason}}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe do
@ -102,19 +103,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
if Enum.empty?(rbl_response) do
{:ok, activity}
else
Task.start(fn ->
reason =
case rblquery(query, :txt) do
[[result]] -> result
_ -> "undefined"
end
reason =
case rblquery(query, :txt) do
[[result]] -> to_string(result)
_ -> "undefined"
end
Logger.warning(
"DNSRBL Rejected activity from #{actor_host} for reason: #{inspect(reason)}"
)
end)
:error
{:error, reason}
end
else
_ -> {:ok, activity}

View file

@ -3,14 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
require Logger
@moduledoc "Drop and log everything received"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(activity) do
Logger.debug("REJECTING #{inspect(activity)}")
{:reject, activity}
{:reject, %{activity: activity}}
end
@impl true

View file

@ -44,7 +44,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
end),
activity <- Map.put(activity, "object", object),
activity <- maybe_delist(activity) do
{:ok, activity}
{:filter, activity}
end
end
@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
with object <- process_remove(object, :url, config_remove_url()),
object <- process_remove(object, :shortcode, config_remove_shortcode()) do
{:ok, object}
{:filter, object}
end
end
@ -60,16 +60,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
def filter(%{"type" => "EmojiReact"} = object) do
with {:ok, _} <-
matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
{:ok, object}
{:filter, object}
else
_ ->
{:reject, "[EmojiPolicy] Rejected for having disallowed emoji"}
reason = "Has disallowed emoji"
{:reject, %{activity: object, reason: reason}}
end
end
@impl true
def filter(activity) do
{:ok, activity}
{:pass, activity}
end
defp match_string?(string, pattern) when is_binary(pattern) do

View file

@ -38,10 +38,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
activity = Map.put(activity, "object", child)
{:ok, activity}
if match?(^child, object["inReplyTo"]) do
{:pass, activity}
else
{:filter, activity}
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
def describe, do: {:ok, %{}}
end

View file

@ -39,14 +39,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.FODirectReply do
|> put_in(["object", "cc"], [])
|> put_in(["object", "to"], direct_to)
{:ok, updated_activity}
{:filter, updated_activity}
else
_ -> {:ok, activity}
_ ->
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -19,15 +19,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
try_follow(follower, activity)
else
nil ->
Logger.warning(
Logger.error(
"#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
account does not exist, or the account is not correctly configured as a bot."
)
{:ok, activity}
{:pass, activity}
_ ->
{:ok, activity}
{:pass, activity}
end
end
@ -45,15 +45,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
false <- User.following?(follower, user),
false <- User.locked?(user),
false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
Logger.debug(
"#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
)
CommonAPI.follow(user, follower)
end
end)
{:ok, activity}
{:filter, activity}
end
@impl true

View file

@ -42,14 +42,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
|> Map.put("cc", cc)
|> Map.put("object", object)
{:ok, activity}
{:filter, activity}
else
{:ok, activity}
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -36,7 +36,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
@impl true
def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do
tag =
updated_tag =
tag
|> prepend_author(
object["inReplyTo"],
@ -48,11 +48,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
)
|> Enum.uniq()
{:ok, put_in(activity["object"]["tag"], tag)}
if match?(^tag, updated_tag) do
{:pass, activity}
else
updated_activity = put_in(activity["object"]["tag"], updated_tag)
{:filter, updated_activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -113,7 +113,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
do: "<span class=\"recipients-inline\">#{added_mentions}</span>",
else: ""
content =
updated_content =
cond do
# For Markdown posts, insert the mentions inside the first <p> tag
recipients_inline != "" && String.starts_with?(content, "<p>") ->
@ -126,11 +126,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
content
end
{:ok, put_in(activity["object"]["content"], content)}
if match?(^content, updated_content) do
{:pass, activity}
else
updated_activity = put_in(activity["object"]["content"], updated_content)
{:filter, updated_activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -21,7 +21,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
defp check_reject(activity, hashtags) do
if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
{:reject, "[HashtagPolicy] Matches with rejected keyword"}
reason = "Matches with rejected keyword"
{:reject, %{activity: activity, reason: reason}}
else
{:ok, activity}
end
@ -83,23 +84,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags
if hashtags != [] do
with {:ok, activity} <- check_reject(activity, hashtags),
{:ok, activity} <-
with {:ok, processed_activity} <- check_reject(activity, hashtags),
{:ok, processed_activity} <-
(if type == "Create" do
check_ftl_removal(activity, hashtags)
check_ftl_removal(processed_activity, hashtags)
else
{:ok, activity}
{:ok, processed_activity}
end),
{:ok, activity} <- check_sensitive(activity) do
{:ok, activity}
{:ok, processed_activity} <- check_sensitive(processed_activity) do
if match?(^activity, processed_activity) do
{:pass, processed_activity}
else
{:filter, processed_activity}
end
end
else
{:ok, activity}
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe do

View file

@ -43,7 +43,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp reject_activity(activity, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(activity) do
if recipients > threshold do
{:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
reason = "#{recipients} recipients is over the limit of #{threshold}"
{:reject, %{activity: activity, reason: reason}}
else
{:ok, activity}
end
@ -84,15 +85,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
with {:ok, activity} <- reject_activity(activity, reject_threshold),
{:ok, activity} <- delist_activity(activity, delist_threshold) do
{:ok, activity}
else
e -> e
{:ok, processed_activity} <- delist_activity(activity, delist_threshold) do
if match?(^activity, processed_activity) do
{:pass, processed_activity}
else
{:filter, processed_activity}
end
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe,

View file

@ -31,24 +31,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
else
template = Pleroma.Config.get([:mrf_inline_quote, :template])
content =
updated_content =
if String.ends_with?(content, "</p>"),
do:
String.trim_trailing(content, "</p>") <>
build_inline_quote(template, quote_url) <> "</p>",
else: content <> build_inline_quote(template, quote_url)
Map.put(object, "content", content)
Map.put(object, "content", updated_content)
end
end
@impl true
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
{:ok, Map.put(activity, "object", filter_object(object))}
updated_object = filter_object(object)
if match?(^updated_object, object) do
{:pass, activity}
else
updated_activity = Map.put(activity, "object", updated_object)
{:filter, updated_activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(payload, pattern)
end) do
{:reject, "[KeywordPolicy] Matches with rejected keyword"}
{:reject, "Matches rejected keyword"}
else
{:ok, activity}
end
@ -112,18 +112,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
def filter(%{"type" => type, "object" => %{"content" => _content}} = activity)
when type in ["Create", "Update"] do
with {:ok, activity} <- check_reject(activity),
{:ok, activity} <- check_ftl_removal(activity),
{:ok, activity} <- check_replace(activity) do
{:ok, activity}
{:ok, processed_activity} <- check_ftl_removal(activity),
{:ok, processed_activity} <- check_replace(processed_activity) do
if match?(^activity, processed_activity) do
{:pass, processed_activity}
else
{:filter, processed_activity}
end
else
{:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, _} = e -> e
_e -> {:reject, "[KeywordPolicy] "}
{:reject, reason} ->
{:reject, %{activity: activity, reason: reason}}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe do

View file

@ -53,11 +53,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do
preload(activity)
{:ok, activity}
{:filter, activity}
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -14,14 +14,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
if rejected_mention =
Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
reason = "Rejected for mention of #{rejected_mention}"
{:reject, %{activity: activity, reason: reason}}
else
{:ok, activity}
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -15,14 +15,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
true <- note?(activity),
false <- has_attachment?(activity),
true <- only_mentions?(activity) do
{:reject, "[NoEmptyPolicy]"}
reason = "no content"
{:reject, %{activity: activity, reason: reason}}
else
_ ->
{:ok, activity}
{:pass, activity}
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
defp local?(actor) do
if actor |> String.starts_with?("#{Endpoint.url()}") do

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@impl true
def filter(activity) do
{:ok, activity}
{:pass, activity}
end
@impl true

View file

@ -17,11 +17,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
} = activity
)
when type in ["Create", "Update"] and content in [".", "<p>.</p>"] do
{:ok, put_in(activity, ["object", "content"], "")}
{:filter, put_in(activity, ["object", "content"], "")}
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -16,16 +16,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
when type in ["Create", "Update"] do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
content =
object["content"]
content = object["content"]
updated_content =
content
|> HTML.filter_tags(scrub_policy)
activity = put_in(activity, ["object", "content"], content)
{:ok, activity}
if match?(^content, updated_content) do
{:pass, activity}
else
updated_activity = put_in(activity, ["object", "content"], updated_content)
{:filter, updated_activity}
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -64,7 +64,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
Jason.decode(body)
else
error ->
Logger.warning("""
Logger.error("""
[NsfwApiPolicy]: The API server failed. Skipping.
#{inspect(error)}
""")
@ -134,17 +134,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
@impl true
def filter(activity) do
with {:sfw, activity} <- check_object_nsfw(activity) do
{:ok, activity}
{:pass, activity}
else
{:nsfw, _data} -> handle_nsfw(activity)
{:nsfw, _data} ->
handle_nsfw(activity)
end
end
defp handle_nsfw(activity) do
if Config.get([@policy, :reject]) do
{:reject, activity}
{:reject, %{activity: activity, reason: :nsfw}}
else
{:ok,
{:filter,
activity
|> maybe_unlist()
|> maybe_mark_sensitive()}

View file

@ -11,24 +11,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@moduledoc "Filter activities depending on their age"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp check_date(%{"object" => %{"published" => published}} = activity) do
with %DateTime{} = now <- DateTime.utc_now(),
{:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published),
max_ttl <- Config.get([:mrf_object_age, :threshold]),
{:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do
{:ok, activity}
else
{:ttl, true} ->
{:reject, nil}
defp invalid_age?(%{"object" => %{"published" => published}}) do
now = DateTime.utc_now()
{:ok, %DateTime{} = then, _} = DateTime.from_iso8601(published)
max_ttl = Config.get([:mrf_object_age, :threshold])
e ->
{:error, e}
end
DateTime.diff(now, then) > max_ttl
end
defp check_reject(activity, actions) do
if :reject in actions do
{:reject, "[ObjectAgePolicy]"}
{:reject, %{activity: activity, reason: :object_age}}
else
{:ok, activity}
end
@ -54,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, activity}
else
_e ->
{:reject, "[ObjectAgePolicy] Unhandled error"}
e ->
{:reject, %{activity: activity, reason: "Unhandled error", error: e}}
end
else
{:ok, activity}
@ -77,8 +70,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, activity}
else
_e ->
{:reject, "[ObjectAgePolicy] Unhandled error"}
e ->
{:reject, %{activity: activity, reason: "Unhandled error", error: e}}
end
else
{:ok, activity}
@ -88,14 +81,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@impl true
def filter(%{"type" => "Create", "object" => %{"published" => _}} = activity) do
with actions <- Config.get([:mrf_object_age, :actions]),
{:reject, _} <- check_date(activity),
true <- invalid_age?(activity),
{:ok, activity} <- check_reject(activity, actions),
{:ok, activity} <- check_delist(activity, actions),
{:ok, activity} <- check_strip_followers(activity, actions) do
{:ok, activity}
{:filter, activity}
else
# check_date() is allowed to short-circuit the pipeline
e -> e
false ->
{:pass, activity}
{:reject, _reason} = result ->
result
end
end

View file

@ -3,7 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
@callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
@type legacy_return :: {:ok | :reject, map()} | {:filter, map()}
@type return :: {:pass, map()} | {:filter, map()} | {:reject, %{activity: map(), reason: any()}}
@callback filter(Pleroma.Activity.t()) :: legacy_return() | return()
@callback describe() :: {:ok | :error, map()}
@callback config_description() :: %{
optional(:children) => [map()],

View file

@ -46,14 +46,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
|> put_in(["object", "to"], updated_to)
|> put_in(["object", "cc"], updated_cc)
{:ok, updated_activity}
{:filter, updated_activity}
else
_ -> {:ok, activity}
_ ->
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -12,11 +12,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
@impl true
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
{:ok, Map.put(activity, "object", filter_object(object))}
updated_object = filter_object(object)
if match?(^object, updated_object) do
{:pass, activity}
else
updated_activity = Map.put(activity, "object", updated_object)
{:filter, updated_activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -13,15 +13,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
require Pleroma.Constants
@impl true
def filter(%{"type" => "Create"} = object) do
user = User.get_cached_by_ap_id(object["actor"])
def filter(%{"type" => "Create"} = activity) do
user = User.get_cached_by_ap_id(activity["actor"])
# Determine visibility
visibility =
cond do
Pleroma.Constants.as_public() in object["to"] -> "public"
Pleroma.Constants.as_public() in object["cc"] -> "unlisted"
user.follower_address in object["to"] -> "followers"
Pleroma.Constants.as_public() in activity["to"] -> "public"
Pleroma.Constants.as_public() in activity["cc"] -> "unlisted"
user.follower_address in activity["to"] -> "followers"
true -> "direct"
end
@ -29,21 +29,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
cond do
visibility in ["public", "unlisted"] ->
{:ok, object}
{:pass, activity}
visibility == "followers" and Keyword.get(policy, :allow_followersonly) ->
{:ok, object}
{:pass, activity}
visibility == "direct" and Keyword.get(policy, :allow_direct) ->
{:ok, object}
{:pass, activity}
true ->
{:reject, "[RejectNonPublic] visibility: #{visibility}"}
reason = "visibility: #{visibility}"
{:reject, %{activity: activity, reason: reason}}
end
end
@impl true
def filter(object), do: {:ok, object}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe,

View file

@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
accepts == [] -> {:ok, activity}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, activity}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, activity}
true -> {:reject, "[SimplePolicy] host not in accept list"}
true -> {:reject, %{activity: activity, reason: "host not in accept list"}}
end
end
@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
{:reject, "[SimplePolicy] host in reject list"}
{:reject, %{activity: activity, reason: "host in reject list"}}
else
{:ok, activity}
end
@ -142,7 +142,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, "[SimplePolicy] host in report_removal list"}
{:reject, %{activity: activity, reason: "host in report_removal list"}}
else
{:ok, activity}
end
@ -200,7 +200,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, "[SimplePolicy] host in reject_deletes list"}
{:reject, %{activity: activity, reason: "host in reject_deletes list"}}
else
{:ok, activity}
end
@ -218,9 +218,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, activity} <- check_followers_only(actor_info, activity),
{:ok, activity} <- check_report_removal(actor_info, activity),
{:ok, activity} <- check_object(activity) do
{:ok, activity}
{:pass, activity}
else
{:reject, _} = e -> e
{:reject, _} = result ->
result
end
end
@ -232,24 +233,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, activity} <- check_reject(actor_info, activity),
{:ok, activity} <- check_avatar_removal(actor_info, activity),
{:ok, activity} <- check_banner_removal(actor_info, activity) do
{:ok, activity}
{:pass, activity}
else
{:reject, _} = e -> e
{:reject, _} = result ->
result
end
end
def filter(activity) when is_binary(activity) do
uri = URI.parse(activity)
def filter(ap_id) when is_binary(ap_id) do
uri = URI.parse(ap_id)
with {:ok, activity} <- check_accept(uri, activity),
{:ok, activity} <- check_reject(uri, activity) do
{:ok, activity}
with {_, {:ok, ap_id}} <- {:check_accept, check_accept(uri, ap_id)},
{_, {:ok, ap_id}} <- {:check_reject, check_reject(uri, ap_id)} do
{:pass, ap_id}
else
{:reject, _} = e -> e
{:check_accept, {:reject, _}} ->
fake_activity = %{data: %{id: ap_id}}
{:reject, %{activity: fake_activity, reason: "Host not in accept list"}}
{:check_reject, {:reject, _}} ->
fake_activity = %{data: %{id: ap_id}}
{:reject, %{activity: fake_activity, reason: "Host in reject list"}}
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe do

View file

@ -97,10 +97,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end
end
{:ok, activity}
{:pass, activity}
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
@spec config_description :: %{

View file

@ -6,8 +6,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.MRF
require Logger
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp lookup_subchain(actor) do
@ -21,19 +19,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@impl true
def filter(%{"actor" => actor} = activity) do
with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug(
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
)
with {:ok, _match, subchain} <- lookup_subchain(actor) do
MRF.filter(subchain, activity)
else
_e -> {:ok, activity}
_e ->
{:pass, activity}
end
end
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -125,26 +125,40 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
if user.local == true do
{:ok, activity}
else
{:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
reason = "Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"
{:reject, %{activity: activity, reason: reason}}
end
end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(
"mrf_tag:disable-any-subscription",
%{"type" => "Follow", "actor" => actor} = activity
) do
reason = "Follow from #{actor} tagged with mrf_tag:disable-any-subscription"
{:reject, %{activity: activity, reason: reason}}
end
defp process_tag(_, activity), do: {:ok, activity}
def filter_activity(actor, activity) do
User.get_cached_by_ap_id(actor)
|> get_tags()
|> Enum.reduce({:ok, activity}, fn
tag, {:ok, activity} ->
process_tag(tag, activity)
result =
User.get_cached_by_ap_id(actor)
|> get_tags()
|> Enum.reduce({:ok, activity}, fn
tag, {:ok, activity} ->
process_tag(tag, activity)
_, error ->
error
end)
_, error ->
error
end)
case result do
{:ok, activity} ->
{:pass, activity}
{:reject, _} = reject ->
reject
end
end
@impl true
@ -156,7 +170,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
do: filter_activity(actor, activity)
@impl true
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe, do: {:ok, %{}}

View file

@ -14,7 +14,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
if actor in allow_list do
{:ok, activity}
else
{:reject, "[UserAllowListPolicy] #{actor} not in the list"}
reason = "#{actor} not in the list"
{:reject, %{activity: activity, reason: reason}}
end
end
@ -28,10 +29,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
[]
)
filter_by_list(activity, allow_list)
case filter_by_list(activity, allow_list) do
{:ok, activity} ->
{:pass, activity}
{:reject, _} = reject ->
reject
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe do

View file

@ -8,33 +8,36 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(%{"type" => "Undo", "object" => object} = activity) do
with {:ok, _} <- filter(object) do
{:ok, activity}
else
{:reject, _} = e -> e
end
end
def filter(%{"type" => "Undo", "object" => object} = _activity), do: filter(object)
def filter(%{"type" => activity_type} = activity) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
{_, true} <-
{:accepted,
{:accepted_vocab,
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, activity_type)},
{_, false} <-
{:rejected,
{:rejected_vocab,
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, activity_type)},
{:ok, _} <- filter(activity["object"]) do
{:ok, activity}
{:pass, _} <- filter(activity["object"]) do
{:pass, activity}
else
{:reject, _} = e -> e
{:accepted, _} -> {:reject, "[VocabularyPolicy] #{activity_type} not in accept list"}
{:rejected, _} -> {:reject, "[VocabularyPolicy] #{activity_type} in reject list"}
{:reject, reason} ->
{:reject, %{activity: activity, reason: reason}}
{:accepted_vocab, _} ->
reason = "#{activity_type} not in accept list"
{:reject, %{activity: activity, reason: reason}}
{:rejected_vocab, _} ->
reason = "#{activity_type} in reject list"
{:reject, %{activity: activity, reason: reason}}
end
end
def filter(activity), do: {:ok, activity}
def filter(activity) do
{:pass, activity}
end
@impl true
def describe,