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