Merge branch 'develop' into feature/admin-api-status-count-per-instance
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.CommonAPI
18
19 import Mock
20 import Pleroma.Factory
21 import ExUnit.CaptureLog
22
23 setup_all do
24 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
25 :ok
26 end
27
28 setup do: clear_config([:instance, :max_remote_account_fields])
29
30 describe "handle_incoming" do
31 test "it ignores an incoming notice if we already have it" do
32 activity = insert(:note_activity)
33
34 data =
35 File.read!("test/fixtures/mastodon-post-activity.json")
36 |> Poison.decode!()
37 |> Map.put("object", Object.normalize(activity).data)
38
39 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
40
41 assert activity == returned_activity
42 end
43
44 @tag capture_log: true
45 test "it fetches reply-to activities if we don't have them" do
46 data =
47 File.read!("test/fixtures/mastodon-post-activity.json")
48 |> Poison.decode!()
49
50 object =
51 data["object"]
52 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
53
54 data = Map.put(data, "object", object)
55 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
56 returned_object = Object.normalize(returned_activity, false)
57
58 assert activity =
59 Activity.get_create_by_object_ap_id(
60 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
61 )
62
63 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
64 end
65
66 test "it does not fetch reply-to activities beyond max replies depth limit" do
67 data =
68 File.read!("test/fixtures/mastodon-post-activity.json")
69 |> Poison.decode!()
70
71 object =
72 data["object"]
73 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
74
75 data = Map.put(data, "object", object)
76
77 with_mock Pleroma.Web.Federator,
78 allowed_thread_distance?: fn _ -> false end do
79 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
80
81 returned_object = Object.normalize(returned_activity, false)
82
83 refute Activity.get_create_by_object_ap_id(
84 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
85 )
86
87 assert returned_object.data["inReplyToAtomUri"] ==
88 "https://shitposter.club/notice/2827873"
89 end
90 end
91
92 test "it does not crash if the object in inReplyTo can't be fetched" do
93 data =
94 File.read!("test/fixtures/mastodon-post-activity.json")
95 |> Poison.decode!()
96
97 object =
98 data["object"]
99 |> Map.put("inReplyTo", "https://404.site/whatever")
100
101 data =
102 data
103 |> Map.put("object", object)
104
105 assert capture_log(fn ->
106 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
107 end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
108 end
109
110 test "it works for incoming notices" do
111 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
112
113 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
114
115 assert data["id"] ==
116 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
117
118 assert data["context"] ==
119 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
120
121 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
122
123 assert data["cc"] == [
124 "http://mastodon.example.org/users/admin/followers",
125 "http://localtesting.pleroma.lol/users/lain"
126 ]
127
128 assert data["actor"] == "http://mastodon.example.org/users/admin"
129
130 object_data = Object.normalize(data["object"]).data
131
132 assert object_data["id"] ==
133 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
134
135 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
136
137 assert object_data["cc"] == [
138 "http://mastodon.example.org/users/admin/followers",
139 "http://localtesting.pleroma.lol/users/lain"
140 ]
141
142 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
143 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
144
145 assert object_data["context"] ==
146 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
147
148 assert object_data["sensitive"] == true
149
150 user = User.get_cached_by_ap_id(object_data["actor"])
151
152 assert user.note_count == 1
153 end
154
155 test "it works for incoming notices with hashtags" do
156 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
157
158 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
159 object = Object.normalize(data["object"])
160
161 assert Enum.at(object.data["tag"], 2) == "moo"
162 end
163
164 test "it works for incoming questions" do
165 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
166
167 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
168
169 object = Object.normalize(activity)
170
171 assert Enum.all?(object.data["oneOf"], fn choice ->
172 choice["name"] in [
173 "Dunno",
174 "Everyone knows that!",
175 "25 char limit is dumb",
176 "I can't even fit a funny"
177 ]
178 end)
179 end
180
181 test "it works for incoming listens" do
182 data = %{
183 "@context" => "https://www.w3.org/ns/activitystreams",
184 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
185 "cc" => [],
186 "type" => "Listen",
187 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
188 "actor" => "http://mastodon.example.org/users/admin",
189 "object" => %{
190 "type" => "Audio",
191 "id" => "http://mastodon.example.org/users/admin/listens/1234",
192 "attributedTo" => "http://mastodon.example.org/users/admin",
193 "title" => "lain radio episode 1",
194 "artist" => "lain",
195 "album" => "lain radio",
196 "length" => 180_000
197 }
198 }
199
200 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
201
202 object = Object.normalize(activity)
203
204 assert object.data["title"] == "lain radio episode 1"
205 assert object.data["artist"] == "lain"
206 assert object.data["album"] == "lain radio"
207 assert object.data["length"] == 180_000
208 end
209
210 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
211 user = insert(:user)
212
213 {:ok, activity} =
214 CommonAPI.post(user, %{
215 "status" => "suya...",
216 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
217 })
218
219 object = Object.normalize(activity)
220
221 data =
222 File.read!("test/fixtures/mastodon-vote.json")
223 |> Poison.decode!()
224 |> Kernel.put_in(["to"], user.ap_id)
225 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
226 |> Kernel.put_in(["object", "to"], user.ap_id)
227
228 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
229 answer_object = Object.normalize(activity)
230 assert answer_object.data["type"] == "Answer"
231 object = Object.get_by_ap_id(object.data["id"])
232
233 assert Enum.any?(
234 object.data["oneOf"],
235 fn
236 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
237 _ -> false
238 end
239 )
240 end
241
242 test "it works for incoming notices with contentMap" do
243 data =
244 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
245
246 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
247 object = Object.normalize(data["object"])
248
249 assert object.data["content"] ==
250 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
251 end
252
253 test "it works for incoming notices with to/cc not being an array (kroeg)" do
254 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
255
256 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
257 object = Object.normalize(data["object"])
258
259 assert object.data["content"] ==
260 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
261 end
262
263 test "it works for incoming announces with actor being inlined (kroeg)" do
264 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
265
266 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
267
268 assert data["actor"] == "https://puckipedia.com/"
269 end
270
271 test "it works for incoming notices with tag not being an array (kroeg)" do
272 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
273
274 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
275 object = Object.normalize(data["object"])
276
277 assert object.data["emoji"] == %{
278 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
279 }
280
281 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
282
283 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
284 object = Object.normalize(data["object"])
285
286 assert "test" in object.data["tag"]
287 end
288
289 test "it works for incoming notices with url not being a string (prismo)" do
290 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
291
292 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
293 object = Object.normalize(data["object"])
294
295 assert object.data["url"] == "https://prismo.news/posts/83"
296 end
297
298 test "it cleans up incoming notices which are not really DMs" do
299 user = insert(:user)
300 other_user = insert(:user)
301
302 to = [user.ap_id, other_user.ap_id]
303
304 data =
305 File.read!("test/fixtures/mastodon-post-activity.json")
306 |> Poison.decode!()
307 |> Map.put("to", to)
308 |> Map.put("cc", [])
309
310 object =
311 data["object"]
312 |> Map.put("to", to)
313 |> Map.put("cc", [])
314
315 data = Map.put(data, "object", object)
316
317 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
318
319 assert data["to"] == []
320 assert data["cc"] == to
321
322 object_data = Object.normalize(activity).data
323
324 assert object_data["to"] == []
325 assert object_data["cc"] == to
326 end
327
328 test "it works for incoming announces" do
329 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
330
331 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
332
333 assert data["actor"] == "http://mastodon.example.org/users/admin"
334 assert data["type"] == "Announce"
335
336 assert data["id"] ==
337 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
338
339 assert data["object"] ==
340 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
341
342 assert Activity.get_create_by_object_ap_id(data["object"])
343 end
344
345 test "it works for incoming announces with an existing activity" do
346 user = insert(:user)
347 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
348
349 data =
350 File.read!("test/fixtures/mastodon-announce.json")
351 |> Poison.decode!()
352 |> Map.put("object", activity.data["object"])
353
354 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
355
356 assert data["actor"] == "http://mastodon.example.org/users/admin"
357 assert data["type"] == "Announce"
358
359 assert data["id"] ==
360 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
361
362 assert data["object"] == activity.data["object"]
363
364 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
365 end
366
367 test "it works for incoming announces with an inlined activity" do
368 data =
369 File.read!("test/fixtures/mastodon-announce-private.json")
370 |> Poison.decode!()
371
372 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
373
374 assert data["actor"] == "http://mastodon.example.org/users/admin"
375 assert data["type"] == "Announce"
376
377 assert data["id"] ==
378 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
379
380 object = Object.normalize(data["object"])
381
382 assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368"
383 assert object.data["content"] == "this is a private toot"
384 end
385
386 @tag capture_log: true
387 test "it rejects incoming announces with an inlined activity from another origin" do
388 data =
389 File.read!("test/fixtures/bogus-mastodon-announce.json")
390 |> Poison.decode!()
391
392 assert :error = Transmogrifier.handle_incoming(data)
393 end
394
395 test "it does not clobber the addressing on announce activities" do
396 user = insert(:user)
397 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
398
399 data =
400 File.read!("test/fixtures/mastodon-announce.json")
401 |> Poison.decode!()
402 |> Map.put("object", Object.normalize(activity).data["id"])
403 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
404 |> Map.put("cc", [])
405
406 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
407
408 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
409 end
410
411 test "it ensures that as:Public activities make it to their followers collection" do
412 user = insert(:user)
413
414 data =
415 File.read!("test/fixtures/mastodon-post-activity.json")
416 |> Poison.decode!()
417 |> Map.put("actor", user.ap_id)
418 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
419 |> Map.put("cc", [])
420
421 object =
422 data["object"]
423 |> Map.put("attributedTo", user.ap_id)
424 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
425 |> Map.put("cc", [])
426 |> Map.put("id", user.ap_id <> "/activities/12345678")
427
428 data = Map.put(data, "object", object)
429
430 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
431
432 assert data["cc"] == [User.ap_followers(user)]
433 end
434
435 test "it ensures that address fields become lists" do
436 user = insert(:user)
437
438 data =
439 File.read!("test/fixtures/mastodon-post-activity.json")
440 |> Poison.decode!()
441 |> Map.put("actor", user.ap_id)
442 |> Map.put("to", nil)
443 |> Map.put("cc", nil)
444
445 object =
446 data["object"]
447 |> Map.put("attributedTo", user.ap_id)
448 |> Map.put("to", nil)
449 |> Map.put("cc", nil)
450 |> Map.put("id", user.ap_id <> "/activities/12345678")
451
452 data = Map.put(data, "object", object)
453
454 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
455
456 assert !is_nil(data["to"])
457 assert !is_nil(data["cc"])
458 end
459
460 test "it strips internal likes" do
461 data =
462 File.read!("test/fixtures/mastodon-post-activity.json")
463 |> Poison.decode!()
464
465 likes = %{
466 "first" =>
467 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
468 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
469 "totalItems" => 3,
470 "type" => "OrderedCollection"
471 }
472
473 object = Map.put(data["object"], "likes", likes)
474 data = Map.put(data, "object", object)
475
476 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
477
478 refute Map.has_key?(object.data, "likes")
479 end
480
481 test "it strips internal reactions" do
482 user = insert(:user)
483 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
484 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
485
486 %{object: object} = Activity.get_by_id_with_object(activity.id)
487 assert Map.has_key?(object.data, "reactions")
488 assert Map.has_key?(object.data, "reaction_count")
489
490 object_data = Transmogrifier.strip_internal_fields(object.data)
491 refute Map.has_key?(object_data, "reactions")
492 refute Map.has_key?(object_data, "reaction_count")
493 end
494
495 test "it works for incoming update activities" do
496 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
497
498 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
499 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
500
501 object =
502 update_data["object"]
503 |> Map.put("actor", data["actor"])
504 |> Map.put("id", data["actor"])
505
506 update_data =
507 update_data
508 |> Map.put("actor", data["actor"])
509 |> Map.put("object", object)
510
511 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
512
513 assert data["id"] == update_data["id"]
514
515 user = User.get_cached_by_ap_id(data["actor"])
516 assert user.name == "gargle"
517
518 assert user.avatar["url"] == [
519 %{
520 "href" =>
521 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
522 }
523 ]
524
525 assert user.banner["url"] == [
526 %{
527 "href" =>
528 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
529 }
530 ]
531
532 assert user.bio == "<p>Some bio</p>"
533 end
534
535 test "it works with alsoKnownAs" do
536 {:ok, %Activity{data: %{"actor" => actor}}} =
537 "test/fixtures/mastodon-post-activity.json"
538 |> File.read!()
539 |> Poison.decode!()
540 |> Transmogrifier.handle_incoming()
541
542 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
543
544 {:ok, _activity} =
545 "test/fixtures/mastodon-update.json"
546 |> File.read!()
547 |> Poison.decode!()
548 |> Map.put("actor", actor)
549 |> Map.update!("object", fn object ->
550 object
551 |> Map.put("actor", actor)
552 |> Map.put("id", actor)
553 |> Map.put("alsoKnownAs", [
554 "http://mastodon.example.org/users/foo",
555 "http://example.org/users/bar"
556 ])
557 end)
558 |> Transmogrifier.handle_incoming()
559
560 assert User.get_cached_by_ap_id(actor).also_known_as == [
561 "http://mastodon.example.org/users/foo",
562 "http://example.org/users/bar"
563 ]
564 end
565
566 test "it works with custom profile fields" do
567 {:ok, activity} =
568 "test/fixtures/mastodon-post-activity.json"
569 |> File.read!()
570 |> Poison.decode!()
571 |> Transmogrifier.handle_incoming()
572
573 user = User.get_cached_by_ap_id(activity.actor)
574
575 assert user.fields == [
576 %{"name" => "foo", "value" => "bar"},
577 %{"name" => "foo1", "value" => "bar1"}
578 ]
579
580 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
581
582 object =
583 update_data["object"]
584 |> Map.put("actor", user.ap_id)
585 |> Map.put("id", user.ap_id)
586
587 update_data =
588 update_data
589 |> Map.put("actor", user.ap_id)
590 |> Map.put("object", object)
591
592 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
593
594 user = User.get_cached_by_ap_id(user.ap_id)
595
596 assert user.fields == [
597 %{"name" => "foo", "value" => "updated"},
598 %{"name" => "foo1", "value" => "updated"}
599 ]
600
601 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
602
603 update_data =
604 put_in(update_data, ["object", "attachment"], [
605 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
606 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
607 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
608 ])
609
610 {:ok, _} = Transmogrifier.handle_incoming(update_data)
611
612 user = User.get_cached_by_ap_id(user.ap_id)
613
614 assert user.fields == [
615 %{"name" => "foo", "value" => "updated"},
616 %{"name" => "foo1", "value" => "updated"}
617 ]
618
619 update_data = put_in(update_data, ["object", "attachment"], [])
620
621 {:ok, _} = Transmogrifier.handle_incoming(update_data)
622
623 user = User.get_cached_by_ap_id(user.ap_id)
624
625 assert user.fields == []
626 end
627
628 test "it works for incoming update activities which lock the account" do
629 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
630
631 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
632 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
633
634 object =
635 update_data["object"]
636 |> Map.put("actor", data["actor"])
637 |> Map.put("id", data["actor"])
638 |> Map.put("manuallyApprovesFollowers", true)
639
640 update_data =
641 update_data
642 |> Map.put("actor", data["actor"])
643 |> Map.put("object", object)
644
645 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
646
647 user = User.get_cached_by_ap_id(data["actor"])
648 assert user.locked == true
649 end
650
651 test "it works for incomming unfollows with an existing follow" do
652 user = insert(:user)
653
654 follow_data =
655 File.read!("test/fixtures/mastodon-follow-activity.json")
656 |> Poison.decode!()
657 |> Map.put("object", user.ap_id)
658
659 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
660
661 data =
662 File.read!("test/fixtures/mastodon-unfollow-activity.json")
663 |> Poison.decode!()
664 |> Map.put("object", follow_data)
665
666 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
667
668 assert data["type"] == "Undo"
669 assert data["object"]["type"] == "Follow"
670 assert data["object"]["object"] == user.ap_id
671 assert data["actor"] == "http://mastodon.example.org/users/admin"
672
673 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
674 end
675
676 test "it works for incoming follows to locked account" do
677 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
678 user = insert(:user, locked: true)
679
680 data =
681 File.read!("test/fixtures/mastodon-follow-activity.json")
682 |> Poison.decode!()
683 |> Map.put("object", user.ap_id)
684
685 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
686
687 assert data["type"] == "Follow"
688 assert data["object"] == user.ap_id
689 assert data["state"] == "pending"
690 assert data["actor"] == "http://mastodon.example.org/users/admin"
691
692 assert [^pending_follower] = User.get_follow_requests(user)
693 end
694
695 test "it works for incoming blocks" do
696 user = insert(:user)
697
698 data =
699 File.read!("test/fixtures/mastodon-block-activity.json")
700 |> Poison.decode!()
701 |> Map.put("object", user.ap_id)
702
703 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
704
705 assert data["type"] == "Block"
706 assert data["object"] == user.ap_id
707 assert data["actor"] == "http://mastodon.example.org/users/admin"
708
709 blocker = User.get_cached_by_ap_id(data["actor"])
710
711 assert User.blocks?(blocker, user)
712 end
713
714 test "incoming blocks successfully tear down any follow relationship" do
715 blocker = insert(:user)
716 blocked = insert(:user)
717
718 data =
719 File.read!("test/fixtures/mastodon-block-activity.json")
720 |> Poison.decode!()
721 |> Map.put("object", blocked.ap_id)
722 |> Map.put("actor", blocker.ap_id)
723
724 {:ok, blocker} = User.follow(blocker, blocked)
725 {:ok, blocked} = User.follow(blocked, blocker)
726
727 assert User.following?(blocker, blocked)
728 assert User.following?(blocked, blocker)
729
730 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
731
732 assert data["type"] == "Block"
733 assert data["object"] == blocked.ap_id
734 assert data["actor"] == blocker.ap_id
735
736 blocker = User.get_cached_by_ap_id(data["actor"])
737 blocked = User.get_cached_by_ap_id(data["object"])
738
739 assert User.blocks?(blocker, blocked)
740
741 refute User.following?(blocker, blocked)
742 refute User.following?(blocked, blocker)
743 end
744
745 test "it works for incoming accepts which were pre-accepted" do
746 follower = insert(:user)
747 followed = insert(:user)
748
749 {:ok, follower} = User.follow(follower, followed)
750 assert User.following?(follower, followed) == true
751
752 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
753
754 accept_data =
755 File.read!("test/fixtures/mastodon-accept-activity.json")
756 |> Poison.decode!()
757 |> Map.put("actor", followed.ap_id)
758
759 object =
760 accept_data["object"]
761 |> Map.put("actor", follower.ap_id)
762 |> Map.put("id", follow_activity.data["id"])
763
764 accept_data = Map.put(accept_data, "object", object)
765
766 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
767 refute activity.local
768
769 assert activity.data["object"] == follow_activity.data["id"]
770
771 assert activity.data["id"] == accept_data["id"]
772
773 follower = User.get_cached_by_id(follower.id)
774
775 assert User.following?(follower, followed) == true
776 end
777
778 test "it works for incoming accepts which were orphaned" do
779 follower = insert(:user)
780 followed = insert(:user, locked: true)
781
782 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
783
784 accept_data =
785 File.read!("test/fixtures/mastodon-accept-activity.json")
786 |> Poison.decode!()
787 |> Map.put("actor", followed.ap_id)
788
789 accept_data =
790 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
791
792 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
793 assert activity.data["object"] == follow_activity.data["id"]
794
795 follower = User.get_cached_by_id(follower.id)
796
797 assert User.following?(follower, followed) == true
798 end
799
800 test "it works for incoming accepts which are referenced by IRI only" do
801 follower = insert(:user)
802 followed = insert(:user, locked: true)
803
804 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
805
806 accept_data =
807 File.read!("test/fixtures/mastodon-accept-activity.json")
808 |> Poison.decode!()
809 |> Map.put("actor", followed.ap_id)
810 |> Map.put("object", follow_activity.data["id"])
811
812 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
813 assert activity.data["object"] == follow_activity.data["id"]
814
815 follower = User.get_cached_by_id(follower.id)
816
817 assert User.following?(follower, followed) == true
818 end
819
820 test "it fails for incoming accepts which cannot be correlated" do
821 follower = insert(:user)
822 followed = insert(:user, locked: true)
823
824 accept_data =
825 File.read!("test/fixtures/mastodon-accept-activity.json")
826 |> Poison.decode!()
827 |> Map.put("actor", followed.ap_id)
828
829 accept_data =
830 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
831
832 :error = Transmogrifier.handle_incoming(accept_data)
833
834 follower = User.get_cached_by_id(follower.id)
835
836 refute User.following?(follower, followed) == true
837 end
838
839 test "it fails for incoming rejects which cannot be correlated" do
840 follower = insert(:user)
841 followed = insert(:user, locked: true)
842
843 accept_data =
844 File.read!("test/fixtures/mastodon-reject-activity.json")
845 |> Poison.decode!()
846 |> Map.put("actor", followed.ap_id)
847
848 accept_data =
849 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
850
851 :error = Transmogrifier.handle_incoming(accept_data)
852
853 follower = User.get_cached_by_id(follower.id)
854
855 refute User.following?(follower, followed) == true
856 end
857
858 test "it works for incoming rejects which are orphaned" do
859 follower = insert(:user)
860 followed = insert(:user, locked: true)
861
862 {:ok, follower} = User.follow(follower, followed)
863 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
864
865 assert User.following?(follower, followed) == true
866
867 reject_data =
868 File.read!("test/fixtures/mastodon-reject-activity.json")
869 |> Poison.decode!()
870 |> Map.put("actor", followed.ap_id)
871
872 reject_data =
873 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
874
875 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
876 refute activity.local
877 assert activity.data["id"] == reject_data["id"]
878
879 follower = User.get_cached_by_id(follower.id)
880
881 assert User.following?(follower, followed) == false
882 end
883
884 test "it works for incoming rejects which are referenced by IRI only" do
885 follower = insert(:user)
886 followed = insert(:user, locked: true)
887
888 {:ok, follower} = User.follow(follower, followed)
889 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
890
891 assert User.following?(follower, followed) == true
892
893 reject_data =
894 File.read!("test/fixtures/mastodon-reject-activity.json")
895 |> Poison.decode!()
896 |> Map.put("actor", followed.ap_id)
897 |> Map.put("object", follow_activity.data["id"])
898
899 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
900
901 follower = User.get_cached_by_id(follower.id)
902
903 assert User.following?(follower, followed) == false
904 end
905
906 test "it rejects activities without a valid ID" do
907 user = insert(:user)
908
909 data =
910 File.read!("test/fixtures/mastodon-follow-activity.json")
911 |> Poison.decode!()
912 |> Map.put("object", user.ap_id)
913 |> Map.put("id", "")
914
915 :error = Transmogrifier.handle_incoming(data)
916 end
917
918 test "skip converting the content when it is nil" do
919 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
920
921 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
922
923 result =
924 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
925
926 assert result["content"] == nil
927 end
928
929 test "it converts content of object to html" do
930 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
931
932 {:ok, %{"content" => content_markdown}} =
933 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
934
935 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
936 Fetcher.fetch_object_from_id(object_id)
937
938 assert content_markdown ==
939 "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
940
941 assert content ==
942 "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
943
944 assert object.data["mediaType"] == "text/html"
945 end
946
947 test "it remaps video URLs as attachments if necessary" do
948 {:ok, object} =
949 Fetcher.fetch_object_from_id(
950 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
951 )
952
953 attachment = %{
954 "type" => "Link",
955 "mediaType" => "video/mp4",
956 "url" => [
957 %{
958 "href" =>
959 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
960 "mediaType" => "video/mp4"
961 }
962 ]
963 }
964
965 assert object.data["url"] ==
966 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
967
968 assert object.data["attachment"] == [attachment]
969 end
970
971 test "it accepts Flag activities" do
972 user = insert(:user)
973 other_user = insert(:user)
974
975 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
976 object = Object.normalize(activity)
977
978 note_obj = %{
979 "type" => "Note",
980 "id" => activity.data["id"],
981 "content" => "test post",
982 "published" => object.data["published"],
983 "actor" => AccountView.render("show.json", %{user: user})
984 }
985
986 message = %{
987 "@context" => "https://www.w3.org/ns/activitystreams",
988 "cc" => [user.ap_id],
989 "object" => [user.ap_id, activity.data["id"]],
990 "type" => "Flag",
991 "content" => "blocked AND reported!!!",
992 "actor" => other_user.ap_id
993 }
994
995 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
996
997 assert activity.data["object"] == [user.ap_id, note_obj]
998 assert activity.data["content"] == "blocked AND reported!!!"
999 assert activity.data["actor"] == other_user.ap_id
1000 assert activity.data["cc"] == [user.ap_id]
1001 end
1002
1003 test "it correctly processes messages with non-array to field" do
1004 user = insert(:user)
1005
1006 message = %{
1007 "@context" => "https://www.w3.org/ns/activitystreams",
1008 "to" => "https://www.w3.org/ns/activitystreams#Public",
1009 "type" => "Create",
1010 "object" => %{
1011 "content" => "blah blah blah",
1012 "type" => "Note",
1013 "attributedTo" => user.ap_id,
1014 "inReplyTo" => nil
1015 },
1016 "actor" => user.ap_id
1017 }
1018
1019 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1020
1021 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1022 end
1023
1024 test "it correctly processes messages with non-array cc field" do
1025 user = insert(:user)
1026
1027 message = %{
1028 "@context" => "https://www.w3.org/ns/activitystreams",
1029 "to" => user.follower_address,
1030 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1031 "type" => "Create",
1032 "object" => %{
1033 "content" => "blah blah blah",
1034 "type" => "Note",
1035 "attributedTo" => user.ap_id,
1036 "inReplyTo" => nil
1037 },
1038 "actor" => user.ap_id
1039 }
1040
1041 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1042
1043 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1044 assert [user.follower_address] == activity.data["to"]
1045 end
1046
1047 test "it accepts Move activities" do
1048 old_user = insert(:user)
1049 new_user = insert(:user)
1050
1051 message = %{
1052 "@context" => "https://www.w3.org/ns/activitystreams",
1053 "type" => "Move",
1054 "actor" => old_user.ap_id,
1055 "object" => old_user.ap_id,
1056 "target" => new_user.ap_id
1057 }
1058
1059 assert :error = Transmogrifier.handle_incoming(message)
1060
1061 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1062
1063 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1064 assert activity.actor == old_user.ap_id
1065 assert activity.data["actor"] == old_user.ap_id
1066 assert activity.data["object"] == old_user.ap_id
1067 assert activity.data["target"] == new_user.ap_id
1068 assert activity.data["type"] == "Move"
1069 end
1070 end
1071
1072 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1073 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1074 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1075
1076 setup do
1077 data =
1078 "test/fixtures/mastodon-post-activity.json"
1079 |> File.read!()
1080 |> Poison.decode!()
1081
1082 items = get_in(data, ["object", "replies", "first", "items"])
1083 assert length(items) > 0
1084
1085 %{data: data, items: items}
1086 end
1087
1088 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1089 data: data,
1090 items: items
1091 } do
1092 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1093
1094 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1095
1096 for id <- items do
1097 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1098 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1099 end
1100 end
1101
1102 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1103 %{data: data} do
1104 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1105
1106 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1107
1108 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1109 end
1110 end
1111
1112 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1113 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1114 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1115
1116 setup do
1117 user = insert(:user)
1118
1119 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1120
1121 {:ok, reply1} =
1122 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1123
1124 {:ok, reply2} =
1125 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1126
1127 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1128
1129 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1130
1131 Repo.delete(activity.object)
1132 Repo.delete(activity)
1133
1134 %{federation_output: federation_output, replies_uris: replies_uris}
1135 end
1136
1137 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1138 federation_output: federation_output,
1139 replies_uris: replies_uris
1140 } do
1141 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1142
1143 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1144
1145 for id <- replies_uris do
1146 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1147 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1148 end
1149 end
1150
1151 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1152 %{federation_output: federation_output} do
1153 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1154
1155 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1156
1157 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1158 end
1159 end
1160
1161 describe "prepare outgoing" do
1162 test "it inlines private announced objects" do
1163 user = insert(:user)
1164
1165 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1166
1167 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1168
1169 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1170
1171 assert modified["object"]["content"] == "hey"
1172 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1173 end
1174
1175 test "it turns mentions into tags" do
1176 user = insert(:user)
1177 other_user = insert(:user)
1178
1179 {:ok, activity} =
1180 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1181
1182 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1183 object = modified["object"]
1184
1185 expected_mention = %{
1186 "href" => other_user.ap_id,
1187 "name" => "@#{other_user.nickname}",
1188 "type" => "Mention"
1189 }
1190
1191 expected_tag = %{
1192 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1193 "type" => "Hashtag",
1194 "name" => "#2hu"
1195 }
1196
1197 assert Enum.member?(object["tag"], expected_tag)
1198 assert Enum.member?(object["tag"], expected_mention)
1199 end
1200
1201 test "it adds the sensitive property" do
1202 user = insert(:user)
1203
1204 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1205 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1206
1207 assert modified["object"]["sensitive"]
1208 end
1209
1210 test "it adds the json-ld context and the conversation property" do
1211 user = insert(:user)
1212
1213 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1214 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1215
1216 assert modified["@context"] ==
1217 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1218
1219 assert modified["object"]["conversation"] == modified["context"]
1220 end
1221
1222 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1223 user = insert(:user)
1224
1225 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1226 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1227
1228 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1229 end
1230
1231 test "it strips internal hashtag data" do
1232 user = insert(:user)
1233
1234 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1235
1236 expected_tag = %{
1237 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1238 "type" => "Hashtag",
1239 "name" => "#2hu"
1240 }
1241
1242 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1243
1244 assert modified["object"]["tag"] == [expected_tag]
1245 end
1246
1247 test "it strips internal fields" do
1248 user = insert(:user)
1249
1250 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1251
1252 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1253
1254 assert length(modified["object"]["tag"]) == 2
1255
1256 assert is_nil(modified["object"]["emoji"])
1257 assert is_nil(modified["object"]["like_count"])
1258 assert is_nil(modified["object"]["announcements"])
1259 assert is_nil(modified["object"]["announcement_count"])
1260 assert is_nil(modified["object"]["context_id"])
1261 end
1262
1263 test "it strips internal fields of article" do
1264 activity = insert(:article_activity)
1265
1266 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1267
1268 assert length(modified["object"]["tag"]) == 2
1269
1270 assert is_nil(modified["object"]["emoji"])
1271 assert is_nil(modified["object"]["like_count"])
1272 assert is_nil(modified["object"]["announcements"])
1273 assert is_nil(modified["object"]["announcement_count"])
1274 assert is_nil(modified["object"]["context_id"])
1275 assert is_nil(modified["object"]["likes"])
1276 end
1277
1278 test "the directMessage flag is present" do
1279 user = insert(:user)
1280 other_user = insert(:user)
1281
1282 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1283
1284 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1285
1286 assert modified["directMessage"] == false
1287
1288 {:ok, activity} =
1289 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1290
1291 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1292
1293 assert modified["directMessage"] == false
1294
1295 {:ok, activity} =
1296 CommonAPI.post(user, %{
1297 "status" => "@#{other_user.nickname} :moominmamma:",
1298 "visibility" => "direct"
1299 })
1300
1301 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1302
1303 assert modified["directMessage"] == true
1304 end
1305
1306 test "it strips BCC field" do
1307 user = insert(:user)
1308 {:ok, list} = Pleroma.List.create("foo", user)
1309
1310 {:ok, activity} =
1311 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1312
1313 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1314
1315 assert is_nil(modified["bcc"])
1316 end
1317
1318 test "it can handle Listen activities" do
1319 listen_activity = insert(:listen)
1320
1321 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1322
1323 assert modified["type"] == "Listen"
1324
1325 user = insert(:user)
1326
1327 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1328
1329 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1330 end
1331 end
1332
1333 describe "user upgrade" do
1334 test "it upgrades a user to activitypub" do
1335 user =
1336 insert(:user, %{
1337 nickname: "rye@niu.moe",
1338 local: false,
1339 ap_id: "https://niu.moe/users/rye",
1340 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1341 })
1342
1343 user_two = insert(:user)
1344 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1345
1346 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1347 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1348 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1349
1350 user = User.get_cached_by_id(user.id)
1351 assert user.note_count == 1
1352
1353 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1354 ObanHelpers.perform_all()
1355
1356 assert user.ap_enabled
1357 assert user.note_count == 1
1358 assert user.follower_address == "https://niu.moe/users/rye/followers"
1359 assert user.following_address == "https://niu.moe/users/rye/following"
1360
1361 user = User.get_cached_by_id(user.id)
1362 assert user.note_count == 1
1363
1364 activity = Activity.get_by_id(activity.id)
1365 assert user.follower_address in activity.recipients
1366
1367 assert %{
1368 "url" => [
1369 %{
1370 "href" =>
1371 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1372 }
1373 ]
1374 } = user.avatar
1375
1376 assert %{
1377 "url" => [
1378 %{
1379 "href" =>
1380 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1381 }
1382 ]
1383 } = user.banner
1384
1385 refute "..." in activity.recipients
1386
1387 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1388 refute user.follower_address in unrelated_activity.recipients
1389
1390 user_two = User.get_cached_by_id(user_two.id)
1391 assert User.following?(user_two, user)
1392 refute "..." in User.following(user_two)
1393 end
1394 end
1395
1396 describe "actor rewriting" do
1397 test "it fixes the actor URL property to be a proper URI" do
1398 data = %{
1399 "url" => %{"href" => "http://example.com"}
1400 }
1401
1402 rewritten = Transmogrifier.maybe_fix_user_object(data)
1403 assert rewritten["url"] == "http://example.com"
1404 end
1405 end
1406
1407 describe "actor origin containment" do
1408 test "it rejects activities which reference objects with bogus origins" do
1409 data = %{
1410 "@context" => "https://www.w3.org/ns/activitystreams",
1411 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1412 "actor" => "http://mastodon.example.org/users/admin",
1413 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1414 "object" => "https://info.pleroma.site/activity.json",
1415 "type" => "Announce"
1416 }
1417
1418 assert capture_log(fn ->
1419 :error = Transmogrifier.handle_incoming(data)
1420 end) =~ "Object containment failed"
1421 end
1422
1423 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1424 data = %{
1425 "@context" => "https://www.w3.org/ns/activitystreams",
1426 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1427 "actor" => "http://mastodon.example.org/users/admin",
1428 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1429 "object" => "https://info.pleroma.site/activity2.json",
1430 "type" => "Announce"
1431 }
1432
1433 assert capture_log(fn ->
1434 :error = Transmogrifier.handle_incoming(data)
1435 end) =~ "Object containment failed"
1436 end
1437
1438 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1439 data = %{
1440 "@context" => "https://www.w3.org/ns/activitystreams",
1441 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1442 "actor" => "http://mastodon.example.org/users/admin",
1443 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1444 "object" => "https://info.pleroma.site/activity3.json",
1445 "type" => "Announce"
1446 }
1447
1448 assert capture_log(fn ->
1449 :error = Transmogrifier.handle_incoming(data)
1450 end) =~ "Object containment failed"
1451 end
1452 end
1453
1454 describe "reserialization" do
1455 test "successfully reserializes a message with inReplyTo == nil" do
1456 user = insert(:user)
1457
1458 message = %{
1459 "@context" => "https://www.w3.org/ns/activitystreams",
1460 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1461 "cc" => [],
1462 "type" => "Create",
1463 "object" => %{
1464 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1465 "cc" => [],
1466 "type" => "Note",
1467 "content" => "Hi",
1468 "inReplyTo" => nil,
1469 "attributedTo" => user.ap_id
1470 },
1471 "actor" => user.ap_id
1472 }
1473
1474 {:ok, activity} = Transmogrifier.handle_incoming(message)
1475
1476 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1477 end
1478
1479 test "successfully reserializes a message with AS2 objects in IR" do
1480 user = insert(:user)
1481
1482 message = %{
1483 "@context" => "https://www.w3.org/ns/activitystreams",
1484 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1485 "cc" => [],
1486 "type" => "Create",
1487 "object" => %{
1488 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1489 "cc" => [],
1490 "type" => "Note",
1491 "content" => "Hi",
1492 "inReplyTo" => nil,
1493 "attributedTo" => user.ap_id,
1494 "tag" => [
1495 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1496 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1497 ]
1498 },
1499 "actor" => user.ap_id
1500 }
1501
1502 {:ok, activity} = Transmogrifier.handle_incoming(message)
1503
1504 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1505 end
1506 end
1507
1508 test "Rewrites Answers to Notes" do
1509 user = insert(:user)
1510
1511 {:ok, poll_activity} =
1512 CommonAPI.post(user, %{
1513 "status" => "suya...",
1514 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1515 })
1516
1517 poll_object = Object.normalize(poll_activity)
1518 # TODO: Replace with CommonAPI vote creation when implemented
1519 data =
1520 File.read!("test/fixtures/mastodon-vote.json")
1521 |> Poison.decode!()
1522 |> Kernel.put_in(["to"], user.ap_id)
1523 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1524 |> Kernel.put_in(["object", "to"], user.ap_id)
1525
1526 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1527 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1528
1529 assert data["object"]["type"] == "Note"
1530 end
1531
1532 describe "fix_explicit_addressing" do
1533 setup do
1534 user = insert(:user)
1535 [user: user]
1536 end
1537
1538 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1539 explicitly_mentioned_actors = [
1540 "https://pleroma.gold/users/user1",
1541 "https://pleroma.gold/user2"
1542 ]
1543
1544 object = %{
1545 "actor" => user.ap_id,
1546 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1547 "cc" => [],
1548 "tag" =>
1549 Enum.map(explicitly_mentioned_actors, fn href ->
1550 %{"type" => "Mention", "href" => href}
1551 end)
1552 }
1553
1554 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1555 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1556 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1557 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1558 end
1559
1560 test "does not move actor's follower collection to cc", %{user: user} do
1561 object = %{
1562 "actor" => user.ap_id,
1563 "to" => [user.follower_address],
1564 "cc" => []
1565 }
1566
1567 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1568 assert user.follower_address in fixed_object["to"]
1569 refute user.follower_address in fixed_object["cc"]
1570 end
1571
1572 test "removes recipient's follower collection from cc", %{user: user} do
1573 recipient = insert(:user)
1574
1575 object = %{
1576 "actor" => user.ap_id,
1577 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1578 "cc" => [user.follower_address, recipient.follower_address]
1579 }
1580
1581 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1582
1583 assert user.follower_address in fixed_object["cc"]
1584 refute recipient.follower_address in fixed_object["cc"]
1585 refute recipient.follower_address in fixed_object["to"]
1586 end
1587 end
1588
1589 describe "fix_summary/1" do
1590 test "returns fixed object" do
1591 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1592 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1593 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1594 end
1595 end
1596
1597 describe "fix_in_reply_to/2" do
1598 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1599
1600 setup do
1601 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1602 [data: data]
1603 end
1604
1605 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1606 assert Transmogrifier.fix_in_reply_to(data) == data
1607 end
1608
1609 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1610 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1611
1612 object_with_reply =
1613 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1614
1615 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1616 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1617 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1618
1619 object_with_reply =
1620 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1621
1622 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1623 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1624 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1625
1626 object_with_reply =
1627 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1628
1629 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1630 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1631 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1632
1633 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1634 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1635 assert modified_object["inReplyTo"] == []
1636 assert modified_object["inReplyToAtomUri"] == ""
1637 end
1638
1639 @tag capture_log: true
1640 test "returns modified object when allowed incoming reply", %{data: data} do
1641 object_with_reply =
1642 Map.put(
1643 data["object"],
1644 "inReplyTo",
1645 "https://shitposter.club/notice/2827873"
1646 )
1647
1648 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1649 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1650
1651 assert modified_object["inReplyTo"] ==
1652 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1653
1654 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1655
1656 assert modified_object["conversation"] ==
1657 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1658
1659 assert modified_object["context"] ==
1660 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1661 end
1662 end
1663
1664 describe "fix_url/1" do
1665 test "fixes data for object when url is map" do
1666 object = %{
1667 "url" => %{
1668 "type" => "Link",
1669 "mimeType" => "video/mp4",
1670 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1671 }
1672 }
1673
1674 assert Transmogrifier.fix_url(object) == %{
1675 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1676 }
1677 end
1678
1679 test "fixes data for video object" do
1680 object = %{
1681 "type" => "Video",
1682 "url" => [
1683 %{
1684 "type" => "Link",
1685 "mimeType" => "video/mp4",
1686 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1687 },
1688 %{
1689 "type" => "Link",
1690 "mimeType" => "video/mp4",
1691 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1692 },
1693 %{
1694 "type" => "Link",
1695 "mimeType" => "text/html",
1696 "href" => "https://peertube.-2d4c2d1630e3"
1697 },
1698 %{
1699 "type" => "Link",
1700 "mimeType" => "text/html",
1701 "href" => "https://peertube.-2d4c2d16377-42"
1702 }
1703 ]
1704 }
1705
1706 assert Transmogrifier.fix_url(object) == %{
1707 "attachment" => [
1708 %{
1709 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1710 "mimeType" => "video/mp4",
1711 "type" => "Link"
1712 }
1713 ],
1714 "type" => "Video",
1715 "url" => "https://peertube.-2d4c2d1630e3"
1716 }
1717 end
1718
1719 test "fixes url for not Video object" do
1720 object = %{
1721 "type" => "Text",
1722 "url" => [
1723 %{
1724 "type" => "Link",
1725 "mimeType" => "text/html",
1726 "href" => "https://peertube.-2d4c2d1630e3"
1727 },
1728 %{
1729 "type" => "Link",
1730 "mimeType" => "text/html",
1731 "href" => "https://peertube.-2d4c2d16377-42"
1732 }
1733 ]
1734 }
1735
1736 assert Transmogrifier.fix_url(object) == %{
1737 "type" => "Text",
1738 "url" => "https://peertube.-2d4c2d1630e3"
1739 }
1740
1741 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1742 "type" => "Text",
1743 "url" => ""
1744 }
1745 end
1746
1747 test "retunrs not modified object" do
1748 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1749 end
1750 end
1751
1752 describe "get_obj_helper/2" do
1753 test "returns nil when cannot normalize object" do
1754 assert capture_log(fn ->
1755 refute Transmogrifier.get_obj_helper("test-obj-id")
1756 end) =~ "Unsupported URI scheme"
1757 end
1758
1759 @tag capture_log: true
1760 test "returns {:ok, %Object{}} for success case" do
1761 assert {:ok, %Object{}} =
1762 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1763 end
1764 end
1765
1766 describe "fix_attachments/1" do
1767 test "returns not modified object" do
1768 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1769 assert Transmogrifier.fix_attachments(data) == data
1770 end
1771
1772 test "returns modified object when attachment is map" do
1773 assert Transmogrifier.fix_attachments(%{
1774 "attachment" => %{
1775 "mediaType" => "video/mp4",
1776 "url" => "https://peertube.moe/stat-480.mp4"
1777 }
1778 }) == %{
1779 "attachment" => [
1780 %{
1781 "mediaType" => "video/mp4",
1782 "url" => [
1783 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1784 ]
1785 }
1786 ]
1787 }
1788 end
1789
1790 test "returns modified object when attachment is list" do
1791 assert Transmogrifier.fix_attachments(%{
1792 "attachment" => [
1793 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1794 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1795 ]
1796 }) == %{
1797 "attachment" => [
1798 %{
1799 "mediaType" => "video/mp4",
1800 "url" => [
1801 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1802 ]
1803 },
1804 %{
1805 "mediaType" => "video/mp4",
1806 "url" => [
1807 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1808 ]
1809 }
1810 ]
1811 }
1812 end
1813 end
1814
1815 describe "fix_emoji/1" do
1816 test "returns not modified object when object not contains tags" do
1817 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1818 assert Transmogrifier.fix_emoji(data) == data
1819 end
1820
1821 test "returns object with emoji when object contains list tags" do
1822 assert Transmogrifier.fix_emoji(%{
1823 "tag" => [
1824 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1825 %{"type" => "Hashtag"}
1826 ]
1827 }) == %{
1828 "emoji" => %{"bib" => "/test"},
1829 "tag" => [
1830 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1831 %{"type" => "Hashtag"}
1832 ]
1833 }
1834 end
1835
1836 test "returns object with emoji when object contains map tag" do
1837 assert Transmogrifier.fix_emoji(%{
1838 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1839 }) == %{
1840 "emoji" => %{"bib" => "/test"},
1841 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1842 }
1843 end
1844 end
1845
1846 describe "set_replies/1" do
1847 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1848
1849 test "returns unmodified object if activity doesn't have self-replies" do
1850 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1851 assert Transmogrifier.set_replies(data) == data
1852 end
1853
1854 test "sets `replies` collection with a limited number of self-replies" do
1855 [user, another_user] = insert_list(2, :user)
1856
1857 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
1858
1859 {:ok, %{id: id2} = self_reply1} =
1860 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
1861
1862 {:ok, self_reply2} =
1863 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
1864
1865 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1866 {:ok, _} =
1867 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
1868
1869 {:ok, _} =
1870 CommonAPI.post(user, %{
1871 "status" => "self-reply to self-reply",
1872 "in_reply_to_status_id" => id2
1873 })
1874
1875 {:ok, _} =
1876 CommonAPI.post(another_user, %{
1877 "status" => "another user's reply",
1878 "in_reply_to_status_id" => id1
1879 })
1880
1881 object = Object.normalize(activity)
1882 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1883
1884 assert %{"type" => "Collection", "items" => ^replies_uris} =
1885 Transmogrifier.set_replies(object.data)["replies"]
1886 end
1887 end
1888
1889 test "take_emoji_tags/1" do
1890 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1891
1892 assert Transmogrifier.take_emoji_tags(user) == [
1893 %{
1894 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1895 "id" => "https://example.org/firefox.png",
1896 "name" => ":firefox:",
1897 "type" => "Emoji",
1898 "updated" => "1970-01-01T00:00:00Z"
1899 }
1900 ]
1901 end
1902 end