extend reject MRF to check if originating instance is blocked

This commit is contained in:
FloatingGhost 2022-12-09 19:57:29 +00:00
parent d5828f1c5e
commit 6f83ae27aa
5 changed files with 224 additions and 72 deletions

View file

@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Regular task to prune local transient activities - Regular task to prune local transient activities
- Task to manually run the transient prune job (pleroma.database prune\_task) - Task to manually run the transient prune job (pleroma.database prune\_task)
- Ability to follow hashtags - Ability to follow hashtags
- Option to extend `reject` in MRF-Simple to apply to entire threads, where the originating instance is rejected
## Changed ## Changed
- MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py) - MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py)

View file

@ -391,7 +391,8 @@ config :pleroma, :mrf_simple,
accept: [], accept: [],
avatar_removal: [], avatar_removal: [],
banner_removal: [], banner_removal: [],
reject_deletes: [] reject_deletes: [],
handle_threads: true
config :pleroma, :mrf_keyword, config :pleroma, :mrf_keyword,
reject: [], reject: [],

View file

@ -25,7 +25,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
def check_simple_policy_tuples do def check_simple_policy_tuples do
has_strings = has_strings =
Config.get([:mrf_simple]) Config.get([:mrf_simple])
|> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end) |> Enum.any?(fn {_, v} -> is_list(v) and Enum.any?(v, &is_binary/1) end)
if has_strings do if has_strings do
Logger.warn(""" Logger.warn("""
@ -66,6 +66,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
new_config = new_config =
Config.get([:mrf_simple]) Config.get([:mrf_simple])
|> Enum.filter(fn {k, v} -> not is_atom(v) end)
|> Enum.map(fn {k, v} -> |> Enum.map(fn {k, v} ->
{k, {k,
Enum.map(v, fn Enum.map(v, fn

View file

@ -13,20 +13,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
require Pleroma.Constants require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info) do
accepts = accepts =
instance_list(:accept) instance_list(:accept)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
cond do cond do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, nil}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, nil}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, nil}
true -> {:reject, "[SimplePolicy] host not in accept list"} true -> {:reject, "[SimplePolicy] host not in accept list"}
end end
end end
defp check_reject(%{host: actor_host} = _actor_info, object) do defp check_reject(%{host: actor_host} = _actor_info) do
rejects = rejects =
instance_list(:reject) instance_list(:reject)
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(rejects, actor_host) do if MRF.subdomain_match?(rejects, actor_host) do
{:reject, "[SimplePolicy] host in reject list"} {:reject, "[SimplePolicy] host in reject list"}
else else
{:ok, object} {:ok, nil}
end end
end end
@ -178,6 +178,55 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(_actor_info, object), do: {:ok, object} defp check_banner_removal(_actor_info, object), do: {:ok, object}
defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do
rest
|> String.split(",", parts: 2, trim: true)
|> hd()
|> case do
nil -> nil
hostname -> URI.parse("//" <> hostname)
end
end
defp extract_context_uri(%{"context" => "http" <> _ = context}), do: URI.parse(context)
defp extract_context_uri(_), do: nil
defp check_context(activity) do
uri = extract_context_uri(activity)
with {:uri, true} <- {:uri, Kernel.match?(%URI{}, uri)},
{:ok, _} <- check_accept(uri),
{:ok, _} <- check_reject(uri) do
{:ok, activity}
else
# Can't check.
{:uri, false} -> {:ok, activity}
{:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end
end
defp check_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}} = activity) do
with {:ok, _} <- filter(in_reply_to) do
{:ok, activity}
end
end
defp check_reply_to(activity), do: {:ok, activity}
defp maybe_check_thread(activity) do
if Config.get([:mrf_simple, :handle_threads], true) do
with {:ok, _} <- check_context(activity),
{:ok, _} <- check_reply_to(activity) do
{:ok, activity}
end
else
{:ok, activity}
end
end
defp check_object(%{"object" => object} = activity) do defp check_object(%{"object" => object} = activity) do
with {:ok, _object} <- filter(object) do with {:ok, _object} <- filter(object) do
{:ok, activity} {:ok, activity}
@ -210,13 +259,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
def filter(%{"actor" => actor} = object) do def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_accept(actor_info, object), with {:ok, _} <- check_accept(actor_info),
{:ok, object} <- check_reject(actor_info, object), {:ok, _} <- check_reject(actor_info),
{:ok, object} <- check_media_removal(actor_info, object), {:ok, object} <- check_media_removal(actor_info, object),
{:ok, object} <- check_media_nsfw(actor_info, object), {:ok, object} <- check_media_nsfw(actor_info, object),
{:ok, object} <- check_ftl_removal(actor_info, object), {:ok, object} <- check_ftl_removal(actor_info, object),
{:ok, object} <- check_followers_only(actor_info, object), {:ok, object} <- check_followers_only(actor_info, object),
{:ok, object} <- check_report_removal(actor_info, object), {:ok, object} <- check_report_removal(actor_info, object),
{:ok, object} <- maybe_check_thread(object),
{:ok, object} <- check_object(object) do {:ok, object} <- check_object(object) do
{:ok, object} {:ok, object}
else else
@ -230,8 +280,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_accept(actor_info, object), with {:ok, _} <- check_accept(actor_info),
{:ok, object} <- check_reject(actor_info, object), {:ok, _} <- check_reject(actor_info),
{:ok, object} <- check_avatar_removal(actor_info, object), {:ok, object} <- check_avatar_removal(actor_info, object),
{:ok, object} <- check_banner_removal(actor_info, object) do {:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object} {:ok, object}
@ -242,11 +292,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end end
end end
def filter(%{"id" => id} = object) do
with {:ok, _} <- filter(id) do
{:ok, object}
end
end
def filter(object) when is_binary(object) do def filter(object) when is_binary(object) do
uri = URI.parse(object) uri = URI.parse(object)
with {:ok, object} <- check_accept(uri, object), with {:ok, _} <- check_accept(uri),
{:ok, object} <- check_reject(uri, object) do {:ok, _} <- check_reject(uri) do
{:ok, object} {:ok, object}
else else
{:reject, nil} -> {:reject, "[SimplePolicy]"} {:reject, nil} -> {:reject, "[SimplePolicy]"}
@ -288,6 +344,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
mrf_simple_excluded = mrf_simple_excluded =
Config.get(:mrf_simple) Config.get(:mrf_simple)
|> Enum.filter(fn {_, v} -> is_list(v) end)
|> Enum.map(fn {rule, instances} -> |> Enum.map(fn {rule, instances} ->
{rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)} {rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
end) end)
@ -332,7 +389,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
label: "MRF Simple", label: "MRF Simple",
description: "Simple ingress policies", description: "Simple ingress policies",
children: children:
[ ([
%{ %{
key: :media_removal, key: :media_removal,
description: description:
@ -388,10 +445,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
type: {:list, :tuple}, type: {:list, :tuple},
key_placeholder: "instance", key_placeholder: "instance",
value_placeholder: "reason", value_placeholder: "reason",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}] suggestions: [
{"example.com", "Some reason"},
{"*.example.com", "Another reason"}
]
} }
) )
end) end)) ++
[
%{
key: :handle_threads,
label: "Apply to entire threads",
type: :boolean,
description:
"Enable to filter replies to threads based from their originating instance, using the reject and accept rules"
}
]
} }
end end
end end

