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