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