34e77fa79efe59eadf620f5112f9dcd67e2854c7
[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 end
837
838 test "it fails for incoming accepts which cannot be correlated" do
839 follower = insert(:user)
840 followed = insert(:user, locked: true)
841
842 accept_data =
843 File.read!("test/fixtures/mastodon-accept-activity.json")
844 |> Poison.decode!()
845 |> Map.put("actor", followed.ap_id)
846
847 accept_data =
848 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
849
850 :error = Transmogrifier.handle_incoming(accept_data)
851
852 follower = User.get_cached_by_id(follower.id)
853
854 refute User.following?(follower, followed) == true
855 end
856
857 test "it fails for incoming rejects which cannot be correlated" do
858 follower = insert(:user)
859 followed = insert(:user, locked: true)
860
861 accept_data =
862 File.read!("test/fixtures/mastodon-reject-activity.json")
863 |> Poison.decode!()
864 |> Map.put("actor", followed.ap_id)
865
866 accept_data =
867 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
868
869 :error = Transmogrifier.handle_incoming(accept_data)
870
871 follower = User.get_cached_by_id(follower.id)
872
873 refute User.following?(follower, followed) == true
874 end
875
876 test "it works for incoming rejects which are orphaned" do
877 follower = insert(:user)
878 followed = insert(:user, locked: true)
879
880 {:ok, follower} = User.follow(follower, followed)
881 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
882
883 assert User.following?(follower, followed) == true
884
885 reject_data =
886 File.read!("test/fixtures/mastodon-reject-activity.json")
887 |> Poison.decode!()
888 |> Map.put("actor", followed.ap_id)
889
890 reject_data =
891 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
892
893 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
894 refute activity.local
895 assert activity.data["id"] == reject_data["id"]
896
897 follower = User.get_cached_by_id(follower.id)
898
899 assert User.following?(follower, followed) == false
900 end
901
902 test "it works for incoming rejects which are referenced by IRI only" do
903 follower = insert(:user)
904 followed = insert(:user, locked: true)
905
906 {:ok, follower} = User.follow(follower, followed)
907 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
908
909 assert User.following?(follower, followed) == true
910
911 reject_data =
912 File.read!("test/fixtures/mastodon-reject-activity.json")
913 |> Poison.decode!()
914 |> Map.put("actor", followed.ap_id)
915 |> Map.put("object", follow_activity.data["id"])
916
917 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
918
919 follower = User.get_cached_by_id(follower.id)
920
921 assert User.following?(follower, followed) == false
922 end
923
924 test "it rejects activities without a valid ID" do
925 user = insert(:user)
926
927 data =
928 File.read!("test/fixtures/mastodon-follow-activity.json")
929 |> Poison.decode!()
930 |> Map.put("object", user.ap_id)
931 |> Map.put("id", "")
932
933 :error = Transmogrifier.handle_incoming(data)
934 end
935
936 test "skip converting the content when it is nil" do
937 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
938
939 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
940
941 result =
942 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
943
944 assert result["content"] == nil
945 end
946
947 test "it converts content of object to html" do
948 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
949
950 {:ok, %{"content" => content_markdown}} =
951 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
952
953 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
954 Fetcher.fetch_object_from_id(object_id)
955
956 assert content_markdown ==
957 "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..."
958
959 assert content ==
960 "<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>"
961
962 assert object.data["mediaType"] == "text/html"
963 end
964
965 test "it remaps video URLs as attachments if necessary" do
966 {:ok, object} =
967 Fetcher.fetch_object_from_id(
968 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
969 )
970
971 attachment = %{
972 "type" => "Link",
973 "mediaType" => "video/mp4",
974 "url" => [
975 %{
976 "href" =>
977 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
978 "mediaType" => "video/mp4"
979 }
980 ]
981 }
982
983 assert object.data["url"] ==
984 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
985
986 assert object.data["attachment"] == [attachment]
987 end
988
989 test "it accepts Flag activities" do
990 user = insert(:user)
991 other_user = insert(:user)
992
993 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
994 object = Object.normalize(activity)
995
996 note_obj = %{
997 "type" => "Note",
998 "id" => activity.data["id"],
999 "content" => "test post",
1000 "published" => object.data["published"],
1001 "actor" => AccountView.render("show.json", %{user: user})
1002 }
1003
1004 message = %{
1005 "@context" => "https://www.w3.org/ns/activitystreams",
1006 "cc" => [user.ap_id],
1007 "object" => [user.ap_id, activity.data["id"]],
1008 "type" => "Flag",
1009 "content" => "blocked AND reported!!!",
1010 "actor" => other_user.ap_id
1011 }
1012
1013 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1014
1015 assert activity.data["object"] == [user.ap_id, note_obj]
1016 assert activity.data["content"] == "blocked AND reported!!!"
1017 assert activity.data["actor"] == other_user.ap_id
1018 assert activity.data["cc"] == [user.ap_id]
1019 end
1020
1021 test "it correctly processes messages with non-array to field" do
1022 user = insert(:user)
1023
1024 message = %{
1025 "@context" => "https://www.w3.org/ns/activitystreams",
1026 "to" => "https://www.w3.org/ns/activitystreams#Public",
1027 "type" => "Create",
1028 "object" => %{
1029 "content" => "blah blah blah",
1030 "type" => "Note",
1031 "attributedTo" => user.ap_id,
1032 "inReplyTo" => nil
1033 },
1034 "actor" => user.ap_id
1035 }
1036
1037 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1038
1039 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1040 end
1041
1042 test "it correctly processes messages with non-array cc field" do
1043 user = insert(:user)
1044
1045 message = %{
1046 "@context" => "https://www.w3.org/ns/activitystreams",
1047 "to" => user.follower_address,
1048 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1049 "type" => "Create",
1050 "object" => %{
1051 "content" => "blah blah blah",
1052 "type" => "Note",
1053 "attributedTo" => user.ap_id,
1054 "inReplyTo" => nil
1055 },
1056 "actor" => user.ap_id
1057 }
1058
1059 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1060
1061 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1062 assert [user.follower_address] == activity.data["to"]
1063 end
1064
1065 test "it accepts Move activities" do
1066 old_user = insert(:user)
1067 new_user = insert(:user)
1068
1069 message = %{
1070 "@context" => "https://www.w3.org/ns/activitystreams",
1071 "type" => "Move",
1072 "actor" => old_user.ap_id,
1073 "object" => old_user.ap_id,
1074 "target" => new_user.ap_id
1075 }
1076
1077 assert :error = Transmogrifier.handle_incoming(message)
1078
1079 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1080
1081 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1082 assert activity.actor == old_user.ap_id
1083 assert activity.data["actor"] == old_user.ap_id
1084 assert activity.data["object"] == old_user.ap_id
1085 assert activity.data["target"] == new_user.ap_id
1086 assert activity.data["type"] == "Move"
1087 end
1088 end
1089
1090 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1091 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1092 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1093
1094 setup do
1095 data =
1096 "test/fixtures/mastodon-post-activity.json"
1097 |> File.read!()
1098 |> Poison.decode!()
1099
1100 items = get_in(data, ["object", "replies", "first", "items"])
1101 assert length(items) > 0
1102
1103 %{data: data, items: items}
1104 end
1105
1106 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1107 data: data,
1108 items: items
1109 } do
1110 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1111
1112 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1113
1114 for id <- items do
1115 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1116 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1117 end
1118 end
1119
1120 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1121 %{data: data} do
1122 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1123
1124 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1125
1126 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1127 end
1128 end
1129
1130 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1131 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1132 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1133
1134 setup do
1135 user = insert(:user)
1136
1137 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1138
1139 {:ok, reply1} =
1140 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1141
1142 {:ok, reply2} =
1143 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1144
1145 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1146
1147 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1148
1149 Repo.delete(activity.object)
1150 Repo.delete(activity)
1151
1152 %{federation_output: federation_output, replies_uris: replies_uris}
1153 end
1154
1155 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1156 federation_output: federation_output,
1157 replies_uris: replies_uris
1158 } do
1159 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1160
1161 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1162
1163 for id <- replies_uris do
1164 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1165 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1166 end
1167 end
1168
1169 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1170 %{federation_output: federation_output} do
1171 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1172
1173 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1174
1175 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1176 end
1177 end
1178
1179 describe "prepare outgoing" do
1180 test "it inlines private announced objects" do
1181 user = insert(:user)
1182
1183 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1184
1185 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1186
1187 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1188
1189 assert modified["object"]["content"] == "hey"
1190 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1191 end
1192
1193 test "it turns mentions into tags" do
1194 user = insert(:user)
1195 other_user = insert(:user)
1196
1197 {:ok, activity} =
1198 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1199
1200 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1201 object = modified["object"]
1202
1203 expected_mention = %{
1204 "href" => other_user.ap_id,
1205 "name" => "@#{other_user.nickname}",
1206 "type" => "Mention"
1207 }
1208
1209 expected_tag = %{
1210 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1211 "type" => "Hashtag",
1212 "name" => "#2hu"
1213 }
1214
1215 assert Enum.member?(object["tag"], expected_tag)
1216 assert Enum.member?(object["tag"], expected_mention)
1217 end
1218
1219 test "it adds the sensitive property" do
1220 user = insert(:user)
1221
1222 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1223 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1224
1225 assert modified["object"]["sensitive"]
1226 end
1227
1228 test "it adds the json-ld context and the conversation property" do
1229 user = insert(:user)
1230
1231 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1232 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1233
1234 assert modified["@context"] ==
1235 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1236
1237 assert modified["object"]["conversation"] == modified["context"]
1238 end
1239
1240 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1241 user = insert(:user)
1242
1243 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1244 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1245
1246 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1247 end
1248
1249 test "it strips internal hashtag data" do
1250 user = insert(:user)
1251
1252 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1253
1254 expected_tag = %{
1255 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1256 "type" => "Hashtag",
1257 "name" => "#2hu"
1258 }
1259
1260 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1261
1262 assert modified["object"]["tag"] == [expected_tag]
1263 end
1264
1265 test "it strips internal fields" do
1266 user = insert(:user)
1267
1268 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1269
1270 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1271
1272 assert length(modified["object"]["tag"]) == 2
1273
1274 assert is_nil(modified["object"]["emoji"])
1275 assert is_nil(modified["object"]["like_count"])
1276 assert is_nil(modified["object"]["announcements"])
1277 assert is_nil(modified["object"]["announcement_count"])
1278 assert is_nil(modified["object"]["context_id"])
1279 end
1280
1281 test "it strips internal fields of article" do
1282 activity = insert(:article_activity)
1283
1284 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1285
1286 assert length(modified["object"]["tag"]) == 2
1287
1288 assert is_nil(modified["object"]["emoji"])
1289 assert is_nil(modified["object"]["like_count"])
1290 assert is_nil(modified["object"]["announcements"])
1291 assert is_nil(modified["object"]["announcement_count"])
1292 assert is_nil(modified["object"]["context_id"])
1293 assert is_nil(modified["object"]["likes"])
1294 end
1295
1296 test "the directMessage flag is present" do
1297 user = insert(:user)
1298 other_user = insert(:user)
1299
1300 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1301
1302 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1303
1304 assert modified["directMessage"] == false
1305
1306 {:ok, activity} =
1307 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1308
1309 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1310
1311 assert modified["directMessage"] == false
1312
1313 {:ok, activity} =
1314 CommonAPI.post(user, %{
1315 "status" => "@#{other_user.nickname} :moominmamma:",
1316 "visibility" => "direct"
1317 })
1318
1319 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1320
1321 assert modified["directMessage"] == true
1322 end
1323
1324 test "it strips BCC field" do
1325 user = insert(:user)
1326 {:ok, list} = Pleroma.List.create("foo", user)
1327
1328 {:ok, activity} =
1329 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1330
1331 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1332
1333 assert is_nil(modified["bcc"])
1334 end
1335
1336 test "it can handle Listen activities" do
1337 listen_activity = insert(:listen)
1338
1339 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1340
1341 assert modified["type"] == "Listen"
1342
1343 user = insert(:user)
1344
1345 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1346
1347 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1348 end
1349 end
1350
1351 describe "user upgrade" do
1352 test "it upgrades a user to activitypub" do
1353 user =
1354 insert(:user, %{
1355 nickname: "rye@niu.moe",
1356 local: false,
1357 ap_id: "https://niu.moe/users/rye",
1358 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1359 })
1360
1361 user_two = insert(:user)
1362 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1363
1364 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1365 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1366 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1367
1368 user = User.get_cached_by_id(user.id)
1369 assert user.note_count == 1
1370
1371 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1372 ObanHelpers.perform_all()
1373
1374 assert user.ap_enabled
1375 assert user.note_count == 1
1376 assert user.follower_address == "https://niu.moe/users/rye/followers"
1377 assert user.following_address == "https://niu.moe/users/rye/following"
1378
1379 user = User.get_cached_by_id(user.id)
1380 assert user.note_count == 1
1381
1382 activity = Activity.get_by_id(activity.id)
1383 assert user.follower_address in activity.recipients
1384
1385 assert %{
1386 "url" => [
1387 %{
1388 "href" =>
1389 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1390 }
1391 ]
1392 } = user.avatar
1393
1394 assert %{
1395 "url" => [
1396 %{
1397 "href" =>
1398 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1399 }
1400 ]
1401 } = user.banner
1402
1403 refute "..." in activity.recipients
1404
1405 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1406 refute user.follower_address in unrelated_activity.recipients
1407
1408 user_two = User.get_cached_by_id(user_two.id)
1409 assert User.following?(user_two, user)
1410 refute "..." in User.following(user_two)
1411 end
1412 end
1413
1414 describe "actor rewriting" do
1415 test "it fixes the actor URL property to be a proper URI" do
1416 data = %{
1417 "url" => %{"href" => "http://example.com"}
1418 }
1419
1420 rewritten = Transmogrifier.maybe_fix_user_object(data)
1421 assert rewritten["url"] == "http://example.com"
1422 end
1423 end
1424
1425 describe "actor origin containment" do
1426 test "it rejects activities which reference objects with bogus origins" do
1427 data = %{
1428 "@context" => "https://www.w3.org/ns/activitystreams",
1429 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1430 "actor" => "http://mastodon.example.org/users/admin",
1431 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1432 "object" => "https://info.pleroma.site/activity.json",
1433 "type" => "Announce"
1434 }
1435
1436 assert capture_log(fn ->
1437 :error = Transmogrifier.handle_incoming(data)
1438 end) =~ "Object containment failed"
1439 end
1440
1441 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1442 data = %{
1443 "@context" => "https://www.w3.org/ns/activitystreams",
1444 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1445 "actor" => "http://mastodon.example.org/users/admin",
1446 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1447 "object" => "https://info.pleroma.site/activity2.json",
1448 "type" => "Announce"
1449 }
1450
1451 assert capture_log(fn ->
1452 :error = Transmogrifier.handle_incoming(data)
1453 end) =~ "Object containment failed"
1454 end
1455
1456 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1457 data = %{
1458 "@context" => "https://www.w3.org/ns/activitystreams",
1459 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1460 "actor" => "http://mastodon.example.org/users/admin",
1461 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1462 "object" => "https://info.pleroma.site/activity3.json",
1463 "type" => "Announce"
1464 }
1465
1466 assert capture_log(fn ->
1467 :error = Transmogrifier.handle_incoming(data)
1468 end) =~ "Object containment failed"
1469 end
1470 end
1471
1472 describe "reserialization" do
1473 test "successfully reserializes a message with inReplyTo == nil" do
1474 user = insert(:user)
1475
1476 message = %{
1477 "@context" => "https://www.w3.org/ns/activitystreams",
1478 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1479 "cc" => [],
1480 "type" => "Create",
1481 "object" => %{
1482 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1483 "cc" => [],
1484 "type" => "Note",
1485 "content" => "Hi",
1486 "inReplyTo" => nil,
1487 "attributedTo" => user.ap_id
1488 },
1489 "actor" => user.ap_id
1490 }
1491
1492 {:ok, activity} = Transmogrifier.handle_incoming(message)
1493
1494 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1495 end
1496
1497 test "successfully reserializes a message with AS2 objects in IR" do
1498 user = insert(:user)
1499
1500 message = %{
1501 "@context" => "https://www.w3.org/ns/activitystreams",
1502 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1503 "cc" => [],
1504 "type" => "Create",
1505 "object" => %{
1506 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1507 "cc" => [],
1508 "type" => "Note",
1509 "content" => "Hi",
1510 "inReplyTo" => nil,
1511 "attributedTo" => user.ap_id,
1512 "tag" => [
1513 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1514 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1515 ]
1516 },
1517 "actor" => user.ap_id
1518 }
1519
1520 {:ok, activity} = Transmogrifier.handle_incoming(message)
1521
1522 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1523 end
1524 end
1525
1526 test "Rewrites Answers to Notes" do
1527 user = insert(:user)
1528
1529 {:ok, poll_activity} =
1530 CommonAPI.post(user, %{
1531 "status" => "suya...",
1532 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1533 })
1534
1535 poll_object = Object.normalize(poll_activity)
1536 # TODO: Replace with CommonAPI vote creation when implemented
1537 data =
1538 File.read!("test/fixtures/mastodon-vote.json")
1539 |> Poison.decode!()
1540 |> Kernel.put_in(["to"], user.ap_id)
1541 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1542 |> Kernel.put_in(["object", "to"], user.ap_id)
1543
1544 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1545 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1546
1547 assert data["object"]["type"] == "Note"
1548 end
1549
1550 describe "fix_explicit_addressing" do
1551 setup do
1552 user = insert(:user)
1553 [user: user]
1554 end
1555
1556 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1557 explicitly_mentioned_actors = [
1558 "https://pleroma.gold/users/user1",
1559 "https://pleroma.gold/user2"
1560 ]
1561
1562 object = %{
1563 "actor" => user.ap_id,
1564 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1565 "cc" => [],
1566 "tag" =>
1567 Enum.map(explicitly_mentioned_actors, fn href ->
1568 %{"type" => "Mention", "href" => href}
1569 end)
1570 }
1571
1572 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1573 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1574 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1575 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1576 end
1577
1578 test "does not move actor's follower collection to cc", %{user: user} do
1579 object = %{
1580 "actor" => user.ap_id,
1581 "to" => [user.follower_address],
1582 "cc" => []
1583 }
1584
1585 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1586 assert user.follower_address in fixed_object["to"]
1587 refute user.follower_address in fixed_object["cc"]
1588 end
1589
1590 test "removes recipient's follower collection from cc", %{user: user} do
1591 recipient = insert(:user)
1592
1593 object = %{
1594 "actor" => user.ap_id,
1595 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1596 "cc" => [user.follower_address, recipient.follower_address]
1597 }
1598
1599 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1600
1601 assert user.follower_address in fixed_object["cc"]
1602 refute recipient.follower_address in fixed_object["cc"]
1603 refute recipient.follower_address in fixed_object["to"]
1604 end
1605 end
1606
1607 describe "fix_summary/1" do
1608 test "returns fixed object" do
1609 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1610 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1611 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1612 end
1613 end
1614
1615 describe "fix_in_reply_to/2" do
1616 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1617
1618 setup do
1619 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1620 [data: data]
1621 end
1622
1623 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1624 assert Transmogrifier.fix_in_reply_to(data) == data
1625 end
1626
1627 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1628 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1629
1630 object_with_reply =
1631 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1632
1633 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1634 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1635 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1636
1637 object_with_reply =
1638 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1639
1640 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1641 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1642 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1643
1644 object_with_reply =
1645 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1646
1647 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1648 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1649 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1650
1651 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1652 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1653 assert modified_object["inReplyTo"] == []
1654 assert modified_object["inReplyToAtomUri"] == ""
1655 end
1656
1657 @tag capture_log: true
1658 test "returns modified object when allowed incoming reply", %{data: data} do
1659 object_with_reply =
1660 Map.put(
1661 data["object"],
1662 "inReplyTo",
1663 "https://shitposter.club/notice/2827873"
1664 )
1665
1666 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1667 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1668
1669 assert modified_object["inReplyTo"] ==
1670 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1671
1672 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1673
1674 assert modified_object["conversation"] ==
1675 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1676
1677 assert modified_object["context"] ==
1678 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1679 end
1680 end
1681
1682 describe "fix_url/1" do
1683 test "fixes data for object when url is map" do
1684 object = %{
1685 "url" => %{
1686 "type" => "Link",
1687 "mimeType" => "video/mp4",
1688 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1689 }
1690 }
1691
1692 assert Transmogrifier.fix_url(object) == %{
1693 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1694 }
1695 end
1696
1697 test "fixes data for video object" do
1698 object = %{
1699 "type" => "Video",
1700 "url" => [
1701 %{
1702 "type" => "Link",
1703 "mimeType" => "video/mp4",
1704 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1705 },
1706 %{
1707 "type" => "Link",
1708 "mimeType" => "video/mp4",
1709 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1710 },
1711 %{
1712 "type" => "Link",
1713 "mimeType" => "text/html",
1714 "href" => "https://peertube.-2d4c2d1630e3"
1715 },
1716 %{
1717 "type" => "Link",
1718 "mimeType" => "text/html",
1719 "href" => "https://peertube.-2d4c2d16377-42"
1720 }
1721 ]
1722 }
1723
1724 assert Transmogrifier.fix_url(object) == %{
1725 "attachment" => [
1726 %{
1727 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1728 "mimeType" => "video/mp4",
1729 "type" => "Link"
1730 }
1731 ],
1732 "type" => "Video",
1733 "url" => "https://peertube.-2d4c2d1630e3"
1734 }
1735 end
1736
1737 test "fixes url for not Video object" do
1738 object = %{
1739 "type" => "Text",
1740 "url" => [
1741 %{
1742 "type" => "Link",
1743 "mimeType" => "text/html",
1744 "href" => "https://peertube.-2d4c2d1630e3"
1745 },
1746 %{
1747 "type" => "Link",
1748 "mimeType" => "text/html",
1749 "href" => "https://peertube.-2d4c2d16377-42"
1750 }
1751 ]
1752 }
1753
1754 assert Transmogrifier.fix_url(object) == %{
1755 "type" => "Text",
1756 "url" => "https://peertube.-2d4c2d1630e3"
1757 }
1758
1759 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1760 "type" => "Text",
1761 "url" => ""
1762 }
1763 end
1764
1765 test "retunrs not modified object" do
1766 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1767 end
1768 end
1769
1770 describe "get_obj_helper/2" do
1771 test "returns nil when cannot normalize object" do
1772 assert capture_log(fn ->
1773 refute Transmogrifier.get_obj_helper("test-obj-id")
1774 end) =~ "Unsupported URI scheme"
1775 end
1776
1777 @tag capture_log: true
1778 test "returns {:ok, %Object{}} for success case" do
1779 assert {:ok, %Object{}} =
1780 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1781 end
1782 end
1783
1784 describe "fix_attachments/1" do
1785 test "returns not modified object" do
1786 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1787 assert Transmogrifier.fix_attachments(data) == data
1788 end
1789
1790 test "returns modified object when attachment is map" do
1791 assert Transmogrifier.fix_attachments(%{
1792 "attachment" => %{
1793 "mediaType" => "video/mp4",
1794 "url" => "https://peertube.moe/stat-480.mp4"
1795 }
1796 }) == %{
1797 "attachment" => [
1798 %{
1799 "mediaType" => "video/mp4",
1800 "url" => [
1801 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1802 ]
1803 }
1804 ]
1805 }
1806 end
1807
1808 test "returns modified object when attachment is list" do
1809 assert Transmogrifier.fix_attachments(%{
1810 "attachment" => [
1811 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1812 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1813 ]
1814 }) == %{
1815 "attachment" => [
1816 %{
1817 "mediaType" => "video/mp4",
1818 "url" => [
1819 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1820 ]
1821 },
1822 %{
1823 "mediaType" => "video/mp4",
1824 "url" => [
1825 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1826 ]
1827 }
1828 ]
1829 }
1830 end
1831 end
1832
1833 describe "fix_emoji/1" do
1834 test "returns not modified object when object not contains tags" do
1835 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1836 assert Transmogrifier.fix_emoji(data) == data
1837 end
1838
1839 test "returns object with emoji when object contains list tags" do
1840 assert Transmogrifier.fix_emoji(%{
1841 "tag" => [
1842 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1843 %{"type" => "Hashtag"}
1844 ]
1845 }) == %{
1846 "emoji" => %{"bib" => "/test"},
1847 "tag" => [
1848 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1849 %{"type" => "Hashtag"}
1850 ]
1851 }
1852 end
1853
1854 test "returns object with emoji when object contains map tag" do
1855 assert Transmogrifier.fix_emoji(%{
1856 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1857 }) == %{
1858 "emoji" => %{"bib" => "/test"},
1859 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1860 }
1861 end
1862 end
1863
1864 describe "set_replies/1" do
1865 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1866
1867 test "returns unmodified object if activity doesn't have self-replies" do
1868 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1869 assert Transmogrifier.set_replies(data) == data
1870 end
1871
1872 test "sets `replies` collection with a limited number of self-replies" do
1873 [user, another_user] = insert_list(2, :user)
1874
1875 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
1876
1877 {:ok, %{id: id2} = self_reply1} =
1878 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
1879
1880 {:ok, self_reply2} =
1881 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
1882
1883 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1884 {:ok, _} =
1885 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
1886
1887 {:ok, _} =
1888 CommonAPI.post(user, %{
1889 "status" => "self-reply to self-reply",
1890 "in_reply_to_status_id" => id2
1891 })
1892
1893 {:ok, _} =
1894 CommonAPI.post(another_user, %{
1895 "status" => "another user's reply",
1896 "in_reply_to_status_id" => id1
1897 })
1898
1899 object = Object.normalize(activity)
1900 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1901
1902 assert %{"type" => "Collection", "items" => ^replies_uris} =
1903 Transmogrifier.set_replies(object.data)["replies"]
1904 end
1905 end
1906
1907 test "take_emoji_tags/1" do
1908 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1909
1910 assert Transmogrifier.take_emoji_tags(user) == [
1911 %{
1912 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1913 "id" => "https://example.org/firefox.png",
1914 "name" => ":firefox:",
1915 "type" => "Emoji",
1916 "updated" => "1970-01-01T00:00:00Z"
1917 }
1918 ]
1919 end
1920 end