1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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" => "Create", "object" => %{"attachment" => child_attachment}} = object
45 when length(child_attachment) > 0 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
71 instance_list(:media_nsfw)
72 |> MRF.subdomains_regex()
75 if MRF.subdomain_match?(media_nsfw, actor_host) do
76 Kernel.put_in(object, ["object", "sensitive"], true)
84 defp check_media_nsfw(_actor_info, object), do: {:ok, object}
86 defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
88 instance_list(:federated_timeline_removal)
89 |> MRF.subdomains_regex()
92 with true <- MRF.subdomain_match?(timeline_removal, actor_host),
93 user <- User.get_cached_by_ap_id(object["actor"]),
94 true <- Pleroma.Constants.as_public() in object["to"] do
95 to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
97 cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
109 defp intersection(list1, list2) do
110 list1 -- list1 -- list2
113 defp check_followers_only(%{host: actor_host} = _actor_info, object) do
115 instance_list(:followers_only)
116 |> MRF.subdomains_regex()
119 with true <- MRF.subdomain_match?(followers_only, actor_host),
120 user <- User.get_cached_by_ap_id(object["actor"]) do
121 # Don't use Map.get/3 intentionally, these must not be nil
122 fixed_to = object["to"] || []
123 fixed_cc = object["cc"] || []
125 to = FollowingRelationship.followers_ap_ids(user, fixed_to)
126 cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
129 |> Map.put("to", intersection([user.follower_address | to], fixed_to))
130 |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
138 defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
140 instance_list(:report_removal)
141 |> MRF.subdomains_regex()
143 if MRF.subdomain_match?(report_removal, actor_host) do
144 {:reject, "[SimplePolicy] host in report_removal list"}
150 defp check_report_removal(_actor_info, object), do: {:ok, object}
152 defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
154 instance_list(:avatar_removal)
155 |> MRF.subdomains_regex()
157 if MRF.subdomain_match?(avatar_removal, actor_host) do
158 {:ok, Map.delete(object, "icon")}
164 defp check_avatar_removal(_actor_info, object), do: {:ok, object}
166 defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
168 instance_list(:banner_removal)
169 |> MRF.subdomains_regex()
171 if MRF.subdomain_match?(banner_removal, actor_host) do
172 {:ok, Map.delete(object, "image")}
178 defp check_banner_removal(_actor_info, object), do: {:ok, object}
180 defp check_object(%{"object" => object} = activity) do
181 with {:ok, _object} <- filter(object) do
186 defp check_object(object), do: {:ok, object}
188 defp instance_list(config_key) do
189 Config.get([:mrf_simple, config_key])
191 {instance, _} -> instance
196 def filter(%{"type" => "Delete", "actor" => actor} = object) do
197 %{host: actor_host} = URI.parse(actor)
200 instance_list(:reject_deletes)
201 |> MRF.subdomains_regex()
203 if MRF.subdomain_match?(reject_deletes, actor_host) do
204 {:reject, "[SimplePolicy] host in reject_deletes list"}
211 def filter(%{"actor" => actor} = object) do
212 actor_info = URI.parse(actor)
214 with {:ok, object} <- check_accept(actor_info, object),
215 {:ok, object} <- check_reject(actor_info, object),
216 {:ok, object} <- check_media_removal(actor_info, object),
217 {:ok, object} <- check_media_nsfw(actor_info, object),
218 {:ok, object} <- check_ftl_removal(actor_info, object),
219 {:ok, object} <- check_followers_only(actor_info, object),
220 {:ok, object} <- check_report_removal(actor_info, object),
221 {:ok, object} <- check_object(object) do
224 {:reject, nil} -> {:reject, "[SimplePolicy]"}
225 {:reject, _} = e -> e
226 _ -> {:reject, "[SimplePolicy]"}
230 def filter(%{"id" => actor, "type" => obj_type} = object)
231 when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
232 actor_info = URI.parse(actor)
234 with {:ok, object} <- check_accept(actor_info, object),
235 {:ok, object} <- check_reject(actor_info, object),
236 {:ok, object} <- check_avatar_removal(actor_info, object),
237 {:ok, object} <- check_banner_removal(actor_info, object) do
240 {:reject, nil} -> {:reject, "[SimplePolicy]"}
241 {:reject, _} = e -> e
242 _ -> {:reject, "[SimplePolicy]"}
246 def filter(object) when is_binary(object) do
247 uri = URI.parse(object)
249 with {:ok, object} <- check_accept(uri, object),
250 {:ok, object} <- check_reject(uri, object) do
253 {:reject, nil} -> {:reject, "[SimplePolicy]"}
254 {:reject, _} = e -> e
255 _ -> {:reject, "[SimplePolicy]"}
259 def filter(object), do: {:ok, object}
263 exclusions = Config.get([:mrf, :transparency_exclusions])
266 Config.get(:mrf_simple)
267 |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn {v, _} -> v in exclusions end)} end)
270 {:ok, %{mrf_simple: mrf_simple}}
274 def config_description do
277 related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
279 description: "Simple ingress policies",
285 "List of instances to strip media attachments from and the reason for doing so"
291 "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
294 key: :federated_timeline_removal,
296 "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
301 "List of instances to reject activities from (except deletes) and the reason for doing so"
306 "List of instances to only accept activities from (except deletes) and the reason for doing so"
309 key: :followers_only,
311 "Force posts from the given instances to be visible by followers only and the reason for doing so"
314 key: :report_removal,
315 description: "List of instances to reject reports from and the reason for doing so"
318 key: :avatar_removal,
319 description: "List of instances to strip avatars from and the reason for doing so"
322 key: :banner_removal,
323 description: "List of instances to strip banners from and the reason for doing so"
326 key: :reject_deletes,
327 description: "List of instances to reject deletions from and the reason for doing so"
330 |> Enum.map(fn setting ->
334 type: {:list, :tuple},
335 key_placeholder: "instance",
336 value_placeholder: "reason",
337 suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]