1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
6 @moduledoc "Filter activities depending on their origin instance"
7 @behaviour Pleroma.Web.ActivityPub.MRF.Policy
10 alias Pleroma.FollowingRelationship
12 alias Pleroma.Web.ActivityPub.MRF
14 require Pleroma.Constants
16 defp check_accept(%{host: actor_host} = _actor_info, object) do
18 instance_list(:accept)
19 |> MRF.subdomains_regex()
22 accepts == [] -> {:ok, object}
23 actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
24 MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
25 true -> {:reject, "[SimplePolicy] host not in accept list"}
29 defp check_reject(%{host: actor_host} = _actor_info, object) do
31 instance_list(:reject)
32 |> MRF.subdomains_regex()
34 if MRF.subdomain_match?(rejects, actor_host) do
35 {:reject, "[SimplePolicy] host in reject list"}
41 defp check_media_removal(
42 %{host: actor_host} = _actor_info,
43 %{"type" => type, "object" => %{"attachment" => child_attachment}} = object
45 when length(child_attachment) > 0 and type in ["Create", "Update"] do
47 instance_list(:media_removal)
48 |> MRF.subdomains_regex()
51 if MRF.subdomain_match?(media_removal, actor_host) do
52 child_object = Map.delete(object["object"], "attachment")
53 Map.put(object, "object", child_object)
61 defp check_media_removal(_actor_info, object), do: {:ok, object}
63 defp check_media_nsfw(
64 %{host: actor_host} = _actor_info,
67 "object" => %{} = _child_object
70 when type in ["Create", "Update"] do
72 instance_list(:media_nsfw)
73 |> MRF.subdomains_regex()
76 if MRF.subdomain_match?(media_nsfw, actor_host) do
77 Kernel.put_in(object, ["object", "sensitive"], true)
85 defp check_media_nsfw(_actor_info, object), do: {:ok, object}
87 defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
89 instance_list(:federated_timeline_removal)
90 |> MRF.subdomains_regex()
93 with true <- MRF.subdomain_match?(timeline_removal, actor_host),
94 user <- User.get_cached_by_ap_id(object["actor"]),
95 true <- Pleroma.Constants.as_public() in object["to"] do
96 to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
98 cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
110 defp intersection(list1, list2) do
111 list1 -- list1 -- list2
114 defp check_followers_only(%{host: actor_host} = _actor_info, object) do
116 instance_list(:followers_only)
117 |> MRF.subdomains_regex()
120 with true <- MRF.subdomain_match?(followers_only, actor_host),
121 user <- User.get_cached_by_ap_id(object["actor"]) do
122 # Don't use Map.get/3 intentionally, these must not be nil
123 fixed_to = object["to"] || []
124 fixed_cc = object["cc"] || []
126 to = FollowingRelationship.followers_ap_ids(user, fixed_to)
127 cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
130 |> Map.put("to", intersection([user.follower_address | to], fixed_to))
131 |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
139 defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
141 instance_list(:report_removal)
142 |> MRF.subdomains_regex()
144 if MRF.subdomain_match?(report_removal, actor_host) do
145 {:reject, "[SimplePolicy] host in report_removal list"}
151 defp check_report_removal(_actor_info, object), do: {:ok, object}
153 defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
155 instance_list(:avatar_removal)
156 |> MRF.subdomains_regex()
158 if MRF.subdomain_match?(avatar_removal, actor_host) do
159 {:ok, Map.delete(object, "icon")}
165 defp check_avatar_removal(_actor_info, object), do: {:ok, object}
167 defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
169 instance_list(:banner_removal)
170 |> MRF.subdomains_regex()
172 if MRF.subdomain_match?(banner_removal, actor_host) do
173 {:ok, Map.delete(object, "image")}
179 defp check_banner_removal(_actor_info, object), do: {:ok, object}
181 defp check_object(%{"object" => object} = activity) do
182 with {:ok, _object} <- filter(object) do
187 defp check_object(object), do: {:ok, object}
189 defp instance_list(config_key) do
190 Config.get([:mrf_simple, config_key])
191 |> MRF.instance_list_from_tuples()
195 def filter(%{"type" => "Delete", "actor" => actor} = object) do
196 %{host: actor_host} = URI.parse(actor)
199 instance_list(:reject_deletes)
200 |> MRF.subdomains_regex()
202 if MRF.subdomain_match?(reject_deletes, actor_host) do
203 {:reject, "[SimplePolicy] host in reject_deletes list"}
210 def filter(%{"actor" => actor} = object) do
211 actor_info = URI.parse(actor)
213 with {:ok, object} <- check_accept(actor_info, object),
214 {:ok, object} <- check_reject(actor_info, object),
215 {:ok, object} <- check_media_removal(actor_info, object),
216 {:ok, object} <- check_media_nsfw(actor_info, object),
217 {:ok, object} <- check_ftl_removal(actor_info, object),
218 {:ok, object} <- check_followers_only(actor_info, object),
219 {:ok, object} <- check_report_removal(actor_info, object),
220 {:ok, object} <- check_object(object) do
223 {:reject, nil} -> {:reject, "[SimplePolicy]"}
224 {:reject, _} = e -> e
225 _ -> {:reject, "[SimplePolicy]"}
229 def filter(%{"id" => actor, "type" => obj_type} = object)
230 when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
231 actor_info = URI.parse(actor)
233 with {:ok, object} <- check_accept(actor_info, object),
234 {:ok, object} <- check_reject(actor_info, object),
235 {:ok, object} <- check_avatar_removal(actor_info, object),
236 {:ok, object} <- check_banner_removal(actor_info, object) do
239 {:reject, nil} -> {:reject, "[SimplePolicy]"}
240 {:reject, _} = e -> e
241 _ -> {:reject, "[SimplePolicy]"}
245 def filter(object) when is_binary(object) do
246 uri = URI.parse(object)
248 with {:ok, object} <- check_accept(uri, object),
249 {:ok, object} <- check_reject(uri, object) do
252 {:reject, nil} -> {:reject, "[SimplePolicy]"}
253 {:reject, _} = e -> e
254 _ -> {:reject, "[SimplePolicy]"}
258 def filter(object), do: {:ok, object}
262 exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
264 mrf_simple_excluded =
265 Config.get(:mrf_simple)
266 |> Enum.map(fn {rule, instances} ->
267 {rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
272 |> Enum.map(fn {rule, instances} ->
273 {rule, Enum.map(instances, fn {host, _} -> host end)}
277 # This is for backwards compatibility. We originally didn't sent
278 # extra info like a reason why an instance was rejected/quarantined/etc.
279 # Because we didn't want to break backwards compatibility it was decided
280 # to add an extra "info" key.
283 |> Enum.map(fn {rule, instances} ->
284 {rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
286 |> Enum.reject(fn {_, instances} -> instances == [] end)
287 |> Enum.map(fn {rule, instances} ->
290 |> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
297 {:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
301 def config_description do
304 related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
306 description: "Simple ingress policies",
312 "List of instances to strip media attachments from and the reason for doing so"
318 "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
321 key: :federated_timeline_removal,
323 "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
328 "List of instances to reject activities from (except deletes) and the reason for doing so"
333 "List of instances to only accept activities from (except deletes) and the reason for doing so"
336 key: :followers_only,
338 "Force posts from the given instances to be visible by followers only and the reason for doing so"
341 key: :report_removal,
342 description: "List of instances to reject reports from and the reason for doing so"
345 key: :avatar_removal,
346 description: "List of instances to strip avatars from and the reason for doing so"
349 key: :banner_removal,
350 description: "List of instances to strip banners from and the reason for doing so"
353 key: :reject_deletes,
354 description: "List of instances to reject deletions from and the reason for doing so"
357 |> Enum.map(fn setting ->
361 type: {:list, :tuple},
362 key_placeholder: "instance",
363 value_placeholder: "reason",
364 suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]