Feature/1087 wildcard option for blocks Closes #1087 See merge request pleroma/pleroma!1467tags/v1.1.4
@@ -873,10 +873,13 @@ defmodule Pleroma.User do | |||
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do | |||
blocks = info.blocks | |||
domain_blocks = info.domain_blocks | |||
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks) | |||
%{host: host} = URI.parse(ap_id) | |||
Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host)) | |||
Enum.member?(blocks, ap_id) || | |||
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host) | |||
end | |||
def subscribed_to?(user, %{ap_id: ap_id}) do | |||
@@ -25,4 +25,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do | |||
defp get_policies(policy) when is_atom(policy), do: [policy] | |||
defp get_policies(policies) when is_list(policies), do: policies | |||
defp get_policies(_), do: [] | |||
@spec subdomains_regex([String.t()]) :: [Regex.t()] | |||
def subdomains_regex(domains) when is_list(domains) do | |||
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$) | |||
end | |||
@spec subdomain_match?([Regex.t()], String.t()) :: boolean() | |||
def subdomain_match?(domains, host) do | |||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) | |||
end | |||
end |
@@ -4,22 +4,29 @@ | |||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
alias Pleroma.User | |||
alias Pleroma.Web.ActivityPub.MRF | |||
@moduledoc "Filter activities depending on their origin instance" | |||
@behaviour Pleroma.Web.ActivityPub.MRF | |||
@behaviour MRF | |||
defp check_accept(%{host: actor_host} = _actor_info, object) do | |||
accepts = Pleroma.Config.get([:mrf_simple, :accept]) | |||
accepts = | |||
Pleroma.Config.get([:mrf_simple, :accept]) | |||
|> MRF.subdomains_regex() | |||
cond do | |||
accepts == [] -> {:ok, object} | |||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} | |||
Enum.member?(accepts, actor_host) -> {:ok, object} | |||
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} | |||
true -> {:reject, nil} | |||
end | |||
end | |||
defp check_reject(%{host: actor_host} = _actor_info, object) do | |||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do | |||
rejects = | |||
Pleroma.Config.get([:mrf_simple, :reject]) | |||
|> MRF.subdomains_regex() | |||
if MRF.subdomain_match?(rejects, actor_host) do | |||
{:reject, nil} | |||
else | |||
{:ok, object} | |||
@@ -31,8 +38,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object | |||
) | |||
when length(child_attachment) > 0 do | |||
media_removal = | |||
Pleroma.Config.get([:mrf_simple, :media_removal]) | |||
|> MRF.subdomains_regex() | |||
object = | |||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do | |||
if MRF.subdomain_match?(media_removal, actor_host) do | |||
child_object = Map.delete(object["object"], "attachment") | |||
Map.put(object, "object", child_object) | |||
else | |||
@@ -51,8 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
"object" => child_object | |||
} = object | |||
) do | |||
media_nsfw = | |||
Pleroma.Config.get([:mrf_simple, :media_nsfw]) | |||
|> MRF.subdomains_regex() | |||
object = | |||
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do | |||
if MRF.subdomain_match?(media_nsfw, actor_host) do | |||
tags = (child_object["tag"] || []) ++ ["nsfw"] | |||
child_object = Map.put(child_object, "tag", tags) | |||
child_object = Map.put(child_object, "sensitive", true) | |||
@@ -67,12 +82,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
defp check_media_nsfw(_actor_info, object), do: {:ok, object} | |||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do | |||
timeline_removal = | |||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]) | |||
|> MRF.subdomains_regex() | |||
object = | |||
with true <- | |||
Enum.member?( | |||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]), | |||
actor_host | |||
), | |||
with true <- MRF.subdomain_match?(timeline_removal, actor_host), | |||
user <- User.get_cached_by_ap_id(object["actor"]), | |||
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do | |||
to = | |||
@@ -94,7 +109,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
end | |||
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do | |||
if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do | |||
report_removal = | |||
Pleroma.Config.get([:mrf_simple, :report_removal]) | |||
|> MRF.subdomains_regex() | |||
if MRF.subdomain_match?(report_removal, actor_host) do | |||
{:reject, nil} | |||
else | |||
{:ok, object} | |||
@@ -104,7 +123,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
defp check_report_removal(_actor_info, object), do: {:ok, object} | |||
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do | |||
if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do | |||
avatar_removal = | |||
Pleroma.Config.get([:mrf_simple, :avatar_removal]) | |||
|> MRF.subdomains_regex() | |||
if MRF.subdomain_match?(avatar_removal, actor_host) do | |||
{:ok, Map.delete(object, "icon")} | |||
else | |||
{:ok, object} | |||
@@ -114,7 +137,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
defp check_avatar_removal(_actor_info, object), do: {:ok, object} | |||
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do | |||
if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do | |||
banner_removal = | |||
Pleroma.Config.get([:mrf_simple, :banner_removal]) | |||
|> MRF.subdomains_regex() | |||
if MRF.subdomain_match?(banner_removal, actor_host) do | |||
{:ok, Map.delete(object, "image")} | |||
else | |||
{:ok, object} | |||
@@ -87,8 +87,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do | |||
if public do | |||
true | |||
else | |||
inbox_info = URI.parse(inbox) | |||
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host) | |||
%{host: host} = URI.parse(inbox) | |||
quarantined_instances = | |||
Config.get([:instance, :quarantined_instances], []) | |||
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex() | |||
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) | |||
end | |||
end | |||
@@ -824,6 +824,48 @@ defmodule Pleroma.UserTest do | |||
assert User.blocks?(user, collateral_user) | |||
end | |||
test "does not block domain with same end" do | |||
user = insert(:user) | |||
collateral_user = | |||
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"}) | |||
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") | |||
refute User.blocks?(user, collateral_user) | |||
end | |||
test "does not block domain with same end if wildcard added" do | |||
user = insert(:user) | |||
collateral_user = | |||
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"}) | |||
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com") | |||
refute User.blocks?(user, collateral_user) | |||
end | |||
test "blocks domain with wildcard for subdomain" do | |||
user = insert(:user) | |||
user_from_subdomain = | |||
insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"}) | |||
user_with_two_subdomains = | |||
insert(:user, %{ | |||
ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully" | |||
}) | |||
user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) | |||
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com") | |||
assert User.blocks?(user, user_from_subdomain) | |||
assert User.blocks?(user, user_with_two_subdomains) | |||
assert User.blocks?(user, user_domain) | |||
end | |||
test "unblocks domains" do | |||
user = insert(:user) | |||
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) | |||
@@ -0,0 +1,46 @@ | |||
defmodule Pleroma.Web.ActivityPub.MRFTest do | |||
use ExUnit.Case, async: true | |||
alias Pleroma.Web.ActivityPub.MRF | |||
test "subdomains_regex/1" do | |||
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ | |||
~r/^unsafe.tld$/, | |||
~r/^(.*\.)*unsafe.tld$/ | |||
] | |||
end | |||
describe "subdomain_match/2" do | |||
test "common domains" do | |||
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) | |||
assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/] | |||
assert MRF.subdomain_match?(regexes, "unsafe.tld") | |||
assert MRF.subdomain_match?(regexes, "unsafe2.tld") | |||
refute MRF.subdomain_match?(regexes, "example.com") | |||
end | |||
test "wildcard domains with one subdomain" do | |||
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) | |||
assert regexes == [~r/^(.*\.)*unsafe.tld$/] | |||
assert MRF.subdomain_match?(regexes, "unsafe.tld") | |||
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") | |||
refute MRF.subdomain_match?(regexes, "anotherunsafe.tld") | |||
refute MRF.subdomain_match?(regexes, "unsafe.tldanother") | |||
end | |||
test "wildcard domains with two subdomains" do | |||
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) | |||
assert regexes == [~r/^(.*\.)*unsafe.tld$/] | |||
assert MRF.subdomain_match?(regexes, "unsafe.tld") | |||
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") | |||
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld") | |||
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother") | |||
end | |||
end | |||
end |
@@ -49,6 +49,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :media_removal], ["*.remote.instance"]) | |||
media_message = build_media_message() | |||
local_message = build_local_message() | |||
assert SimplePolicy.filter(media_message) == | |||
{:ok, | |||
media_message | |||
|> Map.put("object", Map.delete(media_message["object"], "attachment"))} | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
end | |||
describe "when :media_nsfw" do | |||
@@ -74,6 +87,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"]) | |||
media_message = build_media_message() | |||
local_message = build_local_message() | |||
assert SimplePolicy.filter(media_message) == | |||
{:ok, | |||
media_message | |||
|> put_in(["object", "tag"], ["foo", "nsfw"]) | |||
|> put_in(["object", "sensitive"], true)} | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
end | |||
defp build_media_message do | |||
@@ -106,6 +133,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(report_message) == {:reject, nil} | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :report_removal], ["*.remote.instance"]) | |||
report_message = build_report_message() | |||
local_message = build_local_message() | |||
assert SimplePolicy.filter(report_message) == {:reject, nil} | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
end | |||
defp build_report_message do | |||
@@ -146,6 +182,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
test "match with wildcard domain" do | |||
{actor, ftl_message} = build_ftl_actor_and_message() | |||
ftl_message_actor_host = | |||
ftl_message | |||
|> Map.fetch!("actor") | |||
|> URI.parse() | |||
|> Map.fetch!(:host) | |||
Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host]) | |||
local_message = build_local_message() | |||
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message) | |||
assert actor.follower_address in ftl_message["to"] | |||
refute actor.follower_address in ftl_message["cc"] | |||
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"] | |||
assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"] | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
end | |||
test "has a matching host but only as:Public in to" do | |||
{_actor, ftl_message} = build_ftl_actor_and_message() | |||
@@ -192,6 +249,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(remote_message) == {:reject, nil} | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :reject], ["*.remote.instance"]) | |||
remote_message = build_remote_message() | |||
assert SimplePolicy.filter(remote_message) == {:reject, nil} | |||
end | |||
end | |||
describe "when :accept" do | |||
@@ -224,6 +289,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message} | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :accept], ["*.remote.instance"]) | |||
local_message = build_local_message() | |||
remote_message = build_remote_message() | |||
assert SimplePolicy.filter(local_message) == {:ok, local_message} | |||
assert SimplePolicy.filter(remote_message) == {:ok, remote_message} | |||
end | |||
end | |||
describe "when :avatar_removal" do | |||
@@ -251,6 +326,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
refute filtered["icon"] | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"]) | |||
remote_user = build_remote_user() | |||
{:ok, filtered} = SimplePolicy.filter(remote_user) | |||
refute filtered["icon"] | |||
end | |||
end | |||
describe "when :banner_removal" do | |||
@@ -278,6 +362,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
refute filtered["image"] | |||
end | |||
test "match with wildcard domain" do | |||
Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"]) | |||
remote_user = build_remote_user() | |||
{:ok, filtered} = SimplePolicy.filter(remote_user) | |||
refute filtered["image"] | |||
end | |||
end | |||
defp build_local_message do | |||