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