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