4753133169d5dd3248216595925b7b1403bdf4a3
[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 returns an error for incoming unlikes wihout a like activity" do
345 user = insert(:user)
346 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
347
348 data =
349 File.read!("test/fixtures/mastodon-undo-like.json")
350 |> Poison.decode!()
351 |> Map.put("object", activity.data["object"])
352
353 assert Transmogrifier.handle_incoming(data) == :error
354 end
355
356 test "it works for incoming unlikes with an existing like activity" do
357 user = insert(:user)
358 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
359
360 like_data =
361 File.read!("test/fixtures/mastodon-like.json")
362 |> Poison.decode!()
363 |> Map.put("object", activity.data["object"])
364
365 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
366
367 data =
368 File.read!("test/fixtures/mastodon-undo-like.json")
369 |> Poison.decode!()
370 |> Map.put("object", like_data)
371 |> Map.put("actor", like_data["actor"])
372
373 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
374
375 assert data["actor"] == "http://mastodon.example.org/users/admin"
376 assert data["type"] == "Undo"
377 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
378 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
379 end
380
381 test "it works for incoming unlikes with an existing like activity and a compact object" do
382 user = insert(:user)
383 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
384
385 like_data =
386 File.read!("test/fixtures/mastodon-like.json")
387 |> Poison.decode!()
388 |> Map.put("object", activity.data["object"])
389
390 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
391
392 data =
393 File.read!("test/fixtures/mastodon-undo-like.json")
394 |> Poison.decode!()
395 |> Map.put("object", like_data["id"])
396 |> Map.put("actor", like_data["actor"])
397
398 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
399
400 assert data["actor"] == "http://mastodon.example.org/users/admin"
401 assert data["type"] == "Undo"
402 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
403 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
404 end
405
406 test "it works for incoming announces" do
407 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
408
409 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
410
411 assert data["actor"] == "http://mastodon.example.org/users/admin"
412 assert data["type"] == "Announce"
413
414 assert data["id"] ==
415 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
416
417 assert data["object"] ==
418 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
419
420 assert Activity.get_create_by_object_ap_id(data["object"])
421 end
422
423 test "it works for incoming announces with an existing activity" do
424 user = insert(:user)
425 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
426
427 data =
428 File.read!("test/fixtures/mastodon-announce.json")
429 |> Poison.decode!()
430 |> Map.put("object", activity.data["object"])
431
432 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
433
434 assert data["actor"] == "http://mastodon.example.org/users/admin"
435 assert data["type"] == "Announce"
436
437 assert data["id"] ==
438 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
439
440 assert data["object"] == activity.data["object"]
441
442 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
443 end
444
445 test "it does not clobber the addressing on announce activities" do
446 user = insert(:user)
447 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
448
449 data =
450 File.read!("test/fixtures/mastodon-announce.json")
451 |> Poison.decode!()
452 |> Map.put("object", Object.normalize(activity).data["id"])
453 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
454 |> Map.put("cc", [])
455
456 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
457
458 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
459 end
460
461 test "it ensures that as:Public activities make it to their followers collection" do
462 user = insert(:user)
463
464 data =
465 File.read!("test/fixtures/mastodon-post-activity.json")
466 |> Poison.decode!()
467 |> Map.put("actor", user.ap_id)
468 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
469 |> Map.put("cc", [])
470
471 object =
472 data["object"]
473 |> Map.put("attributedTo", user.ap_id)
474 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
475 |> Map.put("cc", [])
476 |> Map.put("id", user.ap_id <> "/activities/12345678")
477
478 data = Map.put(data, "object", object)
479
480 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
481
482 assert data["cc"] == [User.ap_followers(user)]
483 end
484
485 test "it ensures that address fields become lists" do
486 user = insert(:user)
487
488 data =
489 File.read!("test/fixtures/mastodon-post-activity.json")
490 |> Poison.decode!()
491 |> Map.put("actor", user.ap_id)
492 |> Map.put("to", nil)
493 |> Map.put("cc", nil)
494
495 object =
496 data["object"]
497 |> Map.put("attributedTo", user.ap_id)
498 |> Map.put("to", nil)
499 |> Map.put("cc", nil)
500 |> Map.put("id", user.ap_id <> "/activities/12345678")
501
502 data = Map.put(data, "object", object)
503
504 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
505
506 assert !is_nil(data["to"])
507 assert !is_nil(data["cc"])
508 end
509
510 test "it strips internal likes" do
511 data =
512 File.read!("test/fixtures/mastodon-post-activity.json")
513 |> Poison.decode!()
514
515 likes = %{
516 "first" =>
517 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
518 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
519 "totalItems" => 3,
520 "type" => "OrderedCollection"
521 }
522
523 object = Map.put(data["object"], "likes", likes)
524 data = Map.put(data, "object", object)
525
526 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
527
528 refute Map.has_key?(object.data, "likes")
529 end
530
531 test "it works for incoming update activities" do
532 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
533
534 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
535 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
536
537 object =
538 update_data["object"]
539 |> Map.put("actor", data["actor"])
540 |> Map.put("id", data["actor"])
541
542 update_data =
543 update_data
544 |> Map.put("actor", data["actor"])
545 |> Map.put("object", object)
546
547 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
548
549 assert data["id"] == update_data["id"]
550
551 user = User.get_cached_by_ap_id(data["actor"])
552 assert user.name == "gargle"
553
554 assert user.avatar["url"] == [
555 %{
556 "href" =>
557 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
558 }
559 ]
560
561 assert user.info.banner["url"] == [
562 %{
563 "href" =>
564 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
565 }
566 ]
567
568 assert user.bio == "<p>Some bio</p>"
569 end
570
571 test "it works with custom profile fields" do
572 {:ok, activity} =
573 "test/fixtures/mastodon-post-activity.json"
574 |> File.read!()
575 |> Poison.decode!()
576 |> Transmogrifier.handle_incoming()
577
578 user = User.get_cached_by_ap_id(activity.actor)
579
580 assert User.Info.fields(user.info) == [
581 %{"name" => "foo", "value" => "bar"},
582 %{"name" => "foo1", "value" => "bar1"}
583 ]
584
585 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
586
587 object =
588 update_data["object"]
589 |> Map.put("actor", user.ap_id)
590 |> Map.put("id", user.ap_id)
591
592 update_data =
593 update_data
594 |> Map.put("actor", user.ap_id)
595 |> Map.put("object", object)
596
597 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
598
599 user = User.get_cached_by_ap_id(user.ap_id)
600
601 assert User.Info.fields(user.info) == [
602 %{"name" => "foo", "value" => "updated"},
603 %{"name" => "foo1", "value" => "updated"}
604 ]
605
606 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
607
608 update_data =
609 put_in(update_data, ["object", "attachment"], [
610 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
611 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
612 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
613 ])
614
615 {:ok, _} = Transmogrifier.handle_incoming(update_data)
616
617 user = User.get_cached_by_ap_id(user.ap_id)
618
619 assert User.Info.fields(user.info) == [
620 %{"name" => "foo", "value" => "updated"},
621 %{"name" => "foo1", "value" => "updated"}
622 ]
623
624 update_data = put_in(update_data, ["object", "attachment"], [])
625
626 {:ok, _} = Transmogrifier.handle_incoming(update_data)
627
628 user = User.get_cached_by_ap_id(user.ap_id)
629
630 assert User.Info.fields(user.info) == []
631 end
632
633 test "it works for incoming update activities which lock the account" do
634 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
635
636 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
637 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
638
639 object =
640 update_data["object"]
641 |> Map.put("actor", data["actor"])
642 |> Map.put("id", data["actor"])
643 |> Map.put("manuallyApprovesFollowers", true)
644
645 update_data =
646 update_data
647 |> Map.put("actor", data["actor"])
648 |> Map.put("object", object)
649
650 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
651
652 user = User.get_cached_by_ap_id(data["actor"])
653 assert user.info.locked == true
654 end
655
656 test "it works for incoming deletes" do
657 activity = insert(:note_activity)
658
659 data =
660 File.read!("test/fixtures/mastodon-delete.json")
661 |> Poison.decode!()
662
663 object =
664 data["object"]
665 |> Map.put("id", activity.data["object"])
666
667 data =
668 data
669 |> Map.put("object", object)
670 |> Map.put("actor", activity.data["actor"])
671
672 {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
673
674 refute Activity.get_by_id(activity.id)
675 end
676
677 test "it fails for incoming deletes with spoofed origin" do
678 activity = insert(:note_activity)
679
680 data =
681 File.read!("test/fixtures/mastodon-delete.json")
682 |> Poison.decode!()
683
684 object =
685 data["object"]
686 |> Map.put("id", activity.data["object"])
687
688 data =
689 data
690 |> Map.put("object", object)
691
692 assert capture_log(fn ->
693 :error = Transmogrifier.handle_incoming(data)
694 end) =~
695 "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
696
697 assert Activity.get_by_id(activity.id)
698 end
699
700 test "it works for incoming user deletes" do
701 %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
702
703 data =
704 File.read!("test/fixtures/mastodon-delete-user.json")
705 |> Poison.decode!()
706
707 {:ok, _} = Transmogrifier.handle_incoming(data)
708 ObanHelpers.perform_all()
709
710 refute User.get_cached_by_ap_id(ap_id)
711 end
712
713 test "it fails for incoming user deletes with spoofed origin" do
714 %{ap_id: ap_id} = insert(:user)
715
716 data =
717 File.read!("test/fixtures/mastodon-delete-user.json")
718 |> Poison.decode!()
719 |> Map.put("actor", ap_id)
720
721 assert :error == Transmogrifier.handle_incoming(data)
722 assert User.get_cached_by_ap_id(ap_id)
723 end
724
725 test "it works for incoming unannounces with an existing notice" do
726 user = insert(:user)
727 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
728
729 announce_data =
730 File.read!("test/fixtures/mastodon-announce.json")
731 |> Poison.decode!()
732 |> Map.put("object", activity.data["object"])
733
734 {:ok, %Activity{data: announce_data, local: false}} =
735 Transmogrifier.handle_incoming(announce_data)
736
737 data =
738 File.read!("test/fixtures/mastodon-undo-announce.json")
739 |> Poison.decode!()
740 |> Map.put("object", announce_data)
741 |> Map.put("actor", announce_data["actor"])
742
743 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
744
745 assert data["type"] == "Undo"
746 assert object_data = data["object"]
747 assert object_data["type"] == "Announce"
748 assert object_data["object"] == activity.data["object"]
749
750 assert object_data["id"] ==
751 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
752 end
753
754 test "it works for incomming unfollows with an existing follow" do
755 user = insert(:user)
756
757 follow_data =
758 File.read!("test/fixtures/mastodon-follow-activity.json")
759 |> Poison.decode!()
760 |> Map.put("object", user.ap_id)
761
762 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
763
764 data =
765 File.read!("test/fixtures/mastodon-unfollow-activity.json")
766 |> Poison.decode!()
767 |> Map.put("object", follow_data)
768
769 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
770
771 assert data["type"] == "Undo"
772 assert data["object"]["type"] == "Follow"
773 assert data["object"]["object"] == user.ap_id
774 assert data["actor"] == "http://mastodon.example.org/users/admin"
775
776 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
777 end
778
779 test "it works for incoming blocks" do
780 user = insert(:user)
781
782 data =
783 File.read!("test/fixtures/mastodon-block-activity.json")
784 |> Poison.decode!()
785 |> Map.put("object", user.ap_id)
786
787 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
788
789 assert data["type"] == "Block"
790 assert data["object"] == user.ap_id
791 assert data["actor"] == "http://mastodon.example.org/users/admin"
792
793 blocker = User.get_cached_by_ap_id(data["actor"])
794
795 assert User.blocks?(blocker, user)
796 end
797
798 test "incoming blocks successfully tear down any follow relationship" do
799 blocker = insert(:user)
800 blocked = insert(:user)
801
802 data =
803 File.read!("test/fixtures/mastodon-block-activity.json")
804 |> Poison.decode!()
805 |> Map.put("object", blocked.ap_id)
806 |> Map.put("actor", blocker.ap_id)
807
808 {:ok, blocker} = User.follow(blocker, blocked)
809 {:ok, blocked} = User.follow(blocked, blocker)
810
811 assert User.following?(blocker, blocked)
812 assert User.following?(blocked, blocker)
813
814 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
815
816 assert data["type"] == "Block"
817 assert data["object"] == blocked.ap_id
818 assert data["actor"] == blocker.ap_id
819
820 blocker = User.get_cached_by_ap_id(data["actor"])
821 blocked = User.get_cached_by_ap_id(data["object"])
822
823 assert User.blocks?(blocker, blocked)
824
825 refute User.following?(blocker, blocked)
826 refute User.following?(blocked, blocker)
827 end
828
829 test "it works for incoming unblocks with an existing block" do
830 user = insert(:user)
831
832 block_data =
833 File.read!("test/fixtures/mastodon-block-activity.json")
834 |> Poison.decode!()
835 |> Map.put("object", user.ap_id)
836
837 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
838
839 data =
840 File.read!("test/fixtures/mastodon-unblock-activity.json")
841 |> Poison.decode!()
842 |> Map.put("object", block_data)
843
844 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
845 assert data["type"] == "Undo"
846 assert data["object"]["type"] == "Block"
847 assert data["object"]["object"] == user.ap_id
848 assert data["actor"] == "http://mastodon.example.org/users/admin"
849
850 blocker = User.get_cached_by_ap_id(data["actor"])
851
852 refute User.blocks?(blocker, user)
853 end
854
855 test "it works for incoming accepts which were pre-accepted" do
856 follower = insert(:user)
857 followed = insert(:user)
858
859 {:ok, follower} = User.follow(follower, followed)
860 assert User.following?(follower, followed) == true
861
862 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
863
864 accept_data =
865 File.read!("test/fixtures/mastodon-accept-activity.json")
866 |> Poison.decode!()
867 |> Map.put("actor", followed.ap_id)
868
869 object =
870 accept_data["object"]
871 |> Map.put("actor", follower.ap_id)
872 |> Map.put("id", follow_activity.data["id"])
873
874 accept_data = Map.put(accept_data, "object", object)
875
876 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
877 refute activity.local
878
879 assert activity.data["object"] == follow_activity.data["id"]
880
881 follower = User.get_cached_by_id(follower.id)
882
883 assert User.following?(follower, followed) == true
884 end
885
886 test "it works for incoming accepts which were orphaned" do
887 follower = insert(:user)
888 followed = insert(:user, %{info: %User.Info{locked: true}})
889
890 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
891
892 accept_data =
893 File.read!("test/fixtures/mastodon-accept-activity.json")
894 |> Poison.decode!()
895 |> Map.put("actor", followed.ap_id)
896
897 accept_data =
898 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
899
900 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
901 assert activity.data["object"] == follow_activity.data["id"]
902
903 follower = User.get_cached_by_id(follower.id)
904
905 assert User.following?(follower, followed) == true
906 end
907
908 test "it works for incoming accepts which are referenced by IRI only" do
909 follower = insert(:user)
910 followed = insert(:user, %{info: %User.Info{locked: true}})
911
912 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
913
914 accept_data =
915 File.read!("test/fixtures/mastodon-accept-activity.json")
916 |> Poison.decode!()
917 |> Map.put("actor", followed.ap_id)
918 |> Map.put("object", follow_activity.data["id"])
919
920 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
921 assert activity.data["object"] == follow_activity.data["id"]
922
923 follower = User.get_cached_by_id(follower.id)
924
925 assert User.following?(follower, followed) == true
926 end
927
928 test "it fails for incoming accepts which cannot be correlated" do
929 follower = insert(:user)
930 followed = insert(:user, %{info: %User.Info{locked: true}})
931
932 accept_data =
933 File.read!("test/fixtures/mastodon-accept-activity.json")
934 |> Poison.decode!()
935 |> Map.put("actor", followed.ap_id)
936
937 accept_data =
938 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
939
940 :error = Transmogrifier.handle_incoming(accept_data)
941
942 follower = User.get_cached_by_id(follower.id)
943
944 refute User.following?(follower, followed) == true
945 end
946
947 test "it fails for incoming rejects which cannot be correlated" do
948 follower = insert(:user)
949 followed = insert(:user, %{info: %User.Info{locked: true}})
950
951 accept_data =
952 File.read!("test/fixtures/mastodon-reject-activity.json")
953 |> Poison.decode!()
954 |> Map.put("actor", followed.ap_id)
955
956 accept_data =
957 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
958
959 :error = Transmogrifier.handle_incoming(accept_data)
960
961 follower = User.get_cached_by_id(follower.id)
962
963 refute User.following?(follower, followed) == true
964 end
965
966 test "it works for incoming rejects which are orphaned" do
967 follower = insert(:user)
968 followed = insert(:user, %{info: %User.Info{locked: true}})
969
970 {:ok, follower} = User.follow(follower, followed)
971 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
972
973 assert User.following?(follower, followed) == true
974
975 reject_data =
976 File.read!("test/fixtures/mastodon-reject-activity.json")
977 |> Poison.decode!()
978 |> Map.put("actor", followed.ap_id)
979
980 reject_data =
981 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
982
983 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
984 refute activity.local
985
986 follower = User.get_cached_by_id(follower.id)
987
988 assert User.following?(follower, followed) == false
989 end
990
991 test "it works for incoming rejects which are referenced by IRI only" do
992 follower = insert(:user)
993 followed = insert(:user, %{info: %User.Info{locked: true}})
994
995 {:ok, follower} = User.follow(follower, followed)
996 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
997
998 assert User.following?(follower, followed) == true
999
1000 reject_data =
1001 File.read!("test/fixtures/mastodon-reject-activity.json")
1002 |> Poison.decode!()
1003 |> Map.put("actor", followed.ap_id)
1004 |> Map.put("object", follow_activity.data["id"])
1005
1006 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1007
1008 follower = User.get_cached_by_id(follower.id)
1009
1010 assert User.following?(follower, followed) == false
1011 end
1012
1013 test "it rejects activities without a valid ID" do
1014 user = insert(:user)
1015
1016 data =
1017 File.read!("test/fixtures/mastodon-follow-activity.json")
1018 |> Poison.decode!()
1019 |> Map.put("object", user.ap_id)
1020 |> Map.put("id", "")
1021
1022 :error = Transmogrifier.handle_incoming(data)
1023 end
1024
1025 test "it remaps video URLs as attachments if necessary" do
1026 {:ok, object} =
1027 Fetcher.fetch_object_from_id(
1028 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1029 )
1030
1031 attachment = %{
1032 "type" => "Link",
1033 "mediaType" => "video/mp4",
1034 "href" =>
1035 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1036 "mimeType" => "video/mp4",
1037 "size" => 5_015_880,
1038 "url" => [
1039 %{
1040 "href" =>
1041 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1042 "mediaType" => "video/mp4",
1043 "type" => "Link"
1044 }
1045 ],
1046 "width" => 480
1047 }
1048
1049 assert object.data["url"] ==
1050 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1051
1052 assert object.data["attachment"] == [attachment]
1053 end
1054
1055 test "it accepts Flag activities" do
1056 user = insert(:user)
1057 other_user = insert(:user)
1058
1059 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1060 object = Object.normalize(activity)
1061
1062 message = %{
1063 "@context" => "https://www.w3.org/ns/activitystreams",
1064 "cc" => [user.ap_id],
1065 "object" => [user.ap_id, object.data["id"]],
1066 "type" => "Flag",
1067 "content" => "blocked AND reported!!!",
1068 "actor" => other_user.ap_id
1069 }
1070
1071 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1072
1073 assert activity.data["object"] == [user.ap_id, object.data["id"]]
1074 assert activity.data["content"] == "blocked AND reported!!!"
1075 assert activity.data["actor"] == other_user.ap_id
1076 assert activity.data["cc"] == [user.ap_id]
1077 end
1078 end
1079
1080 describe "prepare outgoing" do
1081 test "it inlines private announced objects" do
1082 user = insert(:user)
1083
1084 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1085
1086 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1087
1088 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1089
1090 assert modified["object"]["content"] == "hey"
1091 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1092 end
1093
1094 test "it turns mentions into tags" do
1095 user = insert(:user)
1096 other_user = insert(:user)
1097
1098 {:ok, activity} =
1099 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1100
1101 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1102 object = modified["object"]
1103
1104 expected_mention = %{
1105 "href" => other_user.ap_id,
1106 "name" => "@#{other_user.nickname}",
1107 "type" => "Mention"
1108 }
1109
1110 expected_tag = %{
1111 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1112 "type" => "Hashtag",
1113 "name" => "#2hu"
1114 }
1115
1116 assert Enum.member?(object["tag"], expected_tag)
1117 assert Enum.member?(object["tag"], expected_mention)
1118 end
1119
1120 test "it adds the sensitive property" do
1121 user = insert(:user)
1122
1123 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1124 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1125
1126 assert modified["object"]["sensitive"]
1127 end
1128
1129 test "it adds the json-ld context and the conversation property" do
1130 user = insert(:user)
1131
1132 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1133 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1134
1135 assert modified["@context"] ==
1136 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1137
1138 assert modified["object"]["conversation"] == modified["context"]
1139 end
1140
1141 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1142 user = insert(:user)
1143
1144 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1145 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1146
1147 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1148 end
1149
1150 test "it translates ostatus IDs to external URLs" do
1151 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
1152 {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
1153
1154 user = insert(:user)
1155
1156 {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
1157 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1158
1159 assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
1160 end
1161
1162 test "it translates ostatus reply_to IDs to external URLs" do
1163 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
1164 {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
1165
1166 user = insert(:user)
1167
1168 {:ok, activity} =
1169 CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
1170
1171 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1172
1173 assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
1174 end
1175
1176 test "it strips internal hashtag data" do
1177 user = insert(:user)
1178
1179 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1180
1181 expected_tag = %{
1182 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1183 "type" => "Hashtag",
1184 "name" => "#2hu"
1185 }
1186
1187 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1188
1189 assert modified["object"]["tag"] == [expected_tag]
1190 end
1191
1192 test "it strips internal fields" do
1193 user = insert(:user)
1194
1195 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1196
1197 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1198
1199 assert length(modified["object"]["tag"]) == 2
1200
1201 assert is_nil(modified["object"]["emoji"])
1202 assert is_nil(modified["object"]["like_count"])
1203 assert is_nil(modified["object"]["announcements"])
1204 assert is_nil(modified["object"]["announcement_count"])
1205 assert is_nil(modified["object"]["context_id"])
1206 end
1207
1208 test "it strips internal fields of article" do
1209 activity = insert(:article_activity)
1210
1211 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1212
1213 assert length(modified["object"]["tag"]) == 2
1214
1215 assert is_nil(modified["object"]["emoji"])
1216 assert is_nil(modified["object"]["like_count"])
1217 assert is_nil(modified["object"]["announcements"])
1218 assert is_nil(modified["object"]["announcement_count"])
1219 assert is_nil(modified["object"]["context_id"])
1220 assert is_nil(modified["object"]["likes"])
1221 end
1222
1223 test "the directMessage flag is present" do
1224 user = insert(:user)
1225 other_user = insert(:user)
1226
1227 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1228
1229 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1230
1231 assert modified["directMessage"] == false
1232
1233 {:ok, activity} =
1234 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1235
1236 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1237
1238 assert modified["directMessage"] == false
1239
1240 {:ok, activity} =
1241 CommonAPI.post(user, %{
1242 "status" => "@#{other_user.nickname} :moominmamma:",
1243 "visibility" => "direct"
1244 })
1245
1246 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1247
1248 assert modified["directMessage"] == true
1249 end
1250
1251 test "it strips BCC field" do
1252 user = insert(:user)
1253 {:ok, list} = Pleroma.List.create("foo", user)
1254
1255 {:ok, activity} =
1256 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1257
1258 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1259
1260 assert is_nil(modified["bcc"])
1261 end
1262
1263 test "it can handle Listen activities" do
1264 listen_activity = insert(:listen)
1265
1266 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1267
1268 assert modified["type"] == "Listen"
1269
1270 user = insert(:user)
1271
1272 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1273
1274 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1275 end
1276 end
1277
1278 describe "user upgrade" do
1279 test "it upgrades a user to activitypub" do
1280 user =
1281 insert(:user, %{
1282 nickname: "rye@niu.moe",
1283 local: false,
1284 ap_id: "https://niu.moe/users/rye",
1285 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1286 })
1287
1288 user_two = insert(:user, %{following: [user.follower_address]})
1289
1290 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1291 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1292 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1293
1294 user = User.get_cached_by_id(user.id)
1295 assert user.info.note_count == 1
1296
1297 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1298 ObanHelpers.perform_all()
1299
1300 assert user.info.ap_enabled
1301 assert user.info.note_count == 1
1302 assert user.follower_address == "https://niu.moe/users/rye/followers"
1303 assert user.following_address == "https://niu.moe/users/rye/following"
1304
1305 user = User.get_cached_by_id(user.id)
1306 assert user.info.note_count == 1
1307
1308 activity = Activity.get_by_id(activity.id)
1309 assert user.follower_address in activity.recipients
1310
1311 assert %{
1312 "url" => [
1313 %{
1314 "href" =>
1315 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1316 }
1317 ]
1318 } = user.avatar
1319
1320 assert %{
1321 "url" => [
1322 %{
1323 "href" =>
1324 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1325 }
1326 ]
1327 } = user.info.banner
1328
1329 refute "..." in activity.recipients
1330
1331 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1332 refute user.follower_address in unrelated_activity.recipients
1333
1334 user_two = User.get_cached_by_id(user_two.id)
1335 assert user.follower_address in user_two.following
1336 refute "..." in user_two.following
1337 end
1338 end
1339
1340 describe "maybe_retire_websub" do
1341 test "it deletes all websub client subscripitions with the user as topic" do
1342 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
1343 {:ok, ws} = Repo.insert(subscription)
1344
1345 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
1346 {:ok, ws2} = Repo.insert(subscription)
1347
1348 Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
1349
1350 refute Repo.get(WebsubClientSubscription, ws.id)
1351 assert Repo.get(WebsubClientSubscription, ws2.id)
1352 end
1353 end
1354
1355 describe "actor rewriting" do
1356 test "it fixes the actor URL property to be a proper URI" do
1357 data = %{
1358 "url" => %{"href" => "http://example.com"}
1359 }
1360
1361 rewritten = Transmogrifier.maybe_fix_user_object(data)
1362 assert rewritten["url"] == "http://example.com"
1363 end
1364 end
1365
1366 describe "actor origin containment" do
1367 test "it rejects activities which reference objects with bogus origins" do
1368 data = %{
1369 "@context" => "https://www.w3.org/ns/activitystreams",
1370 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1371 "actor" => "http://mastodon.example.org/users/admin",
1372 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1373 "object" => "https://info.pleroma.site/activity.json",
1374 "type" => "Announce"
1375 }
1376
1377 :error = Transmogrifier.handle_incoming(data)
1378 end
1379
1380 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1381 data = %{
1382 "@context" => "https://www.w3.org/ns/activitystreams",
1383 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1384 "actor" => "http://mastodon.example.org/users/admin",
1385 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1386 "object" => "https://info.pleroma.site/activity2.json",
1387 "type" => "Announce"
1388 }
1389
1390 :error = Transmogrifier.handle_incoming(data)
1391 end
1392
1393 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1394 data = %{
1395 "@context" => "https://www.w3.org/ns/activitystreams",
1396 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1397 "actor" => "http://mastodon.example.org/users/admin",
1398 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1399 "object" => "https://info.pleroma.site/activity3.json",
1400 "type" => "Announce"
1401 }
1402
1403 :error = Transmogrifier.handle_incoming(data)
1404 end
1405 end
1406
1407 describe "reserialization" do
1408 test "successfully reserializes a message with inReplyTo == nil" do
1409 user = insert(:user)
1410
1411 message = %{
1412 "@context" => "https://www.w3.org/ns/activitystreams",
1413 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1414 "cc" => [],
1415 "type" => "Create",
1416 "object" => %{
1417 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1418 "cc" => [],
1419 "type" => "Note",
1420 "content" => "Hi",
1421 "inReplyTo" => nil,
1422 "attributedTo" => user.ap_id
1423 },
1424 "actor" => user.ap_id
1425 }
1426
1427 {:ok, activity} = Transmogrifier.handle_incoming(message)
1428
1429 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1430 end
1431
1432 test "successfully reserializes a message with AS2 objects in IR" do
1433 user = insert(:user)
1434
1435 message = %{
1436 "@context" => "https://www.w3.org/ns/activitystreams",
1437 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1438 "cc" => [],
1439 "type" => "Create",
1440 "object" => %{
1441 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1442 "cc" => [],
1443 "type" => "Note",
1444 "content" => "Hi",
1445 "inReplyTo" => nil,
1446 "attributedTo" => user.ap_id,
1447 "tag" => [
1448 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1449 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1450 ]
1451 },
1452 "actor" => user.ap_id
1453 }
1454
1455 {:ok, activity} = Transmogrifier.handle_incoming(message)
1456
1457 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1458 end
1459 end
1460
1461 test "Rewrites Answers to Notes" do
1462 user = insert(:user)
1463
1464 {:ok, poll_activity} =
1465 CommonAPI.post(user, %{
1466 "status" => "suya...",
1467 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1468 })
1469
1470 poll_object = Object.normalize(poll_activity)
1471 # TODO: Replace with CommonAPI vote creation when implemented
1472 data =
1473 File.read!("test/fixtures/mastodon-vote.json")
1474 |> Poison.decode!()
1475 |> Kernel.put_in(["to"], user.ap_id)
1476 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1477 |> Kernel.put_in(["object", "to"], user.ap_id)
1478
1479 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1480 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1481
1482 assert data["object"]["type"] == "Note"
1483 end
1484
1485 describe "fix_explicit_addressing" do
1486 setup do
1487 user = insert(:user)
1488 [user: user]
1489 end
1490
1491 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1492 explicitly_mentioned_actors = [
1493 "https://pleroma.gold/users/user1",
1494 "https://pleroma.gold/user2"
1495 ]
1496
1497 object = %{
1498 "actor" => user.ap_id,
1499 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1500 "cc" => [],
1501 "tag" =>
1502 Enum.map(explicitly_mentioned_actors, fn href ->
1503 %{"type" => "Mention", "href" => href}
1504 end)
1505 }
1506
1507 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1508 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1509 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1510 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1511 end
1512
1513 test "does not move actor's follower collection to cc", %{user: user} do
1514 object = %{
1515 "actor" => user.ap_id,
1516 "to" => [user.follower_address],
1517 "cc" => []
1518 }
1519
1520 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1521 assert user.follower_address in fixed_object["to"]
1522 refute user.follower_address in fixed_object["cc"]
1523 end
1524
1525 test "removes recipient's follower collection from cc", %{user: user} do
1526 recipient = insert(:user)
1527
1528 object = %{
1529 "actor" => user.ap_id,
1530 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1531 "cc" => [user.follower_address, recipient.follower_address]
1532 }
1533
1534 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1535
1536 assert user.follower_address in fixed_object["cc"]
1537 refute recipient.follower_address in fixed_object["cc"]
1538 refute recipient.follower_address in fixed_object["to"]
1539 end
1540 end
1541
1542 describe "fix_summary/1" do
1543 test "returns fixed object" do
1544 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1545 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1546 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1547 end
1548 end
1549
1550 describe "fix_in_reply_to/2" do
1551 clear_config([:instance, :federation_incoming_replies_max_depth])
1552
1553 setup do
1554 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1555 [data: data]
1556 end
1557
1558 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1559 assert Transmogrifier.fix_in_reply_to(data) == data
1560 end
1561
1562 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1563 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1564
1565 object_with_reply =
1566 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1567
1568 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1569 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1570 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1571
1572 object_with_reply =
1573 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1574
1575 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1576 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1577 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1578
1579 object_with_reply =
1580 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1581
1582 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1583 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1584 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1585
1586 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1587 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1588 assert modified_object["inReplyTo"] == []
1589 assert modified_object["inReplyToAtomUri"] == ""
1590 end
1591
1592 test "returns modified object when allowed incoming reply", %{data: data} do
1593 object_with_reply =
1594 Map.put(
1595 data["object"],
1596 "inReplyTo",
1597 "https://shitposter.club/notice/2827873"
1598 )
1599
1600 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1601 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1602
1603 assert modified_object["inReplyTo"] ==
1604 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1605
1606 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1607
1608 assert modified_object["conversation"] ==
1609 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1610
1611 assert modified_object["context"] ==
1612 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1613 end
1614 end
1615
1616 describe "fix_url/1" do
1617 test "fixes data for object when url is map" do
1618 object = %{
1619 "url" => %{
1620 "type" => "Link",
1621 "mimeType" => "video/mp4",
1622 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1623 }
1624 }
1625
1626 assert Transmogrifier.fix_url(object) == %{
1627 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1628 }
1629 end
1630
1631 test "fixes data for video object" do
1632 object = %{
1633 "type" => "Video",
1634 "url" => [
1635 %{
1636 "type" => "Link",
1637 "mimeType" => "video/mp4",
1638 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1639 },
1640 %{
1641 "type" => "Link",
1642 "mimeType" => "video/mp4",
1643 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1644 },
1645 %{
1646 "type" => "Link",
1647 "mimeType" => "text/html",
1648 "href" => "https://peertube.-2d4c2d1630e3"
1649 },
1650 %{
1651 "type" => "Link",
1652 "mimeType" => "text/html",
1653 "href" => "https://peertube.-2d4c2d16377-42"
1654 }
1655 ]
1656 }
1657
1658 assert Transmogrifier.fix_url(object) == %{
1659 "attachment" => [
1660 %{
1661 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1662 "mimeType" => "video/mp4",
1663 "type" => "Link"
1664 }
1665 ],
1666 "type" => "Video",
1667 "url" => "https://peertube.-2d4c2d1630e3"
1668 }
1669 end
1670
1671 test "fixes url for not Video object" do
1672 object = %{
1673 "type" => "Text",
1674 "url" => [
1675 %{
1676 "type" => "Link",
1677 "mimeType" => "text/html",
1678 "href" => "https://peertube.-2d4c2d1630e3"
1679 },
1680 %{
1681 "type" => "Link",
1682 "mimeType" => "text/html",
1683 "href" => "https://peertube.-2d4c2d16377-42"
1684 }
1685 ]
1686 }
1687
1688 assert Transmogrifier.fix_url(object) == %{
1689 "type" => "Text",
1690 "url" => "https://peertube.-2d4c2d1630e3"
1691 }
1692
1693 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1694 "type" => "Text",
1695 "url" => ""
1696 }
1697 end
1698
1699 test "retunrs not modified object" do
1700 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1701 end
1702 end
1703
1704 describe "get_obj_helper/2" do
1705 test "returns nil when cannot normalize object" do
1706 refute Transmogrifier.get_obj_helper("test-obj-id")
1707 end
1708
1709 test "returns {:ok, %Object{}} for success case" do
1710 assert {:ok, %Object{}} =
1711 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1712 end
1713 end
1714
1715 describe "fix_attachments/1" do
1716 test "returns not modified object" do
1717 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1718 assert Transmogrifier.fix_attachments(data) == data
1719 end
1720
1721 test "returns modified object when attachment is map" do
1722 assert Transmogrifier.fix_attachments(%{
1723 "attachment" => %{
1724 "mediaType" => "video/mp4",
1725 "url" => "https://peertube.moe/stat-480.mp4"
1726 }
1727 }) == %{
1728 "attachment" => [
1729 %{
1730 "mediaType" => "video/mp4",
1731 "url" => [
1732 %{
1733 "href" => "https://peertube.moe/stat-480.mp4",
1734 "mediaType" => "video/mp4",
1735 "type" => "Link"
1736 }
1737 ]
1738 }
1739 ]
1740 }
1741 end
1742
1743 test "returns modified object when attachment is list" do
1744 assert Transmogrifier.fix_attachments(%{
1745 "attachment" => [
1746 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1747 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1748 ]
1749 }) == %{
1750 "attachment" => [
1751 %{
1752 "mediaType" => "video/mp4",
1753 "url" => [
1754 %{
1755 "href" => "https://pe.er/stat-480.mp4",
1756 "mediaType" => "video/mp4",
1757 "type" => "Link"
1758 }
1759 ]
1760 },
1761 %{
1762 "href" => "https://pe.er/stat-480.mp4",
1763 "mediaType" => "video/mp4",
1764 "mimeType" => "video/mp4",
1765 "url" => [
1766 %{
1767 "href" => "https://pe.er/stat-480.mp4",
1768 "mediaType" => "video/mp4",
1769 "type" => "Link"
1770 }
1771 ]
1772 }
1773 ]
1774 }
1775 end
1776 end
1777
1778 describe "fix_emoji/1" do
1779 test "returns not modified object when object not contains tags" do
1780 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1781 assert Transmogrifier.fix_emoji(data) == data
1782 end
1783
1784 test "returns object with emoji when object contains list tags" do
1785 assert Transmogrifier.fix_emoji(%{
1786 "tag" => [
1787 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1788 %{"type" => "Hashtag"}
1789 ]
1790 }) == %{
1791 "emoji" => %{"bib" => "/test"},
1792 "tag" => [
1793 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1794 %{"type" => "Hashtag"}
1795 ]
1796 }
1797 end
1798
1799 test "returns object with emoji when object contains map tag" do
1800 assert Transmogrifier.fix_emoji(%{
1801 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1802 }) == %{
1803 "emoji" => %{"bib" => "/test"},
1804 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1805 }
1806 end
1807 end
1808 end