Merge branch 'feature/federated-flags' into 'develop'
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Object
9 alias Pleroma.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 works for incoming update activities" do
339 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
340
341 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
342 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
343
344 object =
345 update_data["object"]
346 |> Map.put("actor", data["actor"])
347 |> Map.put("id", data["actor"])
348
349 update_data =
350 update_data
351 |> Map.put("actor", data["actor"])
352 |> Map.put("object", object)
353
354 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
355
356 user = User.get_cached_by_ap_id(data["actor"])
357 assert user.name == "gargle"
358
359 assert user.avatar["url"] == [
360 %{
361 "href" =>
362 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
363 }
364 ]
365
366 assert user.info.banner["url"] == [
367 %{
368 "href" =>
369 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
370 }
371 ]
372
373 assert user.bio == "<p>Some bio</p>"
374 end
375
376 test "it works for incoming update activities which lock the account" do
377 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
378
379 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
380 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
381
382 object =
383 update_data["object"]
384 |> Map.put("actor", data["actor"])
385 |> Map.put("id", data["actor"])
386 |> Map.put("manuallyApprovesFollowers", true)
387
388 update_data =
389 update_data
390 |> Map.put("actor", data["actor"])
391 |> Map.put("object", object)
392
393 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
394
395 user = User.get_cached_by_ap_id(data["actor"])
396 assert user.info.locked == true
397 end
398
399 test "it works for incoming deletes" do
400 activity = insert(:note_activity)
401
402 data =
403 File.read!("test/fixtures/mastodon-delete.json")
404 |> Poison.decode!()
405
406 object =
407 data["object"]
408 |> Map.put("id", activity.data["object"]["id"])
409
410 data =
411 data
412 |> Map.put("object", object)
413 |> Map.put("actor", activity.data["actor"])
414
415 {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
416
417 refute Repo.get(Activity, activity.id)
418 end
419
420 test "it fails for incoming deletes with spoofed origin" do
421 activity = insert(:note_activity)
422
423 data =
424 File.read!("test/fixtures/mastodon-delete.json")
425 |> Poison.decode!()
426
427 object =
428 data["object"]
429 |> Map.put("id", activity.data["object"]["id"])
430
431 data =
432 data
433 |> Map.put("object", object)
434
435 :error = Transmogrifier.handle_incoming(data)
436
437 assert Repo.get(Activity, activity.id)
438 end
439
440 test "it works for incoming unannounces with an existing notice" do
441 user = insert(:user)
442 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
443
444 announce_data =
445 File.read!("test/fixtures/mastodon-announce.json")
446 |> Poison.decode!()
447 |> Map.put("object", activity.data["object"]["id"])
448
449 {:ok, %Activity{data: announce_data, local: false}} =
450 Transmogrifier.handle_incoming(announce_data)
451
452 data =
453 File.read!("test/fixtures/mastodon-undo-announce.json")
454 |> Poison.decode!()
455 |> Map.put("object", announce_data)
456 |> Map.put("actor", announce_data["actor"])
457
458 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
459
460 assert data["type"] == "Undo"
461 assert data["object"]["type"] == "Announce"
462 assert data["object"]["object"] == activity.data["object"]["id"]
463
464 assert data["object"]["id"] ==
465 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
466 end
467
468 test "it works for incomming unfollows with an existing follow" do
469 user = insert(:user)
470
471 follow_data =
472 File.read!("test/fixtures/mastodon-follow-activity.json")
473 |> Poison.decode!()
474 |> Map.put("object", user.ap_id)
475
476 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
477
478 data =
479 File.read!("test/fixtures/mastodon-unfollow-activity.json")
480 |> Poison.decode!()
481 |> Map.put("object", follow_data)
482
483 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
484
485 assert data["type"] == "Undo"
486 assert data["object"]["type"] == "Follow"
487 assert data["object"]["object"] == user.ap_id
488 assert data["actor"] == "http://mastodon.example.org/users/admin"
489
490 refute User.following?(User.get_by_ap_id(data["actor"]), user)
491 end
492
493 test "it works for incoming blocks" do
494 user = insert(:user)
495
496 data =
497 File.read!("test/fixtures/mastodon-block-activity.json")
498 |> Poison.decode!()
499 |> Map.put("object", user.ap_id)
500
501 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
502
503 assert data["type"] == "Block"
504 assert data["object"] == user.ap_id
505 assert data["actor"] == "http://mastodon.example.org/users/admin"
506
507 blocker = User.get_by_ap_id(data["actor"])
508
509 assert User.blocks?(blocker, user)
510 end
511
512 test "incoming blocks successfully tear down any follow relationship" do
513 blocker = insert(:user)
514 blocked = insert(:user)
515
516 data =
517 File.read!("test/fixtures/mastodon-block-activity.json")
518 |> Poison.decode!()
519 |> Map.put("object", blocked.ap_id)
520 |> Map.put("actor", blocker.ap_id)
521
522 {:ok, blocker} = User.follow(blocker, blocked)
523 {:ok, blocked} = User.follow(blocked, blocker)
524
525 assert User.following?(blocker, blocked)
526 assert User.following?(blocked, blocker)
527
528 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
529
530 assert data["type"] == "Block"
531 assert data["object"] == blocked.ap_id
532 assert data["actor"] == blocker.ap_id
533
534 blocker = User.get_by_ap_id(data["actor"])
535 blocked = User.get_by_ap_id(data["object"])
536
537 assert User.blocks?(blocker, blocked)
538
539 refute User.following?(blocker, blocked)
540 refute User.following?(blocked, blocker)
541 end
542
543 test "it works for incoming unblocks with an existing block" do
544 user = insert(:user)
545
546 block_data =
547 File.read!("test/fixtures/mastodon-block-activity.json")
548 |> Poison.decode!()
549 |> Map.put("object", user.ap_id)
550
551 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
552
553 data =
554 File.read!("test/fixtures/mastodon-unblock-activity.json")
555 |> Poison.decode!()
556 |> Map.put("object", block_data)
557
558 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
559 assert data["type"] == "Undo"
560 assert data["object"]["type"] == "Block"
561 assert data["object"]["object"] == user.ap_id
562 assert data["actor"] == "http://mastodon.example.org/users/admin"
563
564 blocker = User.get_by_ap_id(data["actor"])
565
566 refute User.blocks?(blocker, user)
567 end
568
569 test "it works for incoming accepts which were pre-accepted" do
570 follower = insert(:user)
571 followed = insert(:user)
572
573 {:ok, follower} = User.follow(follower, followed)
574 assert User.following?(follower, followed) == true
575
576 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
577
578 accept_data =
579 File.read!("test/fixtures/mastodon-accept-activity.json")
580 |> Poison.decode!()
581 |> Map.put("actor", followed.ap_id)
582
583 object =
584 accept_data["object"]
585 |> Map.put("actor", follower.ap_id)
586 |> Map.put("id", follow_activity.data["id"])
587
588 accept_data = Map.put(accept_data, "object", object)
589
590 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
591 refute activity.local
592
593 assert activity.data["object"] == follow_activity.data["id"]
594
595 follower = Repo.get(User, follower.id)
596
597 assert User.following?(follower, followed) == true
598 end
599
600 test "it works for incoming accepts which were orphaned" do
601 follower = insert(:user)
602 followed = insert(:user, %{info: %User.Info{locked: true}})
603
604 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
605
606 accept_data =
607 File.read!("test/fixtures/mastodon-accept-activity.json")
608 |> Poison.decode!()
609 |> Map.put("actor", followed.ap_id)
610
611 accept_data =
612 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
613
614 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
615 assert activity.data["object"] == follow_activity.data["id"]
616
617 follower = Repo.get(User, follower.id)
618
619 assert User.following?(follower, followed) == true
620 end
621
622 test "it works for incoming accepts which are referenced by IRI only" do
623 follower = insert(:user)
624 followed = insert(:user, %{info: %User.Info{locked: true}})
625
626 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
627
628 accept_data =
629 File.read!("test/fixtures/mastodon-accept-activity.json")
630 |> Poison.decode!()
631 |> Map.put("actor", followed.ap_id)
632 |> Map.put("object", follow_activity.data["id"])
633
634 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
635 assert activity.data["object"] == follow_activity.data["id"]
636
637 follower = Repo.get(User, follower.id)
638
639 assert User.following?(follower, followed) == true
640 end
641
642 test "it fails for incoming accepts which cannot be correlated" do
643 follower = insert(:user)
644 followed = insert(:user, %{info: %User.Info{locked: true}})
645
646 accept_data =
647 File.read!("test/fixtures/mastodon-accept-activity.json")
648 |> Poison.decode!()
649 |> Map.put("actor", followed.ap_id)
650
651 accept_data =
652 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
653
654 :error = Transmogrifier.handle_incoming(accept_data)
655
656 follower = Repo.get(User, follower.id)
657
658 refute User.following?(follower, followed) == true
659 end
660
661 test "it fails for incoming rejects which cannot be correlated" do
662 follower = insert(:user)
663 followed = insert(:user, %{info: %User.Info{locked: true}})
664
665 accept_data =
666 File.read!("test/fixtures/mastodon-reject-activity.json")
667 |> Poison.decode!()
668 |> Map.put("actor", followed.ap_id)
669
670 accept_data =
671 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
672
673 :error = Transmogrifier.handle_incoming(accept_data)
674
675 follower = Repo.get(User, follower.id)
676
677 refute User.following?(follower, followed) == true
678 end
679
680 test "it works for incoming rejects which are orphaned" do
681 follower = insert(:user)
682 followed = insert(:user, %{info: %User.Info{locked: true}})
683
684 {:ok, follower} = User.follow(follower, followed)
685 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
686
687 assert User.following?(follower, followed) == true
688
689 reject_data =
690 File.read!("test/fixtures/mastodon-reject-activity.json")
691 |> Poison.decode!()
692 |> Map.put("actor", followed.ap_id)
693
694 reject_data =
695 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
696
697 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
698 refute activity.local
699
700 follower = Repo.get(User, follower.id)
701
702 assert User.following?(follower, followed) == false
703 end
704
705 test "it works for incoming rejects which are referenced by IRI only" do
706 follower = insert(:user)
707 followed = insert(:user, %{info: %User.Info{locked: true}})
708
709 {:ok, follower} = User.follow(follower, followed)
710 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
711
712 assert User.following?(follower, followed) == true
713
714 reject_data =
715 File.read!("test/fixtures/mastodon-reject-activity.json")
716 |> Poison.decode!()
717 |> Map.put("actor", followed.ap_id)
718 |> Map.put("object", follow_activity.data["id"])
719
720 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
721
722 follower = Repo.get(User, follower.id)
723
724 assert User.following?(follower, followed) == false
725 end
726
727 test "it rejects activities without a valid ID" do
728 user = insert(:user)
729
730 data =
731 File.read!("test/fixtures/mastodon-follow-activity.json")
732 |> Poison.decode!()
733 |> Map.put("object", user.ap_id)
734 |> Map.put("id", "")
735
736 :error = Transmogrifier.handle_incoming(data)
737 end
738
739 test "it remaps video URLs as attachments if necessary" do
740 {:ok, object} =
741 ActivityPub.fetch_object_from_id(
742 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
743 )
744
745 attachment = %{
746 "type" => "Link",
747 "mediaType" => "video/mp4",
748 "href" =>
749 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
750 "mimeType" => "video/mp4",
751 "size" => 5_015_880,
752 "url" => [
753 %{
754 "href" =>
755 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
756 "mediaType" => "video/mp4",
757 "type" => "Link"
758 }
759 ],
760 "width" => 480
761 }
762
763 assert object.data["url"] ==
764 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
765
766 assert object.data["attachment"] == [attachment]
767 end
768
769 test "it accepts Flag activities" do
770 user = insert(:user)
771 other_user = insert(:user)
772
773 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
774 object = Object.normalize(activity.data["object"])
775
776 message = %{
777 "@context" => "https://www.w3.org/ns/activitystreams",
778 "cc" => [user.ap_id],
779 "object" => [user.ap_id, object.data["id"]],
780 "type" => "Flag",
781 "content" => "blocked AND reported!!!",
782 "actor" => other_user.ap_id
783 }
784
785 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
786
787 assert activity.data["object"] == [user.ap_id, object.data["id"]]
788 assert activity.data["content"] == "blocked AND reported!!!"
789 assert activity.data["actor"] == other_user.ap_id
790 assert activity.data["cc"] == [user.ap_id]
791 end
792 end
793
794 describe "prepare outgoing" do
795 test "it turns mentions into tags" do
796 user = insert(:user)
797 other_user = insert(:user)
798
799 {:ok, activity} =
800 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
801
802 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
803 object = modified["object"]
804
805 expected_mention = %{
806 "href" => other_user.ap_id,
807 "name" => "@#{other_user.nickname}",
808 "type" => "Mention"
809 }
810
811 expected_tag = %{
812 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
813 "type" => "Hashtag",
814 "name" => "#2hu"
815 }
816
817 assert Enum.member?(object["tag"], expected_tag)
818 assert Enum.member?(object["tag"], expected_mention)
819 end
820
821 test "it adds the sensitive property" do
822 user = insert(:user)
823
824 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
825 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
826
827 assert modified["object"]["sensitive"]
828 end
829
830 test "it adds the json-ld context and the conversation property" do
831 user = insert(:user)
832
833 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
834 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
835
836 assert modified["@context"] ==
837 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
838
839 assert modified["object"]["conversation"] == modified["context"]
840 end
841
842 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
843 user = insert(:user)
844
845 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
846 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
847
848 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
849 end
850
851 test "it translates ostatus IDs to external URLs" do
852 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
853 {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
854
855 user = insert(:user)
856
857 {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
858 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
859
860 assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
861 end
862
863 test "it translates ostatus reply_to IDs to external URLs" do
864 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
865 {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
866
867 user = insert(:user)
868
869 {:ok, activity} =
870 CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
871
872 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
873
874 assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
875 end
876
877 test "it strips internal hashtag data" do
878 user = insert(:user)
879
880 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
881
882 expected_tag = %{
883 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
884 "type" => "Hashtag",
885 "name" => "#2hu"
886 }
887
888 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
889
890 assert modified["object"]["tag"] == [expected_tag]
891 end
892
893 test "it strips internal fields" do
894 user = insert(:user)
895
896 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :moominmamma:"})
897
898 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
899
900 assert length(modified["object"]["tag"]) == 2
901
902 assert is_nil(modified["object"]["emoji"])
903 assert is_nil(modified["object"]["like_count"])
904 assert is_nil(modified["object"]["announcements"])
905 assert is_nil(modified["object"]["announcement_count"])
906 assert is_nil(modified["object"]["context_id"])
907 end
908
909 test "it strips internal fields of article" do
910 activity = insert(:article_activity)
911
912 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
913
914 assert length(modified["object"]["tag"]) == 2
915
916 assert is_nil(modified["object"]["emoji"])
917 assert is_nil(modified["object"]["like_count"])
918 assert is_nil(modified["object"]["announcements"])
919 assert is_nil(modified["object"]["announcement_count"])
920 assert is_nil(modified["object"]["context_id"])
921 end
922
923 test "it adds like collection to object" do
924 activity = insert(:note_activity)
925 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
926
927 assert modified["object"]["likes"]["type"] == "OrderedCollection"
928 assert modified["object"]["likes"]["totalItems"] == 0
929 end
930
931 test "the directMessage flag is present" do
932 user = insert(:user)
933 other_user = insert(:user)
934
935 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
936
937 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
938
939 assert modified["directMessage"] == false
940
941 {:ok, activity} =
942 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
943
944 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
945
946 assert modified["directMessage"] == false
947
948 {:ok, activity} =
949 CommonAPI.post(user, %{
950 "status" => "@#{other_user.nickname} :moominmamma:",
951 "visibility" => "direct"
952 })
953
954 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
955
956 assert modified["directMessage"] == true
957 end
958 end
959
960 describe "user upgrade" do
961 test "it upgrades a user to activitypub" do
962 user =
963 insert(:user, %{
964 nickname: "rye@niu.moe",
965 local: false,
966 ap_id: "https://niu.moe/users/rye",
967 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
968 })
969
970 user_two = insert(:user, %{following: [user.follower_address]})
971
972 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
973 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
974 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
975
976 user = Repo.get(User, user.id)
977 assert user.info.note_count == 1
978
979 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
980 assert user.info.ap_enabled
981 assert user.info.note_count == 1
982 assert user.follower_address == "https://niu.moe/users/rye/followers"
983
984 # Wait for the background task
985 :timer.sleep(1000)
986
987 user = Repo.get(User, user.id)
988 assert user.info.note_count == 1
989
990 activity = Repo.get(Activity, activity.id)
991 assert user.follower_address in activity.recipients
992
993 assert %{
994 "url" => [
995 %{
996 "href" =>
997 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
998 }
999 ]
1000 } = user.avatar
1001
1002 assert %{
1003 "url" => [
1004 %{
1005 "href" =>
1006 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1007 }
1008 ]
1009 } = user.info.banner
1010
1011 refute "..." in activity.recipients
1012
1013 unrelated_activity = Repo.get(Activity, unrelated_activity.id)
1014 refute user.follower_address in unrelated_activity.recipients
1015
1016 user_two = Repo.get(User, user_two.id)
1017 assert user.follower_address in user_two.following
1018 refute "..." in user_two.following
1019 end
1020 end
1021
1022 describe "maybe_retire_websub" do
1023 test "it deletes all websub client subscripitions with the user as topic" do
1024 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
1025 {:ok, ws} = Repo.insert(subscription)
1026
1027 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
1028 {:ok, ws2} = Repo.insert(subscription)
1029
1030 Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
1031
1032 refute Repo.get(WebsubClientSubscription, ws.id)
1033 assert Repo.get(WebsubClientSubscription, ws2.id)
1034 end
1035 end
1036
1037 describe "actor rewriting" do
1038 test "it fixes the actor URL property to be a proper URI" do
1039 data = %{
1040 "url" => %{"href" => "http://example.com"}
1041 }
1042
1043 rewritten = Transmogrifier.maybe_fix_user_object(data)
1044 assert rewritten["url"] == "http://example.com"
1045 end
1046 end
1047
1048 describe "actor origin containment" do
1049 test "it rejects objects with a bogus origin" do
1050 {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity.json")
1051 end
1052
1053 test "it rejects activities which reference objects with bogus origins" do
1054 data = %{
1055 "@context" => "https://www.w3.org/ns/activitystreams",
1056 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1057 "actor" => "http://mastodon.example.org/users/admin",
1058 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1059 "object" => "https://info.pleroma.site/activity.json",
1060 "type" => "Announce"
1061 }
1062
1063 :error = Transmogrifier.handle_incoming(data)
1064 end
1065
1066 test "it rejects objects when attributedTo is wrong (variant 1)" do
1067 {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity2.json")
1068 end
1069
1070 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1071 data = %{
1072 "@context" => "https://www.w3.org/ns/activitystreams",
1073 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1074 "actor" => "http://mastodon.example.org/users/admin",
1075 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1076 "object" => "https://info.pleroma.site/activity2.json",
1077 "type" => "Announce"
1078 }
1079
1080 :error = Transmogrifier.handle_incoming(data)
1081 end
1082
1083 test "it rejects objects when attributedTo is wrong (variant 2)" do
1084 {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity3.json")
1085 end
1086
1087 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1088 data = %{
1089 "@context" => "https://www.w3.org/ns/activitystreams",
1090 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1091 "actor" => "http://mastodon.example.org/users/admin",
1092 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1093 "object" => "https://info.pleroma.site/activity3.json",
1094 "type" => "Announce"
1095 }
1096
1097 :error = Transmogrifier.handle_incoming(data)
1098 end
1099 end
1100
1101 describe "general origin containment" do
1102 test "contain_origin_from_id() catches obvious spoofing attempts" do
1103 data = %{
1104 "id" => "http://example.com/~alyssa/activities/1234.json"
1105 }
1106
1107 :error =
1108 Transmogrifier.contain_origin_from_id(
1109 "http://example.org/~alyssa/activities/1234.json",
1110 data
1111 )
1112 end
1113
1114 test "contain_origin_from_id() allows alternate IDs within the same origin domain" do
1115 data = %{
1116 "id" => "http://example.com/~alyssa/activities/1234.json"
1117 }
1118
1119 :ok =
1120 Transmogrifier.contain_origin_from_id(
1121 "http://example.com/~alyssa/activities/1234",
1122 data
1123 )
1124 end
1125
1126 test "contain_origin_from_id() allows matching IDs" do
1127 data = %{
1128 "id" => "http://example.com/~alyssa/activities/1234.json"
1129 }
1130
1131 :ok =
1132 Transmogrifier.contain_origin_from_id(
1133 "http://example.com/~alyssa/activities/1234.json",
1134 data
1135 )
1136 end
1137
1138 test "users cannot be collided through fake direction spoofing attempts" do
1139 insert(:user, %{
1140 nickname: "rye@niu.moe",
1141 local: false,
1142 ap_id: "https://niu.moe/users/rye",
1143 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1144 })
1145
1146 {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
1147 end
1148
1149 test "all objects with fake directions are rejected by the object fetcher" do
1150 {:error, _} =
1151 ActivityPub.fetch_and_contain_remote_object_from_id(
1152 "https://info.pleroma.site/activity4.json"
1153 )
1154 end
1155 end
1156
1157 describe "reserialization" do
1158 test "successfully reserializes a message with inReplyTo == nil" do
1159 user = insert(:user)
1160
1161 message = %{
1162 "@context" => "https://www.w3.org/ns/activitystreams",
1163 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1164 "cc" => [],
1165 "type" => "Create",
1166 "object" => %{
1167 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1168 "cc" => [],
1169 "type" => "Note",
1170 "content" => "Hi",
1171 "inReplyTo" => nil,
1172 "attributedTo" => user.ap_id
1173 },
1174 "actor" => user.ap_id
1175 }
1176
1177 {:ok, activity} = Transmogrifier.handle_incoming(message)
1178
1179 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1180 end
1181
1182 test "successfully reserializes a message with AS2 objects in IR" do
1183 user = insert(:user)
1184
1185 message = %{
1186 "@context" => "https://www.w3.org/ns/activitystreams",
1187 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1188 "cc" => [],
1189 "type" => "Create",
1190 "object" => %{
1191 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1192 "cc" => [],
1193 "type" => "Note",
1194 "content" => "Hi",
1195 "inReplyTo" => nil,
1196 "attributedTo" => user.ap_id,
1197 "tag" => [
1198 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1199 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1200 ]
1201 },
1202 "actor" => user.ap_id
1203 }
1204
1205 {:ok, activity} = Transmogrifier.handle_incoming(message)
1206
1207 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1208 end
1209 end
1210 end