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