ff9f84497143781a54ccb77dacd97b41386832a8
[anni] / lib / pleroma / web / activity_pub / mrf.ex
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
5 defmodule Pleroma.Web.ActivityPub.MRF do
6   require Logger
7
8   @behaviour Pleroma.Web.ActivityPub.MRF.PipelineFiltering
9
10   @mrf_config_descriptions [
11     %{
12       group: :pleroma,
13       key: :mrf,
14       tab: :mrf,
15       label: "MRF",
16       type: :group,
17       description: "General MRF settings",
18       children: [
19         %{
20           key: :policies,
21           type: [:module, {:list, :module}],
22           description:
23             "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
24           suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
25         },
26         %{
27           key: :transparency,
28           label: "MRF transparency",
29           type: :boolean,
30           description:
31             "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
32         },
33         %{
34           key: :transparency_exclusions,
35           label: "MRF transparency exclusions",
36           type: {:list, :tuple},
37           key_placeholder: "instance",
38           value_placeholder: "reason",
39           description:
40             "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",
41           suggestions: [
42             "exclusion.com"
43           ]
44         }
45       ]
46     }
47   ]
48
49   @default_description %{
50     label: "",
51     description: ""
52   }
53
54   @required_description_keys [:key, :related_policy]
55
56   def filter_one(policy, message) do
57     should_plug_history? =
58       if function_exported?(policy, :history_awareness, 0) do
59         policy.history_awareness()
60       else
61         :manual
62       end
63       |> Kernel.==(:auto)
64
65     if not should_plug_history? do
66       policy.filter(message)
67     else
68       main_result = policy.filter(message)
69
70       with {_, {:ok, main_message}} <- {:main, main_result},
71            {_,
72             %{
73               "formerRepresentations" => %{
74                 "orderedItems" => [_ | _]
75               }
76             }} = {_, object} <- {:object, message["object"]},
77            {_, {:ok, new_history}} <-
78              {:history,
79               Pleroma.Object.Updater.for_each_history_item(
80                 object["formerRepresentations"],
81                 object,
82                 fn item ->
83                   with {:ok, filtered} <- policy.filter(Map.put(message, "object", item)) do
84                     {:ok, filtered["object"]}
85                   else
86                     e -> e
87                   end
88                 end
89               )} do
90         {:ok, put_in(main_message, ["object", "formerRepresentations"], new_history)}
91       else
92         {:main, _} -> main_result
93         {:object, _} -> main_result
94         {:history, e} -> e
95       end
96     end
97   end
98
99   def filter(policies, %{} = message) do
100     policies
101     |> Enum.reduce({:ok, message}, fn
102       policy, {:ok, message} -> filter_one(policy, message)
103       _, error -> error
104     end)
105   end
106
107   def filter(%{} = object), do: get_policies() |> filter(object)
108
109   @impl true
110   def pipeline_filter(%{} = message, meta) do
111     object = meta[:object_data]
112     ap_id = message["object"]
113
114     if object && ap_id do
115       with {:ok, message} <- filter(Map.put(message, "object", object)) do
116         meta = Keyword.put(meta, :object_data, message["object"])
117         {:ok, Map.put(message, "object", ap_id), meta}
118       else
119         {err, message} -> {err, message, meta}
120       end
121     else
122       {err, message} = filter(message)
123
124       {err, message, meta}
125     end
126   end
127
128   def get_policies do
129     Pleroma.Config.get([:mrf, :policies], [])
130     |> get_policies()
131     |> Enum.concat([Pleroma.Web.ActivityPub.MRF.HashtagPolicy])
132   end
133
134   defp get_policies(policy) when is_atom(policy), do: [policy]
135   defp get_policies(policies) when is_list(policies), do: policies
136   defp get_policies(_), do: []
137
138   @spec subdomains_regex([String.t()]) :: [Regex.t()]
139   def subdomains_regex(domains) when is_list(domains) do
140     for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
141   end
142
143   @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
144   def subdomain_match?(domains, host) do
145     Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
146   end
147
148   @spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
149   def instance_list_from_tuples(list) do
150     Enum.map(list, fn {instance, _} -> instance end)
151   end
152
153   def describe(policies) do
154     {:ok, policy_configs} =
155       policies
156       |> Enum.reduce({:ok, %{}}, fn
157         policy, {:ok, data} ->
158           {:ok, policy_data} = policy.describe()
159           {:ok, Map.merge(data, policy_data)}
160
161         _, error ->
162           error
163       end)
164
165     mrf_policies =
166       get_policies()
167       |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
168
169     exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
170
171     base =
172       %{
173         mrf_policies: mrf_policies,
174         exclusions: length(exclusions) > 0
175       }
176       |> Map.merge(policy_configs)
177
178     {:ok, base}
179   end
180
181   def describe, do: get_policies() |> describe()
182
183   def config_descriptions do
184     Pleroma.Web.ActivityPub.MRF.Policy
185     |> Pleroma.Docs.Generator.list_behaviour_implementations()
186     |> config_descriptions()
187   end
188
189   def config_descriptions(policies) do
190     Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc ->
191       if function_exported?(policy, :config_description, 0) do
192         description =
193           @default_description
194           |> Map.merge(policy.config_description)
195           |> Map.put(:group, :pleroma)
196           |> Map.put(:tab, :mrf)
197           |> Map.put(:type, :group)
198
199         if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do
200           [description | acc]
201         else
202           Logger.warn(
203             "#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
204           )
205
206           acc
207         end
208       else
209         Logger.debug(
210           "#{policy} is excluded from config descriptions, because does not implement `config_description/0` method."
211         )
212
213         acc
214       end
215     end)
216   end
217 end