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