76e7ad8ea2036eade1c6f1e2a5ec5278e8201ec1
[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 alsoKnownAs" do
596 {:ok, %Activity{data: %{"actor" => actor}}} =
597 "test/fixtures/mastodon-post-activity.json"
598 |> File.read!()
599 |> Poison.decode!()
600 |> Transmogrifier.handle_incoming()
601
602 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
603
604 {:ok, _activity} =
605 "test/fixtures/mastodon-update.json"
606 |> File.read!()
607 |> Poison.decode!()
608 |> Map.put("actor", actor)
609 |> Map.update!("object", fn object ->
610 object
611 |> Map.put("actor", actor)
612 |> Map.put("id", actor)
613 |> Map.put("alsoKnownAs", [
614 "http://mastodon.example.org/users/foo",
615 "http://example.org/users/bar"
616 ])
617 end)
618 |> Transmogrifier.handle_incoming()
619
620 assert User.get_cached_by_ap_id(actor).also_known_as == [
621 "http://mastodon.example.org/users/foo",
622 "http://example.org/users/bar"
623 ]
624 end
625
626 test "it works with custom profile fields" do
627 {:ok, activity} =
628 "test/fixtures/mastodon-post-activity.json"
629 |> File.read!()
630 |> Poison.decode!()
631 |> Transmogrifier.handle_incoming()
632
633 user = User.get_cached_by_ap_id(activity.actor)
634
635 assert User.fields(user) == [
636 %{"name" => "foo", "value" => "bar"},
637 %{"name" => "foo1", "value" => "bar1"}
638 ]
639
640 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
641
642 object =
643 update_data["object"]
644 |> Map.put("actor", user.ap_id)
645 |> Map.put("id", user.ap_id)
646
647 update_data =
648 update_data
649 |> Map.put("actor", user.ap_id)
650 |> Map.put("object", object)
651
652 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
653
654 user = User.get_cached_by_ap_id(user.ap_id)
655
656 assert User.fields(user) == [
657 %{"name" => "foo", "value" => "updated"},
658 %{"name" => "foo1", "value" => "updated"}
659 ]
660
661 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
662
663 update_data =
664 put_in(update_data, ["object", "attachment"], [
665 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
666 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
667 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
668 ])
669
670 {:ok, _} = Transmogrifier.handle_incoming(update_data)
671
672 user = User.get_cached_by_ap_id(user.ap_id)
673
674 assert User.fields(user) == [
675 %{"name" => "foo", "value" => "updated"},
676 %{"name" => "foo1", "value" => "updated"}
677 ]
678
679 update_data = put_in(update_data, ["object", "attachment"], [])
680
681 {:ok, _} = Transmogrifier.handle_incoming(update_data)
682
683 user = User.get_cached_by_ap_id(user.ap_id)
684
685 assert User.fields(user) == []
686 end
687
688 test "it works for incoming update activities which lock the account" do
689 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
690
691 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
692 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
693
694 object =
695 update_data["object"]
696 |> Map.put("actor", data["actor"])
697 |> Map.put("id", data["actor"])
698 |> Map.put("manuallyApprovesFollowers", true)
699
700 update_data =
701 update_data
702 |> Map.put("actor", data["actor"])
703 |> Map.put("object", object)
704
705 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
706
707 user = User.get_cached_by_ap_id(data["actor"])
708 assert user.locked == true
709 end
710
711 test "it works for incoming deletes" do
712 activity = insert(:note_activity)
713 deleting_user = insert(:user)
714
715 data =
716 File.read!("test/fixtures/mastodon-delete.json")
717 |> Poison.decode!()
718
719 object =
720 data["object"]
721 |> Map.put("id", activity.data["object"])
722
723 data =
724 data
725 |> Map.put("object", object)
726 |> Map.put("actor", deleting_user.ap_id)
727
728 {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
729 Transmogrifier.handle_incoming(data)
730
731 assert id == data["id"]
732 refute Activity.get_by_id(activity.id)
733 assert actor == deleting_user.ap_id
734 end
735
736 test "it fails for incoming deletes with spoofed origin" do
737 activity = insert(:note_activity)
738
739 data =
740 File.read!("test/fixtures/mastodon-delete.json")
741 |> Poison.decode!()
742
743 object =
744 data["object"]
745 |> Map.put("id", activity.data["object"])
746
747 data =
748 data
749 |> Map.put("object", object)
750
751 assert capture_log(fn ->
752 :error = Transmogrifier.handle_incoming(data)
753 end) =~
754 "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}"
755
756 assert Activity.get_by_id(activity.id)
757 end
758
759 test "it works for incoming user deletes" do
760 %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
761
762 data =
763 File.read!("test/fixtures/mastodon-delete-user.json")
764 |> Poison.decode!()
765
766 {:ok, _} = Transmogrifier.handle_incoming(data)
767 ObanHelpers.perform_all()
768
769 refute User.get_cached_by_ap_id(ap_id)
770 end
771
772 test "it fails for incoming user deletes with spoofed origin" do
773 %{ap_id: ap_id} = insert(:user)
774
775 data =
776 File.read!("test/fixtures/mastodon-delete-user.json")
777 |> Poison.decode!()
778 |> Map.put("actor", ap_id)
779
780 assert :error == Transmogrifier.handle_incoming(data)
781 assert User.get_cached_by_ap_id(ap_id)
782 end
783
784 test "it works for incoming unannounces with an existing notice" do
785 user = insert(:user)
786 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
787
788 announce_data =
789 File.read!("test/fixtures/mastodon-announce.json")
790 |> Poison.decode!()
791 |> Map.put("object", activity.data["object"])
792
793 {:ok, %Activity{data: announce_data, local: false}} =
794 Transmogrifier.handle_incoming(announce_data)
795
796 data =
797 File.read!("test/fixtures/mastodon-undo-announce.json")
798 |> Poison.decode!()
799 |> Map.put("object", announce_data)
800 |> Map.put("actor", announce_data["actor"])
801
802 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
803
804 assert data["type"] == "Undo"
805 assert object_data = data["object"]
806 assert object_data["type"] == "Announce"
807 assert object_data["object"] == activity.data["object"]
808
809 assert object_data["id"] ==
810 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
811 end
812
813 test "it works for incomming unfollows with an existing follow" do
814 user = insert(:user)
815
816 follow_data =
817 File.read!("test/fixtures/mastodon-follow-activity.json")
818 |> Poison.decode!()
819 |> Map.put("object", user.ap_id)
820
821 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
822
823 data =
824 File.read!("test/fixtures/mastodon-unfollow-activity.json")
825 |> Poison.decode!()
826 |> Map.put("object", follow_data)
827
828 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
829
830 assert data["type"] == "Undo"
831 assert data["object"]["type"] == "Follow"
832 assert data["object"]["object"] == user.ap_id
833 assert data["actor"] == "http://mastodon.example.org/users/admin"
834
835 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
836 end
837
838 test "it works for incoming blocks" do
839 user = insert(:user)
840
841 data =
842 File.read!("test/fixtures/mastodon-block-activity.json")
843 |> Poison.decode!()
844 |> Map.put("object", user.ap_id)
845
846 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
847
848 assert data["type"] == "Block"
849 assert data["object"] == user.ap_id
850 assert data["actor"] == "http://mastodon.example.org/users/admin"
851
852 blocker = User.get_cached_by_ap_id(data["actor"])
853
854 assert User.blocks?(blocker, user)
855 end
856
857 test "incoming blocks successfully tear down any follow relationship" do
858 blocker = insert(:user)
859 blocked = insert(:user)
860
861 data =
862 File.read!("test/fixtures/mastodon-block-activity.json")
863 |> Poison.decode!()
864 |> Map.put("object", blocked.ap_id)
865 |> Map.put("actor", blocker.ap_id)
866
867 {:ok, blocker} = User.follow(blocker, blocked)
868 {:ok, blocked} = User.follow(blocked, blocker)
869
870 assert User.following?(blocker, blocked)
871 assert User.following?(blocked, blocker)
872
873 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
874
875 assert data["type"] == "Block"
876 assert data["object"] == blocked.ap_id
877 assert data["actor"] == blocker.ap_id
878
879 blocker = User.get_cached_by_ap_id(data["actor"])
880 blocked = User.get_cached_by_ap_id(data["object"])
881
882 assert User.blocks?(blocker, blocked)
883
884 refute User.following?(blocker, blocked)
885 refute User.following?(blocked, blocker)
886 end
887
888 test "it works for incoming unblocks with an existing block" do
889 user = insert(:user)
890
891 block_data =
892 File.read!("test/fixtures/mastodon-block-activity.json")
893 |> Poison.decode!()
894 |> Map.put("object", user.ap_id)
895
896 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
897
898 data =
899 File.read!("test/fixtures/mastodon-unblock-activity.json")
900 |> Poison.decode!()
901 |> Map.put("object", block_data)
902
903 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
904 assert data["type"] == "Undo"
905 assert data["object"]["type"] == "Block"
906 assert data["object"]["object"] == user.ap_id
907 assert data["actor"] == "http://mastodon.example.org/users/admin"
908
909 blocker = User.get_cached_by_ap_id(data["actor"])
910
911 refute User.blocks?(blocker, user)
912 end
913
914 test "it works for incoming accepts which were pre-accepted" do
915 follower = insert(:user)
916 followed = insert(:user)
917
918 {:ok, follower} = User.follow(follower, followed)
919 assert User.following?(follower, followed) == true
920
921 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
922
923 accept_data =
924 File.read!("test/fixtures/mastodon-accept-activity.json")
925 |> Poison.decode!()
926 |> Map.put("actor", followed.ap_id)
927
928 object =
929 accept_data["object"]
930 |> Map.put("actor", follower.ap_id)
931 |> Map.put("id", follow_activity.data["id"])
932
933 accept_data = Map.put(accept_data, "object", object)
934
935 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
936 refute activity.local
937
938 assert activity.data["object"] == follow_activity.data["id"]
939
940 assert activity.data["id"] == accept_data["id"]
941
942 follower = User.get_cached_by_id(follower.id)
943
944 assert User.following?(follower, followed) == true
945 end
946
947 test "it works for incoming accepts which were orphaned" do
948 follower = insert(:user)
949 followed = insert(:user, locked: true)
950
951 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
952
953 accept_data =
954 File.read!("test/fixtures/mastodon-accept-activity.json")
955 |> Poison.decode!()
956 |> Map.put("actor", followed.ap_id)
957
958 accept_data =
959 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
960
961 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
962 assert activity.data["object"] == follow_activity.data["id"]
963
964 follower = User.get_cached_by_id(follower.id)
965
966 assert User.following?(follower, followed) == true
967 end
968
969 test "it works for incoming accepts which are referenced by IRI only" do
970 follower = insert(:user)
971 followed = insert(:user, locked: true)
972
973 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
974
975 accept_data =
976 File.read!("test/fixtures/mastodon-accept-activity.json")
977 |> Poison.decode!()
978 |> Map.put("actor", followed.ap_id)
979 |> Map.put("object", follow_activity.data["id"])
980
981 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
982 assert activity.data["object"] == follow_activity.data["id"]
983
984 follower = User.get_cached_by_id(follower.id)
985
986 assert User.following?(follower, followed) == true
987 end
988
989 test "it fails for incoming accepts which cannot be correlated" do
990 follower = insert(:user)
991 followed = insert(:user, locked: true)
992
993 accept_data =
994 File.read!("test/fixtures/mastodon-accept-activity.json")
995 |> Poison.decode!()
996 |> Map.put("actor", followed.ap_id)
997
998 accept_data =
999 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1000
1001 :error = Transmogrifier.handle_incoming(accept_data)
1002
1003 follower = User.get_cached_by_id(follower.id)
1004
1005 refute User.following?(follower, followed) == true
1006 end
1007
1008 test "it fails for incoming rejects which cannot be correlated" do
1009 follower = insert(:user)
1010 followed = insert(:user, locked: true)
1011
1012 accept_data =
1013 File.read!("test/fixtures/mastodon-reject-activity.json")
1014 |> Poison.decode!()
1015 |> Map.put("actor", followed.ap_id)
1016
1017 accept_data =
1018 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1019
1020 :error = Transmogrifier.handle_incoming(accept_data)
1021
1022 follower = User.get_cached_by_id(follower.id)
1023
1024 refute User.following?(follower, followed) == true
1025 end
1026
1027 test "it works for incoming rejects which are orphaned" do
1028 follower = insert(:user)
1029 followed = insert(:user, locked: true)
1030
1031 {:ok, follower} = User.follow(follower, followed)
1032 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
1033
1034 assert User.following?(follower, followed) == true
1035
1036 reject_data =
1037 File.read!("test/fixtures/mastodon-reject-activity.json")
1038 |> Poison.decode!()
1039 |> Map.put("actor", followed.ap_id)
1040
1041 reject_data =
1042 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
1043
1044 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
1045 refute activity.local
1046 assert activity.data["id"] == reject_data["id"]
1047
1048 follower = User.get_cached_by_id(follower.id)
1049
1050 assert User.following?(follower, followed) == false
1051 end
1052
1053 test "it works for incoming rejects which are referenced by IRI only" do
1054 follower = insert(:user)
1055 followed = insert(:user, locked: true)
1056
1057 {:ok, follower} = User.follow(follower, followed)
1058 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1059
1060 assert User.following?(follower, followed) == true
1061
1062 reject_data =
1063 File.read!("test/fixtures/mastodon-reject-activity.json")
1064 |> Poison.decode!()
1065 |> Map.put("actor", followed.ap_id)
1066 |> Map.put("object", follow_activity.data["id"])
1067
1068 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1069
1070 follower = User.get_cached_by_id(follower.id)
1071
1072 assert User.following?(follower, followed) == false
1073 end
1074
1075 test "it rejects activities without a valid ID" do
1076 user = insert(:user)
1077
1078 data =
1079 File.read!("test/fixtures/mastodon-follow-activity.json")
1080 |> Poison.decode!()
1081 |> Map.put("object", user.ap_id)
1082 |> Map.put("id", "")
1083
1084 :error = Transmogrifier.handle_incoming(data)
1085 end
1086
1087 test "it remaps video URLs as attachments if necessary" do
1088 {:ok, object} =
1089 Fetcher.fetch_object_from_id(
1090 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1091 )
1092
1093 attachment = %{
1094 "type" => "Link",
1095 "mediaType" => "video/mp4",
1096 "href" =>
1097 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1098 "mimeType" => "video/mp4",
1099 "size" => 5_015_880,
1100 "url" => [
1101 %{
1102 "href" =>
1103 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1104 "mediaType" => "video/mp4",
1105 "type" => "Link"
1106 }
1107 ],
1108 "width" => 480
1109 }
1110
1111 assert object.data["url"] ==
1112 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1113
1114 assert object.data["attachment"] == [attachment]
1115 end
1116
1117 test "it accepts Flag activities" do
1118 user = insert(:user)
1119 other_user = insert(:user)
1120
1121 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1122 object = Object.normalize(activity)
1123
1124 message = %{
1125 "@context" => "https://www.w3.org/ns/activitystreams",
1126 "cc" => [user.ap_id],
1127 "object" => [user.ap_id, object.data["id"]],
1128 "type" => "Flag",
1129 "content" => "blocked AND reported!!!",
1130 "actor" => other_user.ap_id
1131 }
1132
1133 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1134
1135 assert activity.data["object"] == [user.ap_id, object.data["id"]]
1136 assert activity.data["content"] == "blocked AND reported!!!"
1137 assert activity.data["actor"] == other_user.ap_id
1138 assert activity.data["cc"] == [user.ap_id]
1139 end
1140
1141 test "it correctly processes messages with non-array to field" do
1142 user = insert(:user)
1143
1144 message = %{
1145 "@context" => "https://www.w3.org/ns/activitystreams",
1146 "to" => "https://www.w3.org/ns/activitystreams#Public",
1147 "type" => "Create",
1148 "object" => %{
1149 "content" => "blah blah blah",
1150 "type" => "Note",
1151 "attributedTo" => user.ap_id,
1152 "inReplyTo" => nil
1153 },
1154 "actor" => user.ap_id
1155 }
1156
1157 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1158
1159 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1160 end
1161
1162 test "it correctly processes messages with non-array cc field" do
1163 user = insert(:user)
1164
1165 message = %{
1166 "@context" => "https://www.w3.org/ns/activitystreams",
1167 "to" => user.follower_address,
1168 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1169 "type" => "Create",
1170 "object" => %{
1171 "content" => "blah blah blah",
1172 "type" => "Note",
1173 "attributedTo" => user.ap_id,
1174 "inReplyTo" => nil
1175 },
1176 "actor" => user.ap_id
1177 }
1178
1179 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1180
1181 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1182 assert [user.follower_address] == activity.data["to"]
1183 end
1184
1185 test "it accepts Move activities" do
1186 old_user = insert(:user)
1187 new_user = insert(:user)
1188
1189 message = %{
1190 "@context" => "https://www.w3.org/ns/activitystreams",
1191 "type" => "Move",
1192 "actor" => old_user.ap_id,
1193 "object" => old_user.ap_id,
1194 "target" => new_user.ap_id
1195 }
1196
1197 assert :error = Transmogrifier.handle_incoming(message)
1198
1199 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1200
1201 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1202 assert activity.actor == old_user.ap_id
1203 assert activity.data["actor"] == old_user.ap_id
1204 assert activity.data["object"] == old_user.ap_id
1205 assert activity.data["target"] == new_user.ap_id
1206 assert activity.data["type"] == "Move"
1207 end
1208 end
1209
1210 describe "prepare outgoing" do
1211 test "it inlines private announced objects" do
1212 user = insert(:user)
1213
1214 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1215
1216 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1217
1218 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1219
1220 assert modified["object"]["content"] == "hey"
1221 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1222 end
1223
1224 test "it turns mentions into tags" do
1225 user = insert(:user)
1226 other_user = insert(:user)
1227
1228 {:ok, activity} =
1229 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1230
1231 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1232 object = modified["object"]
1233
1234 expected_mention = %{
1235 "href" => other_user.ap_id,
1236 "name" => "@#{other_user.nickname}",
1237 "type" => "Mention"
1238 }
1239
1240 expected_tag = %{
1241 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1242 "type" => "Hashtag",
1243 "name" => "#2hu"
1244 }
1245
1246 assert Enum.member?(object["tag"], expected_tag)
1247 assert Enum.member?(object["tag"], expected_mention)
1248 end
1249
1250 test "it adds the sensitive property" do
1251 user = insert(:user)
1252
1253 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1254 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1255
1256 assert modified["object"]["sensitive"]
1257 end
1258
1259 test "it adds the json-ld context and the conversation property" do
1260 user = insert(:user)
1261
1262 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1263 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1264
1265 assert modified["@context"] ==
1266 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1267
1268 assert modified["object"]["conversation"] == modified["context"]
1269 end
1270
1271 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1272 user = insert(:user)
1273
1274 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1275 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1276
1277 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1278 end
1279
1280 test "it strips internal hashtag data" do
1281 user = insert(:user)
1282
1283 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1284
1285 expected_tag = %{
1286 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1287 "type" => "Hashtag",
1288 "name" => "#2hu"
1289 }
1290
1291 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1292
1293 assert modified["object"]["tag"] == [expected_tag]
1294 end
1295
1296 test "it strips internal fields" do
1297 user = insert(:user)
1298
1299 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1300
1301 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1302
1303 assert length(modified["object"]["tag"]) == 2
1304
1305 assert is_nil(modified["object"]["emoji"])
1306 assert is_nil(modified["object"]["like_count"])
1307 assert is_nil(modified["object"]["announcements"])
1308 assert is_nil(modified["object"]["announcement_count"])
1309 assert is_nil(modified["object"]["context_id"])
1310 end
1311
1312 test "it strips internal fields of article" do
1313 activity = insert(:article_activity)
1314
1315 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1316
1317 assert length(modified["object"]["tag"]) == 2
1318
1319 assert is_nil(modified["object"]["emoji"])
1320 assert is_nil(modified["object"]["like_count"])
1321 assert is_nil(modified["object"]["announcements"])
1322 assert is_nil(modified["object"]["announcement_count"])
1323 assert is_nil(modified["object"]["context_id"])
1324 assert is_nil(modified["object"]["likes"])
1325 end
1326
1327 test "the directMessage flag is present" do
1328 user = insert(:user)
1329 other_user = insert(:user)
1330
1331 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1332
1333 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1334
1335 assert modified["directMessage"] == false
1336
1337 {:ok, activity} =
1338 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1339
1340 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1341
1342 assert modified["directMessage"] == false
1343
1344 {:ok, activity} =
1345 CommonAPI.post(user, %{
1346 "status" => "@#{other_user.nickname} :moominmamma:",
1347 "visibility" => "direct"
1348 })
1349
1350 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1351
1352 assert modified["directMessage"] == true
1353 end
1354
1355 test "it strips BCC field" do
1356 user = insert(:user)
1357 {:ok, list} = Pleroma.List.create("foo", user)
1358
1359 {:ok, activity} =
1360 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1361
1362 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1363
1364 assert is_nil(modified["bcc"])
1365 end
1366
1367 test "it can handle Listen activities" do
1368 listen_activity = insert(:listen)
1369
1370 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1371
1372 assert modified["type"] == "Listen"
1373
1374 user = insert(:user)
1375
1376 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1377
1378 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1379 end
1380 end
1381
1382 describe "user upgrade" do
1383 test "it upgrades a user to activitypub" do
1384 user =
1385 insert(:user, %{
1386 nickname: "rye@niu.moe",
1387 local: false,
1388 ap_id: "https://niu.moe/users/rye",
1389 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1390 })
1391
1392 user_two = insert(:user)
1393 Pleroma.FollowingRelationship.follow(user_two, user, "accept")
1394
1395 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1396 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1397 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1398
1399 user = User.get_cached_by_id(user.id)
1400 assert user.note_count == 1
1401
1402 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1403 ObanHelpers.perform_all()
1404
1405 assert user.ap_enabled
1406 assert user.note_count == 1
1407 assert user.follower_address == "https://niu.moe/users/rye/followers"
1408 assert user.following_address == "https://niu.moe/users/rye/following"
1409
1410 user = User.get_cached_by_id(user.id)
1411 assert user.note_count == 1
1412
1413 activity = Activity.get_by_id(activity.id)
1414 assert user.follower_address in activity.recipients
1415
1416 assert %{
1417 "url" => [
1418 %{
1419 "href" =>
1420 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1421 }
1422 ]
1423 } = user.avatar
1424
1425 assert %{
1426 "url" => [
1427 %{
1428 "href" =>
1429 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1430 }
1431 ]
1432 } = user.banner
1433
1434 refute "..." in activity.recipients
1435
1436 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1437 refute user.follower_address in unrelated_activity.recipients
1438
1439 user_two = User.get_cached_by_id(user_two.id)
1440 assert User.following?(user_two, user)
1441 refute "..." in User.following(user_two)
1442 end
1443 end
1444
1445 describe "actor rewriting" do
1446 test "it fixes the actor URL property to be a proper URI" do
1447 data = %{
1448 "url" => %{"href" => "http://example.com"}
1449 }
1450
1451 rewritten = Transmogrifier.maybe_fix_user_object(data)
1452 assert rewritten["url"] == "http://example.com"
1453 end
1454 end
1455
1456 describe "actor origin containment" do
1457 test "it rejects activities which reference objects with bogus origins" do
1458 data = %{
1459 "@context" => "https://www.w3.org/ns/activitystreams",
1460 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1461 "actor" => "http://mastodon.example.org/users/admin",
1462 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1463 "object" => "https://info.pleroma.site/activity.json",
1464 "type" => "Announce"
1465 }
1466
1467 :error = Transmogrifier.handle_incoming(data)
1468 end
1469
1470 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1471 data = %{
1472 "@context" => "https://www.w3.org/ns/activitystreams",
1473 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1474 "actor" => "http://mastodon.example.org/users/admin",
1475 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1476 "object" => "https://info.pleroma.site/activity2.json",
1477 "type" => "Announce"
1478 }
1479
1480 :error = Transmogrifier.handle_incoming(data)
1481 end
1482
1483 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1484 data = %{
1485 "@context" => "https://www.w3.org/ns/activitystreams",
1486 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1487 "actor" => "http://mastodon.example.org/users/admin",
1488 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1489 "object" => "https://info.pleroma.site/activity3.json",
1490 "type" => "Announce"
1491 }
1492
1493 :error = Transmogrifier.handle_incoming(data)
1494 end
1495 end
1496
1497 describe "reserialization" do
1498 test "successfully reserializes a message with inReplyTo == nil" do
1499 user = insert(:user)
1500
1501 message = %{
1502 "@context" => "https://www.w3.org/ns/activitystreams",
1503 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1504 "cc" => [],
1505 "type" => "Create",
1506 "object" => %{
1507 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1508 "cc" => [],
1509 "type" => "Note",
1510 "content" => "Hi",
1511 "inReplyTo" => nil,
1512 "attributedTo" => user.ap_id
1513 },
1514 "actor" => user.ap_id
1515 }
1516
1517 {:ok, activity} = Transmogrifier.handle_incoming(message)
1518
1519 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1520 end
1521
1522 test "successfully reserializes a message with AS2 objects in IR" do
1523 user = insert(:user)
1524
1525 message = %{
1526 "@context" => "https://www.w3.org/ns/activitystreams",
1527 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1528 "cc" => [],
1529 "type" => "Create",
1530 "object" => %{
1531 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1532 "cc" => [],
1533 "type" => "Note",
1534 "content" => "Hi",
1535 "inReplyTo" => nil,
1536 "attributedTo" => user.ap_id,
1537 "tag" => [
1538 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1539 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1540 ]
1541 },
1542 "actor" => user.ap_id
1543 }
1544
1545 {:ok, activity} = Transmogrifier.handle_incoming(message)
1546
1547 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1548 end
1549 end
1550
1551 test "Rewrites Answers to Notes" do
1552 user = insert(:user)
1553
1554 {:ok, poll_activity} =
1555 CommonAPI.post(user, %{
1556 "status" => "suya...",
1557 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1558 })
1559
1560 poll_object = Object.normalize(poll_activity)
1561 # TODO: Replace with CommonAPI vote creation when implemented
1562 data =
1563 File.read!("test/fixtures/mastodon-vote.json")
1564 |> Poison.decode!()
1565 |> Kernel.put_in(["to"], user.ap_id)
1566 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1567 |> Kernel.put_in(["object", "to"], user.ap_id)
1568
1569 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1570 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1571
1572 assert data["object"]["type"] == "Note"
1573 end
1574
1575 describe "fix_explicit_addressing" do
1576 setup do
1577 user = insert(:user)
1578 [user: user]
1579 end
1580
1581 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1582 explicitly_mentioned_actors = [
1583 "https://pleroma.gold/users/user1",
1584 "https://pleroma.gold/user2"
1585 ]
1586
1587 object = %{
1588 "actor" => user.ap_id,
1589 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1590 "cc" => [],
1591 "tag" =>
1592 Enum.map(explicitly_mentioned_actors, fn href ->
1593 %{"type" => "Mention", "href" => href}
1594 end)
1595 }
1596
1597 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1598 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1599 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1600 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1601 end
1602
1603 test "does not move actor's follower collection to cc", %{user: user} do
1604 object = %{
1605 "actor" => user.ap_id,
1606 "to" => [user.follower_address],
1607 "cc" => []
1608 }
1609
1610 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1611 assert user.follower_address in fixed_object["to"]
1612 refute user.follower_address in fixed_object["cc"]
1613 end
1614
1615 test "removes recipient's follower collection from cc", %{user: user} do
1616 recipient = insert(:user)
1617
1618 object = %{
1619 "actor" => user.ap_id,
1620 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1621 "cc" => [user.follower_address, recipient.follower_address]
1622 }
1623
1624 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1625
1626 assert user.follower_address in fixed_object["cc"]
1627 refute recipient.follower_address in fixed_object["cc"]
1628 refute recipient.follower_address in fixed_object["to"]
1629 end
1630 end
1631
1632 describe "fix_summary/1" do
1633 test "returns fixed object" do
1634 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1635 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1636 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1637 end
1638 end
1639
1640 describe "fix_in_reply_to/2" do
1641 clear_config([:instance, :federation_incoming_replies_max_depth])
1642
1643 setup do
1644 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1645 [data: data]
1646 end
1647
1648 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1649 assert Transmogrifier.fix_in_reply_to(data) == data
1650 end
1651
1652 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1653 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1654
1655 object_with_reply =
1656 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1657
1658 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1659 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1660 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1661
1662 object_with_reply =
1663 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1664
1665 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1666 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1667 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1668
1669 object_with_reply =
1670 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1671
1672 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1673 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1674 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1675
1676 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1677 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1678 assert modified_object["inReplyTo"] == []
1679 assert modified_object["inReplyToAtomUri"] == ""
1680 end
1681
1682 test "returns modified object when allowed incoming reply", %{data: data} do
1683 object_with_reply =
1684 Map.put(
1685 data["object"],
1686 "inReplyTo",
1687 "https://shitposter.club/notice/2827873"
1688 )
1689
1690 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1691 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1692
1693 assert modified_object["inReplyTo"] ==
1694 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1695
1696 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1697
1698 assert modified_object["conversation"] ==
1699 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1700
1701 assert modified_object["context"] ==
1702 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1703 end
1704 end
1705
1706 describe "fix_url/1" do
1707 test "fixes data for object when url is map" do
1708 object = %{
1709 "url" => %{
1710 "type" => "Link",
1711 "mimeType" => "video/mp4",
1712 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1713 }
1714 }
1715
1716 assert Transmogrifier.fix_url(object) == %{
1717 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1718 }
1719 end
1720
1721 test "fixes data for video object" do
1722 object = %{
1723 "type" => "Video",
1724 "url" => [
1725 %{
1726 "type" => "Link",
1727 "mimeType" => "video/mp4",
1728 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1729 },
1730 %{
1731 "type" => "Link",
1732 "mimeType" => "video/mp4",
1733 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1734 },
1735 %{
1736 "type" => "Link",
1737 "mimeType" => "text/html",
1738 "href" => "https://peertube.-2d4c2d1630e3"
1739 },
1740 %{
1741 "type" => "Link",
1742 "mimeType" => "text/html",
1743 "href" => "https://peertube.-2d4c2d16377-42"
1744 }
1745 ]
1746 }
1747
1748 assert Transmogrifier.fix_url(object) == %{
1749 "attachment" => [
1750 %{
1751 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1752 "mimeType" => "video/mp4",
1753 "type" => "Link"
1754 }
1755 ],
1756 "type" => "Video",
1757 "url" => "https://peertube.-2d4c2d1630e3"
1758 }
1759 end
1760
1761 test "fixes url for not Video object" do
1762 object = %{
1763 "type" => "Text",
1764 "url" => [
1765 %{
1766 "type" => "Link",
1767 "mimeType" => "text/html",
1768 "href" => "https://peertube.-2d4c2d1630e3"
1769 },
1770 %{
1771 "type" => "Link",
1772 "mimeType" => "text/html",
1773 "href" => "https://peertube.-2d4c2d16377-42"
1774 }
1775 ]
1776 }
1777
1778 assert Transmogrifier.fix_url(object) == %{
1779 "type" => "Text",
1780 "url" => "https://peertube.-2d4c2d1630e3"
1781 }
1782
1783 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1784 "type" => "Text",
1785 "url" => ""
1786 }
1787 end
1788
1789 test "retunrs not modified object" do
1790 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1791 end
1792 end
1793
1794 describe "get_obj_helper/2" do
1795 test "returns nil when cannot normalize object" do
1796 refute Transmogrifier.get_obj_helper("test-obj-id")
1797 end
1798
1799 test "returns {:ok, %Object{}} for success case" do
1800 assert {:ok, %Object{}} =
1801 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1802 end
1803 end
1804
1805 describe "fix_attachments/1" do
1806 test "returns not modified object" do
1807 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1808 assert Transmogrifier.fix_attachments(data) == data
1809 end
1810
1811 test "returns modified object when attachment is map" do
1812 assert Transmogrifier.fix_attachments(%{
1813 "attachment" => %{
1814 "mediaType" => "video/mp4",
1815 "url" => "https://peertube.moe/stat-480.mp4"
1816 }
1817 }) == %{
1818 "attachment" => [
1819 %{
1820 "mediaType" => "video/mp4",
1821 "url" => [
1822 %{
1823 "href" => "https://peertube.moe/stat-480.mp4",
1824 "mediaType" => "video/mp4",
1825 "type" => "Link"
1826 }
1827 ]
1828 }
1829 ]
1830 }
1831 end
1832
1833 test "returns modified object when attachment is list" do
1834 assert Transmogrifier.fix_attachments(%{
1835 "attachment" => [
1836 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1837 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1838 ]
1839 }) == %{
1840 "attachment" => [
1841 %{
1842 "mediaType" => "video/mp4",
1843 "url" => [
1844 %{
1845 "href" => "https://pe.er/stat-480.mp4",
1846 "mediaType" => "video/mp4",
1847 "type" => "Link"
1848 }
1849 ]
1850 },
1851 %{
1852 "href" => "https://pe.er/stat-480.mp4",
1853 "mediaType" => "video/mp4",
1854 "mimeType" => "video/mp4",
1855 "url" => [
1856 %{
1857 "href" => "https://pe.er/stat-480.mp4",
1858 "mediaType" => "video/mp4",
1859 "type" => "Link"
1860 }
1861 ]
1862 }
1863 ]
1864 }
1865 end
1866 end
1867
1868 describe "fix_emoji/1" do
1869 test "returns not modified object when object not contains tags" do
1870 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1871 assert Transmogrifier.fix_emoji(data) == data
1872 end
1873
1874 test "returns object with emoji when object contains list tags" do
1875 assert Transmogrifier.fix_emoji(%{
1876 "tag" => [
1877 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1878 %{"type" => "Hashtag"}
1879 ]
1880 }) == %{
1881 "emoji" => %{"bib" => "/test"},
1882 "tag" => [
1883 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1884 %{"type" => "Hashtag"}
1885 ]
1886 }
1887 end
1888
1889 test "returns object with emoji when object contains map tag" do
1890 assert Transmogrifier.fix_emoji(%{
1891 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1892 }) == %{
1893 "emoji" => %{"bib" => "/test"},
1894 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1895 }
1896 end
1897 end
1898 end