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