Merge branch 'feature/undo-validator-reduced' into 'develop'
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.CommonAPI
18
19 import Mock
20 import Pleroma.Factory
21 import ExUnit.CaptureLog
22
23 setup_all do
24 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
25 :ok
26 end
27
28 setup do: clear_config([:instance, :max_remote_account_fields])
29
30 describe "handle_incoming" do
31 test "it ignores an incoming notice if we already have it" do
32 activity = insert(:note_activity)
33
34 data =
35 File.read!("test/fixtures/mastodon-post-activity.json")
36 |> Poison.decode!()
37 |> Map.put("object", Object.normalize(activity).data)
38
39 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
40
41 assert activity == returned_activity
42 end
43
44 @tag capture_log: true
45 test "it fetches reply-to activities if we don't have them" do
46 data =
47 File.read!("test/fixtures/mastodon-post-activity.json")
48 |> Poison.decode!()
49
50 object =
51 data["object"]
52 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
53
54 data = Map.put(data, "object", object)
55 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
56 returned_object = Object.normalize(returned_activity, false)
57
58 assert activity =
59 Activity.get_create_by_object_ap_id(
60 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
61 )
62
63 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
64 end
65
66 test "it does not fetch reply-to activities beyond max replies depth limit" do
67 data =
68 File.read!("test/fixtures/mastodon-post-activity.json")
69 |> Poison.decode!()
70
71 object =
72 data["object"]
73 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
74
75 data = Map.put(data, "object", object)
76
77 with_mock Pleroma.Web.Federator,
78 allowed_thread_distance?: fn _ -> false end do
79 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
80
81 returned_object = Object.normalize(returned_activity, false)
82
83 refute Activity.get_create_by_object_ap_id(
84 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
85 )
86
87 assert returned_object.data["inReplyToAtomUri"] ==
88 "https://shitposter.club/notice/2827873"
89 end
90 end
91
92 test "it does not crash if the object in inReplyTo can't be fetched" do
93 data =
94 File.read!("test/fixtures/mastodon-post-activity.json")
95 |> Poison.decode!()
96
97 object =
98 data["object"]
99 |> Map.put("inReplyTo", "https://404.site/whatever")
100
101 data =
102 data
103 |> Map.put("object", object)
104
105 assert capture_log(fn ->
106 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
107 end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
108 end
109
110 test "it works for incoming notices" do
111 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
112
113 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
114
115 assert data["id"] ==
116 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
117
118 assert data["context"] ==
119 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
120
121 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
122
123 assert data["cc"] == [
124 "http://mastodon.example.org/users/admin/followers",
125 "http://localtesting.pleroma.lol/users/lain"
126 ]
127
128 assert data["actor"] == "http://mastodon.example.org/users/admin"
129
130 object_data = Object.normalize(data["object"]).data
131
132 assert object_data["id"] ==
133 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
134
135 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
136
137 assert object_data["cc"] == [
138 "http://mastodon.example.org/users/admin/followers",
139 "http://localtesting.pleroma.lol/users/lain"
140 ]
141
142 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
143 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
144
145 assert object_data["context"] ==
146 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
147
148 assert object_data["sensitive"] == true
149
150 user = User.get_cached_by_ap_id(object_data["actor"])
151
152 assert user.note_count == 1
153 end
154
155 test "it works for incoming notices with hashtags" do
156 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
157
158 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
159 object = Object.normalize(data["object"])
160
161 assert Enum.at(object.data["tag"], 2) == "moo"
162 end
163
164 test "it works for incoming questions" do
165 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
166
167 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
168
169 object = Object.normalize(activity)
170
171 assert Enum.all?(object.data["oneOf"], fn choice ->
172 choice["name"] in [
173 "Dunno",
174 "Everyone knows that!",
175 "25 char limit is dumb",
176 "I can't even fit a funny"
177 ]
178 end)
179 end
180
181 test "it works for incoming listens" do
182 data = %{
183 "@context" => "https://www.w3.org/ns/activitystreams",
184 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
185 "cc" => [],
186 "type" => "Listen",
187 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
188 "actor" => "http://mastodon.example.org/users/admin",
189 "object" => %{
190 "type" => "Audio",
191 "id" => "http://mastodon.example.org/users/admin/listens/1234",
192 "attributedTo" => "http://mastodon.example.org/users/admin",
193 "title" => "lain radio episode 1",
194 "artist" => "lain",
195 "album" => "lain radio",
196 "length" => 180_000
197 }
198 }
199
200 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
201
202 object = Object.normalize(activity)
203
204 assert object.data["title"] == "lain radio episode 1"
205 assert object.data["artist"] == "lain"
206 assert object.data["album"] == "lain radio"
207 assert object.data["length"] == 180_000
208 end
209
210 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
211 user = insert(:user)
212
213 {:ok, activity} =
214 CommonAPI.post(user, %{
215 "status" => "suya...",
216 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
217 })
218
219 object = Object.normalize(activity)
220
221 data =
222 File.read!("test/fixtures/mastodon-vote.json")
223 |> Poison.decode!()
224 |> Kernel.put_in(["to"], user.ap_id)
225 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
226 |> Kernel.put_in(["object", "to"], user.ap_id)
227
228 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
229 answer_object = Object.normalize(activity)
230 assert answer_object.data["type"] == "Answer"
231 object = Object.get_by_ap_id(object.data["id"])
232
233 assert Enum.any?(
234 object.data["oneOf"],
235 fn
236 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
237 _ -> false
238 end
239 )
240 end
241
242 test "it works for incoming notices with contentMap" do
243 data =
244 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
245
246 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
247 object = Object.normalize(data["object"])
248
249 assert object.data["content"] ==
250 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
251 end
252
253 test "it works for incoming notices with to/cc not being an array (kroeg)" do
254 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
255
256 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
257 object = Object.normalize(data["object"])
258
259 assert object.data["content"] ==
260 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
261 end
262
263 test "it works for incoming announces with actor being inlined (kroeg)" do
264 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
265
266 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
267
268 assert data["actor"] == "https://puckipedia.com/"
269 end
270
271 test "it works for incoming notices with tag not being an array (kroeg)" do
272 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
273
274 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
275 object = Object.normalize(data["object"])
276
277 assert object.data["emoji"] == %{
278 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
279 }
280
281 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
282
283 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
284 object = Object.normalize(data["object"])
285
286 assert "test" in object.data["tag"]
287 end
288
289 test "it works for incoming notices with url not being a string (prismo)" do
290 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
291
292 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
293 object = Object.normalize(data["object"])
294
295 assert object.data["url"] == "https://prismo.news/posts/83"
296 end
297
298 test "it cleans up incoming notices which are not really DMs" do
299 user = insert(:user)
300 other_user = insert(:user)
301
302 to = [user.ap_id, other_user.ap_id]
303
304 data =
305 File.read!("test/fixtures/mastodon-post-activity.json")
306 |> Poison.decode!()
307 |> Map.put("to", to)
308 |> Map.put("cc", [])
309
310 object =
311 data["object"]
312 |> Map.put("to", to)
313 |> Map.put("cc", [])
314
315 data = Map.put(data, "object", object)
316
317 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
318
319 assert data["to"] == []
320 assert data["cc"] == to
321
322 object_data = Object.normalize(activity).data
323
324 assert object_data["to"] == []
325 assert object_data["cc"] == to
326 end
327
328 test "it works for incoming emoji reactions" do
329 user = insert(:user)
330 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
331
332 data =
333 File.read!("test/fixtures/emoji-reaction.json")
334 |> Poison.decode!()
335 |> Map.put("object", activity.data["object"])
336
337 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
338
339 assert data["actor"] == "http://mastodon.example.org/users/admin"
340 assert data["type"] == "EmojiReact"
341 assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
342 assert data["object"] == activity.data["object"]
343 assert data["content"] == "👌"
344 end
345
346 test "it reject invalid emoji reactions" do
347 user = insert(:user)
348 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
349
350 data =
351 File.read!("test/fixtures/emoji-reaction-too-long.json")
352 |> Poison.decode!()
353 |> Map.put("object", activity.data["object"])
354
355 assert :error = Transmogrifier.handle_incoming(data)
356
357 data =
358 File.read!("test/fixtures/emoji-reaction-no-emoji.json")
359 |> Poison.decode!()
360 |> Map.put("object", activity.data["object"])
361
362 assert :error = Transmogrifier.handle_incoming(data)
363 end
364
365 test "it works for incoming announces" do
366 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
367
368 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
369
370 assert data["actor"] == "http://mastodon.example.org/users/admin"
371 assert data["type"] == "Announce"
372
373 assert data["id"] ==
374 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
375
376 assert data["object"] ==
377 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
378
379 assert Activity.get_create_by_object_ap_id(data["object"])
380 end
381
382 test "it works for incoming announces with an existing activity" do
383 user = insert(:user)
384 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
385
386 data =
387 File.read!("test/fixtures/mastodon-announce.json")
388 |> Poison.decode!()
389 |> Map.put("object", activity.data["object"])
390
391 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
392
393 assert data["actor"] == "http://mastodon.example.org/users/admin"
394 assert data["type"] == "Announce"
395
396 assert data["id"] ==
397 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
398
399 assert data["object"] == activity.data["object"]
400
401 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
402 end
403
404 test "it works for incoming announces with an inlined activity" do
405 data =
406 File.read!("test/fixtures/mastodon-announce-private.json")
407 |> Poison.decode!()
408
409 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
410
411 assert data["actor"] == "http://mastodon.example.org/users/admin"
412 assert data["type"] == "Announce"
413
414 assert data["id"] ==
415 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
416
417 object = Object.normalize(data["object"])
418
419 assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368"
420 assert object.data["content"] == "this is a private toot"
421 end
422
423 @tag capture_log: true
424 test "it rejects incoming announces with an inlined activity from another origin" do
425 data =
426 File.read!("test/fixtures/bogus-mastodon-announce.json")
427 |> Poison.decode!()
428
429 assert :error = Transmogrifier.handle_incoming(data)
430 end
431
432 test "it does not clobber the addressing on announce activities" do
433 user = insert(:user)
434 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
435
436 data =
437 File.read!("test/fixtures/mastodon-announce.json")
438 |> Poison.decode!()
439 |> Map.put("object", Object.normalize(activity).data["id"])
440 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
441 |> Map.put("cc", [])
442
443 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
444
445 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
446 end
447
448 test "it ensures that as:Public activities make it to their followers collection" do
449 user = insert(:user)
450
451 data =
452 File.read!("test/fixtures/mastodon-post-activity.json")
453 |> Poison.decode!()
454 |> Map.put("actor", user.ap_id)
455 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
456 |> Map.put("cc", [])
457
458 object =
459 data["object"]
460 |> Map.put("attributedTo", user.ap_id)
461 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
462 |> Map.put("cc", [])
463 |> Map.put("id", user.ap_id <> "/activities/12345678")
464
465 data = Map.put(data, "object", object)
466
467 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
468
469 assert data["cc"] == [User.ap_followers(user)]
470 end
471
472 test "it ensures that address fields become lists" do
473 user = insert(:user)
474
475 data =
476 File.read!("test/fixtures/mastodon-post-activity.json")
477 |> Poison.decode!()
478 |> Map.put("actor", user.ap_id)
479 |> Map.put("to", nil)
480 |> Map.put("cc", nil)
481
482 object =
483 data["object"]
484 |> Map.put("attributedTo", user.ap_id)
485 |> Map.put("to", nil)
486 |> Map.put("cc", nil)
487 |> Map.put("id", user.ap_id <> "/activities/12345678")
488
489 data = Map.put(data, "object", object)
490
491 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
492
493 assert !is_nil(data["to"])
494 assert !is_nil(data["cc"])
495 end
496
497 test "it strips internal likes" do
498 data =
499 File.read!("test/fixtures/mastodon-post-activity.json")
500 |> Poison.decode!()
501
502 likes = %{
503 "first" =>
504 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
505 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
506 "totalItems" => 3,
507 "type" => "OrderedCollection"
508 }
509
510 object = Map.put(data["object"], "likes", likes)
511 data = Map.put(data, "object", object)
512
513 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
514
515 refute Map.has_key?(object.data, "likes")
516 end
517
518 test "it strips internal reactions" do
519 user = insert(:user)
520 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
521 {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
522
523 %{object: object} = Activity.get_by_id_with_object(activity.id)
524 assert Map.has_key?(object.data, "reactions")
525 assert Map.has_key?(object.data, "reaction_count")
526
527 object_data = Transmogrifier.strip_internal_fields(object.data)
528 refute Map.has_key?(object_data, "reactions")
529 refute Map.has_key?(object_data, "reaction_count")
530 end
531
532 test "it works for incoming update activities" do
533 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
534
535 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
536 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
537
538 object =
539 update_data["object"]
540 |> Map.put("actor", data["actor"])
541 |> Map.put("id", data["actor"])
542
543 update_data =
544 update_data
545 |> Map.put("actor", data["actor"])
546 |> Map.put("object", object)
547
548 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
549
550 assert data["id"] == update_data["id"]
551
552 user = User.get_cached_by_ap_id(data["actor"])
553 assert user.name == "gargle"
554
555 assert user.avatar["url"] == [
556 %{
557 "href" =>
558 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
559 }
560 ]
561
562 assert user.banner["url"] == [
563 %{
564 "href" =>
565 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
566 }
567 ]
568
569 assert user.bio == "<p>Some bio</p>"
570 end
571
572 test "it works with alsoKnownAs" do
573 {:ok, %Activity{data: %{"actor" => actor}}} =
574 "test/fixtures/mastodon-post-activity.json"
575 |> File.read!()
576 |> Poison.decode!()
577 |> Transmogrifier.handle_incoming()
578
579 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
580
581 {:ok, _activity} =
582 "test/fixtures/mastodon-update.json"
583 |> File.read!()
584 |> Poison.decode!()
585 |> Map.put("actor", actor)
586 |> Map.update!("object", fn object ->
587 object
588 |> Map.put("actor", actor)
589 |> Map.put("id", actor)
590 |> Map.put("alsoKnownAs", [
591 "http://mastodon.example.org/users/foo",
592 "http://example.org/users/bar"
593 ])
594 end)
595 |> Transmogrifier.handle_incoming()
596
597 assert User.get_cached_by_ap_id(actor).also_known_as == [
598 "http://mastodon.example.org/users/foo",
599 "http://example.org/users/bar"
600 ]
601 end
602
603 test "it works with custom profile fields" do
604 {:ok, activity} =
605 "test/fixtures/mastodon-post-activity.json"
606 |> File.read!()
607 |> Poison.decode!()
608 |> Transmogrifier.handle_incoming()
609
610 user = User.get_cached_by_ap_id(activity.actor)
611
612 assert user.fields == [
613 %{"name" => "foo", "value" => "bar"},
614 %{"name" => "foo1", "value" => "bar1"}
615 ]
616
617 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
618
619 object =
620 update_data["object"]
621 |> Map.put("actor", user.ap_id)
622 |> Map.put("id", user.ap_id)
623
624 update_data =
625 update_data
626 |> Map.put("actor", user.ap_id)
627 |> Map.put("object", object)
628
629 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
630
631 user = User.get_cached_by_ap_id(user.ap_id)
632
633 assert user.fields == [
634 %{"name" => "foo", "value" => "updated"},
635 %{"name" => "foo1", "value" => "updated"}
636 ]
637
638 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
639
640 update_data =
641 put_in(update_data, ["object", "attachment"], [
642 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
643 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
644 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
645 ])
646
647 {:ok, _} = Transmogrifier.handle_incoming(update_data)
648
649 user = User.get_cached_by_ap_id(user.ap_id)
650
651 assert user.fields == [
652 %{"name" => "foo", "value" => "updated"},
653 %{"name" => "foo1", "value" => "updated"}
654 ]
655
656 update_data = put_in(update_data, ["object", "attachment"], [])
657
658 {:ok, _} = Transmogrifier.handle_incoming(update_data)
659
660 user = User.get_cached_by_ap_id(user.ap_id)
661
662 assert user.fields == []
663 end
664
665 test "it works for incoming update activities which lock the account" do
666 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
667
668 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
669 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
670
671 object =
672 update_data["object"]
673 |> Map.put("actor", data["actor"])
674 |> Map.put("id", data["actor"])
675 |> Map.put("manuallyApprovesFollowers", true)
676
677 update_data =
678 update_data
679 |> Map.put("actor", data["actor"])
680 |> Map.put("object", object)
681
682 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
683
684 user = User.get_cached_by_ap_id(data["actor"])
685 assert user.locked == true
686 end
687
688 test "it works for incomming unfollows with an existing follow" do
689 user = insert(:user)
690
691 follow_data =
692 File.read!("test/fixtures/mastodon-follow-activity.json")
693 |> Poison.decode!()
694 |> Map.put("object", user.ap_id)
695
696 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
697
698 data =
699 File.read!("test/fixtures/mastodon-unfollow-activity.json")
700 |> Poison.decode!()
701 |> Map.put("object", follow_data)
702
703 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
704
705 assert data["type"] == "Undo"
706 assert data["object"]["type"] == "Follow"
707 assert data["object"]["object"] == user.ap_id
708 assert data["actor"] == "http://mastodon.example.org/users/admin"
709
710 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
711 end
712
713 test "it works for incoming follows to locked account" do
714 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
715 user = insert(:user, locked: true)
716
717 data =
718 File.read!("test/fixtures/mastodon-follow-activity.json")
719 |> Poison.decode!()
720 |> Map.put("object", user.ap_id)
721
722 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
723
724 assert data["type"] == "Follow"
725 assert data["object"] == user.ap_id
726 assert data["state"] == "pending"
727 assert data["actor"] == "http://mastodon.example.org/users/admin"
728
729 assert [^pending_follower] = User.get_follow_requests(user)
730 end
731
732 test "it works for incoming blocks" do
733 user = insert(:user)
734
735 data =
736 File.read!("test/fixtures/mastodon-block-activity.json")
737 |> Poison.decode!()
738 |> Map.put("object", user.ap_id)
739
740 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
741
742 assert data["type"] == "Block"
743 assert data["object"] == user.ap_id
744 assert data["actor"] == "http://mastodon.example.org/users/admin"
745
746 blocker = User.get_cached_by_ap_id(data["actor"])
747
748 assert User.blocks?(blocker, user)
749 end
750
751 test "incoming blocks successfully tear down any follow relationship" do
752 blocker = insert(:user)
753 blocked = insert(:user)
754
755 data =
756 File.read!("test/fixtures/mastodon-block-activity.json")
757 |> Poison.decode!()
758 |> Map.put("object", blocked.ap_id)
759 |> Map.put("actor", blocker.ap_id)
760
761 {:ok, blocker} = User.follow(blocker, blocked)
762 {:ok, blocked} = User.follow(blocked, blocker)
763
764 assert User.following?(blocker, blocked)
765 assert User.following?(blocked, blocker)
766
767 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
768
769 assert data["type"] == "Block"
770 assert data["object"] == blocked.ap_id
771 assert data["actor"] == blocker.ap_id
772
773 blocker = User.get_cached_by_ap_id(data["actor"])
774 blocked = User.get_cached_by_ap_id(data["object"])
775
776 assert User.blocks?(blocker, blocked)
777
778 refute User.following?(blocker, blocked)
779 refute User.following?(blocked, blocker)
780 end
781
782 test "it works for incoming accepts which were pre-accepted" do
783 follower = insert(:user)
784 followed = insert(:user)
785
786 {:ok, follower} = User.follow(follower, followed)
787 assert User.following?(follower, followed) == true
788
789 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
790
791 accept_data =
792 File.read!("test/fixtures/mastodon-accept-activity.json")
793 |> Poison.decode!()
794 |> Map.put("actor", followed.ap_id)
795
796 object =
797 accept_data["object"]
798 |> Map.put("actor", follower.ap_id)
799 |> Map.put("id", follow_activity.data["id"])
800
801 accept_data = Map.put(accept_data, "object", object)
802
803 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
804 refute activity.local
805
806 assert activity.data["object"] == follow_activity.data["id"]
807
808 assert activity.data["id"] == accept_data["id"]
809
810 follower = User.get_cached_by_id(follower.id)
811
812 assert User.following?(follower, followed) == true
813 end
814
815 test "it works for incoming accepts which were orphaned" do
816 follower = insert(:user)
817 followed = insert(:user, locked: true)
818
819 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
820
821 accept_data =
822 File.read!("test/fixtures/mastodon-accept-activity.json")
823 |> Poison.decode!()
824 |> Map.put("actor", followed.ap_id)
825
826 accept_data =
827 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
828
829 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
830 assert activity.data["object"] == follow_activity.data["id"]
831
832 follower = User.get_cached_by_id(follower.id)
833
834 assert User.following?(follower, followed) == true
835 end
836
837 test "it works for incoming accepts which are referenced by IRI only" do
838 follower = insert(:user)
839 followed = insert(:user, locked: true)
840
841 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
842
843 accept_data =
844 File.read!("test/fixtures/mastodon-accept-activity.json")
845 |> Poison.decode!()
846 |> Map.put("actor", followed.ap_id)
847 |> Map.put("object", follow_activity.data["id"])
848
849 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
850 assert activity.data["object"] == follow_activity.data["id"]
851
852 follower = User.get_cached_by_id(follower.id)
853
854 assert User.following?(follower, followed) == true
855 end
856
857 test "it fails for incoming accepts which cannot be correlated" do
858 follower = insert(:user)
859 followed = insert(:user, locked: true)
860
861 accept_data =
862 File.read!("test/fixtures/mastodon-accept-activity.json")
863 |> Poison.decode!()
864 |> Map.put("actor", followed.ap_id)
865
866 accept_data =
867 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
868
869 :error = Transmogrifier.handle_incoming(accept_data)
870
871 follower = User.get_cached_by_id(follower.id)
872
873 refute User.following?(follower, followed) == true
874 end
875
876 test "it fails for incoming rejects which cannot be correlated" do
877 follower = insert(:user)
878 followed = insert(:user, locked: true)
879
880 accept_data =
881 File.read!("test/fixtures/mastodon-reject-activity.json")
882 |> Poison.decode!()
883 |> Map.put("actor", followed.ap_id)
884
885 accept_data =
886 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
887
888 :error = Transmogrifier.handle_incoming(accept_data)
889
890 follower = User.get_cached_by_id(follower.id)
891
892 refute User.following?(follower, followed) == true
893 end
894
895 test "it works for incoming rejects which are orphaned" do
896 follower = insert(:user)
897 followed = insert(:user, locked: true)
898
899 {:ok, follower} = User.follow(follower, followed)
900 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
901
902 assert User.following?(follower, followed) == true
903
904 reject_data =
905 File.read!("test/fixtures/mastodon-reject-activity.json")
906 |> Poison.decode!()
907 |> Map.put("actor", followed.ap_id)
908
909 reject_data =
910 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
911
912 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
913 refute activity.local
914 assert activity.data["id"] == reject_data["id"]
915
916 follower = User.get_cached_by_id(follower.id)
917
918 assert User.following?(follower, followed) == false
919 end
920
921 test "it works for incoming rejects which are referenced by IRI only" do
922 follower = insert(:user)
923 followed = insert(:user, locked: true)
924
925 {:ok, follower} = User.follow(follower, followed)
926 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
927
928 assert User.following?(follower, followed) == true
929
930 reject_data =
931 File.read!("test/fixtures/mastodon-reject-activity.json")
932 |> Poison.decode!()
933 |> Map.put("actor", followed.ap_id)
934 |> Map.put("object", follow_activity.data["id"])
935
936 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
937
938 follower = User.get_cached_by_id(follower.id)
939
940 assert User.following?(follower, followed) == false
941 end
942
943 test "it rejects activities without a valid ID" do
944 user = insert(:user)
945
946 data =
947 File.read!("test/fixtures/mastodon-follow-activity.json")
948 |> Poison.decode!()
949 |> Map.put("object", user.ap_id)
950 |> Map.put("id", "")
951
952 :error = Transmogrifier.handle_incoming(data)
953 end
954
955 test "skip converting the content when it is nil" do
956 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
957
958 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
959
960 result =
961 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
962
963 assert result["content"] == nil
964 end
965
966 test "it converts content of object to html" do
967 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
968
969 {:ok, %{"content" => content_markdown}} =
970 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
971
972 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
973 Fetcher.fetch_object_from_id(object_id)
974
975 assert content_markdown ==
976 "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
977
978 assert content ==
979 "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
980
981 assert object.data["mediaType"] == "text/html"
982 end
983
984 test "it remaps video URLs as attachments if necessary" do
985 {:ok, object} =
986 Fetcher.fetch_object_from_id(
987 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
988 )
989
990 attachment = %{
991 "type" => "Link",
992 "mediaType" => "video/mp4",
993 "url" => [
994 %{
995 "href" =>
996 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
997 "mediaType" => "video/mp4"
998 }
999 ]
1000 }
1001
1002 assert object.data["url"] ==
1003 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1004
1005 assert object.data["attachment"] == [attachment]
1006 end
1007
1008 test "it accepts Flag activities" do
1009 user = insert(:user)
1010 other_user = insert(:user)
1011
1012 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1013 object = Object.normalize(activity)
1014
1015 note_obj = %{
1016 "type" => "Note",
1017 "id" => activity.data["id"],
1018 "content" => "test post",
1019 "published" => object.data["published"],
1020 "actor" => AccountView.render("show.json", %{user: user})
1021 }
1022
1023 message = %{
1024 "@context" => "https://www.w3.org/ns/activitystreams",
1025 "cc" => [user.ap_id],
1026 "object" => [user.ap_id, activity.data["id"]],
1027 "type" => "Flag",
1028 "content" => "blocked AND reported!!!",
1029 "actor" => other_user.ap_id
1030 }
1031
1032 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1033
1034 assert activity.data["object"] == [user.ap_id, note_obj]
1035 assert activity.data["content"] == "blocked AND reported!!!"
1036 assert activity.data["actor"] == other_user.ap_id
1037 assert activity.data["cc"] == [user.ap_id]
1038 end
1039
1040 test "it correctly processes messages with non-array to field" do
1041 user = insert(:user)
1042
1043 message = %{
1044 "@context" => "https://www.w3.org/ns/activitystreams",
1045 "to" => "https://www.w3.org/ns/activitystreams#Public",
1046 "type" => "Create",
1047 "object" => %{
1048 "content" => "blah blah blah",
1049 "type" => "Note",
1050 "attributedTo" => user.ap_id,
1051 "inReplyTo" => nil
1052 },
1053 "actor" => user.ap_id
1054 }
1055
1056 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1057
1058 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1059 end
1060
1061 test "it correctly processes messages with non-array cc field" do
1062 user = insert(:user)
1063
1064 message = %{
1065 "@context" => "https://www.w3.org/ns/activitystreams",
1066 "to" => user.follower_address,
1067 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1068 "type" => "Create",
1069 "object" => %{
1070 "content" => "blah blah blah",
1071 "type" => "Note",
1072 "attributedTo" => user.ap_id,
1073 "inReplyTo" => nil
1074 },
1075 "actor" => user.ap_id
1076 }
1077
1078 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1079
1080 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1081 assert [user.follower_address] == activity.data["to"]
1082 end
1083
1084 test "it accepts Move activities" do
1085 old_user = insert(:user)
1086 new_user = insert(:user)
1087
1088 message = %{
1089 "@context" => "https://www.w3.org/ns/activitystreams",
1090 "type" => "Move",
1091 "actor" => old_user.ap_id,
1092 "object" => old_user.ap_id,
1093 "target" => new_user.ap_id
1094 }
1095
1096 assert :error = Transmogrifier.handle_incoming(message)
1097
1098 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1099
1100 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1101 assert activity.actor == old_user.ap_id
1102 assert activity.data["actor"] == old_user.ap_id
1103 assert activity.data["object"] == old_user.ap_id
1104 assert activity.data["target"] == new_user.ap_id
1105 assert activity.data["type"] == "Move"
1106 end
1107 end
1108
1109 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1110 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1111 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1112
1113 setup do
1114 data =
1115 "test/fixtures/mastodon-post-activity.json"
1116 |> File.read!()
1117 |> Poison.decode!()
1118
1119 items = get_in(data, ["object", "replies", "first", "items"])
1120 assert length(items) > 0
1121
1122 %{data: data, items: items}
1123 end
1124
1125 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1126 data: data,
1127 items: items
1128 } do
1129 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1130
1131 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1132
1133 for id <- items do
1134 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1135 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1136 end
1137 end
1138
1139 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1140 %{data: data} do
1141 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1142
1143 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1144
1145 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1146 end
1147 end
1148
1149 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1150 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1151 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1152
1153 setup do
1154 user = insert(:user)
1155
1156 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1157
1158 {:ok, reply1} =
1159 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1160
1161 {:ok, reply2} =
1162 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1163
1164 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1165
1166 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1167
1168 Repo.delete(activity.object)
1169 Repo.delete(activity)
1170
1171 %{federation_output: federation_output, replies_uris: replies_uris}
1172 end
1173
1174 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1175 federation_output: federation_output,
1176 replies_uris: replies_uris
1177 } do
1178 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1179
1180 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1181
1182 for id <- replies_uris do
1183 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1184 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1185 end
1186 end
1187
1188 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1189 %{federation_output: federation_output} do
1190 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1191
1192 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1193
1194 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1195 end
1196 end
1197
1198 describe "prepare outgoing" do
1199 test "it inlines private announced objects" do
1200 user = insert(:user)
1201
1202 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1203
1204 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1205
1206 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1207
1208 assert modified["object"]["content"] == "hey"
1209 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1210 end
1211
1212 test "it turns mentions into tags" do
1213 user = insert(:user)
1214 other_user = insert(:user)
1215
1216 {:ok, activity} =
1217 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1218
1219 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1220 object = modified["object"]
1221
1222 expected_mention = %{
1223 "href" => other_user.ap_id,
1224 "name" => "@#{other_user.nickname}",
1225 "type" => "Mention"
1226 }
1227
1228 expected_tag = %{
1229 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1230 "type" => "Hashtag",
1231 "name" => "#2hu"
1232 }
1233
1234 assert Enum.member?(object["tag"], expected_tag)
1235 assert Enum.member?(object["tag"], expected_mention)
1236 end
1237
1238 test "it adds the sensitive property" do
1239 user = insert(:user)
1240
1241 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1242 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1243
1244 assert modified["object"]["sensitive"]
1245 end
1246
1247 test "it adds the json-ld context and the conversation property" do
1248 user = insert(:user)
1249
1250 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1251 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1252
1253 assert modified["@context"] ==
1254 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1255
1256 assert modified["object"]["conversation"] == modified["context"]
1257 end
1258
1259 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1260 user = insert(:user)
1261
1262 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1263 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1264
1265 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1266 end
1267
1268 test "it strips internal hashtag data" do
1269 user = insert(:user)
1270
1271 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1272
1273 expected_tag = %{
1274 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1275 "type" => "Hashtag",
1276 "name" => "#2hu"
1277 }
1278
1279 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1280
1281 assert modified["object"]["tag"] == [expected_tag]
1282 end
1283
1284 test "it strips internal fields" do
1285 user = insert(:user)
1286
1287 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1288
1289 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1290
1291 assert length(modified["object"]["tag"]) == 2
1292
1293 assert is_nil(modified["object"]["emoji"])
1294 assert is_nil(modified["object"]["like_count"])
1295 assert is_nil(modified["object"]["announcements"])
1296 assert is_nil(modified["object"]["announcement_count"])
1297 assert is_nil(modified["object"]["context_id"])
1298 end
1299
1300 test "it strips internal fields of article" do
1301 activity = insert(:article_activity)
1302
1303 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1304
1305 assert length(modified["object"]["tag"]) == 2
1306
1307 assert is_nil(modified["object"]["emoji"])
1308 assert is_nil(modified["object"]["like_count"])
1309 assert is_nil(modified["object"]["announcements"])
1310 assert is_nil(modified["object"]["announcement_count"])
1311 assert is_nil(modified["object"]["context_id"])
1312 assert is_nil(modified["object"]["likes"])
1313 end
1314
1315 test "the directMessage flag is present" do
1316 user = insert(:user)
1317 other_user = insert(:user)
1318
1319 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1320
1321 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1322
1323 assert modified["directMessage"] == false
1324
1325 {:ok, activity} =
1326 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1327
1328 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1329
1330 assert modified["directMessage"] == false
1331
1332 {:ok, activity} =
1333 CommonAPI.post(user, %{
1334 "status" => "@#{other_user.nickname} :moominmamma:",
1335 "visibility" => "direct"
1336 })
1337
1338 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1339
1340 assert modified["directMessage"] == true
1341 end
1342
1343 test "it strips BCC field" do
1344 user = insert(:user)
1345 {:ok, list} = Pleroma.List.create("foo", user)
1346
1347 {:ok, activity} =
1348 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1349
1350 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1351
1352 assert is_nil(modified["bcc"])
1353 end
1354
1355 test "it can handle Listen activities" do
1356 listen_activity = insert(:listen)
1357
1358 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1359
1360 assert modified["type"] == "Listen"
1361
1362 user = insert(:user)
1363
1364 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1365
1366 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1367 end
1368 end
1369
1370 describe "user upgrade" do
1371 test "it upgrades a user to activitypub" do
1372 user =
1373 insert(:user, %{
1374 nickname: "rye@niu.moe",
1375 local: false,
1376 ap_id: "https://niu.moe/users/rye",
1377 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1378 })
1379
1380 user_two = insert(:user)
1381 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1382
1383 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1384 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1385 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1386
1387 user = User.get_cached_by_id(user.id)
1388 assert user.note_count == 1
1389
1390 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1391 ObanHelpers.perform_all()
1392
1393 assert user.ap_enabled
1394 assert user.note_count == 1
1395 assert user.follower_address == "https://niu.moe/users/rye/followers"
1396 assert user.following_address == "https://niu.moe/users/rye/following"
1397
1398 user = User.get_cached_by_id(user.id)
1399 assert user.note_count == 1
1400
1401 activity = Activity.get_by_id(activity.id)
1402 assert user.follower_address in activity.recipients
1403
1404 assert %{
1405 "url" => [
1406 %{
1407 "href" =>
1408 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1409 }
1410 ]
1411 } = user.avatar
1412
1413 assert %{
1414 "url" => [
1415 %{
1416 "href" =>
1417 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1418 }
1419 ]
1420 } = user.banner
1421
1422 refute "..." in activity.recipients
1423
1424 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1425 refute user.follower_address in unrelated_activity.recipients
1426
1427 user_two = User.get_cached_by_id(user_two.id)
1428 assert User.following?(user_two, user)
1429 refute "..." in User.following(user_two)
1430 end
1431 end
1432
1433 describe "actor rewriting" do
1434 test "it fixes the actor URL property to be a proper URI" do
1435 data = %{
1436 "url" => %{"href" => "http://example.com"}
1437 }
1438
1439 rewritten = Transmogrifier.maybe_fix_user_object(data)
1440 assert rewritten["url"] == "http://example.com"
1441 end
1442 end
1443
1444 describe "actor origin containment" do
1445 test "it rejects activities which reference objects with bogus origins" do
1446 data = %{
1447 "@context" => "https://www.w3.org/ns/activitystreams",
1448 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1449 "actor" => "http://mastodon.example.org/users/admin",
1450 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1451 "object" => "https://info.pleroma.site/activity.json",
1452 "type" => "Announce"
1453 }
1454
1455 assert capture_log(fn ->
1456 :error = Transmogrifier.handle_incoming(data)
1457 end) =~ "Object containment failed"
1458 end
1459
1460 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1461 data = %{
1462 "@context" => "https://www.w3.org/ns/activitystreams",
1463 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1464 "actor" => "http://mastodon.example.org/users/admin",
1465 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1466 "object" => "https://info.pleroma.site/activity2.json",
1467 "type" => "Announce"
1468 }
1469
1470 assert capture_log(fn ->
1471 :error = Transmogrifier.handle_incoming(data)
1472 end) =~ "Object containment failed"
1473 end
1474
1475 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1476 data = %{
1477 "@context" => "https://www.w3.org/ns/activitystreams",
1478 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1479 "actor" => "http://mastodon.example.org/users/admin",
1480 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1481 "object" => "https://info.pleroma.site/activity3.json",
1482 "type" => "Announce"
1483 }
1484
1485 assert capture_log(fn ->
1486 :error = Transmogrifier.handle_incoming(data)
1487 end) =~ "Object containment failed"
1488 end
1489 end
1490
1491 describe "reserialization" do
1492 test "successfully reserializes a message with inReplyTo == nil" do
1493 user = insert(:user)
1494
1495 message = %{
1496 "@context" => "https://www.w3.org/ns/activitystreams",
1497 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1498 "cc" => [],
1499 "type" => "Create",
1500 "object" => %{
1501 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1502 "cc" => [],
1503 "type" => "Note",
1504 "content" => "Hi",
1505 "inReplyTo" => nil,
1506 "attributedTo" => user.ap_id
1507 },
1508 "actor" => user.ap_id
1509 }
1510
1511 {:ok, activity} = Transmogrifier.handle_incoming(message)
1512
1513 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1514 end
1515
1516 test "successfully reserializes a message with AS2 objects in IR" do
1517 user = insert(:user)
1518
1519 message = %{
1520 "@context" => "https://www.w3.org/ns/activitystreams",
1521 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1522 "cc" => [],
1523 "type" => "Create",
1524 "object" => %{
1525 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1526 "cc" => [],
1527 "type" => "Note",
1528 "content" => "Hi",
1529 "inReplyTo" => nil,
1530 "attributedTo" => user.ap_id,
1531 "tag" => [
1532 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1533 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1534 ]
1535 },
1536 "actor" => user.ap_id
1537 }
1538
1539 {:ok, activity} = Transmogrifier.handle_incoming(message)
1540
1541 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1542 end
1543 end
1544
1545 test "Rewrites Answers to Notes" do
1546 user = insert(:user)
1547
1548 {:ok, poll_activity} =
1549 CommonAPI.post(user, %{
1550 "status" => "suya...",
1551 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1552 })
1553
1554 poll_object = Object.normalize(poll_activity)
1555 # TODO: Replace with CommonAPI vote creation when implemented
1556 data =
1557 File.read!("test/fixtures/mastodon-vote.json")
1558 |> Poison.decode!()
1559 |> Kernel.put_in(["to"], user.ap_id)
1560 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1561 |> Kernel.put_in(["object", "to"], user.ap_id)
1562
1563 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1564 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1565
1566 assert data["object"]["type"] == "Note"
1567 end
1568
1569 describe "fix_explicit_addressing" do
1570 setup do
1571 user = insert(:user)
1572 [user: user]
1573 end
1574
1575 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1576 explicitly_mentioned_actors = [
1577 "https://pleroma.gold/users/user1",
1578 "https://pleroma.gold/user2"
1579 ]
1580
1581 object = %{
1582 "actor" => user.ap_id,
1583 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1584 "cc" => [],
1585 "tag" =>
1586 Enum.map(explicitly_mentioned_actors, fn href ->
1587 %{"type" => "Mention", "href" => href}
1588 end)
1589 }
1590
1591 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1592 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1593 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1594 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1595 end
1596
1597 test "does not move actor's follower collection to cc", %{user: user} do
1598 object = %{
1599 "actor" => user.ap_id,
1600 "to" => [user.follower_address],
1601 "cc" => []
1602 }
1603
1604 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1605 assert user.follower_address in fixed_object["to"]
1606 refute user.follower_address in fixed_object["cc"]
1607 end
1608
1609 test "removes recipient's follower collection from cc", %{user: user} do
1610 recipient = insert(:user)
1611
1612 object = %{
1613 "actor" => user.ap_id,
1614 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1615 "cc" => [user.follower_address, recipient.follower_address]
1616 }
1617
1618 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1619
1620 assert user.follower_address in fixed_object["cc"]
1621 refute recipient.follower_address in fixed_object["cc"]
1622 refute recipient.follower_address in fixed_object["to"]
1623 end
1624 end
1625
1626 describe "fix_summary/1" do
1627 test "returns fixed object" do
1628 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1629 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1630 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1631 end
1632 end
1633
1634 describe "fix_in_reply_to/2" do
1635 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1636
1637 setup do
1638 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1639 [data: data]
1640 end
1641
1642 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1643 assert Transmogrifier.fix_in_reply_to(data) == data
1644 end
1645
1646 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1647 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1648
1649 object_with_reply =
1650 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1651
1652 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1653 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1654 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1655
1656 object_with_reply =
1657 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1658
1659 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1660 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1661 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1662
1663 object_with_reply =
1664 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1665
1666 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1667 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1668 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1669
1670 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1671 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1672 assert modified_object["inReplyTo"] == []
1673 assert modified_object["inReplyToAtomUri"] == ""
1674 end
1675
1676 @tag capture_log: true
1677 test "returns modified object when allowed incoming reply", %{data: data} do
1678 object_with_reply =
1679 Map.put(
1680 data["object"],
1681 "inReplyTo",
1682 "https://shitposter.club/notice/2827873"
1683 )
1684
1685 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1686 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1687
1688 assert modified_object["inReplyTo"] ==
1689 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1690
1691 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1692
1693 assert modified_object["conversation"] ==
1694 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1695
1696 assert modified_object["context"] ==
1697 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1698 end
1699 end
1700
1701 describe "fix_url/1" do
1702 test "fixes data for object when url is map" do
1703 object = %{
1704 "url" => %{
1705 "type" => "Link",
1706 "mimeType" => "video/mp4",
1707 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1708 }
1709 }
1710
1711 assert Transmogrifier.fix_url(object) == %{
1712 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1713 }
1714 end
1715
1716 test "fixes data for video object" do
1717 object = %{
1718 "type" => "Video",
1719 "url" => [
1720 %{
1721 "type" => "Link",
1722 "mimeType" => "video/mp4",
1723 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1724 },
1725 %{
1726 "type" => "Link",
1727 "mimeType" => "video/mp4",
1728 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1729 },
1730 %{
1731 "type" => "Link",
1732 "mimeType" => "text/html",
1733 "href" => "https://peertube.-2d4c2d1630e3"
1734 },
1735 %{
1736 "type" => "Link",
1737 "mimeType" => "text/html",
1738 "href" => "https://peertube.-2d4c2d16377-42"
1739 }
1740 ]
1741 }
1742
1743 assert Transmogrifier.fix_url(object) == %{
1744 "attachment" => [
1745 %{
1746 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1747 "mimeType" => "video/mp4",
1748 "type" => "Link"
1749 }
1750 ],
1751 "type" => "Video",
1752 "url" => "https://peertube.-2d4c2d1630e3"
1753 }
1754 end
1755
1756 test "fixes url for not Video object" do
1757 object = %{
1758 "type" => "Text",
1759 "url" => [
1760 %{
1761 "type" => "Link",
1762 "mimeType" => "text/html",
1763 "href" => "https://peertube.-2d4c2d1630e3"
1764 },
1765 %{
1766 "type" => "Link",
1767 "mimeType" => "text/html",
1768 "href" => "https://peertube.-2d4c2d16377-42"
1769 }
1770 ]
1771 }
1772
1773 assert Transmogrifier.fix_url(object) == %{
1774 "type" => "Text",
1775 "url" => "https://peertube.-2d4c2d1630e3"
1776 }
1777
1778 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1779 "type" => "Text",
1780 "url" => ""
1781 }
1782 end
1783
1784 test "retunrs not modified object" do
1785 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1786 end
1787 end
1788
1789 describe "get_obj_helper/2" do
1790 test "returns nil when cannot normalize object" do
1791 assert capture_log(fn ->
1792 refute Transmogrifier.get_obj_helper("test-obj-id")
1793 end) =~ "Unsupported URI scheme"
1794 end
1795
1796 @tag capture_log: true
1797 test "returns {:ok, %Object{}} for success case" do
1798 assert {:ok, %Object{}} =
1799 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1800 end
1801 end
1802
1803 describe "fix_attachments/1" do
1804 test "returns not modified object" do
1805 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1806 assert Transmogrifier.fix_attachments(data) == data
1807 end
1808
1809 test "returns modified object when attachment is map" do
1810 assert Transmogrifier.fix_attachments(%{
1811 "attachment" => %{
1812 "mediaType" => "video/mp4",
1813 "url" => "https://peertube.moe/stat-480.mp4"
1814 }
1815 }) == %{
1816 "attachment" => [
1817 %{
1818 "mediaType" => "video/mp4",
1819 "url" => [
1820 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1821 ]
1822 }
1823 ]
1824 }
1825 end
1826
1827 test "returns modified object when attachment is list" do
1828 assert Transmogrifier.fix_attachments(%{
1829 "attachment" => [
1830 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1831 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1832 ]
1833 }) == %{
1834 "attachment" => [
1835 %{
1836 "mediaType" => "video/mp4",
1837 "url" => [
1838 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1839 ]
1840 },
1841 %{
1842 "mediaType" => "video/mp4",
1843 "url" => [
1844 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1845 ]
1846 }
1847 ]
1848 }
1849 end
1850 end
1851
1852 describe "fix_emoji/1" do
1853 test "returns not modified object when object not contains tags" do
1854 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1855 assert Transmogrifier.fix_emoji(data) == data
1856 end
1857
1858 test "returns object with emoji when object contains list tags" do
1859 assert Transmogrifier.fix_emoji(%{
1860 "tag" => [
1861 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1862 %{"type" => "Hashtag"}
1863 ]
1864 }) == %{
1865 "emoji" => %{"bib" => "/test"},
1866 "tag" => [
1867 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1868 %{"type" => "Hashtag"}
1869 ]
1870 }
1871 end
1872
1873 test "returns object with emoji when object contains map tag" do
1874 assert Transmogrifier.fix_emoji(%{
1875 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1876 }) == %{
1877 "emoji" => %{"bib" => "/test"},
1878 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1879 }
1880 end
1881 end
1882
1883 describe "set_replies/1" do
1884 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1885
1886 test "returns unmodified object if activity doesn't have self-replies" do
1887 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1888 assert Transmogrifier.set_replies(data) == data
1889 end
1890
1891 test "sets `replies` collection with a limited number of self-replies" do
1892 [user, another_user] = insert_list(2, :user)
1893
1894 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
1895
1896 {:ok, %{id: id2} = self_reply1} =
1897 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
1898
1899 {:ok, self_reply2} =
1900 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
1901
1902 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1903 {:ok, _} =
1904 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
1905
1906 {:ok, _} =
1907 CommonAPI.post(user, %{
1908 "status" => "self-reply to self-reply",
1909 "in_reply_to_status_id" => id2
1910 })
1911
1912 {:ok, _} =
1913 CommonAPI.post(another_user, %{
1914 "status" => "another user's reply",
1915 "in_reply_to_status_id" => id1
1916 })
1917
1918 object = Object.normalize(activity)
1919 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1920
1921 assert %{"type" => "Collection", "items" => ^replies_uris} =
1922 Transmogrifier.set_replies(object.data)["replies"]
1923 end
1924 end
1925
1926 test "take_emoji_tags/1" do
1927 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1928
1929 assert Transmogrifier.take_emoji_tags(user) == [
1930 %{
1931 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1932 "id" => "https://example.org/firefox.png",
1933 "name" => ":firefox:",
1934 "type" => "Emoji",
1935 "updated" => "1970-01-01T00:00:00Z"
1936 }
1937 ]
1938 end
1939 end