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