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