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