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