Merge branch 'simplePolicy_reasons_for_instance_specific_policies' of https://git.pleroma.social/ilja/pleroma into simplePolicy_reasons_for_instance_specific_policies

This commit is contained in:
Ilja 2020-12-13 22:34:44 +01:00
commit 16f0d8ea71
16 changed files with 879 additions and 147 deletions

View File

@ -758,12 +758,12 @@
},
%{
key: :quarantined_instances,
type: {:list, :string},
type: {:list, :tuple},
description:
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent",
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent and the reason for doing so",
suggestions: [
"quarantined.com",
"*.quarantined.com"
{"quarantined.com", "Reason"},
{"*.quarantined.com", "Reason"}
]
},
%{

View File

@ -20,6 +20,142 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
|> Enum.any?(fn {_, v} -> Enum.any?(v, fn e -> is_binary(e) end) end)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf_simple,
media_removal: ["instance.tld"],
media_nsfw: ["instance.tld"],
federated_timeline_removal: ["instance.tld"],
report_removal: ["instance.tld"],
reject: ["instance.tld"],
followers_only: ["instance.tld"],
accept: ["instance.tld"],
avatar_removal: ["instance.tld"],
banner_removal: ["instance.tld"],
reject_deletes: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf_simple,
media_removal: [{"instance.tld", "Reason for media removal"}],
media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
report_removal: [{"instance.tld", "Reason for report removal"}],
reject: [{"instance.tld", "Reason for reject"}],
followers_only: [{"instance.tld", "Reason for followers only"}],
accept: [{"instance.tld", "Reason for accept"}],
avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
banner_removal: [{"instance.tld", "Reason for banner removal"}],
reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
```
""")
new_config =
Config.get([:mrf_simple])
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)}
end)
Config.put([:mrf_simple], new_config)
:error
else
:ok
end
end
def check_quarantined_instances_tuples do
has_strings =
Config.get([:instance, :quarantined_instances]) |> Enum.any?(fn e -> is_binary(e) end)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :instance,
quarantined_instances: ["instance.tld"]
```
Is now
```
config :pleroma, :instance,
quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
```
""")
new_config =
Config.get([:instance, :quarantined_instances])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:instance, :quarantined_instances], new_config)
:error
else
:ok
end
end
def check_transparency_exclusions_tuples do
has_strings =
Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(fn e -> is_binary(e) end)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf,
transparency_exclusions: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf,
transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
```
""")
new_config =
Config.get([:mrf, :transparency_exclusions])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:mrf, :transparency_exclusions], new_config)
:error
else
:ok
end
end
def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@ -34,18 +170,22 @@ def check_hellthread_threshold do
end
def warn do
with :ok <- check_hellthread_threshold(),
:ok <- check_old_mrf_config(),
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
:ok <- check_gun_pool_options(),
:ok <- check_activity_expiration_config(),
:ok <- check_remote_ip_plug_name() do
:ok
else
_ ->
:error
end
[
check_hellthread_threshold(),
check_old_mrf_config(),
check_media_proxy_whitelist_config(),
check_welcome_message_config(),
check_gun_pool_options(),
check_activity_expiration_config(),
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
check_simple_policy_tuples(),
check_remote_ip_plug_name()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
_, _ -> :error
end)
end
def check_welcome_message_config do

View File

@ -106,6 +106,11 @@ def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
@spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
def instance_list_from_tuples(list) do
Enum.map(list, fn {instance, _} -> instance end)
end
def describe(policies) do
{:ok, policy_configs} =
policies

View File