View file

@ -356,6 +356,86 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert {:reject, _} = SimplePolicy.filter(announce) assert {:reject, _} = SimplePolicy.filter(announce)
end end
test "accept by matching context URI if :handle_threads is disabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], false)
remote_message =
build_remote_message()
|> Map.put("context", "https://blocked.tld/contexts/abc")
assert {:ok, _} = SimplePolicy.filter(remote_message)
end
test "accept by matching conversation field if :handle_threads is disabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], false)
remote_message =
build_remote_message()
|> Map.put(
"conversation",
"tag:blocked.tld,1997-06-25:objectId=12345:objectType=Conversation"
)
assert {:ok, _} = SimplePolicy.filter(remote_message)
end
test "accept by matching reply ID if :handle_threads is disabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], false)
remote_message =
build_remote_message()
|> Map.put("type", "Create")
|> Map.put("object", %{
"type" => "Note",
"inReplyTo" => "https://blocked.tld/objects/1"
})
assert {:ok, _} = SimplePolicy.filter(remote_message)
end
test "reject by matching context URI if :handle_threads is enabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], true)
remote_message =
build_remote_message()
|> Map.put("context", "https://blocked.tld/contexts/abc")
assert {:reject, _} = SimplePolicy.filter(remote_message)
end
test "reject by matching conversation field if :handle_threads is enabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], true)
remote_message =
build_remote_message()
|> Map.put(
"conversation",
"tag:blocked.tld,1997-06-25:objectId=12345:objectType=Conversation"
)
assert {:reject, _} = SimplePolicy.filter(remote_message)
end
test "reject by matching reply ID if :handle_threads is enabled" do
clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
clear_config([:mrf_simple, :handle_threads], true)
remote_message =
build_remote_message()
|> Map.put("type", "Create")
|> Map.put("object", %{
"type" => "Note",
"inReplyTo" => "https://blocked.tld/objects/1"
})
assert {:reject, _} = SimplePolicy.filter(remote_message)
end
end end
describe "when :followers_only" do describe "when :followers_only" do