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