Merge branch 'feature/mrf-user-filter' into 'develop'
mrf: add support for filtering users See merge request pleroma/pleroma!1188
This commit is contained in:
commit
75e78d4e23
|
@ -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
|
- Emoji packs and emoji pack manager
|
||||||
- Object pruning (`mix pleroma.database prune_objects`)
|
- Object pruning (`mix pleroma.database prune_objects`)
|
||||||
- OAuth: added job to clean expired access tokens
|
- 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
|
### Changed
|
||||||
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
|
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
|
||||||
|
|
|
@ -314,7 +314,9 @@
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
report_removal: [],
|
report_removal: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
accept: []
|
accept: [],
|
||||||
|
avatar_removal: [],
|
||||||
|
banner_removal: []
|
||||||
|
|
||||||
config :pleroma, :mrf_keyword,
|
config :pleroma, :mrf_keyword,
|
||||||
reject: [],
|
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
|
* `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
|
* `reject`: List of instances to reject any activities from
|
||||||
* `accept`: List of instances to accept 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
|
## :mrf_rejectnonpublic
|
||||||
* `allow_followersonly`: whether to allow followers-only posts
|
* `allow_followersonly`: whether to allow followers-only posts
|
||||||
|
|
|
@ -909,7 +909,7 @@ def upload(file, opts \\ []) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_data_from_user_object(data) do
|
defp object_to_user_data(data) do
|
||||||
avatar =
|
avatar =
|
||||||
data["icon"]["url"] &&
|
data["icon"]["url"] &&
|
||||||
%{
|
%{
|
||||||
|
@ -956,9 +956,19 @@ def user_data_from_user_object(data) do
|
||||||
{:ok, user_data}
|
{:ok, user_data}
|
||||||
end
|
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
|
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
|
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
|
||||||
user_data_from_user_object(data)
|
{:ok, data} <- user_data_from_user_object(data) do
|
||||||
|
{:ok, data}
|
||||||
else
|
else
|
||||||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||||
end
|
end
|
||||||
|
|
|
@ -104,9 +104,29 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
|
||||||
|
|
||||||
defp check_report_removal(_actor_info, object), do: {:ok, object}
|
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
|
@impl true
|
||||||
def filter(object) do
|
def filter(%{"actor" => actor} = object) do
|
||||||
actor_info = URI.parse(object["actor"])
|
actor_info = URI.parse(actor)
|
||||||
|
|
||||||
with {:ok, object} <- check_accept(actor_info, object),
|
with {:ok, object} <- check_accept(actor_info, object),
|
||||||
{:ok, object} <- check_reject(actor_info, object),
|
{:ok, object} <- check_reject(actor_info, object),
|
||||||
|
@ -119,4 +139,18 @@ def filter(object) do
|
||||||
_e -> {:reject, nil}
|
_e -> {:reject, nil}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -19,10 +19,12 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object) do
|
def filter(%{"actor" => actor} = object) do
|
||||||
actor_info = URI.parse(object["actor"])
|
actor_info = URI.parse(actor)
|
||||||
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
|
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
|
||||||
|
|
||||||
filter_by_list(object, allow_list)
|
filter_by_list(object, allow_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
report_removal: [],
|
report_removal: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
accept: []
|
accept: [],
|
||||||
|
avatar_removal: [],
|
||||||
|
banner_removal: []
|
||||||
)
|
)
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
@ -206,6 +208,60 @@ test "has a matching host" do
|
||||||
end
|
end
|
||||||
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
|
defp build_local_message do
|
||||||
%{
|
%{
|
||||||
"actor" => "#{Pleroma.Web.base_url()}/users/alice",
|
"actor" => "#{Pleroma.Web.base_url()}/users/alice",
|
||||||
|
@ -217,4 +273,19 @@ defp build_local_message do
|
||||||
defp build_remote_message do
|
defp build_remote_message do
|
||||||
%{"actor" => "https://remote.instance/users/bob"}
|
%{"actor" => "https://remote.instance/users/bob"}
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue