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