[#878] Uncommented test statement.
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Object
9 alias Pleroma.Object.Fetcher
10 alias Pleroma.Repo
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.CommonAPI
15 alias Pleroma.Web.OStatus
16 alias Pleroma.Web.Websub.WebsubClientSubscription
17
18 import Mock
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
21
22 setup_all do
23 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
24 :ok
25 end
26
27 describe "handle_incoming" do
28 test "it ignores an incoming notice if we already have it" do
29 activity = insert(:note_activity)
30
31 data =
32 File.read!("test/fixtures/mastodon-post-activity.json")
33 |> Poison.decode!()
34 |> Map.put("object", Object.normalize(activity).data)
35
36 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
37
38 assert activity == returned_activity
39 end
40
41 test "it fetches replied-to activities if we don't have them" do
42 data =
43 File.read!("test/fixtures/mastodon-post-activity.json")
44 |> Poison.decode!()
45
46 object =
47 data["object"]
48 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
49
50 data = Map.put(data, "object", object)
51 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
52 returned_object = Object.normalize(returned_activity, false)
53
54 assert activity =
55 Activity.get_create_by_object_ap_id(
56 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
57 )
58
59 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
60 end
61
62 test "it does not fetch replied-to activities beyond max_replies_depth" do
63 data =
64 File.read!("test/fixtures/mastodon-post-activity.json")
65 |> Poison.decode!()
66
67 object =
68 data["object"]
69 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
70
71 data = Map.put(data, "object", object)
72
73 with_mock Pleroma.Web.Federator,
74 allowed_incoming_reply_depth?: fn _ -> false end do
75 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
76
77 returned_object = Object.normalize(returned_activity, false)
78
79 refute Activity.get_create_by_object_ap_id(
80 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
81 )
82
83 assert returned_object.data["inReplyToAtomUri"] ==
84 "https://shitposter.club/notice/2827873"
85 end
86 end
87
88 test "it does not crash if the object in inReplyTo can't be fetched" do
89 data =
90 File.read!("test/fixtures/mastodon-post-activity.json")
91 |> Poison.decode!()
92
93 object =
94 data["object"]
95 |> Map.put("inReplyTo", "https://404.site/whatever")
96
97 data =
98 data
99 |> Map.put("object", object)
100
101 assert capture_log(fn ->
102 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
103 end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil"
104 end
105
106 test "it works for incoming notices" do
107 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
108
109 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
110
111 assert data["id"] ==
112 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
113
114 assert data["context"] ==
115 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
116
117 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
118
119 assert data["cc"] == [
120 "http://mastodon.example.org/users/admin/followers",
121 "http://localtesting.pleroma.lol/users/lain"
122 ]
123
124 assert data["actor"] == "http://mastodon.example.org/users/admin"
125
126 object_data = Object.normalize(data["object"]).data
127
128 assert object_data["id"] ==
129 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
130
131 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
132
133 assert object_data["cc"] == [
134 "http://mastodon.example.org/users/admin/followers",
135 "http://localtesting.pleroma.lol/users/lain"
136 ]
137
138 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
139 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
140
141 assert object_data["context"] ==
142 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
143
144 assert object_data["sensitive"] == true
145
146 user = User.get_cached_by_ap_id(object_data["actor"])
147
148 assert user.info.note_count == 1
149 end
150
151 test "it works for incoming notices with hashtags" do
152 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
153
154 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
155 object = Object.normalize(data["object"])
156
157 assert Enum.at(object.data["tag"], 2) == "moo"
158 end
159
160 test "it works for incoming questions" do
161 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
162
163 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
164
165 object = Object.normalize(activity)
166
167 assert Enum.all?(object.data["oneOf"], fn choice ->
168 choice["name"] in [
169 "Dunno",
170 "Everyone knows that!",
171 "25 char limit is dumb",
172 "I can't even fit a funny"
173 ]
174 end)
175 end
176
177 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
178 user = insert(:user)
179
180 {:ok, activity} =
181 CommonAPI.post(user, %{
182 "status" => "suya...",
183 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
184 })
185
186 object = Object.normalize(activity)
187
188 data =
189 File.read!("test/fixtures/mastodon-vote.json")
190 |> Poison.decode!()
191 |> Kernel.put_in(["to"], user.ap_id)
192 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
193 |> Kernel.put_in(["object", "to"], user.ap_id)
194
195 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
196 answer_object = Object.normalize(activity)
197 assert answer_object.data["type"] == "Answer"
198 object = Object.get_by_ap_id(object.data["id"])
199
200 assert Enum.any?(
201 object.data["oneOf"],
202 fn
203 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
204 _ -> false
205 end
206 )
207 end
208
209 test "it works for incoming notices with contentMap" do
210 data =
211 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
212
213 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
214 object = Object.normalize(data["object"])
215
216 assert object.data["content"] ==
217 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
218 end
219
220 test "it works for incoming notices with to/cc not being an array (kroeg)" do
221 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
222
223 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
224 object = Object.normalize(data["object"])
225
226 assert object.data["content"] ==
227 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
228 end
229
230 test "it works for incoming announces with actor being inlined (kroeg)" do
231 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
232
233 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
234
235 assert data["actor"] == "https://puckipedia.com/"
236 end
237
238 test "it works for incoming notices with tag not being an array (kroeg)" do
239 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
240
241 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
242 object = Object.normalize(data["object"])
243
244 assert object.data["emoji"] == %{
245 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
246 }
247
248 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
249
250 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
251 object = Object.normalize(data["object"])
252
253 assert "test" in object.data["tag"]
254 end
255
256 test "it works for incoming notices with url not being a string (prismo)" do
257 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
258
259 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
260 object = Object.normalize(data["object"])
261
262 assert object.data["url"] == "https://prismo.news/posts/83"
263 end
264
265 test "it cleans up incoming notices which are not really DMs" do
266 user = insert(:user)
267 other_user = insert(:user)
268
269 to = [user.ap_id, other_user.ap_id]
270
271 data =
272 File.read!("test/fixtures/mastodon-post-activity.json")
273 |> Poison.decode!()
274 |> Map.put("to", to)
275 |> Map.put("cc", [])
276
277 object =
278 data["object"]
279 |> Map.put("to", to)
280 |> Map.put("cc", [])
281
282 data = Map.put(data, "object", object)
283
284 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
285
286 assert data["to"] == []
287 assert data["cc"] == to
288
289 object_data = Object.normalize(activity).data
290
291 assert object_data["to"] == []
292 assert object_data["cc"] == to
293 end
294
295 test "it works for incoming likes" do
296 user = insert(:user)
297 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
298
299 data =
300 File.read!("test/fixtures/mastodon-like.json")
301 |> Poison.decode!()
302 |> Map.put("object", activity.data["object"])
303
304 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
305
306 assert data["actor"] == "http://mastodon.example.org/users/admin"
307 assert data["type"] == "Like"
308 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
309 assert data["object"] == activity.data["object"]
310 end
311
312 test "it returns an error for incoming unlikes wihout a like activity" do
313 user = insert(:user)
314 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
315
316 data =
317 File.read!("test/fixtures/mastodon-undo-like.json")
318 |> Poison.decode!()
319 |> Map.put("object", activity.data["object"])
320
321 assert Transmogrifier.handle_incoming(data) == :error
322 end
323
324 test "it works for incoming unlikes with an existing like activity" do
325 user = insert(:user)
326 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
327
328 like_data =
329 File.read!("test/fixtures/mastodon-like.json")
330 |> Poison.decode!()
331 |> Map.put("object", activity.data["object"])
332
333 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
334
335 data =
336 File.read!("test/fixtures/mastodon-undo-like.json")
337 |> Poison.decode!()
338 |> Map.put("object", like_data)
339 |> Map.put("actor", like_data["actor"])
340
341 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
342
343 assert data["actor"] == "http://mastodon.example.org/users/admin"
344 assert data["type"] == "Undo"
345 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
346 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
347 end
348
349 test "it works for incoming announces" do
350 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
351
352 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
353
354 assert data["actor"] == "http://mastodon.example.org/users/admin"
355 assert data["type"] == "Announce"
356
357 assert data["id"] ==
358 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
359
360 assert data["object"] ==
361 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
362
363 assert Activity.get_create_by_object_ap_id(data["object"])
364 end
365
366 test "it works for incoming announces with an existing activity" do
367 user = insert(:user)
368 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
369
370 data =
371 File.read!("test/fixtures/mastodon-announce.json")
372 |> Poison.decode!()
373 |> Map.put("object", activity.data["object"])
374
375 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
376
377 assert data["actor"] == "http://mastodon.example.org/users/admin"
378 assert data["type"] == "Announce"
379
380 assert data["id"] ==
381 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
382
383 assert data["object"] == activity.data["object"]
384
385 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
386 end
387
388 test "it does not clobber the addressing on announce activities" do
389 user = insert(:user)
390 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
391
392 data =
393 File.read!("test/fixtures/mastodon-announce.json")
394 |> Poison.decode!()
395 |> Map.put("object", Object.normalize(activity).data["id"])
396 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
397 |> Map.put("cc", [])
398
399 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
400
401 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
402 end
403
404 test "it ensures that as:Public activities make it to their followers collection" do
405 user = insert(:user)
406
407 data =
408 File.read!("test/fixtures/mastodon-post-activity.json")
409 |> Poison.decode!()
410 |> Map.put("actor", user.ap_id)
411 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
412 |> Map.put("cc", [])
413
414 object =
415 data["object"]
416 |> Map.put("attributedTo", user.ap_id)
417 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
418 |> Map.put("cc", [])
419
420 data = Map.put(data, "object", object)
421
422 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
423
424 assert data["cc"] == [User.ap_followers(user)]
425 end
426
427 test "it ensures that address fields become lists" do
428 user = insert(:user)
429
430 data =
431 File.read!("test/fixtures/mastodon-post-activity.json")
432 |> Poison.decode!()
433 |> Map.put("actor", user.ap_id)
434 |> Map.put("to", nil)
435 |> Map.put("cc", nil)
436
437 object =
438 data["object"]
439 |> Map.put("attributedTo", user.ap_id)
440 |> Map.put("to", nil)
441 |> Map.put("cc", nil)
442
443 data = Map.put(data, "object", object)
444
445 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
446
447 assert !is_nil(data["to"])
448 assert !is_nil(data["cc"])
449 end
450
451 test "it works for incoming update activities" do
452 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
453
454 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
455 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
456
457 object =
458 update_data["object"]
459 |> Map.put("actor", data["actor"])
460 |> Map.put("id", data["actor"])
461
462 update_data =
463 update_data
464 |> Map.put("actor", data["actor"])
465 |> Map.put("object", object)
466
467 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
468
469 user = User.get_cached_by_ap_id(data["actor"])
470 assert user.name == "gargle"
471
472 assert user.avatar["url"] == [
473 %{
474 "href" =>
475 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
476 }
477 ]
478
479 assert user.info.banner["url"] == [
480 %{
481 "href" =>
482 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
483 }
484 ]
485
486 assert user.bio == "<p>Some bio</p>"
487 end
488
489 test "it works for incoming update activities which lock the account" do
490 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
491
492 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
493 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
494
495 object =
496 update_data["object"]
497 |> Map.put("actor", data["actor"])
498 |> Map.put("id", data["actor"])
499 |> Map.put("manuallyApprovesFollowers", true)
500
501 update_data =
502 update_data
503 |> Map.put("actor", data["actor"])
504 |> Map.put("object", object)
505
506 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
507
508 user = User.get_cached_by_ap_id(data["actor"])
509 assert user.info.locked == true
510 end
511
512 test "it works for incoming deletes" do
513 activity = insert(:note_activity)
514
515 data =
516 File.read!("test/fixtures/mastodon-delete.json")
517 |> Poison.decode!()
518
519 object =
520 data["object"]
521 |> Map.put("id", activity.data["object"])
522
523 data =
524 data
525 |> Map.put("object", object)
526 |> Map.put("actor", activity.data["actor"])
527
528 {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
529
530 refute Activity.get_by_id(activity.id)
531 end
532
533 test "it fails for incoming deletes with spoofed origin" do
534 activity = insert(:note_activity)
535
536 data =
537 File.read!("test/fixtures/mastodon-delete.json")
538 |> Poison.decode!()
539
540 object =
541 data["object"]
542 |> Map.put("id", activity.data["object"])
543
544 data =
545 data
546 |> Map.put("object", object)
547
548 assert capture_log(fn ->
549 :error = Transmogrifier.handle_incoming(data)
550 end) =~
551 "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
552
553 assert Activity.get_by_id(activity.id)
554 end
555
556 test "it works for incoming unannounces with an existing notice" do
557 user = insert(:user)
558 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
559
560 announce_data =
561 File.read!("test/fixtures/mastodon-announce.json")
562 |> Poison.decode!()
563 |> Map.put("object", activity.data["object"])
564
565 {:ok, %Activity{data: announce_data, local: false}} =
566 Transmogrifier.handle_incoming(announce_data)
567
568 data =
569 File.read!("test/fixtures/mastodon-undo-announce.json")
570 |> Poison.decode!()
571 |> Map.put("object", announce_data)
572 |> Map.put("actor", announce_data["actor"])
573
574 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
575
576 assert data["type"] == "Undo"
577 assert object_data = data["object"]
578 assert object_data["type"] == "Announce"
579 assert object_data["object"] == activity.data["object"]
580
581 assert object_data["id"] ==
582 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
583 end
584
585 test "it works for incomming unfollows with an existing follow" do
586 user = insert(:user)
587
588 follow_data =
589 File.read!("test/fixtures/mastodon-follow-activity.json")
590 |> Poison.decode!()
591 |> Map.put("object", user.ap_id)
592
593 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
594
595 data =
596 File.read!("test/fixtures/mastodon-unfollow-activity.json")
597 |> Poison.decode!()
598 |> Map.put("object", follow_data)
599
600 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
601
602 assert data["type"] == "Undo"
603 assert data["object"]["type"] == "Follow"
604 assert data["object"]["object"] == user.ap_id
605 assert data["actor"] == "http://mastodon.example.org/users/admin"
606
607 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
608 end
609
610 test "it works for incoming blocks" do
611 user = insert(:user)
612
613 data =
614 File.read!("test/fixtures/mastodon-block-activity.json")
615 |> Poison.decode!()
616 |> Map.put("object", user.ap_id)
617
618 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
619
620 assert data["type"] == "Block"
621 assert data["object"] == user.ap_id
622 assert data["actor"] == "http://mastodon.example.org/users/admin"
623
624 blocker = User.get_cached_by_ap_id(data["actor"])
625
626 assert User.blocks?(blocker, user)
627 end
628
629 test "incoming blocks successfully tear down any follow relationship" do
630 blocker = insert(:user)
631 blocked = insert(:user)
632
633 data =
634 File.read!("test/fixtures/mastodon-block-activity.json")
635 |> Poison.decode!()
636 |> Map.put("object", blocked.ap_id)
637 |> Map.put("actor", blocker.ap_id)
638
639 {:ok, blocker} = User.follow(blocker, blocked)
640 {:ok, blocked} = User.follow(blocked, blocker)
641
642 assert User.following?(blocker, blocked)
643 assert User.following?(blocked, blocker)
644
645 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
646
647 assert data["type"] == "Block"
648 assert data["object"] == blocked.ap_id
649 assert data["actor"] == blocker.ap_id
650
651 blocker = User.get_cached_by_ap_id(data["actor"])
652 blocked = User.get_cached_by_ap_id(data["object"])
653
654 assert User.blocks?(blocker, blocked)
655
656 refute User.following?(blocker, blocked)
657 refute User.following?(blocked, blocker)
658 end
659
660 test "it works for incoming unblocks with an existing block" do
661 user = insert(:user)
662
663 block_data =
664 File.read!("test/fixtures/mastodon-block-activity.json")
665 |> Poison.decode!()
666 |> Map.put("object", user.ap_id)
667
668 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
669
670 data =
671 File.read!("test/fixtures/mastodon-unblock-activity.json")
672 |> Poison.decode!()
673 |> Map.put("object", block_data)
674
675 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
676 assert data["type"] == "Undo"
677 assert data["object"]["type"] == "Block"
678 assert data["object"]["object"] == user.ap_id
679 assert data["actor"] == "http://mastodon.example.org/users/admin"
680
681 blocker = User.get_cached_by_ap_id(data["actor"])
682
683 refute User.blocks?(blocker, user)
684 end
685
686 test "it works for incoming accepts which were pre-accepted" do
687 follower = insert(:user)
688 followed = insert(:user)
689
690 {:ok, follower} = User.follow(follower, followed)
691 assert User.following?(follower, followed) == true
692
693 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
694
695 accept_data =
696 File.read!("test/fixtures/mastodon-accept-activity.json")
697 |> Poison.decode!()
698 |> Map.put("actor", followed.ap_id)
699
700 object =
701 accept_data["object"]
702 |> Map.put("actor", follower.ap_id)
703 |> Map.put("id", follow_activity.data["id"])
704
705 accept_data = Map.put(accept_data, "object", object)
706
707 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
708 refute activity.local
709
710 assert activity.data["object"] == follow_activity.data["id"]
711
712 follower = User.get_cached_by_id(follower.id)
713
714 assert User.following?(follower, followed) == true
715 end
716
717 test "it works for incoming accepts which were orphaned" do
718 follower = insert(:user)
719 followed = insert(:user, %{info: %User.Info{locked: true}})
720
721 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
722
723 accept_data =
724 File.read!("test/fixtures/mastodon-accept-activity.json")
725 |> Poison.decode!()
726 |> Map.put("actor", followed.ap_id)
727
728 accept_data =
729 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
730
731 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
732 assert activity.data["object"] == follow_activity.data["id"]
733
734 follower = User.get_cached_by_id(follower.id)
735
736 assert User.following?(follower, followed) == true
737 end
738
739 test "it works for incoming accepts which are referenced by IRI only" do
740 follower = insert(:user)
741 followed = insert(:user, %{info: %User.Info{locked: true}})
742
743 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
744
745 accept_data =
746 File.read!("test/fixtures/mastodon-accept-activity.json")
747 |> Poison.decode!()
748 |> Map.put("actor", followed.ap_id)
749 |> Map.put("object", follow_activity.data["id"])
750
751 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
752 assert activity.data["object"] == follow_activity.data["id"]
753
754 follower = User.get_cached_by_id(follower.id)
755
756 assert User.following?(follower, followed) == true
757 end
758
759 test "it fails for incoming accepts which cannot be correlated" do
760 follower = insert(:user)
761 followed = insert(:user, %{info: %User.Info{locked: true}})
762
763 accept_data =
764 File.read!("test/fixtures/mastodon-accept-activity.json")
765 |> Poison.decode!()
766 |> Map.put("actor", followed.ap_id)
767
768 accept_data =
769 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
770
771 :error = Transmogrifier.handle_incoming(accept_data)
772
773 follower = User.get_cached_by_id(follower.id)
774
775 refute User.following?(follower, followed) == true
776 end
777
778 test "it fails for incoming rejects which cannot be correlated" do
779 follower = insert(:user)
780 followed = insert(:user, %{info: %User.Info{locked: true}})
781
782 accept_data =
783 File.read!("test/fixtures/mastodon-reject-activity.json")
784 |> Poison.decode!()
785 |> Map.put("actor", followed.ap_id)
786
787 accept_data =
788 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
789
790 :error = Transmogrifier.handle_incoming(accept_data)
791
792 follower = User.get_cached_by_id(follower.id)
793
794 refute User.following?(follower, followed) == true
795 end
796
797 test "it works for incoming rejects which are orphaned" do
798 follower = insert(:user)
799 followed = insert(:user, %{info: %User.Info{locked: true}})
800
801 {:ok, follower} = User.follow(follower, followed)
802 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
803
804 assert User.following?(follower, followed) == true
805
806 reject_data =
807 File.read!("test/fixtures/mastodon-reject-activity.json")
808 |> Poison.decode!()
809 |> Map.put("actor", followed.ap_id)
810
811 reject_data =
812 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
813
814 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
815 refute activity.local
816
817 follower = User.get_cached_by_id(follower.id)
818
819 assert User.following?(follower, followed) == false
820 end
821
822 test "it works for incoming rejects which are referenced by IRI only" do
823 follower = insert(:user)
824 followed = insert(:user, %{info: %User.Info{locked: true}})
825
826 {:ok, follower} = User.follow(follower, followed)
827 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
828
829 assert User.following?(follower, followed) == true
830
831 reject_data =
832 File.read!("test/fixtures/mastodon-reject-activity.json")
833 |> Poison.decode!()
834 |> Map.put("actor", followed.ap_id)
835 |> Map.put("object", follow_activity.data["id"])
836
837 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
838
839 follower = User.get_cached_by_id(follower.id)
840
841 assert User.following?(follower, followed) == false
842 end
843
844 test "it rejects activities without a valid ID" do
845 user = insert(:user)
846
847 data =
848 File.read!("test/fixtures/mastodon-follow-activity.json")
849 |> Poison.decode!()
850 |> Map.put("object", user.ap_id)
851 |> Map.put("id", "")
852
853 :error = Transmogrifier.handle_incoming(data)
854 end
855
856 test "it remaps video URLs as attachments if necessary" do
857 {:ok, object} =
858 Fetcher.fetch_object_from_id(
859 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
860 )
861
862 attachment = %{
863 "type" => "Link",
864 "mediaType" => "video/mp4",
865 "href" =>
866 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
867 "mimeType" => "video/mp4",
868 "size" => 5_015_880,
869 "url" => [
870 %{
871 "href" =>
872 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
873 "mediaType" => "video/mp4",
874 "type" => "Link"
875 }
876 ],
877 "width" => 480
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 message = %{
894 "@context" => "https://www.w3.org/ns/activitystreams",
895 "cc" => [user.ap_id],
896 "object" => [user.ap_id, object.data["id"]],
897 "type" => "Flag",
898 "content" => "blocked AND reported!!!",
899 "actor" => other_user.ap_id
900 }
901
902 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
903
904 assert activity.data["object"] == [user.ap_id, object.data["id"]]
905 assert activity.data["content"] == "blocked AND reported!!!"
906 assert activity.data["actor"] == other_user.ap_id
907 assert activity.data["cc"] == [user.ap_id]
908 end
909 end
910
911 describe "prepare outgoing" do
912 test "it turns mentions into tags" do
913 user = insert(:user)
914 other_user = insert(:user)
915
916 {:ok, activity} =
917 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
918
919 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
920 object = modified["object"]
921
922 expected_mention = %{
923 "href" => other_user.ap_id,
924 "name" => "@#{other_user.nickname}",
925 "type" => "Mention"
926 }
927
928 expected_tag = %{
929 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
930 "type" => "Hashtag",
931 "name" => "#2hu"
932 }
933
934 assert Enum.member?(object["tag"], expected_tag)
935 assert Enum.member?(object["tag"], expected_mention)
936 end
937
938 test "it adds the sensitive property" do
939 user = insert(:user)
940
941 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
942 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
943
944 assert modified["object"]["sensitive"]
945 end
946
947 test "it adds the json-ld context and the conversation property" do
948 user = insert(:user)
949
950 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
951 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
952
953 assert modified["@context"] ==
954 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
955
956 assert modified["object"]["conversation"] == modified["context"]
957 end
958
959 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
960 user = insert(:user)
961
962 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
963 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
964
965 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
966 end
967
968 test "it translates ostatus IDs to external URLs" do
969 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
970 {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
971
972 user = insert(:user)
973
974 {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
975 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
976
977 assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
978 end
979
980 test "it translates ostatus reply_to IDs to external URLs" do
981 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
982 {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
983
984 user = insert(:user)
985
986 {:ok, activity} =
987 CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
988
989 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
990
991 assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
992 end
993
994 test "it strips internal hashtag data" do
995 user = insert(:user)
996
997 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
998
999 expected_tag = %{
1000 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1001 "type" => "Hashtag",
1002 "name" => "#2hu"
1003 }
1004
1005 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1006
1007 assert modified["object"]["tag"] == [expected_tag]
1008 end
1009
1010 test "it strips internal fields" do
1011 user = insert(:user)
1012
1013 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1014
1015 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1016
1017 assert length(modified["object"]["tag"]) == 2
1018
1019 assert is_nil(modified["object"]["emoji"])
1020 assert is_nil(modified["object"]["like_count"])
1021 assert is_nil(modified["object"]["announcements"])
1022 assert is_nil(modified["object"]["announcement_count"])
1023 assert is_nil(modified["object"]["context_id"])
1024 end
1025
1026 test "it strips internal fields of article" do
1027 activity = insert(:article_activity)
1028
1029 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1030
1031 assert length(modified["object"]["tag"]) == 2
1032
1033 assert is_nil(modified["object"]["emoji"])
1034 assert is_nil(modified["object"]["like_count"])
1035 assert is_nil(modified["object"]["announcements"])
1036 assert is_nil(modified["object"]["announcement_count"])
1037 assert is_nil(modified["object"]["context_id"])
1038 end
1039
1040 test "it adds like collection to object" do
1041 activity = insert(:note_activity)
1042 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1043
1044 assert modified["object"]["likes"]["type"] == "OrderedCollection"
1045 assert modified["object"]["likes"]["totalItems"] == 0
1046 end
1047
1048 test "the directMessage flag is present" do
1049 user = insert(:user)
1050 other_user = insert(:user)
1051
1052 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1053
1054 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1055
1056 assert modified["directMessage"] == false
1057
1058 {:ok, activity} =
1059 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1060
1061 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1062
1063 assert modified["directMessage"] == false
1064
1065 {:ok, activity} =
1066 CommonAPI.post(user, %{
1067 "status" => "@#{other_user.nickname} :moominmamma:",
1068 "visibility" => "direct"
1069 })
1070
1071 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1072
1073 assert modified["directMessage"] == true
1074 end
1075 end
1076
1077 describe "user upgrade" do
1078 test "it upgrades a user to activitypub" do
1079 user =
1080 insert(:user, %{
1081 nickname: "rye@niu.moe",
1082 local: false,
1083 ap_id: "https://niu.moe/users/rye",
1084 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1085 })
1086
1087 user_two = insert(:user, %{following: [user.follower_address]})
1088
1089 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1090 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1091 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1092
1093 user = User.get_cached_by_id(user.id)
1094 assert user.info.note_count == 1
1095
1096 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1097 assert user.info.ap_enabled
1098 assert user.info.note_count == 1
1099 assert user.follower_address == "https://niu.moe/users/rye/followers"
1100
1101 user = User.get_cached_by_id(user.id)
1102 assert user.info.note_count == 1
1103
1104 activity = Activity.get_by_id(activity.id)
1105 assert user.follower_address in activity.recipients
1106
1107 assert %{
1108 "url" => [
1109 %{
1110 "href" =>
1111 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1112 }
1113 ]
1114 } = user.avatar
1115
1116 assert %{
1117 "url" => [
1118 %{
1119 "href" =>
1120 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1121 }
1122 ]
1123 } = user.info.banner
1124
1125 refute "..." in activity.recipients
1126
1127 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1128 refute user.follower_address in unrelated_activity.recipients
1129
1130 user_two = User.get_cached_by_id(user_two.id)
1131 assert user.follower_address in user_two.following
1132 refute "..." in user_two.following
1133 end
1134 end
1135
1136 describe "maybe_retire_websub" do
1137 test "it deletes all websub client subscripitions with the user as topic" do
1138 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
1139 {:ok, ws} = Repo.insert(subscription)
1140
1141 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
1142 {:ok, ws2} = Repo.insert(subscription)
1143
1144 Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
1145
1146 refute Repo.get(WebsubClientSubscription, ws.id)
1147 assert Repo.get(WebsubClientSubscription, ws2.id)
1148 end
1149 end
1150
1151 describe "actor rewriting" do
1152 test "it fixes the actor URL property to be a proper URI" do
1153 data = %{
1154 "url" => %{"href" => "http://example.com"}
1155 }
1156
1157 rewritten = Transmogrifier.maybe_fix_user_object(data)
1158 assert rewritten["url"] == "http://example.com"
1159 end
1160 end
1161
1162 describe "actor origin containment" do
1163 test "it rejects activities which reference objects with bogus origins" do
1164 data = %{
1165 "@context" => "https://www.w3.org/ns/activitystreams",
1166 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1167 "actor" => "http://mastodon.example.org/users/admin",
1168 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1169 "object" => "https://info.pleroma.site/activity.json",
1170 "type" => "Announce"
1171 }
1172
1173 :error = Transmogrifier.handle_incoming(data)
1174 end
1175
1176 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1177 data = %{
1178 "@context" => "https://www.w3.org/ns/activitystreams",
1179 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1180 "actor" => "http://mastodon.example.org/users/admin",
1181 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1182 "object" => "https://info.pleroma.site/activity2.json",
1183 "type" => "Announce"
1184 }
1185
1186 :error = Transmogrifier.handle_incoming(data)
1187 end
1188
1189 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1190 data = %{
1191 "@context" => "https://www.w3.org/ns/activitystreams",
1192 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1193 "actor" => "http://mastodon.example.org/users/admin",
1194 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1195 "object" => "https://info.pleroma.site/activity3.json",
1196 "type" => "Announce"
1197 }
1198
1199 :error = Transmogrifier.handle_incoming(data)
1200 end
1201 end
1202
1203 describe "reserialization" do
1204 test "successfully reserializes a message with inReplyTo == nil" do
1205 user = insert(:user)
1206
1207 message = %{
1208 "@context" => "https://www.w3.org/ns/activitystreams",
1209 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1210 "cc" => [],
1211 "type" => "Create",
1212 "object" => %{
1213 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1214 "cc" => [],
1215 "type" => "Note",
1216 "content" => "Hi",
1217 "inReplyTo" => nil,
1218 "attributedTo" => user.ap_id
1219 },
1220 "actor" => user.ap_id
1221 }
1222
1223 {:ok, activity} = Transmogrifier.handle_incoming(message)
1224
1225 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1226 end
1227
1228 test "successfully reserializes a message with AS2 objects in IR" do
1229 user = insert(:user)
1230
1231 message = %{
1232 "@context" => "https://www.w3.org/ns/activitystreams",
1233 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1234 "cc" => [],
1235 "type" => "Create",
1236 "object" => %{
1237 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1238 "cc" => [],
1239 "type" => "Note",
1240 "content" => "Hi",
1241 "inReplyTo" => nil,
1242 "attributedTo" => user.ap_id,
1243 "tag" => [
1244 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1245 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1246 ]
1247 },
1248 "actor" => user.ap_id
1249 }
1250
1251 {:ok, activity} = Transmogrifier.handle_incoming(message)
1252
1253 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1254 end
1255 end
1256
1257 test "Rewrites Answers to Notes" do
1258 user = insert(:user)
1259
1260 {:ok, poll_activity} =
1261 CommonAPI.post(user, %{
1262 "status" => "suya...",
1263 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1264 })
1265
1266 poll_object = Object.normalize(poll_activity)
1267 # TODO: Replace with CommonAPI vote creation when implemented
1268 data =
1269 File.read!("test/fixtures/mastodon-vote.json")
1270 |> Poison.decode!()
1271 |> Kernel.put_in(["to"], user.ap_id)
1272 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1273 |> Kernel.put_in(["object", "to"], user.ap_id)
1274
1275 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1276 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1277
1278 assert data["object"]["type"] == "Note"
1279 end
1280
1281 describe "fix_explicit_addressing" do
1282 setup do
1283 user = insert(:user)
1284 [user: user]
1285 end
1286
1287 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1288 explicitly_mentioned_actors = [
1289 "https://pleroma.gold/users/user1",
1290 "https://pleroma.gold/user2"
1291 ]
1292
1293 object = %{
1294 "actor" => user.ap_id,
1295 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1296 "cc" => [],
1297 "tag" =>
1298 Enum.map(explicitly_mentioned_actors, fn href ->
1299 %{"type" => "Mention", "href" => href}
1300 end)
1301 }
1302
1303 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1304 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1305 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1306 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1307 end
1308
1309 test "does not move actor's follower collection to cc", %{user: user} do
1310 object = %{
1311 "actor" => user.ap_id,
1312 "to" => [user.follower_address],
1313 "cc" => []
1314 }
1315
1316 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1317 assert user.follower_address in fixed_object["to"]
1318 refute user.follower_address in fixed_object["cc"]
1319 end
1320
1321 test "removes recipient's follower collection from cc", %{user: user} do
1322 recipient = insert(:user)
1323
1324 object = %{
1325 "actor" => user.ap_id,
1326 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1327 "cc" => [user.follower_address, recipient.follower_address]
1328 }
1329
1330 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1331
1332 assert user.follower_address in fixed_object["cc"]
1333 refute recipient.follower_address in fixed_object["cc"]
1334 refute recipient.follower_address in fixed_object["to"]
1335 end
1336 end
1337 end