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