logo

pleroma

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

keyword_policy.ex (5648B)


  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.KeywordPolicy do
  5. require Pleroma.Constants
  6. alias Pleroma.Web.ActivityPub.MRF.Utils
  7. @moduledoc "Reject or Word-Replace messages with a keyword or regex"
  8. @behaviour Pleroma.Web.ActivityPub.MRF.Policy
  9. defp string_matches?(string, pattern) when is_binary(pattern) do
  10. String.contains?(string, pattern)
  11. end
  12. defp string_matches?(string, %Regex{} = pattern) do
  13. String.match?(string, pattern)
  14. end
  15. defp object_payload(%{} = object) do
  16. [object["content"], object["summary"], object["name"]]
  17. |> Enum.filter(& &1)
  18. |> Enum.join("\n")
  19. end
  20. defp check_reject(%{"object" => %{} = object} = message) do
  21. with {:ok, _new_object} <-
  22. Pleroma.Object.Updater.do_with_history(object, fn object ->
  23. payload = object_payload(object)
  24. if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
  25. string_matches?(payload, pattern)
  26. end) do
  27. {:reject, "[KeywordPolicy] Matches with rejected keyword"}
  28. else
  29. {:ok, message}
  30. end
  31. end) do
  32. {:ok, message}
  33. else
  34. e -> e
  35. end
  36. end
  37. defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = message) do
  38. check_keyword = fn object ->
  39. payload = object_payload(object)
  40. if Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
  41. string_matches?(payload, pattern)
  42. end) do
  43. {:should_delist, nil}
  44. else
  45. {:ok, %{}}
  46. end
  47. end
  48. should_delist? = fn object ->
  49. with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check_keyword) do
  50. false
  51. else
  52. _ -> true
  53. end
  54. end
  55. if Pleroma.Constants.as_public() in to and should_delist?.(object) do
  56. to = List.delete(to, Pleroma.Constants.as_public())
  57. cc = [Pleroma.Constants.as_public() | message["cc"] || []]
  58. message =
  59. message
  60. |> Map.put("to", to)
  61. |> Map.put("cc", cc)
  62. {:ok, message}
  63. else
  64. {:ok, message}
  65. end
  66. end
  67. defp check_ftl_removal(message) do
  68. {:ok, message}
  69. end
  70. defp check_replace(%{"object" => %{} = object} = message) do
  71. replace_kw = fn object ->
  72. ["content", "name", "summary"]
  73. |> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
  74. |> Enum.reduce(object, fn field, object ->
  75. data =
  76. Enum.reduce(
  77. Pleroma.Config.get([:mrf_keyword, :replace]),
  78. object[field],
  79. fn {pat, repl}, acc -> String.replace(acc, pat, repl) end
  80. )
  81. Map.put(object, field, data)
  82. end)
  83. |> (fn object -> {:ok, object} end).()
  84. end
  85. {:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw)
  86. message = Map.put(message, "object", object)
  87. {:ok, message}
  88. end
  89. @impl true
  90. def filter(%{"type" => type, "object" => %{"content" => _content}} = message)
  91. when type in ["Create", "Update"] do
  92. with {:ok, message} <- check_reject(message),
  93. {:ok, message} <- check_ftl_removal(message),
  94. {:ok, message} <- check_replace(message) do
  95. {:ok, message}
  96. else
  97. {:reject, nil} -> {:reject, "[KeywordPolicy] "}
  98. {:reject, _} = e -> e
  99. _e -> {:reject, "[KeywordPolicy] "}
  100. end
  101. end
  102. @impl true
  103. def filter(message), do: {:ok, message}
  104. @impl true
  105. def describe do
  106. mrf_keyword =
  107. Pleroma.Config.get(:mrf_keyword, [])
  108. |> Enum.map(fn {key, value} ->
  109. {key,
  110. Enum.map(value, fn
  111. {pattern, replacement} ->
  112. %{
  113. "pattern" => Utils.describe_regex_or_string(pattern),
  114. "replacement" => replacement
  115. }
  116. pattern ->
  117. Utils.describe_regex_or_string(pattern)
  118. end)}
  119. end)
  120. |> Enum.into(%{})
  121. {:ok, %{mrf_keyword: mrf_keyword}}
  122. end
  123. @impl true
  124. def config_description do
  125. %{
  126. key: :mrf_keyword,
  127. related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
  128. label: "MRF Keyword",
  129. description:
  130. "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
  131. children: [
  132. %{
  133. key: :reject,
  134. type: {:list, :string},
  135. description: """
  136. A list of patterns which result in message being rejected.
  137. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  138. """,
  139. suggestions: ["foo", ~r/foo/iu]
  140. },
  141. %{
  142. key: :federated_timeline_removal,
  143. type: {:list, :string},
  144. description: """
  145. A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
  146. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  147. """,
  148. suggestions: ["foo", ~r/foo/iu]
  149. },
  150. %{
  151. key: :replace,
  152. type: {:list, :tuple},
  153. key_placeholder: "instance",
  154. value_placeholder: "reason",
  155. description: """
  156. **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
  157. **Replacement**: a string. Leaving the field empty is permitted.
  158. """
  159. }
  160. ]
  161. }
  162. end
  163. end