First
[anni] / test / pleroma / web / activity_pub / mrf / simple_policy_test.exs
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.SimplePolicyTest do
6   use Pleroma.DataCase
7   import Pleroma.Factory
8   alias Pleroma.Web.ActivityPub.MRF.SimplePolicy
9   alias Pleroma.Web.CommonAPI
10
11   setup do:
12           clear_config(:mrf_simple,
13             media_removal: [],
14             media_nsfw: [],
15             federated_timeline_removal: [],
16             report_removal: [],
17             reject: [],
18             followers_only: [],
19             accept: [],
20             avatar_removal: [],
21             banner_removal: [],
22             reject_deletes: []
23           )
24
25   describe "when :media_removal" do
26     test "is empty" do
27       clear_config([:mrf_simple, :media_removal], [])
28       media_message = build_media_message()
29       local_message = build_local_message()
30
31       assert SimplePolicy.filter(media_message) == {:ok, media_message}
32       assert SimplePolicy.filter(local_message) == {:ok, local_message}
33     end
34
35     test "has a matching host" do
36       clear_config([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
37       media_message = build_media_message()
38       local_message = build_local_message()
39
40       assert SimplePolicy.filter(media_message) ==
41                {:ok,
42                 media_message
43                 |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
44
45       assert SimplePolicy.filter(local_message) == {:ok, local_message}
46     end
47
48     test "match with wildcard domain" do
49       clear_config([:mrf_simple, :media_removal], [{"*.remote.instance", "Whatever reason"}])
50       media_message = build_media_message()
51       local_message = build_local_message()
52
53       assert SimplePolicy.filter(media_message) ==
54                {:ok,
55                 media_message
56                 |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
57
58       assert SimplePolicy.filter(local_message) == {:ok, local_message}
59     end
60
61     test "works with Updates" do
62       clear_config([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
63       media_message = build_media_message(type: "Update")
64
65       assert SimplePolicy.filter(media_message) ==
66                {:ok,
67                 media_message
68                 |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
69     end
70   end
71
72   describe "when :media_nsfw" do
73     test "is empty" do
74       clear_config([:mrf_simple, :media_nsfw], [])
75       media_message = build_media_message()
76       local_message = build_local_message()
77
78       assert SimplePolicy.filter(media_message) == {:ok, media_message}
79       assert SimplePolicy.filter(local_message) == {:ok, local_message}
80     end
81
82     test "has a matching host" do
83       clear_config([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
84       media_message = build_media_message()
85       local_message = build_local_message()
86
87       assert SimplePolicy.filter(media_message) ==
88                {:ok, put_in(media_message, ["object", "sensitive"], true)}
89
90       assert SimplePolicy.filter(local_message) == {:ok, local_message}
91     end
92
93     test "match with wildcard domain" do
94       clear_config([:mrf_simple, :media_nsfw], [{"*.remote.instance", "yeah yeah"}])
95       media_message = build_media_message()
96       local_message = build_local_message()
97
98       assert SimplePolicy.filter(media_message) ==
99                {:ok, put_in(media_message, ["object", "sensitive"], true)}
100
101       assert SimplePolicy.filter(local_message) == {:ok, local_message}
102     end
103
104     test "works with Updates" do
105       clear_config([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
106       media_message = build_media_message(type: "Update")
107
108       assert SimplePolicy.filter(media_message) ==
109                {:ok, put_in(media_message, ["object", "sensitive"], true)}
110     end
111   end
112
113   defp build_media_message(opts \\ []) do
114     %{
115       "actor" => "https://remote.instance/users/bob",
116       "type" => opts[:type] || "Create",
117       "object" => %{
118         "attachment" => [%{}],
119         "tag" => ["foo"],
120         "sensitive" => false
121       }
122     }
123   end
124
125   describe "when :report_removal" do
126     test "is empty" do
127       clear_config([:mrf_simple, :report_removal], [])
128       report_message = build_report_message()
129       local_message = build_local_message()
130
131       assert SimplePolicy.filter(report_message) == {:ok, report_message}
132       assert SimplePolicy.filter(local_message) == {:ok, local_message}
133     end
134
135     test "has a matching host" do
136       clear_config([:mrf_simple, :report_removal], [{"remote.instance", "muh"}])
137       report_message = build_report_message()
138       local_message = build_local_message()
139
140       assert {:reject, _} = SimplePolicy.filter(report_message)
141       assert SimplePolicy.filter(local_message) == {:ok, local_message}
142     end
143
144     test "match with wildcard domain" do
145       clear_config([:mrf_simple, :report_removal], [{"*.remote.instance", "suya"}])
146       report_message = build_report_message()
147       local_message = build_local_message()
148
149       assert {:reject, _} = SimplePolicy.filter(report_message)
150       assert SimplePolicy.filter(local_message) == {:ok, local_message}
151     end
152   end
153
154   defp build_report_message do
155     %{
156       "actor" => "https://remote.instance/users/bob",
157       "type" => "Flag"
158     }
159   end
160
161   describe "when :federated_timeline_removal" do
162     test "is empty" do
163       clear_config([:mrf_simple, :federated_timeline_removal], [])
164       {_, ftl_message} = build_ftl_actor_and_message()
165       local_message = build_local_message()
166
167       assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
168       assert SimplePolicy.filter(local_message) == {:ok, local_message}
169     end
170
171     test "has a matching host" do
172       {actor, ftl_message} = build_ftl_actor_and_message()
173
174       ftl_message_actor_host =
175         ftl_message
176         |> Map.fetch!("actor")
177         |> URI.parse()
178         |> Map.fetch!(:host)
179
180       clear_config([:mrf_simple, :federated_timeline_removal], [{ftl_message_actor_host, "uwu"}])
181       local_message = build_local_message()
182
183       assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
184       assert actor.follower_address in ftl_message["to"]
185       refute actor.follower_address in ftl_message["cc"]
186       refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
187       assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
188
189       assert SimplePolicy.filter(local_message) == {:ok, local_message}
190     end
191
192     test "match with wildcard domain" do
193       {actor, ftl_message} = build_ftl_actor_and_message()
194
195       ftl_message_actor_host =
196         ftl_message
197         |> Map.fetch!("actor")
198         |> URI.parse()
199         |> Map.fetch!(:host)
200
201       clear_config([:mrf_simple, :federated_timeline_removal], [
202         {"*." <> ftl_message_actor_host, "owo"}
203       ])
204
205       local_message = build_local_message()
206
207       assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
208       assert actor.follower_address in ftl_message["to"]
209       refute actor.follower_address in ftl_message["cc"]
210       refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
211       assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
212
213       assert SimplePolicy.filter(local_message) == {:ok, local_message}
214     end
215
216     test "has a matching host but only as:Public in to" do
217       {_actor, ftl_message} = build_ftl_actor_and_message()
218
219       ftl_message_actor_host =
220         ftl_message
221         |> Map.fetch!("actor")
222         |> URI.parse()
223         |> Map.fetch!(:host)
224
225       ftl_message = Map.put(ftl_message, "cc", [])
226
227       clear_config([:mrf_simple, :federated_timeline_removal], [
228         {ftl_message_actor_host, "spiderwaifu goes 88w88"}
229       ])
230
231       assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
232       refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
233       assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
234     end
235   end
236
237   defp build_ftl_actor_and_message do
238     actor = insert(:user)
239
240     {actor,
241      %{
242        "actor" => actor.ap_id,
243        "to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
244        "cc" => [actor.follower_address, "http://foo.bar/qux"]
245      }}
246   end
247
248   describe "when :reject" do
249     test "is empty" do
250       clear_config([:mrf_simple, :reject], [])
251
252       remote_message = build_remote_message()
253
254       assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
255     end
256
257     test "activity has a matching host" do
258       clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
259
260       remote_message = build_remote_message()
261
262       assert {:reject, _} = SimplePolicy.filter(remote_message)
263     end
264
265     test "activity matches with wildcard domain" do
266       clear_config([:mrf_simple, :reject], [{"*.remote.instance", ""}])
267
268       remote_message = build_remote_message()
269
270       assert {:reject, _} = SimplePolicy.filter(remote_message)
271     end
272
273     test "actor has a matching host" do
274       clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
275
276       remote_user = build_remote_user()
277
278       assert {:reject, _} = SimplePolicy.filter(remote_user)
279     end
280
281     test "reject Announce when object would be rejected" do
282       clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
283
284       announce = %{
285         "type" => "Announce",
286         "actor" => "https://okay.tld/users/alice",
287         "object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
288       }
289
290       assert {:reject, _} = SimplePolicy.filter(announce)
291     end
292
293     test "reject by URI object" do
294       clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
295
296       announce = %{
297         "type" => "Announce",
298         "actor" => "https://okay.tld/users/alice",
299         "object" => "https://blocked.tld/activities/1"
300       }
301
302       assert {:reject, _} = SimplePolicy.filter(announce)
303     end
304   end
305
306   describe "when :followers_only" do
307     test "is empty" do
308       clear_config([:mrf_simple, :followers_only], [])
309       {_, ftl_message} = build_ftl_actor_and_message()
310       local_message = build_local_message()
311
312       assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
313       assert SimplePolicy.filter(local_message) == {:ok, local_message}
314     end
315
316     test "has a matching host" do
317       actor = insert(:user)
318       following_user = insert(:user)
319       non_following_user = insert(:user)
320
321       {:ok, _, _, _} = CommonAPI.follow(following_user, actor)
322
323       activity = %{
324         "actor" => actor.ap_id,
325         "to" => [
326           "https://www.w3.org/ns/activitystreams#Public",
327           following_user.ap_id,
328           non_following_user.ap_id
329         ],
330         "cc" => [actor.follower_address, "http://foo.bar/qux"]
331       }
332
333       dm_activity = %{
334         "actor" => actor.ap_id,
335         "to" => [
336           following_user.ap_id,
337           non_following_user.ap_id
338         ],
339         "cc" => []
340       }
341
342       actor_domain =
343         activity
344         |> Map.fetch!("actor")
345         |> URI.parse()
346         |> Map.fetch!(:host)
347
348       clear_config([:mrf_simple, :followers_only], [{actor_domain, ""}])
349
350       assert {:ok, new_activity} = SimplePolicy.filter(activity)
351       assert actor.follower_address in new_activity["cc"]
352       assert following_user.ap_id in new_activity["to"]
353       refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["to"]
354       refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["cc"]
355       refute non_following_user.ap_id in new_activity["to"]
356       refute non_following_user.ap_id in new_activity["cc"]
357
358       assert {:ok, new_dm_activity} = SimplePolicy.filter(dm_activity)
359       assert new_dm_activity["to"] == [following_user.ap_id]
360       assert new_dm_activity["cc"] == []
361     end
362   end
363
364   describe "when :accept" do
365     test "is empty" do
366       clear_config([:mrf_simple, :accept], [])
367
368       local_message = build_local_message()
369       remote_message = build_remote_message()
370
371       assert SimplePolicy.filter(local_message) == {:ok, local_message}
372       assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
373     end
374
375     test "is not empty but activity doesn't have a matching host" do
376       clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
377
378       local_message = build_local_message()
379       remote_message = build_remote_message()
380
381       assert SimplePolicy.filter(local_message) == {:ok, local_message}
382       assert {:reject, _} = SimplePolicy.filter(remote_message)
383     end
384
385     test "activity has a matching host" do
386       clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
387
388       local_message = build_local_message()
389       remote_message = build_remote_message()
390
391       assert SimplePolicy.filter(local_message) == {:ok, local_message}
392       assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
393     end
394
395     test "activity matches with wildcard domain" do
396       clear_config([:mrf_simple, :accept], [{"*.remote.instance", ""}])
397
398       local_message = build_local_message()
399       remote_message = build_remote_message()
400
401       assert SimplePolicy.filter(local_message) == {:ok, local_message}
402       assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
403     end
404
405     test "actor has a matching host" do
406       clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
407
408       remote_user = build_remote_user()
409
410       assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
411     end
412   end
413
414   describe "when :avatar_removal" do
415     test "is empty" do
416       clear_config([:mrf_simple, :avatar_removal], [])
417
418       remote_user = build_remote_user()
419
420       assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
421     end
422
423     test "is not empty but it doesn't have a matching host" do
424       clear_config([:mrf_simple, :avatar_removal], [{"non.matching.remote", ""}])
425
426       remote_user = build_remote_user()
427
428       assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
429     end
430
431     test "has a matching host" do
432       clear_config([:mrf_simple, :avatar_removal], [{"remote.instance", ""}])
433
434       remote_user = build_remote_user()
435       {:ok, filtered} = SimplePolicy.filter(remote_user)
436
437       refute filtered["icon"]
438     end
439
440     test "match with wildcard domain" do
441       clear_config([:mrf_simple, :avatar_removal], [{"*.remote.instance", ""}])
442
443       remote_user = build_remote_user()
444       {:ok, filtered} = SimplePolicy.filter(remote_user)
445
446       refute filtered["icon"]
447     end
448   end
449
450   describe "when :banner_removal" do
451     test "is empty" do
452       clear_config([:mrf_simple, :banner_removal], [])
453
454       remote_user = build_remote_user()
455
456       assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
457     end
458
459     test "is not empty but it doesn't have a matching host" do
460       clear_config([:mrf_simple, :banner_removal], [{"non.matching.remote", ""}])
461
462       remote_user = build_remote_user()
463
464       assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
465     end
466
467     test "has a matching host" do
468       clear_config([:mrf_simple, :banner_removal], [{"remote.instance", ""}])
469
470       remote_user = build_remote_user()
471       {:ok, filtered} = SimplePolicy.filter(remote_user)
472
473       refute filtered["image"]
474     end
475
476     test "match with wildcard domain" do
477       clear_config([:mrf_simple, :banner_removal], [{"*.remote.instance", ""}])
478
479       remote_user = build_remote_user()
480       {:ok, filtered} = SimplePolicy.filter(remote_user)
481
482       refute filtered["image"]
483     end
484   end
485
486   describe "when :reject_deletes is empty" do
487     setup do: clear_config([:mrf_simple, :reject_deletes], [])
488
489     test "it accepts deletions even from rejected servers" do
490       clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
491
492       deletion_message = build_remote_deletion_message()
493
494       assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
495     end
496
497     test "it accepts deletions even from non-whitelisted servers" do
498       clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
499
500       deletion_message = build_remote_deletion_message()
501
502       assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
503     end
504   end
505
506   describe "when :reject_deletes is not empty but it doesn't have a matching host" do
507     setup do: clear_config([:mrf_simple, :reject_deletes], [{"non.matching.remote", ""}])
508
509     test "it accepts deletions even from rejected servers" do
510       clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
511
512       deletion_message = build_remote_deletion_message()
513
514       assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
515     end
516
517     test "it accepts deletions even from non-whitelisted servers" do
518       clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
519
520       deletion_message = build_remote_deletion_message()
521
522       assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
523     end
524   end
525
526   describe "when :reject_deletes has a matching host" do
527     setup do: clear_config([:mrf_simple, :reject_deletes], [{"remote.instance", ""}])
528
529     test "it rejects the deletion" do
530       deletion_message = build_remote_deletion_message()
531
532       assert {:reject, _} = SimplePolicy.filter(deletion_message)
533     end
534   end
535
536   describe "when :reject_deletes match with wildcard domain" do
537     setup do: clear_config([:mrf_simple, :reject_deletes], [{"*.remote.instance", ""}])
538
539     test "it rejects the deletion" do
540       deletion_message = build_remote_deletion_message()
541
542       assert {:reject, _} = SimplePolicy.filter(deletion_message)
543     end
544   end
545
546   defp build_local_message do
547     %{
548       "actor" => "#{Pleroma.Web.Endpoint.url()}/users/alice",
549       "to" => [],
550       "cc" => []
551     }
552   end
553
554   defp build_remote_message do
555     %{"actor" => "https://remote.instance/users/bob"}
556   end
557
558   defp build_remote_user do
559     %{
560       "id" => "https://remote.instance/users/bob",
561       "icon" => %{
562         "url" => "http://example.com/image.jpg",
563         "type" => "Image"
564       },
565       "image" => %{
566         "url" => "http://example.com/image.jpg",
567         "type" => "Image"
568       },
569       "type" => "Person"
570     }
571   end
572
573   defp build_remote_deletion_message do
574     %{
575       "type" => "Delete",
576       "actor" => "https://remote.instance/users/bob"
577     }
578   end
579 end