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