logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

simple_policy.ex (11657B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
  5. @moduledoc "Filter activities depending on their origin instance"
  6. @behaviour Pleroma.Web.ActivityPub.MRF.Policy
  7. alias Pleroma.Config
  8. alias Pleroma.FollowingRelationship
  9. alias Pleroma.User
  10. alias Pleroma.Web.ActivityPub.MRF
  11. require Pleroma.Constants
  12. defp check_accept(%{host: actor_host} = _actor_info, object) do
  13. accepts =
  14. instance_list(:accept)
  15. |> MRF.subdomains_regex()
  16. cond do
  17. accepts == [] -> {:ok, object}
  18. actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
  19. MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
  20. true -> {:reject, "[SimplePolicy] host not in accept list"}
  21. end
  22. end
  23. defp check_reject(%{host: actor_host} = _actor_info, object) do
  24. rejects =
  25. instance_list(:reject)
  26. |> MRF.subdomains_regex()
  27. if MRF.subdomain_match?(rejects, actor_host) do
  28. {:reject, "[SimplePolicy] host in reject list"}
  29. else
  30. {:ok, object}
  31. end
  32. end
  33. defp check_media_removal(
  34. %{host: actor_host} = _actor_info,
  35. %{"type" => type, "object" => %{"attachment" => child_attachment}} = object
  36. )
  37. when length(child_attachment) > 0 and type in ["Create", "Update"] do
  38. media_removal =
  39. instance_list(:media_removal)
  40. |> MRF.subdomains_regex()
  41. object =
  42. if MRF.subdomain_match?(media_removal, actor_host) do
  43. child_object = Map.delete(object["object"], "attachment")
  44. Map.put(object, "object", child_object)
  45. else
  46. object
  47. end
  48. {:ok, object}
  49. end
  50. defp check_media_removal(_actor_info, object), do: {:ok, object}
  51. defp check_media_nsfw(
  52. %{host: actor_host} = _actor_info,
  53. %{
  54. "type" => type,
  55. "object" => %{} = _child_object
  56. } = object
  57. )
  58. when type in ["Create", "Update"] do
  59. media_nsfw =
  60. instance_list(:media_nsfw)
  61. |> MRF.subdomains_regex()
  62. object =
  63. if MRF.subdomain_match?(media_nsfw, actor_host) do
  64. Kernel.put_in(object, ["object", "sensitive"], true)
  65. else
  66. object
  67. end
  68. {:ok, object}
  69. end
  70. defp check_media_nsfw(_actor_info, object), do: {:ok, object}
  71. defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
  72. timeline_removal =
  73. instance_list(:federated_timeline_removal)
  74. |> MRF.subdomains_regex()
  75. object =
  76. with true <- MRF.subdomain_match?(timeline_removal, actor_host),
  77. user <- User.get_cached_by_ap_id(object["actor"]),
  78. true <- Pleroma.Constants.as_public() in object["to"] do
  79. to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
  80. cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
  81. object
  82. |> Map.put("to", to)
  83. |> Map.put("cc", cc)
  84. else
  85. _ -> object
  86. end
  87. {:ok, object}
  88. end
  89. defp intersection(list1, list2) do
  90. list1 -- list1 -- list2
  91. end
  92. defp check_followers_only(%{host: actor_host} = _actor_info, object) do
  93. followers_only =
  94. instance_list(:followers_only)
  95. |> MRF.subdomains_regex()
  96. object =
  97. with true <- MRF.subdomain_match?(followers_only, actor_host),
  98. user <- User.get_cached_by_ap_id(object["actor"]) do
  99. # Don't use Map.get/3 intentionally, these must not be nil
  100. fixed_to = object["to"] || []
  101. fixed_cc = object["cc"] || []
  102. to = FollowingRelationship.followers_ap_ids(user, fixed_to)
  103. cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
  104. object
  105. |> Map.put("to", intersection([user.follower_address | to], fixed_to))
  106. |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
  107. else
  108. _ -> object
  109. end
  110. {:ok, object}
  111. end
  112. defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
  113. report_removal =
  114. instance_list(:report_removal)
  115. |> MRF.subdomains_regex()
  116. if MRF.subdomain_match?(report_removal, actor_host) do
  117. {:reject, "[SimplePolicy] host in report_removal list"}
  118. else
  119. {:ok, object}
  120. end
  121. end
  122. defp check_report_removal(_actor_info, object), do: {:ok, object}
  123. defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
  124. avatar_removal =
  125. instance_list(:avatar_removal)
  126. |> MRF.subdomains_regex()
  127. if MRF.subdomain_match?(avatar_removal, actor_host) do
  128. {:ok, Map.delete(object, "icon")}
  129. else
  130. {:ok, object}
  131. end
  132. end
  133. defp check_avatar_removal(_actor_info, object), do: {:ok, object}
  134. defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
  135. banner_removal =
  136. instance_list(:banner_removal)
  137. |> MRF.subdomains_regex()
  138. if MRF.subdomain_match?(banner_removal, actor_host) do
  139. {:ok, Map.delete(object, "image")}
  140. else
  141. {:ok, object}
  142. end
  143. end
  144. defp check_banner_removal(_actor_info, object), do: {:ok, object}
  145. defp check_object(%{"object" => object} = activity) do
  146. with {:ok, _object} <- filter(object) do
  147. {:ok, activity}
  148. end
  149. end
  150. defp check_object(object), do: {:ok, object}
  151. defp instance_list(config_key) do
  152. Config.get([:mrf_simple, config_key])
  153. |> MRF.instance_list_from_tuples()
  154. end
  155. @impl true
  156. def id_filter(id) do
  157. host_info = URI.parse(id)
  158. with {:ok, _} <- check_accept(host_info, %{}),
  159. {:ok, _} <- check_reject(host_info, %{}) do
  160. true
  161. else
  162. _ -> false
  163. end
  164. end
  165. @impl true
  166. def filter(%{"type" => "Delete", "actor" => actor} = object) do
  167. %{host: actor_host} = URI.parse(actor)
  168. reject_deletes =
  169. instance_list(:reject_deletes)
  170. |> MRF.subdomains_regex()
  171. if MRF.subdomain_match?(reject_deletes, actor_host) do
  172. {:reject, "[SimplePolicy] host in reject_deletes list"}
  173. else
  174. {:ok, object}
  175. end
  176. end
  177. @impl true
  178. def filter(%{"actor" => actor} = object) do
  179. actor_info = URI.parse(actor)
  180. with {:ok, object} <- check_accept(actor_info, object),
  181. {:ok, object} <- check_reject(actor_info, object),
  182. {:ok, object} <- check_media_removal(actor_info, object),
  183. {:ok, object} <- check_media_nsfw(actor_info, object),
  184. {:ok, object} <- check_ftl_removal(actor_info, object),
  185. {:ok, object} <- check_followers_only(actor_info, object),
  186. {:ok, object} <- check_report_removal(actor_info, object),
  187. {:ok, object} <- check_object(object) do
  188. {:ok, object}
  189. else
  190. {:reject, nil} -> {:reject, "[SimplePolicy]"}
  191. {:reject, _} = e -> e
  192. _ -> {:reject, "[SimplePolicy]"}
  193. end
  194. end
  195. def filter(%{"id" => actor, "type" => obj_type} = object)
  196. when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
  197. actor_info = URI.parse(actor)
  198. with {:ok, object} <- check_accept(actor_info, object),
  199. {:ok, object} <- check_reject(actor_info, object),
  200. {:ok, object} <- check_avatar_removal(actor_info, object),
  201. {:ok, object} <- check_banner_removal(actor_info, object) do
  202. {:ok, object}
  203. else
  204. {:reject, nil} -> {:reject, "[SimplePolicy]"}
  205. {:reject, _} = e -> e
  206. _ -> {:reject, "[SimplePolicy]"}
  207. end
  208. end
  209. def filter(object) when is_binary(object) do
  210. uri = URI.parse(object)
  211. with {:ok, object} <- check_accept(uri, object),
  212. {:ok, object} <- check_reject(uri, object) do
  213. {:ok, object}
  214. else
  215. {:reject, nil} -> {:reject, "[SimplePolicy]"}
  216. {:reject, _} = e -> e
  217. _ -> {:reject, "[SimplePolicy]"}
  218. end
  219. end
  220. def filter(object), do: {:ok, object}
  221. @impl true
  222. def describe do
  223. exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
  224. mrf_simple_excluded =
  225. Config.get(:mrf_simple)
  226. |> Enum.map(fn {rule, instances} ->
  227. {rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
  228. end)
  229. mrf_simple =
  230. mrf_simple_excluded
  231. |> Enum.map(fn {rule, instances} ->
  232. {rule, Enum.map(instances, fn {host, _} -> host end)}
  233. end)
  234. |> Map.new()
  235. # This is for backwards compatibility. We originally didn't sent
  236. # extra info like a reason why an instance was rejected/quarantined/etc.
  237. # Because we didn't want to break backwards compatibility it was decided
  238. # to add an extra "info" key.
  239. mrf_simple_info =
  240. mrf_simple_excluded
  241. |> Enum.map(fn {rule, instances} ->
  242. {rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
  243. end)
  244. |> Enum.reject(fn {_, instances} -> instances == [] end)
  245. |> Enum.map(fn {rule, instances} ->
  246. instances =
  247. instances
  248. |> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
  249. |> Map.new()
  250. {rule, instances}
  251. end)
  252. |> Map.new()
  253. {:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
  254. end
  255. @impl true
  256. def config_description do
  257. %{
  258. key: :mrf_simple,
  259. related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
  260. label: "MRF Simple",
  261. description: "Simple ingress policies",
  262. children:
  263. [
  264. %{
  265. key: :media_removal,
  266. description:
  267. "List of instances to strip media attachments from and the reason for doing so"
  268. },
  269. %{
  270. key: :media_nsfw,
  271. label: "Media NSFW",
  272. description:
  273. "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
  274. },
  275. %{
  276. key: :federated_timeline_removal,
  277. description:
  278. "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
  279. },
  280. %{
  281. key: :reject,
  282. description:
  283. "List of instances to reject activities from (except deletes) and the reason for doing so"
  284. },
  285. %{
  286. key: :accept,
  287. description:
  288. "List of instances to only accept activities from (except deletes) and the reason for doing so"
  289. },
  290. %{
  291. key: :followers_only,
  292. description:
  293. "Force posts from the given instances to be visible by followers only and the reason for doing so"
  294. },
  295. %{
  296. key: :report_removal,
  297. description: "List of instances to reject reports from and the reason for doing so"
  298. },
  299. %{
  300. key: :avatar_removal,
  301. description: "List of instances to strip avatars from and the reason for doing so"
  302. },
  303. %{
  304. key: :banner_removal,
  305. description: "List of instances to strip banners from and the reason for doing so"
  306. },
  307. %{
  308. key: :reject_deletes,
  309. description: "List of instances to reject deletions from and the reason for doing so"
  310. }
  311. ]
  312. |> Enum.map(fn setting ->
  313. Map.merge(
  314. setting,
  315. %{
  316. type: {:list, :tuple},
  317. key_placeholder: "instance",
  318. value_placeholder: "reason",
  319. suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
  320. }
  321. )
  322. end)
  323. }
  324. end
  325. end