mrf: add support for filtering users See merge request pleroma/pleroma!1188tags/v1.1.4
@@ -43,6 +43,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- Emoji packs and emoji pack manager | |||
- Object pruning (`mix pleroma.database prune_objects`) | |||
- OAuth: added job to clean expired access tokens | |||
- MRF: Support for rejecting reports from specific instances (`mrf_simple`) | |||
- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`) | |||
### Changed | |||
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer | |||
@@ -314,7 +314,9 @@ config :pleroma, :mrf_simple, | |||
federated_timeline_removal: [], | |||
report_removal: [], | |||
reject: [], | |||
accept: [] | |||
accept: [], | |||
avatar_removal: [], | |||
banner_removal: [] | |||
config :pleroma, :mrf_keyword, | |||
reject: [], | |||
@@ -220,6 +220,9 @@ relates to mascots on the mastodon frontend | |||
* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline | |||
* `reject`: List of instances to reject any activities from | |||
* `accept`: List of instances to accept any activities from | |||
* `report_removal`: List of instances to reject reports from | |||
* `avatar_removal`: List of instances to strip avatars from | |||
* `banner_removal`: List of instances to strip banners from | |||
## :mrf_rejectnonpublic | |||
* `allow_followersonly`: whether to allow followers-only posts | |||
@@ -909,7 +909,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
end | |||
def user_data_from_user_object(data) do | |||
defp object_to_user_data(data) do | |||
avatar = | |||
data["icon"]["url"] && | |||
%{ | |||
@@ -956,9 +956,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
{:ok, user_data} | |||
end | |||
def user_data_from_user_object(data) do | |||
with {:ok, data} <- MRF.filter(data), | |||
{:ok, data} <- object_to_user_data(data) do | |||
{:ok, data} | |||
else | |||
e -> {:error, e} | |||
end | |||
end | |||
def fetch_and_prepare_user_from_ap_id(ap_id) do | |||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do | |||
user_data_from_user_object(data) | |||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), | |||
{:ok, data} <- user_data_from_user_object(data) do | |||
{:ok, data} | |||
else | |||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") | |||
end | |||
@@ -104,9 +104,29 @@ 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 | |||
{:ok, Map.delete(object, "icon")} | |||
else | |||
{:ok, object} | |||
end | |||
end | |||
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 | |||
{:ok, Map.delete(object, "image")} | |||
else | |||
{:ok, object} | |||
end | |||
end | |||
defp check_banner_removal(_actor_info, object), do: {:ok, object} | |||
@impl true | |||
def filter(object) do | |||
actor_info = URI.parse(object["actor"]) | |||
def filter(%{"actor" => actor} = object) do | |||
actor_info = URI.parse(actor) | |||
with {:ok, object} <- check_accept(actor_info, object), | |||
{:ok, object} <- check_reject(actor_info, object), | |||
@@ -119,4 +139,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do | |||
_e -> {:reject, nil} | |||
end | |||
end | |||
def filter(%{"id" => actor, "type" => obj_type} = object) | |||
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do | |||
actor_info = URI.parse(actor) | |||
with {:ok, object} <- check_avatar_removal(actor_info, object), | |||
{:ok, object} <- check_banner_removal(actor_info, object) do | |||
{:ok, object} | |||
else | |||
_e -> {:reject, nil} | |||
end | |||
end | |||
def filter(object), do: {:ok, object} | |||
end |
@@ -19,10 +19,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do | |||
end | |||
@impl true | |||
def filter(object) do | |||
actor_info = URI.parse(object["actor"]) | |||
def filter(%{"actor" => actor} = object) do | |||
actor_info = URI.parse(actor) | |||
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], []) | |||
filter_by_list(object, allow_list) | |||
end | |||
def filter(object), do: {:ok, object} | |||
end |
@@ -17,7 +17,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
federated_timeline_removal: [], | |||
report_removal: [], | |||
reject: [], | |||
accept: [] | |||
accept: [], | |||
avatar_removal: [], | |||
banner_removal: [] | |||
) | |||
on_exit(fn -> | |||
@@ -206,6 +208,60 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
end | |||
end | |||
describe "when :avatar_removal" do | |||
test "is empty" do | |||
Config.put([:mrf_simple, :avatar_removal], []) | |||
remote_user = build_remote_user() | |||
assert SimplePolicy.filter(remote_user) == {:ok, remote_user} | |||
end | |||
test "is not empty but it doesn't have a matching host" do | |||
Config.put([:mrf_simple, :avatar_removal], ["non.matching.remote"]) | |||
remote_user = build_remote_user() | |||
assert SimplePolicy.filter(remote_user) == {:ok, remote_user} | |||
end | |||
test "has a matching host" 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 | |||
test "is empty" do | |||
Config.put([:mrf_simple, :banner_removal], []) | |||
remote_user = build_remote_user() | |||
assert SimplePolicy.filter(remote_user) == {:ok, remote_user} | |||
end | |||
test "is not empty but it doesn't have a matching host" do | |||
Config.put([:mrf_simple, :banner_removal], ["non.matching.remote"]) | |||
remote_user = build_remote_user() | |||
assert SimplePolicy.filter(remote_user) == {:ok, remote_user} | |||
end | |||
test "has a matching host" 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 | |||
%{ | |||
"actor" => "#{Pleroma.Web.base_url()}/users/alice", | |||
@@ -217,4 +273,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do | |||
defp build_remote_message do | |||
%{"actor" => "https://remote.instance/users/bob"} | |||
end | |||
defp build_remote_user do | |||
%{ | |||
"id" => "https://remote.instance/users/bob", | |||
"icon" => %{ | |||
"url" => "http://example.com/image.jpg", | |||
"type" => "Image" | |||
}, | |||
"image" => %{ | |||
"url" => "http://example.com/image.jpg", | |||
"type" => "Image" | |||
}, | |||
"type" => "Person" | |||
} | |||
end | |||
end |