Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
4.9KB

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