extend reject MRF to check if originating instance is blocked
[akkoma] / test / pleroma / web / activity_pub / mrf / simple_policy_test.exs
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
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 media_update_message = build_media_update_message()
30 local_message = build_local_message()
31
32 assert SimplePolicy.filter(media_message) == {:ok, media_message}
33 assert SimplePolicy.filter(media_update_message) == {:ok, media_update_message}
34 assert SimplePolicy.filter(local_message) == {:ok, local_message}
35 end
36
37 test "has a matching host" do
38 clear_config([:mrf_simple, :media_removal], [{"remote.instance", "Some reason"}])
39 media_message = build_media_message()
40 media_update_message = build_media_update_message()
41 local_message = build_local_message()
42
43 assert SimplePolicy.filter(media_message) ==
44 {:ok,
45 media_message
46 |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
47
48 assert SimplePolicy.filter(media_update_message) ==
49 {:ok,
50 media_update_message
51 |> Map.put("object", Map.delete(media_update_message["object"], "attachment"))}
52
53 assert SimplePolicy.filter(local_message) == {:ok, local_message}
54 end
55
56 test "match with wildcard domain" do
57 clear_config([:mrf_simple, :media_removal], [{"*.remote.instance", "Whatever reason"}])
58 media_message = build_media_message()
59 media_update_message = build_media_update_message()
60 local_message = build_local_message()
61
62 assert SimplePolicy.filter(media_message) ==
63 {:ok,
64 media_message
65 |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
66
67 assert SimplePolicy.filter(media_update_message) ==
68 {:ok,
69 media_update_message
70 |> Map.put("object", Map.delete(media_update_message["object"], "attachment"))}
71
72 assert SimplePolicy.filter(local_message) == {:ok, local_message}
73 end
74 end
75
76 describe "when :media_nsfw" do
77 test "is empty" do
78 clear_config([:mrf_simple, :media_nsfw], [])
79 media_message = build_media_message()
80 media_update_message = build_media_update_message()
81 local_message = build_local_message()
82
83 assert SimplePolicy.filter(media_message) == {:ok, media_message}
84 assert SimplePolicy.filter(media_update_message) == {:ok, media_update_message}
85 assert SimplePolicy.filter(local_message) == {:ok, local_message}
86 end
87
88 test "has a matching host" do
89 clear_config([:mrf_simple, :media_nsfw], [{"remote.instance", "Whetever"}])
90 media_message = build_media_message()
91 media_update_message = build_media_update_message()
92 local_message = build_local_message()
93
94 assert SimplePolicy.filter(media_message) ==
95 {:ok, put_in(media_message, ["object", "sensitive"], true)}
96
97 assert SimplePolicy.filter(media_update_message) ==
98 {:ok, put_in(media_update_message, ["object", "sensitive"], true)}
99
100 assert SimplePolicy.filter(local_message) == {:ok, local_message}
101 end
102
103 test "match with wildcard domain" do
104 clear_config([:mrf_simple, :media_nsfw], [{"*.remote.instance", "yeah yeah"}])
105 media_message = build_media_message()
106 media_update_message = build_media_update_message()
107 local_message = build_local_message()
108
109 assert SimplePolicy.filter(media_message) ==
110 {:ok, put_in(media_message, ["object", "sensitive"], true)}
111
112 assert SimplePolicy.filter(media_update_message) ==
113 {:ok, put_in(media_update_message, ["object", "sensitive"], true)}
114
115 assert SimplePolicy.filter(local_message) == {:ok, local_message}
116 end
117 end
118
119 defp build_media_message do
120 %{
121 "actor" => "https://remote.instance/users/bob",
122 "type" => "Create",
123 "object" => %{
124 "attachment" => [%{}],
125 "tag" => ["foo"],
126 "sensitive" => false
127 }
128 }
129 end
130
131 defp build_media_update_message do
132 %{
133 "actor" => "https://remote.instance/users/bob",
134 "type" => "Update",
135 "object" => %{
136 "attachment" => [%{}],
137 "tag" => ["foo"],
138 "sensitive" => false
139 }
140 }
141 end
142
143 describe "when :report_removal" do
144 test "is empty" do
145 clear_config([:mrf_simple, :report_removal], [])
146 report_message = build_report_message()
147 local_message = build_local_message()
148
149 assert SimplePolicy.filter(report_message) == {:ok, report_message}
150 assert SimplePolicy.filter(local_message) == {:ok, local_message}
151 end
152
153 test "has a matching host" do
154 clear_config([:mrf_simple, :report_removal], [{"remote.instance", "muh"}])
155 report_message = build_report_message()
156 local_message = build_local_message()
157
158 assert {:reject, _} = SimplePolicy.filter(report_message)
159 assert SimplePolicy.filter(local_message) == {:ok, local_message}
160 end
161
162 test "match with wildcard domain" do
163 clear_config([:mrf_simple, :report_removal], [{"*.remote.instance", "suya"}])
164 report_message = build_report_message()
165 local_message = build_local_message()
166
167 assert {:reject, _} = SimplePolicy.filter(report_message)
168 assert SimplePolicy.filter(local_message) == {:ok, local_message}
169 end
170 end
171
172 defp build_report_message do
173 %{
174 "actor" => "https://remote.instance/users/bob",
175 "type" => "Flag"
176 }
177 end
178
179 describe "when :federated_timeline_removal" do
180 test "is empty" do
181 clear_config([:mrf_simple, :federated_timeline_removal], [])
182 {_, ftl_message} = build_ftl_actor_and_message()
183 local_message = build_local_message()
184
185 assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
186 assert SimplePolicy.filter(local_message) == {:ok, local_message}
187 end
188
189 test "has a matching host" do
190 {actor, ftl_message} = build_ftl_actor_and_message()
191
192 ftl_message_actor_host =
193 ftl_message
194 |> Map.fetch!("actor")
195 |> URI.parse()
196 |> Map.fetch!(:host)
197
198 clear_config([:mrf_simple, :federated_timeline_removal], [{ftl_message_actor_host, "uwu"}])
199 local_message = build_local_message()
200
201 assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
202 assert actor.follower_address in ftl_message["to"]
203 refute actor.follower_address in ftl_message["cc"]
204 refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
205 assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
206
207 assert SimplePolicy.filter(local_message) == {:ok, local_message}
208 end
209
210 test "match with wildcard domain" do
211 {actor, ftl_message} = build_ftl_actor_and_message()
212
213 ftl_message_actor_host =
214 ftl_message
215 |> Map.fetch!("actor")
216 |> URI.parse()
217 |> Map.fetch!(:host)
218
219 clear_config([:mrf_simple, :federated_timeline_removal], [
220 {"*." <> ftl_message_actor_host, "owo"}
221 ])
222
223 local_message = build_local_message()
224
225 assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
226 assert actor.follower_address in ftl_message["to"]
227 refute actor.follower_address in ftl_message["cc"]
228 refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
229 assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
230
231 assert SimplePolicy.filter(local_message) == {:ok, local_message}
232 end
233
234 test "has a matching host but only as:Public in to" do
235 {_actor, ftl_message} = build_ftl_actor_and_message()
236
237 ftl_message_actor_host =
238 ftl_message
239 |> Map.fetch!("actor")
240 |> URI.parse()
241 |> Map.fetch!(:host)
242
243 ftl_message = Map.put(ftl_message, "cc", [])
244
245 clear_config([:mrf_simple, :federated_timeline_removal], [
246 {ftl_message_actor_host, "spiderwaifu goes 88w88"}
247 ])
248
249 assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
250 refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
251 assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
252 end
253 end
254
255 describe "describe/1" do
256 test "returns a description of the policy" do
257 clear_config([:mrf_simple, :reject], [
258 {"remote.instance", "did not give my catboy a burg"}
259 ])
260
261 assert {:ok, %{mrf_simple: %{reject: ["remote.instance"]}}} = SimplePolicy.describe()
262 end
263
264 test "excludes domains listed in :transparency_exclusions" do
265 clear_config([:mrf, :transparency_exclusions], [{"remote.instance", ":("}])
266
267 clear_config([:mrf_simple, :reject], [
268 {"remote.instance", "did not give my catboy a burg"}
269 ])
270
271 {:ok, description} = SimplePolicy.describe()
272 assert %{mrf_simple: %{reject: []}} = description
273 assert description[:mrf_simple_info][:reject] == nil
274 end
275
276 test "obfuscates domains listed in :transparency_obfuscate_domains" do
277 clear_config([:mrf, :transparency_obfuscate_domains], ["remote.instance", "a.b"])
278
279 clear_config([:mrf_simple, :reject], [
280 {"remote.instance", "did not give my catboy a burg"},
281 {"a.b", "spam-poked me on facebook in 2006"}
282 ])
283
284 assert {:ok,
285 %{
286 mrf_simple: %{reject: ["rem***.*****nce", "a.b"]},
287 mrf_simple_info: %{reject: %{"rem***.*****nce" => %{}}}
288 }} = SimplePolicy.describe()
289 end
290 end
291
292 defp build_ftl_actor_and_message do
293 actor = insert(:user)
294
295 {actor,
296 %{
297 "actor" => actor.ap_id,
298 "to" => ["https://www.w3.org/ns/activitystreams#Public", "http://foo.bar/baz"],
299 "cc" => [actor.follower_address, "http://foo.bar/qux"]
300 }}
301 end
302
303 describe "when :reject" do
304 test "is empty" do
305 clear_config([:mrf_simple, :reject], [])
306
307 remote_message = build_remote_message()
308
309 assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
310 end
311
312 test "activity has a matching host" do
313 clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
314
315 remote_message = build_remote_message()
316
317 assert {:reject, _} = SimplePolicy.filter(remote_message)
318 end
319
320 test "activity matches with wildcard domain" do
321 clear_config([:mrf_simple, :reject], [{"*.remote.instance", ""}])
322
323 remote_message = build_remote_message()
324
325 assert {:reject, _} = SimplePolicy.filter(remote_message)
326 end
327
328 test "actor has a matching host" do
329 clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
330
331 remote_user = build_remote_user()
332
333 assert {:reject, _} = SimplePolicy.filter(remote_user)
334 end
335
336 test "reject Announce when object would be rejected" do
337 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
338
339 announce = %{
340 "type" => "Announce",
341 "actor" => "https://okay.tld/users/alice",
342 "object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
343 }
344
345 assert {:reject, _} = SimplePolicy.filter(announce)
346 end
347
348 test "reject by URI object" do
349 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
350
351 announce = %{
352 "type" => "Announce",
353 "actor" => "https://okay.tld/users/alice",
354 "object" => "https://blocked.tld/activities/1"
355 }
356
357 assert {:reject, _} = SimplePolicy.filter(announce)
358 end
359
360 test "accept by matching context URI if :handle_threads is disabled" do
361 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
362 clear_config([:mrf_simple, :handle_threads], false)
363
364 remote_message =
365 build_remote_message()
366 |> Map.put("context", "https://blocked.tld/contexts/abc")
367
368 assert {:ok, _} = SimplePolicy.filter(remote_message)
369 end
370
371 test "accept by matching conversation field if :handle_threads is disabled" do
372 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
373 clear_config([:mrf_simple, :handle_threads], false)
374
375 remote_message =
376 build_remote_message()
377 |> Map.put(
378 "conversation",
379 "tag:blocked.tld,1997-06-25:objectId=12345:objectType=Conversation"
380 )
381
382 assert {:ok, _} = SimplePolicy.filter(remote_message)
383 end
384
385 test "accept by matching reply ID if :handle_threads is disabled" do
386 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
387 clear_config([:mrf_simple, :handle_threads], false)
388
389 remote_message =
390 build_remote_message()
391 |> Map.put("type", "Create")
392 |> Map.put("object", %{
393 "type" => "Note",
394 "inReplyTo" => "https://blocked.tld/objects/1"
395 })
396
397 assert {:ok, _} = SimplePolicy.filter(remote_message)
398 end
399
400 test "reject by matching context URI if :handle_threads is enabled" do
401 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
402 clear_config([:mrf_simple, :handle_threads], true)
403
404 remote_message =
405 build_remote_message()
406 |> Map.put("context", "https://blocked.tld/contexts/abc")
407
408 assert {:reject, _} = SimplePolicy.filter(remote_message)
409 end
410
411 test "reject by matching conversation field if :handle_threads is enabled" do
412 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
413 clear_config([:mrf_simple, :handle_threads], true)
414
415 remote_message =
416 build_remote_message()
417 |> Map.put(
418 "conversation",
419 "tag:blocked.tld,1997-06-25:objectId=12345:objectType=Conversation"
420 )
421
422 assert {:reject, _} = SimplePolicy.filter(remote_message)
423 end
424
425 test "reject by matching reply ID if :handle_threads is enabled" do
426 clear_config([:mrf_simple, :reject], [{"blocked.tld", ""}])
427 clear_config([:mrf_simple, :handle_threads], true)
428
429 remote_message =
430 build_remote_message()
431 |> Map.put("type", "Create")
432 |> Map.put("object", %{
433 "type" => "Note",
434 "inReplyTo" => "https://blocked.tld/objects/1"
435 })
436
437 assert {:reject, _} = SimplePolicy.filter(remote_message)
438 end
439 end
440
441 describe "when :followers_only" do
442 test "is empty" do
443 clear_config([:mrf_simple, :followers_only], [])
444 {_, ftl_message} = build_ftl_actor_and_message()
445 local_message = build_local_message()
446
447 assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message}
448 assert SimplePolicy.filter(local_message) == {:ok, local_message}
449 end
450
451 test "has a matching host" do
452 actor = insert(:user)
453 following_user = insert(:user)
454 non_following_user = insert(:user)
455
456 {:ok, _, _, _} = CommonAPI.follow(following_user, actor)
457
458 activity = %{
459 "actor" => actor.ap_id,
460 "to" => [
461 "https://www.w3.org/ns/activitystreams#Public",
462 following_user.ap_id,
463 non_following_user.ap_id
464 ],
465 "cc" => [actor.follower_address, "http://foo.bar/qux"]
466 }
467
468 dm_activity = %{
469 "actor" => actor.ap_id,
470 "to" => [
471 following_user.ap_id,
472 non_following_user.ap_id
473 ],
474 "cc" => []
475 }
476
477 actor_domain =
478 activity
479 |> Map.fetch!("actor")
480 |> URI.parse()
481 |> Map.fetch!(:host)
482
483 clear_config([:mrf_simple, :followers_only], [{actor_domain, ""}])
484
485 assert {:ok, new_activity} = SimplePolicy.filter(activity)
486 assert actor.follower_address in new_activity["cc"]
487 assert following_user.ap_id in new_activity["to"]
488 refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["to"]
489 refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["cc"]
490 refute non_following_user.ap_id in new_activity["to"]
491 refute non_following_user.ap_id in new_activity["cc"]
492
493 assert {:ok, new_dm_activity} = SimplePolicy.filter(dm_activity)
494 assert new_dm_activity["to"] == [following_user.ap_id]
495 assert new_dm_activity["cc"] == []
496 end
497 end
498
499 describe "when :accept" do
500 test "is empty" do
501 clear_config([:mrf_simple, :accept], [])
502
503 local_message = build_local_message()
504 remote_message = build_remote_message()
505
506 assert SimplePolicy.filter(local_message) == {:ok, local_message}
507 assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
508 end
509
510 test "is not empty but activity doesn't have a matching host" do
511 clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
512
513 local_message = build_local_message()
514 remote_message = build_remote_message()
515
516 assert SimplePolicy.filter(local_message) == {:ok, local_message}
517 assert {:reject, _} = SimplePolicy.filter(remote_message)
518 end
519
520 test "activity has a matching host" do
521 clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
522
523 local_message = build_local_message()
524 remote_message = build_remote_message()
525
526 assert SimplePolicy.filter(local_message) == {:ok, local_message}
527 assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
528 end
529
530 test "activity matches with wildcard domain" do
531 clear_config([:mrf_simple, :accept], [{"*.remote.instance", ""}])
532
533 local_message = build_local_message()
534 remote_message = build_remote_message()
535
536 assert SimplePolicy.filter(local_message) == {:ok, local_message}
537 assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
538 end
539
540 test "actor has a matching host" do
541 clear_config([:mrf_simple, :accept], [{"remote.instance", ""}])
542
543 remote_user = build_remote_user()
544
545 assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
546 end
547 end
548
549 describe "when :avatar_removal" do
550 test "is empty" do
551 clear_config([:mrf_simple, :avatar_removal], [])
552
553 remote_user = build_remote_user()
554
555 assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
556 end
557
558 test "is not empty but it doesn't have a matching host" do
559 clear_config([:mrf_simple, :avatar_removal], [{"non.matching.remote", ""}])
560
561 remote_user = build_remote_user()
562
563 assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
564 end
565
566 test "has a matching host" do
567 clear_config([:mrf_simple, :avatar_removal], [{"remote.instance", ""}])
568
569 remote_user = build_remote_user()
570 {:ok, filtered} = SimplePolicy.filter(remote_user)
571
572 refute filtered["icon"]
573 end
574
575 test "match with wildcard domain" do
576 clear_config([:mrf_simple, :avatar_removal], [{"*.remote.instance", ""}])
577
578 remote_user = build_remote_user()
579 {:ok, filtered} = SimplePolicy.filter(remote_user)
580
581 refute filtered["icon"]
582 end
583 end
584
585 describe "when :banner_removal" do
586 test "is empty" do
587 clear_config([:mrf_simple, :banner_removal], [])
588
589 remote_user = build_remote_user()
590
591 assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
592 end
593
594 test "is not empty but it doesn't have a matching host" do
595 clear_config([:mrf_simple, :banner_removal], [{"non.matching.remote", ""}])
596
597 remote_user = build_remote_user()
598
599 assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
600 end
601
602 test "has a matching host" do
603 clear_config([:mrf_simple, :banner_removal], [{"remote.instance", ""}])
604
605 remote_user = build_remote_user()
606 {:ok, filtered} = SimplePolicy.filter(remote_user)
607
608 refute filtered["image"]
609 end
610
611 test "match with wildcard domain" do
612 clear_config([:mrf_simple, :banner_removal], [{"*.remote.instance", ""}])
613
614 remote_user = build_remote_user()
615 {:ok, filtered} = SimplePolicy.filter(remote_user)
616
617 refute filtered["image"]
618 end
619 end
620
621 describe "when :reject_deletes is empty" do
622 setup do: clear_config([:mrf_simple, :reject_deletes], [])
623
624 test "it accepts deletions even from rejected servers" do
625 clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
626
627 deletion_message = build_remote_deletion_message()
628
629 assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
630 end
631
632 test "it accepts deletions even from non-whitelisted servers" do
633 clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
634
635 deletion_message = build_remote_deletion_message()
636
637 assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
638 end
639 end
640
641 describe "when :reject_deletes is not empty but it doesn't have a matching host" do
642 setup do: clear_config([:mrf_simple, :reject_deletes], [{"non.matching.remote", ""}])
643
644 test "it accepts deletions even from rejected servers" do
645 clear_config([:mrf_simple, :reject], [{"remote.instance", ""}])
646
647 deletion_message = build_remote_deletion_message()
648
649 assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
650 end
651
652 test "it accepts deletions even from non-whitelisted servers" do
653 clear_config([:mrf_simple, :accept], [{"non.matching.remote", ""}])
654
655 deletion_message = build_remote_deletion_message()
656
657 assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message}
658 end
659 end
660
661 describe "when :reject_deletes has a matching host" do
662 setup do: clear_config([:mrf_simple, :reject_deletes], [{"remote.instance", ""}])
663
664 test "it rejects the deletion" do
665 deletion_message = build_remote_deletion_message()
666
667 assert {:reject, _} = SimplePolicy.filter(deletion_message)
668 end
669 end
670
671 describe "when :reject_deletes match with wildcard domain" do
672 setup do: clear_config([:mrf_simple, :reject_deletes], [{"*.remote.instance", ""}])
673
674 test "it rejects the deletion" do
675 deletion_message = build_remote_deletion_message()
676
677 assert {:reject, _} = SimplePolicy.filter(deletion_message)
678 end
679 end
680
681 defp build_local_message do
682 %{
683 "actor" => "#{Pleroma.Web.Endpoint.url()}/users/alice",
684 "to" => [],
685 "cc" => []
686 }
687 end
688
689 defp build_remote_message do
690 %{"actor" => "https://remote.instance/users/bob"}
691 end
692
693 defp build_remote_user do
694 %{
695 "id" => "https://remote.instance/users/bob",
696 "icon" => %{
697 "url" => "http://example.com/image.jpg",
698 "type" => "Image"
699 },
700 "image" => %{
701 "url" => "http://example.com/image.jpg",
702 "type" => "Image"
703 },
704 "type" => "Person"
705 }
706 end
707
708 defp build_remote_deletion_message do
709 %{
710 "type" => "Delete",
711 "actor" => "https://remote.instance/users/bob"
712 }
713 end
714 end