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