[#2497] Specified SHELL in .gitlab-ci.yml as required for `exexec`.
[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} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
1313
1314 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1315
1316 assert modified["directMessage"] == false
1317
1318 {:ok, activity} =
1319 CommonAPI.post(user, %{
1320 status: "@#{other_user.nickname} :moominmamma:",
1321 visibility: "direct"
1322 })
1323
1324 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1325
1326 assert modified["directMessage"] == true
1327 end
1328
1329 test "it strips BCC field" do
1330 user = insert(:user)
1331 {:ok, list} = Pleroma.List.create("foo", user)
1332
1333 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1334
1335 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1336
1337 assert is_nil(modified["bcc"])
1338 end
1339
1340 test "it can handle Listen activities" do
1341 listen_activity = insert(:listen)
1342
1343 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1344
1345 assert modified["type"] == "Listen"
1346
1347 user = insert(:user)
1348
1349 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1350
1351 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1352 end
1353 end
1354
1355 describe "user upgrade" do
1356 test "it upgrades a user to activitypub" do
1357 user =
1358 insert(:user, %{
1359 nickname: "rye@niu.moe",
1360 local: false,
1361 ap_id: "https://niu.moe/users/rye",
1362 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1363 })
1364
1365 user_two = insert(:user)
1366 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1367
1368 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1369 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
1370 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1371
1372 user = User.get_cached_by_id(user.id)
1373 assert user.note_count == 1
1374
1375 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1376 ObanHelpers.perform_all()
1377
1378 assert user.ap_enabled
1379 assert user.note_count == 1
1380 assert user.follower_address == "https://niu.moe/users/rye/followers"
1381 assert user.following_address == "https://niu.moe/users/rye/following"
1382
1383 user = User.get_cached_by_id(user.id)
1384 assert user.note_count == 1
1385
1386 activity = Activity.get_by_id(activity.id)
1387 assert user.follower_address in activity.recipients
1388
1389 assert %{
1390 "url" => [
1391 %{
1392 "href" =>
1393 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1394 }
1395 ]
1396 } = user.avatar
1397
1398 assert %{
1399 "url" => [
1400 %{
1401 "href" =>
1402 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1403 }
1404 ]
1405 } = user.banner
1406
1407 refute "..." in activity.recipients
1408
1409 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1410 refute user.follower_address in unrelated_activity.recipients
1411
1412 user_two = User.get_cached_by_id(user_two.id)
1413 assert User.following?(user_two, user)
1414 refute "..." in User.following(user_two)
1415 end
1416 end
1417
1418 describe "actor rewriting" do
1419 test "it fixes the actor URL property to be a proper URI" do
1420 data = %{
1421 "url" => %{"href" => "http://example.com"}
1422 }
1423
1424 rewritten = Transmogrifier.maybe_fix_user_object(data)
1425 assert rewritten["url"] == "http://example.com"
1426 end
1427 end
1428
1429 describe "actor origin containment" do
1430 test "it rejects activities which reference objects with bogus origins" do
1431 data = %{
1432 "@context" => "https://www.w3.org/ns/activitystreams",
1433 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1434 "actor" => "http://mastodon.example.org/users/admin",
1435 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1436 "object" => "https://info.pleroma.site/activity.json",
1437 "type" => "Announce"
1438 }
1439
1440 assert capture_log(fn ->
1441 :error = Transmogrifier.handle_incoming(data)
1442 end) =~ "Object containment failed"
1443 end
1444
1445 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1446 data = %{
1447 "@context" => "https://www.w3.org/ns/activitystreams",
1448 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1449 "actor" => "http://mastodon.example.org/users/admin",
1450 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1451 "object" => "https://info.pleroma.site/activity2.json",
1452 "type" => "Announce"
1453 }
1454
1455 assert capture_log(fn ->
1456 :error = Transmogrifier.handle_incoming(data)
1457 end) =~ "Object containment failed"
1458 end
1459
1460 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1461 data = %{
1462 "@context" => "https://www.w3.org/ns/activitystreams",
1463 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1464 "actor" => "http://mastodon.example.org/users/admin",
1465 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1466 "object" => "https://info.pleroma.site/activity3.json",
1467 "type" => "Announce"
1468 }
1469
1470 assert capture_log(fn ->
1471 :error = Transmogrifier.handle_incoming(data)
1472 end) =~ "Object containment failed"
1473 end
1474 end
1475
1476 describe "reserialization" do
1477 test "successfully reserializes a message with inReplyTo == nil" do
1478 user = insert(:user)
1479
1480 message = %{
1481 "@context" => "https://www.w3.org/ns/activitystreams",
1482 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1483 "cc" => [],
1484 "type" => "Create",
1485 "object" => %{
1486 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1487 "cc" => [],
1488 "type" => "Note",
1489 "content" => "Hi",
1490 "inReplyTo" => nil,
1491 "attributedTo" => user.ap_id
1492 },
1493 "actor" => user.ap_id
1494 }
1495
1496 {:ok, activity} = Transmogrifier.handle_incoming(message)
1497
1498 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1499 end
1500
1501 test "successfully reserializes a message with AS2 objects in IR" do
1502 user = insert(:user)
1503
1504 message = %{
1505 "@context" => "https://www.w3.org/ns/activitystreams",
1506 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1507 "cc" => [],
1508 "type" => "Create",
1509 "object" => %{
1510 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1511 "cc" => [],
1512 "type" => "Note",
1513 "content" => "Hi",
1514 "inReplyTo" => nil,
1515 "attributedTo" => user.ap_id,
1516 "tag" => [
1517 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1518 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1519 ]
1520 },
1521 "actor" => user.ap_id
1522 }
1523
1524 {:ok, activity} = Transmogrifier.handle_incoming(message)
1525
1526 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1527 end
1528 end
1529
1530 test "Rewrites Answers to Notes" do
1531 user = insert(:user)
1532
1533 {:ok, poll_activity} =
1534 CommonAPI.post(user, %{
1535 status: "suya...",
1536 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1537 })
1538
1539 poll_object = Object.normalize(poll_activity)
1540 # TODO: Replace with CommonAPI vote creation when implemented
1541 data =
1542 File.read!("test/fixtures/mastodon-vote.json")
1543 |> Poison.decode!()
1544 |> Kernel.put_in(["to"], user.ap_id)
1545 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1546 |> Kernel.put_in(["object", "to"], user.ap_id)
1547
1548 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1549 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1550
1551 assert data["object"]["type"] == "Note"
1552 end
1553
1554 describe "fix_explicit_addressing" do
1555 setup do
1556 user = insert(:user)
1557 [user: user]
1558 end
1559
1560 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1561 explicitly_mentioned_actors = [
1562 "https://pleroma.gold/users/user1",
1563 "https://pleroma.gold/user2"
1564 ]
1565
1566 object = %{
1567 "actor" => user.ap_id,
1568 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1569 "cc" => [],
1570 "tag" =>
1571 Enum.map(explicitly_mentioned_actors, fn href ->
1572 %{"type" => "Mention", "href" => href}
1573 end)
1574 }
1575
1576 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1577 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1578 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1579 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1580 end
1581
1582 test "does not move actor's follower collection to cc", %{user: user} do
1583 object = %{
1584 "actor" => user.ap_id,
1585 "to" => [user.follower_address],
1586 "cc" => []
1587 }
1588
1589 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1590 assert user.follower_address in fixed_object["to"]
1591 refute user.follower_address in fixed_object["cc"]
1592 end
1593
1594 test "removes recipient's follower collection from cc", %{user: user} do
1595 recipient = insert(:user)
1596
1597 object = %{
1598 "actor" => user.ap_id,
1599 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1600 "cc" => [user.follower_address, recipient.follower_address]
1601 }
1602
1603 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1604
1605 assert user.follower_address in fixed_object["cc"]
1606 refute recipient.follower_address in fixed_object["cc"]
1607 refute recipient.follower_address in fixed_object["to"]
1608 end
1609 end
1610
1611 describe "fix_summary/1" do
1612 test "returns fixed object" do
1613 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1614 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1615 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1616 end
1617 end
1618
1619 describe "fix_in_reply_to/2" do
1620 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1621
1622 setup do
1623 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1624 [data: data]
1625 end
1626
1627 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1628 assert Transmogrifier.fix_in_reply_to(data) == data
1629 end
1630
1631 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1632 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1633
1634 object_with_reply =
1635 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1636
1637 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1638 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1639 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1640
1641 object_with_reply =
1642 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1643
1644 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1645 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1646 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1647
1648 object_with_reply =
1649 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1650
1651 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1652 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1653 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1654
1655 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1656 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1657 assert modified_object["inReplyTo"] == []
1658 assert modified_object["inReplyToAtomUri"] == ""
1659 end
1660
1661 @tag capture_log: true
1662 test "returns modified object when allowed incoming reply", %{data: data} do
1663 object_with_reply =
1664 Map.put(
1665 data["object"],
1666 "inReplyTo",
1667 "https://shitposter.club/notice/2827873"
1668 )
1669
1670 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1671 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1672
1673 assert modified_object["inReplyTo"] ==
1674 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1675
1676 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1677
1678 assert modified_object["conversation"] ==
1679 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1680
1681 assert modified_object["context"] ==
1682 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1683 end
1684 end
1685
1686 describe "fix_url/1" do
1687 test "fixes data for object when url is map" do
1688 object = %{
1689 "url" => %{
1690 "type" => "Link",
1691 "mimeType" => "video/mp4",
1692 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1693 }
1694 }
1695
1696 assert Transmogrifier.fix_url(object) == %{
1697 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1698 }
1699 end
1700
1701 test "fixes data for video object" do
1702 object = %{
1703 "type" => "Video",
1704 "url" => [
1705 %{
1706 "type" => "Link",
1707 "mimeType" => "video/mp4",
1708 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1709 },
1710 %{
1711 "type" => "Link",
1712 "mimeType" => "video/mp4",
1713 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1714 },
1715 %{
1716 "type" => "Link",
1717 "mimeType" => "text/html",
1718 "href" => "https://peertube.-2d4c2d1630e3"
1719 },
1720 %{
1721 "type" => "Link",
1722 "mimeType" => "text/html",
1723 "href" => "https://peertube.-2d4c2d16377-42"
1724 }
1725 ]
1726 }
1727
1728 assert Transmogrifier.fix_url(object) == %{
1729 "attachment" => [
1730 %{
1731 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1732 "mimeType" => "video/mp4",
1733 "type" => "Link"
1734 }
1735 ],
1736 "type" => "Video",
1737 "url" => "https://peertube.-2d4c2d1630e3"
1738 }
1739 end
1740
1741 test "fixes url for not Video object" do
1742 object = %{
1743 "type" => "Text",
1744 "url" => [
1745 %{
1746 "type" => "Link",
1747 "mimeType" => "text/html",
1748 "href" => "https://peertube.-2d4c2d1630e3"
1749 },
1750 %{
1751 "type" => "Link",
1752 "mimeType" => "text/html",
1753 "href" => "https://peertube.-2d4c2d16377-42"
1754 }
1755 ]
1756 }
1757
1758 assert Transmogrifier.fix_url(object) == %{
1759 "type" => "Text",
1760 "url" => "https://peertube.-2d4c2d1630e3"
1761 }
1762
1763 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1764 "type" => "Text",
1765 "url" => ""
1766 }
1767 end
1768
1769 test "retunrs not modified object" do
1770 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1771 end
1772 end
1773
1774 describe "get_obj_helper/2" do
1775 test "returns nil when cannot normalize object" do
1776 assert capture_log(fn ->
1777 refute Transmogrifier.get_obj_helper("test-obj-id")
1778 end) =~ "Unsupported URI scheme"
1779 end
1780
1781 @tag capture_log: true
1782 test "returns {:ok, %Object{}} for success case" do
1783 assert {:ok, %Object{}} =
1784 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1785 end
1786 end
1787
1788 describe "fix_attachments/1" do
1789 test "returns not modified object" do
1790 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1791 assert Transmogrifier.fix_attachments(data) == data
1792 end
1793
1794 test "returns modified object when attachment is map" do
1795 assert Transmogrifier.fix_attachments(%{
1796 "attachment" => %{
1797 "mediaType" => "video/mp4",
1798 "url" => "https://peertube.moe/stat-480.mp4"
1799 }
1800 }) == %{
1801 "attachment" => [
1802 %{
1803 "mediaType" => "video/mp4",
1804 "url" => [
1805 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1806 ]
1807 }
1808 ]
1809 }
1810 end
1811
1812 test "returns modified object when attachment is list" do
1813 assert Transmogrifier.fix_attachments(%{
1814 "attachment" => [
1815 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1816 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1817 ]
1818 }) == %{
1819 "attachment" => [
1820 %{
1821 "mediaType" => "video/mp4",
1822 "url" => [
1823 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1824 ]
1825 },
1826 %{
1827 "mediaType" => "video/mp4",
1828 "url" => [
1829 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1830 ]
1831 }
1832 ]
1833 }
1834 end
1835 end
1836
1837 describe "fix_emoji/1" do
1838 test "returns not modified object when object not contains tags" do
1839 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1840 assert Transmogrifier.fix_emoji(data) == data
1841 end
1842
1843 test "returns object with emoji when object contains list tags" do
1844 assert Transmogrifier.fix_emoji(%{
1845 "tag" => [
1846 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1847 %{"type" => "Hashtag"}
1848 ]
1849 }) == %{
1850 "emoji" => %{"bib" => "/test"},
1851 "tag" => [
1852 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1853 %{"type" => "Hashtag"}
1854 ]
1855 }
1856 end
1857
1858 test "returns object with emoji when object contains map tag" do
1859 assert Transmogrifier.fix_emoji(%{
1860 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1861 }) == %{
1862 "emoji" => %{"bib" => "/test"},
1863 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1864 }
1865 end
1866 end
1867
1868 describe "set_replies/1" do
1869 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1870
1871 test "returns unmodified object if activity doesn't have self-replies" do
1872 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1873 assert Transmogrifier.set_replies(data) == data
1874 end
1875
1876 test "sets `replies` collection with a limited number of self-replies" do
1877 [user, another_user] = insert_list(2, :user)
1878
1879 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1880
1881 {:ok, %{id: id2} = self_reply1} =
1882 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1883
1884 {:ok, self_reply2} =
1885 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1886
1887 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1888 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1889
1890 {:ok, _} =
1891 CommonAPI.post(user, %{
1892 status: "self-reply to self-reply",
1893 in_reply_to_status_id: id2
1894 })
1895
1896 {:ok, _} =
1897 CommonAPI.post(another_user, %{
1898 status: "another user's reply",
1899 in_reply_to_status_id: id1
1900 })
1901
1902 object = Object.normalize(activity)
1903 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1904
1905 assert %{"type" => "Collection", "items" => ^replies_uris} =
1906 Transmogrifier.set_replies(object.data)["replies"]
1907 end
1908 end
1909
1910 test "take_emoji_tags/1" do
1911 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1912
1913 assert Transmogrifier.take_emoji_tags(user) == [
1914 %{
1915 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1916 "id" => "https://example.org/firefox.png",
1917 "name" => ":firefox:",
1918 "type" => "Emoji",
1919 "updated" => "1970-01-01T00:00:00Z"
1920 }
1921 ]
1922 end
1923 end