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