@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
Config.get([:mrf_simple, :accept])
instance_list(:accept)
|> MRF.subdomains_regex()
cond do
@ -28,7 +28,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
Config.get([:mrf_simple, :reject])
instance_list(:reject)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@ -44,7 +44,7 @@ defp check_media_removal(
)
when length(child_attachment) > 0 do
media_removal =
Config.get([:mrf_simple, :media_removal])
instance_list(:media_removal)
|> MRF.subdomains_regex()
object =
@ -69,7 +69,7 @@ defp check_media_nsfw(
)
when is_map(child_object) do
media_nsfw =
Config.get([:mrf_simple, :media_nsfw])
instance_list(:media_nsfw)
|> MRF.subdomains_regex()
object =
@ -89,7 +89,7 @@ defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
Config.get([:mrf_simple, :federated_timeline_removal])
instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex()
object =
@ -116,7 +116,7 @@ defp intersection(list1, list2) do
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
followers_only =
Config.get([:mrf_simple, :followers_only])
instance_list(:followers_only)
|> MRF.subdomains_regex()
object =
@ -141,7 +141,7 @@ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
Config.get([:mrf_simple, :report_removal])
instance_list(:report_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
@ -155,7 +155,7 @@ defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
Config.get([:mrf_simple, :avatar_removal])
instance_list(:avatar_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@ -169,7 +169,7 @@ defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
Config.get([:mrf_simple, :banner_removal])
instance_list(:banner_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@ -181,12 +181,17 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image
defp check_banner_removal(_actor_info, object), do: {:ok, object}
defp instance_list(config_key) do
Config.get([:mrf_simple, config_key])
|> MRF.instance_list_from_tuples()
end
@impl true
def filter(%{"type" => "Delete", "actor" => actor} = object) do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
Config.get([:mrf_simple, :reject_deletes])
instance_list(:reject_deletes)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
@ -235,14 +240,29 @@ def filter(object), do: {:ok, object}
@impl true
def describe do
exclusions = Config.get([:mrf, :transparency_exclusions])
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
mrf_simple_excluded =
Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn {v, _} -> v in exclusions end)} end)
mrf_simple =
Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
mrf_simple_excluded
|> Enum.map(fn {k, v} ->
{k, Enum.map(v, fn {instance, _} -> instance end)}
end)
|> Enum.into(%{})
{:ok, %{mrf_simple: mrf_simple}}
mrf_simple_info =
mrf_simple_excluded
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn {_, reason} -> reason == "" end)} end)
|> Enum.reject(fn {_, v} -> v == [] end)
|> Enum.map(fn {k, l} ->
{k, l |> Enum.map(fn {i, r} -> {i, %{"reason" => r}} end) |> Enum.into(%{})}
end)
|> Enum.into(%{})
{:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
end
@impl true
@ -253,68 +273,73 @@ def config_description do
label: "MRF Simple",
description: "Simple ingress policies",
children: [
%{
key: :media_removal,
type: {:list, :string},
description: "List of instances to strip media attachments from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :media_nsfw,
label: "Media NSFW",
type: {:list, :string},
description: "List of instances to tag all media as NSFW (sensitive) from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :federated_timeline_removal,
type: {:list, :string},
description:
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :reject,
type: {:list, :string},
description: "List of instances to reject activities from (except deletes)",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :accept,
type: {:list, :string},
description: "List of instances to only accept activities from (except deletes)",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :followers_only,
type: {:list, :string},
description: "Force posts from the given instances to be visible by followers only",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :report_removal,
type: {:list, :string},
description: "List of instances to reject reports from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :avatar_removal,
type: {:list, :string},
description: "List of instances to strip avatars from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :banner_removal,
type: {:list, :string},
description: "List of instances to strip banners from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :reject_deletes,
type: {:list, :string},
description: "List of instances to reject deletions from",
suggestions: ["example.com", "*.example.com"]
}
%{
key: :media_removal,
type: {:list, :tuple},
description:
"List of instances to strip media attachments from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :media_nsfw,
label: "Media NSFW",
type: {:list, :tuple},
description:
"List of instances to tag all media as NSFW (sensitive) from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :federated_timeline_removal,
type: {:list, :tuple},
description:
"List of instances to remove from the Federated Timeline (aka The Whole Known Network) and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :reject,
type: {:list, :tuple},
description:
"List of instances to reject activities (except deletes) from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :accept,
type: {:list, :tuple},
description:
"List of instances to only accept activities (except deletes) from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :followers_only,
type: {:list, :tuple},
description:
"Force posts from the given instances to be visible by followers only and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :report_removal,
type: {:list, :tuple},
description: "List of instances to reject reports from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :avatar_removal,
type: {:list, :tuple},
description: "List of instances to strip avatars from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :banner_removal,
type: {:list, :tuple},
description: "List of instances to strip banners from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
},
%{
key: :reject_deletes,
type: {:list, :tuple},
description: "List of instances to reject deletions from and the reason for doing so",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
}
]
}
end

