Pipeline Ingestion: Video
[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.Tests.ObanHelpers
12 alias Pleroma.User
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.CommonAPI
16
17 import Mock
18 import Pleroma.Factory
19 import ExUnit.CaptureLog
20
21 setup_all do
22 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
23 :ok
24 end
25
26 setup do: clear_config([:instance, :max_remote_account_fields])
27
28 describe "handle_incoming" do
29 test "it works for incoming notices with tag not being an array (kroeg)" do
30 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
31
32 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
33 object = Object.normalize(data["object"])
34
35 assert object.data["emoji"] == %{
36 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
37 }
38
39 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
40
41 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
42 object = Object.normalize(data["object"])
43
44 assert "test" in object.data["tag"]
45 end
46
47 test "it works for incoming notices with url not being a string (prismo)" do
48 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
49
50 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
51 object = Object.normalize(data["object"])
52
53 assert object.data["url"] == "https://prismo.news/posts/83"
54 end
55
56 test "it cleans up incoming notices which are not really DMs" do
57 user = insert(:user)
58 other_user = insert(:user)
59
60 to = [user.ap_id, other_user.ap_id]
61
62 data =
63 File.read!("test/fixtures/mastodon-post-activity.json")
64 |> Poison.decode!()
65 |> Map.put("to", to)
66 |> Map.put("cc", [])
67
68 object =
69 data["object"]
70 |> Map.put("to", to)
71 |> Map.put("cc", [])
72
73 data = Map.put(data, "object", object)
74
75 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
76
77 assert data["to"] == []
78 assert data["cc"] == to
79
80 object_data = Object.normalize(activity).data
81
82 assert object_data["to"] == []
83 assert object_data["cc"] == to
84 end
85
86 test "it ignores an incoming notice if we already have it" do
87 activity = insert(:note_activity)
88
89 data =
90 File.read!("test/fixtures/mastodon-post-activity.json")
91 |> Poison.decode!()
92 |> Map.put("object", Object.normalize(activity).data)
93
94 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
95
96 assert activity == returned_activity
97 end
98
99 @tag capture_log: true
100 test "it fetches reply-to activities if we don't have them" do
101 data =
102 File.read!("test/fixtures/mastodon-post-activity.json")
103 |> Poison.decode!()
104
105 object =
106 data["object"]
107 |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394")
108
109 data = Map.put(data, "object", object)
110 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
111 returned_object = Object.normalize(returned_activity, false)
112
113 assert activity =
114 Activity.get_create_by_object_ap_id(
115 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
116 )
117
118 assert returned_object.data["inReplyTo"] ==
119 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
120 end
121
122 test "it does not fetch reply-to activities beyond max replies depth limit" do
123 data =
124 File.read!("test/fixtures/mastodon-post-activity.json")
125 |> Poison.decode!()
126
127 object =
128 data["object"]
129 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
130
131 data = Map.put(data, "object", object)
132
133 with_mock Pleroma.Web.Federator,
134 allowed_thread_distance?: fn _ -> false end do
135 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
136
137 returned_object = Object.normalize(returned_activity, false)
138
139 refute Activity.get_create_by_object_ap_id(
140 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
141 )
142
143 assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873"
144 end
145 end
146
147 test "it does not crash if the object in inReplyTo can't be fetched" do
148 data =
149 File.read!("test/fixtures/mastodon-post-activity.json")
150 |> Poison.decode!()
151
152 object =
153 data["object"]
154 |> Map.put("inReplyTo", "https://404.site/whatever")
155
156 data =
157 data
158 |> Map.put("object", object)
159
160 assert capture_log(fn ->
161 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
162 end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
163 end
164
165 test "it does not work for deactivated users" do
166 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
167
168 insert(:user, ap_id: data["actor"], deactivated: true)
169
170 assert {:error, _} = Transmogrifier.handle_incoming(data)
171 end
172
173 test "it works for incoming notices" do
174 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
175
176 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
177
178 assert data["id"] ==
179 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
180
181 assert data["context"] ==
182 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
183
184 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
185
186 assert data["cc"] == [
187 "http://mastodon.example.org/users/admin/followers",
188 "http://localtesting.pleroma.lol/users/lain"
189 ]
190
191 assert data["actor"] == "http://mastodon.example.org/users/admin"
192
193 object_data = Object.normalize(data["object"]).data
194
195 assert object_data["id"] ==
196 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
197
198 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
199
200 assert object_data["cc"] == [
201 "http://mastodon.example.org/users/admin/followers",
202 "http://localtesting.pleroma.lol/users/lain"
203 ]
204
205 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
206 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
207
208 assert object_data["context"] ==
209 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
210
211 assert object_data["sensitive"] == true
212
213 user = User.get_cached_by_ap_id(object_data["actor"])
214
215 assert user.note_count == 1
216 end
217
218 test "it works for incoming notices with hashtags" do
219 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
220
221 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
222 object = Object.normalize(data["object"])
223
224 assert Enum.at(object.data["tag"], 2) == "moo"
225 end
226
227 test "it works for incoming notices with contentMap" do
228 data =
229 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
230
231 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
232 object = Object.normalize(data["object"])
233
234 assert object.data["content"] ==
235 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
236 end
237
238 test "it works for incoming notices with to/cc not being an array (kroeg)" do
239 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
240
241 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
242 object = Object.normalize(data["object"])
243
244 assert object.data["content"] ==
245 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
246 end
247
248 test "it ensures that as:Public activities make it to their followers collection" do
249 user = insert(:user)
250
251 data =
252 File.read!("test/fixtures/mastodon-post-activity.json")
253 |> Poison.decode!()
254 |> Map.put("actor", user.ap_id)
255 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
256 |> Map.put("cc", [])
257
258 object =
259 data["object"]
260 |> Map.put("attributedTo", user.ap_id)
261 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
262 |> Map.put("cc", [])
263 |> Map.put("id", user.ap_id <> "/activities/12345678")
264
265 data = Map.put(data, "object", object)
266
267 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
268
269 assert data["cc"] == [User.ap_followers(user)]
270 end
271
272 test "it ensures that address fields become lists" do
273 user = insert(:user)
274
275 data =
276 File.read!("test/fixtures/mastodon-post-activity.json")
277 |> Poison.decode!()
278 |> Map.put("actor", user.ap_id)
279 |> Map.put("to", nil)
280 |> Map.put("cc", nil)
281
282 object =
283 data["object"]
284 |> Map.put("attributedTo", user.ap_id)
285 |> Map.put("to", nil)
286 |> Map.put("cc", nil)
287 |> Map.put("id", user.ap_id <> "/activities/12345678")
288
289 data = Map.put(data, "object", object)
290
291 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
292
293 assert !is_nil(data["to"])
294 assert !is_nil(data["cc"])
295 end
296
297 test "it strips internal likes" do
298 data =
299 File.read!("test/fixtures/mastodon-post-activity.json")
300 |> Poison.decode!()
301
302 likes = %{
303 "first" =>
304 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
305 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
306 "totalItems" => 3,
307 "type" => "OrderedCollection"
308 }
309
310 object = Map.put(data["object"], "likes", likes)
311 data = Map.put(data, "object", object)
312
313 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
314
315 refute Map.has_key?(object.data, "likes")
316 end
317
318 test "it strips internal reactions" do
319 user = insert(:user)
320 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
321 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
322
323 %{object: object} = Activity.get_by_id_with_object(activity.id)
324 assert Map.has_key?(object.data, "reactions")
325 assert Map.has_key?(object.data, "reaction_count")
326
327 object_data = Transmogrifier.strip_internal_fields(object.data)
328 refute Map.has_key?(object_data, "reactions")
329 refute Map.has_key?(object_data, "reaction_count")
330 end
331
332 test "it works for incoming unfollows with an existing follow" do
333 user = insert(:user)
334
335 follow_data =
336 File.read!("test/fixtures/mastodon-follow-activity.json")
337 |> Poison.decode!()
338 |> Map.put("object", user.ap_id)
339
340 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
341
342 data =
343 File.read!("test/fixtures/mastodon-unfollow-activity.json")
344 |> Poison.decode!()
345 |> Map.put("object", follow_data)
346
347 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
348
349 assert data["type"] == "Undo"
350 assert data["object"]["type"] == "Follow"
351 assert data["object"]["object"] == user.ap_id
352 assert data["actor"] == "http://mastodon.example.org/users/admin"
353
354 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
355 end
356
357 test "it accepts Flag activities" do
358 user = insert(:user)
359 other_user = insert(:user)
360
361 {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
362 object = Object.normalize(activity)
363
364 note_obj = %{
365 "type" => "Note",
366 "id" => activity.data["id"],
367 "content" => "test post",
368 "published" => object.data["published"],
369 "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
370 }
371
372 message = %{
373 "@context" => "https://www.w3.org/ns/activitystreams",
374 "cc" => [user.ap_id],
375 "object" => [user.ap_id, activity.data["id"]],
376 "type" => "Flag",
377 "content" => "blocked AND reported!!!",
378 "actor" => other_user.ap_id
379 }
380
381 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
382
383 assert activity.data["object"] == [user.ap_id, note_obj]
384 assert activity.data["content"] == "blocked AND reported!!!"
385 assert activity.data["actor"] == other_user.ap_id
386 assert activity.data["cc"] == [user.ap_id]
387 end
388
389 test "it correctly processes messages with non-array to field" do
390 user = insert(:user)
391
392 message = %{
393 "@context" => "https://www.w3.org/ns/activitystreams",
394 "to" => "https://www.w3.org/ns/activitystreams#Public",
395 "type" => "Create",
396 "object" => %{
397 "content" => "blah blah blah",
398 "type" => "Note",
399 "attributedTo" => user.ap_id,
400 "inReplyTo" => nil
401 },
402 "actor" => user.ap_id
403 }
404
405 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
406
407 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
408 end
409
410 test "it correctly processes messages with non-array cc field" do
411 user = insert(:user)
412
413 message = %{
414 "@context" => "https://www.w3.org/ns/activitystreams",
415 "to" => user.follower_address,
416 "cc" => "https://www.w3.org/ns/activitystreams#Public",
417 "type" => "Create",
418 "object" => %{
419 "content" => "blah blah blah",
420 "type" => "Note",
421 "attributedTo" => user.ap_id,
422 "inReplyTo" => nil
423 },
424 "actor" => user.ap_id
425 }
426
427 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
428
429 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
430 assert [user.follower_address] == activity.data["to"]
431 end
432
433 test "it correctly processes messages with weirdness in address fields" do
434 user = insert(:user)
435
436 message = %{
437 "@context" => "https://www.w3.org/ns/activitystreams",
438 "to" => [nil, user.follower_address],
439 "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
440 "type" => "Create",
441 "object" => %{
442 "content" => "…",
443 "type" => "Note",
444 "attributedTo" => user.ap_id,
445 "inReplyTo" => nil
446 },
447 "actor" => user.ap_id
448 }
449
450 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
451
452 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
453 assert [user.follower_address] == activity.data["to"]
454 end
455
456 test "it accepts Move activities" do
457 old_user = insert(:user)
458 new_user = insert(:user)
459
460 message = %{
461 "@context" => "https://www.w3.org/ns/activitystreams",
462 "type" => "Move",
463 "actor" => old_user.ap_id,
464 "object" => old_user.ap_id,
465 "target" => new_user.ap_id
466 }
467
468 assert :error = Transmogrifier.handle_incoming(message)
469
470 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
471
472 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
473 assert activity.actor == old_user.ap_id
474 assert activity.data["actor"] == old_user.ap_id
475 assert activity.data["object"] == old_user.ap_id
476 assert activity.data["target"] == new_user.ap_id
477 assert activity.data["type"] == "Move"
478 end
479 end
480
481 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
482 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
483 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
484
485 setup do
486 data =
487 "test/fixtures/mastodon-post-activity.json"
488 |> File.read!()
489 |> Poison.decode!()
490
491 items = get_in(data, ["object", "replies", "first", "items"])
492 assert length(items) > 0
493
494 %{data: data, items: items}
495 end
496
497 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
498 data: data,
499 items: items
500 } do
501 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
502
503 {:ok, _activity} = Transmogrifier.handle_incoming(data)
504
505 for id <- items do
506 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
507 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
508 end
509 end
510
511 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
512 %{data: data} do
513 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
514
515 {:ok, _activity} = Transmogrifier.handle_incoming(data)
516
517 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
518 end
519 end
520
521 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
522 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
523 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
524
525 setup do
526 user = insert(:user)
527
528 {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
529
530 {:ok, reply1} =
531 CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
532
533 {:ok, reply2} =
534 CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
535
536 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
537
538 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
539
540 Repo.delete(activity.object)
541 Repo.delete(activity)
542
543 %{federation_output: federation_output, replies_uris: replies_uris}
544 end
545
546 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
547 federation_output: federation_output,
548 replies_uris: replies_uris
549 } do
550 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
551
552 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
553
554 for id <- replies_uris do
555 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
556 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
557 end
558 end
559
560 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
561 %{federation_output: federation_output} do
562 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
563
564 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
565
566 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
567 end
568 end
569
570 describe "prepare outgoing" do
571 test "it inlines private announced objects" do
572 user = insert(:user)
573
574 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
575
576 {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
577
578 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
579
580 assert modified["object"]["content"] == "hey"
581 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
582 end
583
584 test "it turns mentions into tags" do
585 user = insert(:user)
586 other_user = insert(:user)
587
588 {:ok, activity} =
589 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
590
591 with_mock Pleroma.Notification,
592 get_notified_from_activity: fn _, _ -> [] end do
593 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
594
595 object = modified["object"]
596
597 expected_mention = %{
598 "href" => other_user.ap_id,
599 "name" => "@#{other_user.nickname}",
600 "type" => "Mention"
601 }
602
603 expected_tag = %{
604 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
605 "type" => "Hashtag",
606 "name" => "#2hu"
607 }
608
609 refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
610 assert Enum.member?(object["tag"], expected_tag)
611 assert Enum.member?(object["tag"], expected_mention)
612 end
613 end
614
615 test "it adds the sensitive property" do
616 user = insert(:user)
617
618 {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
619 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
620
621 assert modified["object"]["sensitive"]
622 end
623
624 test "it adds the json-ld context and the conversation property" do
625 user = insert(:user)
626
627 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
628 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
629
630 assert modified["@context"] ==
631 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
632
633 assert modified["object"]["conversation"] == modified["context"]
634 end
635
636 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
637 user = insert(:user)
638
639 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
640 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
641
642 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
643 end
644
645 test "it strips internal hashtag data" do
646 user = insert(:user)
647
648 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
649
650 expected_tag = %{
651 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
652 "type" => "Hashtag",
653 "name" => "#2hu"
654 }
655
656 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
657
658 assert modified["object"]["tag"] == [expected_tag]
659 end
660
661 test "it strips internal fields" do
662 user = insert(:user)
663
664 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
665
666 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
667
668 assert length(modified["object"]["tag"]) == 2
669
670 assert is_nil(modified["object"]["emoji"])
671 assert is_nil(modified["object"]["like_count"])
672 assert is_nil(modified["object"]["announcements"])
673 assert is_nil(modified["object"]["announcement_count"])
674 assert is_nil(modified["object"]["context_id"])
675 end
676
677 test "it strips internal fields of article" do
678 activity = insert(:article_activity)
679
680 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
681
682 assert length(modified["object"]["tag"]) == 2
683
684 assert is_nil(modified["object"]["emoji"])
685 assert is_nil(modified["object"]["like_count"])
686 assert is_nil(modified["object"]["announcements"])
687 assert is_nil(modified["object"]["announcement_count"])
688 assert is_nil(modified["object"]["context_id"])
689 assert is_nil(modified["object"]["likes"])
690 end
691
692 test "the directMessage flag is present" do
693 user = insert(:user)
694 other_user = insert(:user)
695
696 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
697
698 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
699
700 assert modified["directMessage"] == false
701
702 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
703
704 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
705
706 assert modified["directMessage"] == false
707
708 {:ok, activity} =
709 CommonAPI.post(user, %{
710 status: "@#{other_user.nickname} :moominmamma:",
711 visibility: "direct"
712 })
713
714 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
715
716 assert modified["directMessage"] == true
717 end
718
719 test "it strips BCC field" do
720 user = insert(:user)
721 {:ok, list} = Pleroma.List.create("foo", user)
722
723 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
724
725 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
726
727 assert is_nil(modified["bcc"])
728 end
729
730 test "it can handle Listen activities" do
731 listen_activity = insert(:listen)
732
733 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
734
735 assert modified["type"] == "Listen"
736
737 user = insert(:user)
738
739 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
740
741 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
742 end
743 end
744
745 describe "user upgrade" do
746 test "it upgrades a user to activitypub" do
747 user =
748 insert(:user, %{
749 nickname: "rye@niu.moe",
750 local: false,
751 ap_id: "https://niu.moe/users/rye",
752 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
753 })
754
755 user_two = insert(:user)
756 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
757
758 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
759 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
760 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
761
762 user = User.get_cached_by_id(user.id)
763 assert user.note_count == 1
764
765 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
766 ObanHelpers.perform_all()
767
768 assert user.ap_enabled
769 assert user.note_count == 1
770 assert user.follower_address == "https://niu.moe/users/rye/followers"
771 assert user.following_address == "https://niu.moe/users/rye/following"
772
773 user = User.get_cached_by_id(user.id)
774 assert user.note_count == 1
775
776 activity = Activity.get_by_id(activity.id)
777 assert user.follower_address in activity.recipients
778
779 assert %{
780 "url" => [
781 %{
782 "href" =>
783 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
784 }
785 ]
786 } = user.avatar
787
788 assert %{
789 "url" => [
790 %{
791 "href" =>
792 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
793 }
794 ]
795 } = user.banner
796
797 refute "..." in activity.recipients
798
799 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
800 refute user.follower_address in unrelated_activity.recipients
801
802 user_two = User.get_cached_by_id(user_two.id)
803 assert User.following?(user_two, user)
804 refute "..." in User.following(user_two)
805 end
806 end
807
808 describe "actor rewriting" do
809 test "it fixes the actor URL property to be a proper URI" do
810 data = %{
811 "url" => %{"href" => "http://example.com"}
812 }
813
814 rewritten = Transmogrifier.maybe_fix_user_object(data)
815 assert rewritten["url"] == "http://example.com"
816 end
817 end
818
819 describe "actor origin containment" do
820 test "it rejects activities which reference objects with bogus origins" do
821 data = %{
822 "@context" => "https://www.w3.org/ns/activitystreams",
823 "id" => "http://mastodon.example.org/users/admin/activities/1234",
824 "actor" => "http://mastodon.example.org/users/admin",
825 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
826 "object" => "https://info.pleroma.site/activity.json",
827 "type" => "Announce"
828 }
829
830 assert capture_log(fn ->
831 {:error, _} = Transmogrifier.handle_incoming(data)
832 end) =~ "Object containment failed"
833 end
834
835 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
836 data = %{
837 "@context" => "https://www.w3.org/ns/activitystreams",
838 "id" => "http://mastodon.example.org/users/admin/activities/1234",
839 "actor" => "http://mastodon.example.org/users/admin",
840 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
841 "object" => "https://info.pleroma.site/activity2.json",
842 "type" => "Announce"
843 }
844
845 assert capture_log(fn ->
846 {:error, _} = Transmogrifier.handle_incoming(data)
847 end) =~ "Object containment failed"
848 end
849
850 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
851 data = %{
852 "@context" => "https://www.w3.org/ns/activitystreams",
853 "id" => "http://mastodon.example.org/users/admin/activities/1234",
854 "actor" => "http://mastodon.example.org/users/admin",
855 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
856 "object" => "https://info.pleroma.site/activity3.json",
857 "type" => "Announce"
858 }
859
860 assert capture_log(fn ->
861 {:error, _} = Transmogrifier.handle_incoming(data)
862 end) =~ "Object containment failed"
863 end
864 end
865
866 describe "reserialization" do
867 test "successfully reserializes a message with inReplyTo == nil" do
868 user = insert(:user)
869
870 message = %{
871 "@context" => "https://www.w3.org/ns/activitystreams",
872 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
873 "cc" => [],
874 "type" => "Create",
875 "object" => %{
876 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
877 "cc" => [],
878 "type" => "Note",
879 "content" => "Hi",
880 "inReplyTo" => nil,
881 "attributedTo" => user.ap_id
882 },
883 "actor" => user.ap_id
884 }
885
886 {:ok, activity} = Transmogrifier.handle_incoming(message)
887
888 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
889 end
890
891 test "successfully reserializes a message with AS2 objects in IR" do
892 user = insert(:user)
893
894 message = %{
895 "@context" => "https://www.w3.org/ns/activitystreams",
896 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
897 "cc" => [],
898 "type" => "Create",
899 "object" => %{
900 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
901 "cc" => [],
902 "type" => "Note",
903 "content" => "Hi",
904 "inReplyTo" => nil,
905 "attributedTo" => user.ap_id,
906 "tag" => [
907 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
908 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
909 ]
910 },
911 "actor" => user.ap_id
912 }
913
914 {:ok, activity} = Transmogrifier.handle_incoming(message)
915
916 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
917 end
918 end
919
920 describe "fix_explicit_addressing" do
921 setup do
922 user = insert(:user)
923 [user: user]
924 end
925
926 test "moves non-explicitly mentioned actors to cc", %{user: user} do
927 explicitly_mentioned_actors = [
928 "https://pleroma.gold/users/user1",
929 "https://pleroma.gold/user2"
930 ]
931
932 object = %{
933 "actor" => user.ap_id,
934 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
935 "cc" => [],
936 "tag" =>
937 Enum.map(explicitly_mentioned_actors, fn href ->
938 %{"type" => "Mention", "href" => href}
939 end)
940 }
941
942 fixed_object = Transmogrifier.fix_explicit_addressing(object)
943 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
944 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
945 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
946 end
947
948 test "does not move actor's follower collection to cc", %{user: user} do
949 object = %{
950 "actor" => user.ap_id,
951 "to" => [user.follower_address],
952 "cc" => []
953 }
954
955 fixed_object = Transmogrifier.fix_explicit_addressing(object)
956 assert user.follower_address in fixed_object["to"]
957 refute user.follower_address in fixed_object["cc"]
958 end
959
960 test "removes recipient's follower collection from cc", %{user: user} do
961 recipient = insert(:user)
962
963 object = %{
964 "actor" => user.ap_id,
965 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
966 "cc" => [user.follower_address, recipient.follower_address]
967 }
968
969 fixed_object = Transmogrifier.fix_explicit_addressing(object)
970
971 assert user.follower_address in fixed_object["cc"]
972 refute recipient.follower_address in fixed_object["cc"]
973 refute recipient.follower_address in fixed_object["to"]
974 end
975 end
976
977 describe "fix_summary/1" do
978 test "returns fixed object" do
979 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
980 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
981 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
982 end
983 end
984
985 describe "fix_in_reply_to/2" do
986 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
987
988 setup do
989 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
990 [data: data]
991 end
992
993 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
994 assert Transmogrifier.fix_in_reply_to(data) == data
995 end
996
997 test "returns object with inReplyTo when denied incoming reply", %{data: data} do
998 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
999
1000 object_with_reply =
1001 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1002
1003 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1004 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1005
1006 object_with_reply =
1007 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1008
1009 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1010 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1011
1012 object_with_reply =
1013 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1014
1015 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1016 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1017
1018 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1019 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1020 assert modified_object["inReplyTo"] == []
1021 end
1022
1023 @tag capture_log: true
1024 test "returns modified object when allowed incoming reply", %{data: data} do
1025 object_with_reply =
1026 Map.put(
1027 data["object"],
1028 "inReplyTo",
1029 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
1030 )
1031
1032 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1033 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1034
1035 assert modified_object["inReplyTo"] ==
1036 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
1037
1038 assert modified_object["context"] ==
1039 "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4"
1040 end
1041 end
1042
1043 describe "fix_url/1" do
1044 test "fixes data for object when url is map" do
1045 object = %{
1046 "url" => %{
1047 "type" => "Link",
1048 "mimeType" => "video/mp4",
1049 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1050 }
1051 }
1052
1053 assert Transmogrifier.fix_url(object) == %{
1054 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1055 }
1056 end
1057
1058 test "returns non-modified object" do
1059 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1060 end
1061 end
1062
1063 describe "get_obj_helper/2" do
1064 test "returns nil when cannot normalize object" do
1065 assert capture_log(fn ->
1066 refute Transmogrifier.get_obj_helper("test-obj-id")
1067 end) =~ "Unsupported URI scheme"
1068 end
1069
1070 @tag capture_log: true
1071 test "returns {:ok, %Object{}} for success case" do
1072 assert {:ok, %Object{}} =
1073 Transmogrifier.get_obj_helper(
1074 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
1075 )
1076 end
1077 end
1078
1079 describe "fix_attachments/1" do
1080 test "returns not modified object" do
1081 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1082 assert Transmogrifier.fix_attachments(data) == data
1083 end
1084
1085 test "returns modified object when attachment is map" do
1086 assert Transmogrifier.fix_attachments(%{
1087 "attachment" => %{
1088 "mediaType" => "video/mp4",
1089 "url" => "https://peertube.moe/stat-480.mp4"
1090 }
1091 }) == %{
1092 "attachment" => [
1093 %{
1094 "mediaType" => "video/mp4",
1095 "type" => "Document",
1096 "url" => [
1097 %{
1098 "href" => "https://peertube.moe/stat-480.mp4",
1099 "mediaType" => "video/mp4",
1100 "type" => "Link"
1101 }
1102 ]
1103 }
1104 ]
1105 }
1106 end
1107
1108 test "returns modified object when attachment is list" do
1109 assert Transmogrifier.fix_attachments(%{
1110 "attachment" => [
1111 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1112 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1113 ]
1114 }) == %{
1115 "attachment" => [
1116 %{
1117 "mediaType" => "video/mp4",
1118 "type" => "Document",
1119 "url" => [
1120 %{
1121 "href" => "https://pe.er/stat-480.mp4",
1122 "mediaType" => "video/mp4",
1123 "type" => "Link"
1124 }
1125 ]
1126 },
1127 %{
1128 "mediaType" => "video/mp4",
1129 "type" => "Document",
1130 "url" => [
1131 %{
1132 "href" => "https://pe.er/stat-480.mp4",
1133 "mediaType" => "video/mp4",
1134 "type" => "Link"
1135 }
1136 ]
1137 }
1138 ]
1139 }
1140 end
1141 end
1142
1143 describe "fix_emoji/1" do
1144 test "returns not modified object when object not contains tags" do
1145 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1146 assert Transmogrifier.fix_emoji(data) == data
1147 end
1148
1149 test "returns object with emoji when object contains list tags" do
1150 assert Transmogrifier.fix_emoji(%{
1151 "tag" => [
1152 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1153 %{"type" => "Hashtag"}
1154 ]
1155 }) == %{
1156 "emoji" => %{"bib" => "/test"},
1157 "tag" => [
1158 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1159 %{"type" => "Hashtag"}
1160 ]
1161 }
1162 end
1163
1164 test "returns object with emoji when object contains map tag" do
1165 assert Transmogrifier.fix_emoji(%{
1166 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1167 }) == %{
1168 "emoji" => %{"bib" => "/test"},
1169 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1170 }
1171 end
1172 end
1173
1174 describe "set_replies/1" do
1175 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1176
1177 test "returns unmodified object if activity doesn't have self-replies" do
1178 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1179 assert Transmogrifier.set_replies(data) == data
1180 end
1181
1182 test "sets `replies` collection with a limited number of self-replies" do
1183 [user, another_user] = insert_list(2, :user)
1184
1185 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1186
1187 {:ok, %{id: id2} = self_reply1} =
1188 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1189
1190 {:ok, self_reply2} =
1191 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1192
1193 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1194 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1195
1196 {:ok, _} =
1197 CommonAPI.post(user, %{
1198 status: "self-reply to self-reply",
1199 in_reply_to_status_id: id2
1200 })
1201
1202 {:ok, _} =
1203 CommonAPI.post(another_user, %{
1204 status: "another user's reply",
1205 in_reply_to_status_id: id1
1206 })
1207
1208 object = Object.normalize(activity)
1209 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1210
1211 assert %{"type" => "Collection", "items" => ^replies_uris} =
1212 Transmogrifier.set_replies(object.data)["replies"]
1213 end
1214 end
1215
1216 test "take_emoji_tags/1" do
1217 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1218
1219 assert Transmogrifier.take_emoji_tags(user) == [
1220 %{
1221 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1222 "id" => "https://example.org/firefox.png",
1223 "name" => ":firefox:",
1224 "type" => "Emoji",
1225 "updated" => "1970-01-01T00:00:00Z"
1226 }
1227 ]
1228 end
1229 end