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])
190 |> MRF.instance_list_from_tuples()
194 def filter(%{"type" => "Delete", "actor" => actor} = object) do
195 %{host: actor_host} = URI.parse(actor)
198 instance_list(:reject_deletes)
199 |> MRF.subdomains_regex()
201 if MRF.subdomain_match?(reject_deletes, actor_host) do
202 {:reject, "[SimplePolicy] host in reject_deletes list"}
209 def filter(%{"actor" => actor} = object) do
210 actor_info = URI.parse(actor)
212 with {:ok, object} <- check_accept(actor_info, object),
213 {:ok, object} <- check_reject(actor_info, object),
214 {:ok, object} <- check_media_removal(actor_info, object),
215 {:ok, object} <- check_media_nsfw(actor_info, object),
216 {:ok, object} <- check_ftl_removal(actor_info, object),
217 {:ok, object} <- check_followers_only(actor_info, object),
218 {:ok, object} <- check_report_removal(actor_info, object),
219 {:ok, object} <- check_object(object) do
222 {:reject, nil} -> {:reject, "[SimplePolicy]"}
223 {:reject, _} = e -> e
224 _ -> {:reject, "[SimplePolicy]"}
228 def filter(%{"id" => actor, "type" => obj_type} = object)
229 when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
230 actor_info = URI.parse(actor)
232 with {:ok, object} <- check_accept(actor_info, object),
233 {:ok, object} <- check_reject(actor_info, object),
234 {:ok, object} <- check_avatar_removal(actor_info, object),
235 {:ok, object} <- check_banner_removal(actor_info, object) do
238 {:reject, nil} -> {:reject, "[SimplePolicy]"}
239 {:reject, _} = e -> e
240 _ -> {:reject, "[SimplePolicy]"}
244 def filter(object) when is_binary(object) do
245 uri = URI.parse(object)
247 with {:ok, object} <- check_accept(uri, object),
248 {:ok, object} <- check_reject(uri, object) do
251 {:reject, nil} -> {:reject, "[SimplePolicy]"}
252 {:reject, _} = e -> e
253 _ -> {:reject, "[SimplePolicy]"}
257 def filter(object), do: {:ok, object}
261 exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
263 mrf_simple_excluded =
264 Config.get(:mrf_simple)
265 |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn {v, _} -> v in exclusions end)} end)
269 |> Enum.map(fn {k, v} ->
270 {k, Enum.map(v, fn {instance, _} -> instance end)}
274 # This is for backwards compatibility. We originally didn't sent
275 # extra info like a reason why an instance was rejected/quarantined/etc.
276 # Because we didn't want to break backwards compatibility it was decided
277 # to add an extra "info" key.
280 |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn {_, reason} -> reason == "" end)} end)
281 |> Enum.reject(fn {_, v} -> v == [] end)
282 |> Enum.map(fn {k, l} ->
283 {k, l |> Enum.map(fn {i, r} -> {i, %{"reason" => r}} end) |> Enum.into(%{})}
287 {:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
291 def config_description do
294 related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
296 description: "Simple ingress policies",
302 "List of instances to strip media attachments from and the reason for doing so"
308 "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
311 key: :federated_timeline_removal,
313 "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
318 "List of instances to reject activities from (except deletes) and the reason for doing so"
323 "List of instances to only accept activities from (except deletes) and the reason for doing so"
326 key: :followers_only,
328 "Force posts from the given instances to be visible by followers only and the reason for doing so"
331 key: :report_removal,
332 description: "List of instances to reject reports from and the reason for doing so"
335 key: :avatar_removal,
336 description: "List of instances to strip avatars from and the reason for doing so"
339 key: :banner_removal,
340 description: "List of instances to strip banners from and the reason for doing so"
343 key: :reject_deletes,
344 description: "List of instances to reject deletions from and the reason for doing so"
347 |> Enum.map(fn setting ->
351 type: {:list, :tuple},
352 key_placeholder: "instance",
353 value_placeholder: "reason",
354 suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]