View File

@ -112,6 +112,7 @@ defp should_federate?(inbox, public) do
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)

View File

@ -88,7 +88,19 @@ def federation do
{:ok, data} = MRF.describe()
data
|> Map.merge(%{quarantined_instances: quarantined})
|> Map.merge(%{
quarantined_instances:
quarantined
|> Enum.map(fn {instance, _reason} -> instance end)
})
|> Map.merge(%{
quarantined_instances_info: %{
"quarantined_instances" =>
quarantined
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|> Enum.into(%{})
}
})
else
%{}
end

View File

@ -0,0 +1,40 @@
defmodule Pleroma.Repo.Migrations.SimplePolicyStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up, do: ConfigDB.get_by_params(%{group: :pleroma, key: :mrf_simple}) |> update_to_tuples
def down, do: ConfigDB.get_by_params(%{group: :pleroma, key: :mrf_simple}) |> update_to_strings
defp update_to_tuples(%{value: value}) do
new_value =
value
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)}
end)
ConfigDB.update_or_create(%{group: :pleroma, key: :mrf_simple, value: new_value})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_to_strings(%{value: value}) do
new_value =
value
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, _} -> instance
instance -> instance
end)}
end)
ConfigDB.update_or_create(%{group: :pleroma, key: :mrf_simple, value: new_value})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View File

@ -0,0 +1,61 @@
defmodule Pleroma.Repo.Migrations.QuarantainedStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
|> update_quarantined_instances_to_tuples
def down,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
|> update_quarantined_instances_to_strings
defp update_quarantined_instances_to_tuples(%{value: settings}) do
settings |> List.keyfind(:quarantined_instances, 0) |> update_to_tuples
end
defp update_quarantined_instances_to_tuples(nil), do: {:ok, nil}
defp update_to_tuples({:quarantined_instances, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, r} -> {v, r}
v -> {v, ""}
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :instance,
value: [quarantined_instances: new_value]
})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_quarantined_instances_to_strings(%{value: settings}) do
settings |> List.keyfind(:quarantined_instances, 0) |> update_to_strings
end
defp update_quarantined_instances_to_strings(nil), do: {:ok, nil}
defp update_to_strings({:quarantined_instances, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, _} -> v
v -> v
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :instance,
value: [quarantined_instances: new_value]
})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View File

@ -0,0 +1,61 @@
defmodule Pleroma.Repo.Migrations.TransparencyExclusionsStringToTuple do
use Ecto.Migration
alias Pleroma.ConfigDB
def up,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :mrf})
|> update_transparency_exclusions_instances_to_tuples
def down,
do:
ConfigDB.get_by_params(%{group: :pleroma, key: :mrf})
|> update_transparency_exclusions_instances_to_strings
defp update_transparency_exclusions_instances_to_tuples(%{value: settings}) do
settings |> List.keyfind(:transparency_exclusions, 0) |> update_to_tuples
end
defp update_transparency_exclusions_instances_to_tuples(nil), do: {:ok, nil}
defp update_to_tuples({:transparency_exclusions, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, r} -> {v, r}
v -> {v, ""}
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :mrf,
value: [transparency_exclusions: new_value]
})
end
defp update_to_tuples(nil), do: {:ok, nil}
defp update_transparency_exclusions_instances_to_strings(%{value: settings}) do
settings |> List.keyfind(:transparency_exclusions, 0) |> update_to_strings
end
defp update_transparency_exclusions_instances_to_strings(nil), do: {:ok, nil}
defp update_to_strings({:transparency_exclusions, instance_list}) do
new_value =
instance_list
|> Enum.map(fn
{v, _} -> v
v -> v
end)
ConfigDB.update_or_create(%{
group: :pleroma,
key: :mrf,
value: [transparency_exclusions: new_value]
})
end
defp update_to_strings(nil), do: {:ok, nil}
end

