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