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