View File

@ -11,6 +11,183 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
alias Pleroma.Config
alias Pleroma.Config.DeprecationWarnings
describe "simple policy tuples" do
test "gives warning when there are still strings" do
clear_config([:mrf_simple],
media_removal: ["some.removal"],
media_nsfw: ["some.nsfw"],
federated_timeline_removal: ["some.tl.removal"],
report_removal: ["some.report.removal"],
reject: ["some.reject"],
followers_only: ["some.followers.only"],
accept: ["some.accept"],
avatar_removal: ["some.avatar.removal"],
banner_removal: ["some.banner.removal"],
reject_deletes: ["some.reject.deletes"]
)
assert capture_log(fn -> DeprecationWarnings.check_simple_policy_tuples() end) =~
"""
!!!DEPRECATION WARNING!!!
Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf_simple,
media_removal: ["instance.tld"],
media_nsfw: ["instance.tld"],
federated_timeline_removal: ["instance.tld"],
report_removal: ["instance.tld"],
reject: ["instance.tld"],
followers_only: ["instance.tld"],
accept: ["instance.tld"],
avatar_removal: ["instance.tld"],
banner_removal: ["instance.tld"],
reject_deletes: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf_simple,
media_removal: [{"instance.tld", "Reason for media removal"}],
media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
report_removal: [{"instance.tld", "Reason for report removal"}],
reject: [{"instance.tld", "Reason for reject"}],
followers_only: [{"instance.tld", "Reason for followers only"}],
accept: [{"instance.tld", "Reason for accept"}],
avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
banner_removal: [{"instance.tld", "Reason for banner removal"}],
reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
```
"""
end
test "transforms config to tuples" do
clear_config([:mrf_simple],
media_removal: ["some.removal", {"some.other.instance", "Some reason"}]
)
expected_config = [
{:media_removal, [{"some.removal", ""}, {"some.other.instance", "Some reason"}]}
]
capture_log(fn -> DeprecationWarnings.warn() end)
assert Config.get([:mrf_simple]) == expected_config
end
test "doesn't give a warning with correct config" do
clear_config([:mrf_simple],
media_removal: [{"some.removal", ""}, {"some.other.instance", "Some reason"}]
)
assert capture_log(fn -> DeprecationWarnings.check_simple_policy_tuples() end) == ""
end
end
describe "quarantined_instances tuples" do
test "gives warning when there are still strings" do
clear_config([:instance, :quarantined_instances], [
{"domain.com", "some reason"},
"somedomain.tld"
])
assert capture_log(fn -> DeprecationWarnings.check_quarantined_instances_tuples() end) =~
"""
!!!DEPRECATION WARNING!!!
Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :instance,
quarantined_instances: ["instance.tld"]
```
Is now
```
config :pleroma, :instance,
quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
```
"""
end
test "transforms config to tuples" do
clear_config([:instance, :quarantined_instances], [
{"domain.com", "some reason"},
"some.tld"
])
expected_config = [{"domain.com", "some reason"}, {"some.tld", ""}]
capture_log(fn -> DeprecationWarnings.warn() end)
assert Config.get([:instance, :quarantined_instances]) == expected_config
end
test "doesn't give a warning with correct config" do
clear_config([:instance, :quarantined_instances], [
{"domain.com", "some reason"},
{"some.tld", ""}
])
assert capture_log(fn -> DeprecationWarnings.check_quarantined_instances_tuples() end) == ""
end
end
describe "transparency_exclusions tuples" do
test "gives warning when there are still strings" do
clear_config([:mrf, :transparency_exclusions], [
{"domain.com", "some reason"},
"somedomain.tld"
])
assert capture_log(fn -> DeprecationWarnings.check_transparency_exclusions_tuples() end) =~
"""
!!!DEPRECATION WARNING!!!
Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf,
transparency_exclusions: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf,
transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
```
"""
end
test "transforms config to tuples" do
clear_config([:mrf, :transparency_exclusions], [
{"domain.com", "some reason"},
"some.tld"
])
expected_config = [{"domain.com", "some reason"}, {"some.tld", ""}]
capture_log(fn -> DeprecationWarnings.warn() end)
assert Config.get([:mrf, :transparency_exclusions]) == expected_config
end
test "doesn't give a warning with correct config" do
clear_config([:mrf, :transparency_exclusions], [
{"domain.com", "some reason"},
{"some.tld", ""}
])
assert capture_log(fn -> DeprecationWarnings.check_transparency_exclusions_tuples() end) ==
""
end
end
test "check_old_mrf_config/0" do
clear_config([:instance, :rewrite_policy], [])
clear_config([:instance, :mrf_transparency], true)

