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