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