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