View File

@ -480,7 +480,7 @@ test "it sends a welcome chat message if it is set" do
)
test "it sends a welcome chat message when Simple policy applied to local instance" do
Pleroma.Config.put([:mrf_simple, :media_nsfw], ["localhost"])
Pleroma.Config.put([:mrf_simple, :media_nsfw], [{"localhost", ""}])
welcome_user = insert(:user)
Pleroma.Config.put([:welcome, :chat_message, :enabled], true)

View File

@ -34,7 +34,7 @@ test "is empty" do
end
test "has a matching host" do
Config.put([:mrf_simple, :media_removal], ["remote.instance"])
Config.put([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
media_message = build_media_message()
local_message = build_local_message()
@ -47,7 +47,7 @@ test "has a matching host" do
end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_removal], ["*.remote.instance"])
Config.put([:mrf_simple, :media_removal], [{"*.remote.instance", "Whatever reason"}])
media_message = build_media_message()
local_message = build_local_message()
@ -71,7 +71,7 @@ test "is empty" do
end
test "has a matching host" do
Config.put([:mrf_simple, :media_nsfw], ["remote.instance"])
Config.put([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
media_message = build_media_message()
local_message = build_local_message()
@ -85,7 +85,7 @@ test "has a matching host" do
end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"])
Config.put([:mrf_simple, :media_nsfw], [{"*.remote.instance", "yeah yeah"}])
media_message = build_media_message()
local_message = build_local_message()
@ -122,7 +122,7 @@ test "is empty" do
end
test "has a matching host" do
Config.put([:mrf_simple, :report_removal], ["remote.instance"])
Config.put([:mrf_simple, :report_removal], [{"remote.instance", "muh"}])
report_message = build_report_message()
local_message = build_local_message()
@ -131,7 +131,7 @@ test "has a matching host" do
end
test "match with wildcard domain" do
Config.put([:mrf_simple, :report_removal], ["*.remote.instance"])
Config.put([:mrf_simple, :report_removal], [{"*.remote.instance", "suya"}])
report_message = build_report_message()
local_message = build_local_message()
@ -166,7 +166,7 @@ test "has a matching host" do
|> URI.parse()
|> Map.fetch!(:host)
Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
Config.put([:mrf_simple, :federated_timeline_removal], [{ftl_message_actor_host, "uwu"}])
local_message = build_local_message()
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
@ -187,7 +187,10 @@ test "match with wildcard domain" do
|> URI.parse()
|> Map.fetch!(:host)
Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host])
Config.put([:mrf_simple, :federated_timeline_removal], [
{"*." <> ftl_message_actor_host, "owo"}
])
local_message = build_local_message()
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
@ -210,7 +213,9 @@ test "has a matching host but only as:Public in to" do
ftl_message = Map.put(ftl_message, "cc", [])
Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
Config.put([:mrf_simple, :federated_timeline_removal], [
{ftl_message_actor_host, "spiderwaifu goes 88w88"}
])
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
@ -239,7 +244,7 @@ test "is empty" do
end
test "activity has a matching host" do
Config.put([:mrf_simple, :reject], ["remote.instance"])
Config.put([:mrf_simple, :reject], [{"remote.instance", ""}])
remote_message = build_remote_message()
@ -247,7 +252,7 @@ test "activity has a matching host" do
end
test "activity matches with wildcard domain" do
Config.put([:mrf_simple, :reject], ["*.remote.instance"])
Config.put([:mrf_simple, :reject], [{"*.remote.instance", ""}])
remote_message = build_remote_message()
@ -255,7 +260,7 @@ test "activity matches with wildcard domain" do
end
test "actor has a matching host" do
Config.put([:mrf_simple, :reject], ["remote.instance"])
Config.put([:mrf_simple, :reject], [{"remote.instance", ""}])
remote_user = build_remote_user()
@ -305,7 +310,7 @@ test "has a matching host" do
|> URI.parse()
|> Map.fetch!(:host)
Config.put([:mrf_simple, :followers_only], [actor_domain])
Config.put([:mrf_simple, :followers_only], [{actor_domain, ""}])
assert {:ok, new_activity} = SimplePolicy.filter(activity)
assert actor.follower_address in new_activity["cc"]
@ -333,7 +338,7 @@ test "is empty" do
end
test "is not empty but activity doesn't have a matching host" do
Config.put([:mrf_simple, :accept], ["non.matching.remote"])
Config.put([:mrf_simple, :accept], [{"non.matching.remote", ""}])
local_message = build_local_message()
remote_message = build_remote_message()
@ -343,7 +348,7 @@ test "is not empty but activity doesn't have a matching host" do
end
test "activity has a matching host" do
Config.put([:mrf_simple, :accept], ["remote.instance"])
Config.put([:mrf_simple, :accept], [{"remote.instance", ""}])
local_message = build_local_message()
remote_message = build_remote_message()
@ -353,7 +358,7 @@ test "activity has a matching host" do
end
test "activity matches with wildcard domain" do
Config.put([:mrf_simple, :accept], ["*.remote.instance"])
Config.put([:mrf_simple, :accept], [{"*.remote.instance", ""}])
local_message = build_local_message()
remote_message = build_remote_message()
@ -363,7 +368,7 @@ test "activity matches with wildcard domain" do
end
test "actor has a matching host" do
Config.put([:mrf_simple, :accept], ["remote.instance"])
Config.put([:mrf_simple, :accept], [{"remote.instance", ""}])
remote_user = build_remote_user()
@ -381,7 +386,7 @@ test "is empty" do
end
test "is not empty but it doesn't have a matching host" do
Config.put([:mrf_simple, :avatar_removal], ["non.matching.remote"])
Config.put([:mrf_simple, :avatar_removal], [{"non.matching.remote", ""}])
remote_user = build_remote_user()
@ -389,7 +394,7 @@ test "is not empty but it doesn't have a matching host" do
end
test "has a matching host" do
Config.put([:mrf_simple, :avatar_removal], ["remote.instance"])
Config.put([:mrf_simple, :avatar_removal], [{"remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
@ -398,7 +403,7 @@ test "has a matching host" do
end
test "match with wildcard domain" do
Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"])
Config.put([:mrf_simple, :avatar_removal], [{"*.remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
@ -417,7 +422,7 @@ test "is empty" do
end
test "is not empty but it doesn't have a matching host" do
Config.put([:mrf_simple, :banner_removal], ["non.matching.remote"])
Config.put([:mrf_simple, :banner_removal], [{"non.matching.remote", ""}])
remote_user = build_remote_user()
@ -425,7 +430,7 @@ test "is not empty but it doesn't have a matching host" do
end
test "has a matching host" do
Config.put([:mrf_simple, :banner_removal], ["remote.instance"])
Config.put([:mrf_simple, :banner_removal], [{"remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
@ -434,7 +439,7 @@ test "has a matching host" do
end
test "match with wildcard domain" do
Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"])
Config.put([:mrf_simple, :banner_removal], [{"*.remote.instance", ""}])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
@ -447,7 +452,7 @@ test "match with wildcard domain" do
setup do: Config.put([:mrf_simple, :reject_deletes], [])
test "it accepts deletions even from rejected servers" do
Config.put([:mrf_simple, :reject], ["remote.instance"])
Config.put([:mrf_simple, :reject], [{"remote.instance", ""}])
deletion_message = build_remote_deletion_message()
@ -455,7 +460,7 @@ test "it accepts deletions even from rejected servers" do
end
test "it accepts deletions even from non-whitelisted servers" do
Config.put([:mrf_simple, :accept], ["non.matching.remote"])
Config.put([:mrf_simple, :accept], [{"non.matching.remote", ""}])
deletion_message = build_remote_deletion_message()
@ -464,10 +469,10 @@ test "it accepts deletions even from non-whitelisted servers" do
end
describe "when :reject_deletes is not empty but it doesn't have a matching host" do
setup do: Config.put([:mrf_simple, :reject_deletes], ["non.matching.remote"])
setup do: Config.put([:mrf_simple, :reject_deletes], [{"non.matching.remote", ""}])
test "it accepts deletions even from rejected servers" do
Config.put([:mrf_simple, :reject], ["remote.instance"])
Config.put([:mrf_simple, :reject], [{"remote.instance", ""}])
deletion_message = build_remote_deletion_message()
@ -475,7 +480,7 @@ test "it accepts deletions even from rejected servers" do
end
test "it accepts deletions even from non-whitelisted servers" do
Config.put([:mrf_simple, :accept], ["non.matching.remote"])
Config.put([:mrf_simple, :accept], [{"non.matching.remote", ""}])
deletion_message = build_remote_deletion_message()
@ -484,7 +489,7 @@ test "it accepts deletions even from non-whitelisted servers" do
end
describe "when :reject_deletes has a matching host" do
setup do: Config.put([:mrf_simple, :reject_deletes], ["remote.instance"])
setup do: Config.put([:mrf_simple, :reject_deletes], [{"remote.instance", ""}])
test "it rejects the deletion" do
deletion_message = build_remote_deletion_message()
@ -494,7 +499,7 @@ test "it rejects the deletion" do
end
describe "when :reject_deletes match with wildcard domain" do
setup do: Config.put([:mrf_simple, :reject_deletes], ["*.remote.instance"])
setup do: Config.put([:mrf_simple, :reject_deletes], [{"*.remote.instance", ""}])
test "it rejects the deletion" do
deletion_message = build_remote_deletion_message()

View File

@ -63,6 +63,15 @@ test "matches are case-insensitive" do
end
end
describe "instance_list_from_tuples/1" do
test "returns a list of instances from a list of {instance, reason} tuples" do
list = [{"some.tld", "a reason"}, {"other.tld", "another reason"}]
expected = ["some.tld", "other.tld"]
assert MRF.instance_list_from_tuples(list) == expected
end
end
describe "describe/0" do
test "it works as expected with noop policy" do
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])

View File

@ -267,6 +267,80 @@ test "publish to url with with different ports" do
end
describe "publish/2" do
test_with_mock "doesn't publish a non-public activity to quarantined instances.",
Pleroma.Web.Federator.Publisher,
[:passthrough],
[] do
Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
follower =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox",
ap_enabled: true
})
actor = insert(:user, follower_address: follower.ap_id)
{:ok, _follower_one} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity =
insert(:followers_only_note_activity,
user: actor,
recipients: [follower.ap_id]
)
res = Publisher.publish(actor, note_activity)
assert res == :ok
assert not called(
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
inbox: "https://domain.com/users/nick1/inbox",
actor_id: actor.id,
id: note_activity.data["id"]
})
)
end
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
Pleroma.Web.Federator.Publisher,
[:passthrough],
[] do
Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
follower =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox",
ap_enabled: true
})
actor = insert(:user, follower_address: follower.ap_id)
{:ok, _follower_one} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity =
insert(:followers_only_note_activity,
user: actor,
recipients: [follower.ap_id]
)
res = Publisher.publish(actor, note_activity)
assert res == :ok
assert called(
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
inbox: "https://domain.com/users/nick1/inbox",
actor_id: actor.id,
id: note_activity.data["id"]
})
)
end
test_with_mock "publishes an activity with BCC to all relevant peers.",
Pleroma.Web.Federator.Publisher,
[:passthrough],

