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