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