View File

@ -152,37 +152,127 @@ test "it shows default features flags", %{conn: conn} do
)
end
test "it shows MRF transparency data if enabled", %{conn: conn} do
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
clear_config([:mrf, :transparency], true)
describe "Quarantined instances" do
setup do
clear_config([:mrf, :transparency], true)
quarantined_instances = [{"example.com", "reason to quarantine"}]
clear_config([:instance, :quarantined_instances], quarantined_instances)
end
simple_config = %{"reject" => ["example.com"]}
clear_config(:mrf_simple, simple_config)
test "shows quarantined instances data if enabled", %{conn: conn} do
expected_config = ["example.com"]
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == simple_config
assert response["metadata"]["federation"]["quarantined_instances"] == expected_config
end
test "shows extra information in the quarantined_info field for relevant entries", %{
conn: conn
} do
clear_config([:mrf, :transparency], true)
expected_config = %{
"quarantined_instances" => %{
"example.com" => %{"reason" => "reason to quarantine"}
}
}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["quarantined_instances_info"] == expected_config
end
end
test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
clear_config([:mrf, :transparency], true)
clear_config([:mrf, :transparency_exclusions], ["other.site"])
describe "MRF SimplePolicy" do
setup do
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
clear_config([:mrf, :transparency], true)
end
simple_config = %{"reject" => ["example.com", "other.site"]}
clear_config(:mrf_simple, simple_config)
test "shows MRF transparency data if enabled", %{conn: conn} do
simple_config = %{"reject" => [{"example.com", ""}]}
clear_config(:mrf_simple, simple_config)
expected_config = %{"reject" => ["example.com"]}
expected_config = %{"reject" => ["example.com"]}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
assert response["metadata"]["federation"]["exclusions"] == true
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
end
test "performs exclusions from MRF transparency data if configured", %{conn: conn} do
clear_config([:mrf, :transparency_exclusions], [
{"other.site", "We don't want them to know"}
])
simple_config = %{"reject" => [{"example.com", ""}, {"other.site", ""}]}
clear_config(:mrf_simple, simple_config)
expected_config = %{"reject" => ["example.com"]}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
assert response["metadata"]["federation"]["exclusions"] == true
end
test "shows extra information in the mrf_simple_info field for relevant entries", %{
conn: conn
} do
simple_config = %{
media_removal: [{"no.media", "LEEWWWDD >//<"}],
media_nsfw: [],
federated_timeline_removal: [{"no.ftl", ""}],
report_removal: [],
reject: [
{"example.instance", "Some reason"},
{"uwu.owo", "awoo to much"},
{"no.reason", ""}
],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: [
{"peak.me", "I want to peak at what they don't want me to see, eheh"}
]
}
clear_config(:mrf_simple, simple_config)
clear_config([:mrf, :transparency_exclusions], [
{"peak.me", "I don't want them to know"}
])
expected_config = %{
"media_removal" => %{
"no.media" => %{"reason" => "LEEWWWDD >//<"}
},
"reject" => %{
"example.instance" => %{"reason" => "Some reason"},
"uwu.owo" => %{"reason" => "awoo to much"}
}
}
response =
conn
|> get("/nodeinfo/2.1.json")
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple_info"] == expected_config
end
end
end

View File

@ -104,6 +104,11 @@ def note_factory(attrs \\ %{}) do
}
end
def followers_only_note_factory(attrs \\ %{}) do
%Pleroma.Object{data: data} = note_factory(attrs)
%Pleroma.Object{data: Map.merge(data, %{"to" => [data["actor"] <> "/followers"]})}
end
def audio_factory(attrs \\ %{}) do
text = sequence(:text, &"lain radio episode #{&1}")
@ -190,6 +195,33 @@ def direct_note_activity_factory do
}
end
def followers_only_note_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user)
note = insert(:followers_only_note, user: user)
data_attrs = attrs[:data_attrs] || %{}
attrs = Map.drop(attrs, [:user, :note, :data_attrs])
data =
%{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"type" => "Create",
"actor" => note.data["actor"],
"to" => note.data["to"],
"object" => note.data,
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"context" => note.data["context"]
}
|> Map.merge(data_attrs)
%Pleroma.Activity{
data: data,
actor: data["actor"],
recipients: data["to"]
}
|> Map.merge(attrs)
end
def note_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user)
note = attrs[:note] || insert(:note, user: user)