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