Merge branch 'bugfix/notification-nil-actor' 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
1088 assert modified["object"]["content"] == "hey"
1089 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1090 end
1091
1092 test "it turns mentions into tags" do
1093 user = insert(:user)
1094 other_user = insert(:user)
1095
1096 {:ok, activity} =
1097 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1098
1099 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1100 object = modified["object"]
1101
1102 expected_mention = %{
1103 "href" => other_user.ap_id,
1104 "name" => "@#{other_user.nickname}",
1105 "type" => "Mention"
1106 }
1107
1108 expected_tag = %{
1109 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1110 "type" => "Hashtag",
1111 "name" => "#2hu"
1112 }
1113
1114 assert Enum.member?(object["tag"], expected_tag)
1115 assert Enum.member?(object["tag"], expected_mention)
1116 end
1117
1118 test "it adds the sensitive property" do
1119 user = insert(:user)
1120
1121 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1122 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1123
1124 assert modified["object"]["sensitive"]
1125 end
1126
1127 test "it adds the json-ld context and the conversation property" do
1128 user = insert(:user)
1129
1130 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1131 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1132
1133 assert modified["@context"] ==
1134 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1135
1136 assert modified["object"]["conversation"] == modified["context"]
1137 end
1138
1139 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1140 user = insert(:user)
1141
1142 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1143 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1144
1145 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1146 end
1147
1148 test "it translates ostatus IDs to external URLs" do
1149 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
1150 {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
1151
1152 user = insert(:user)
1153
1154 {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
1155 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1156
1157 assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
1158 end
1159
1160 test "it translates ostatus reply_to IDs to external URLs" do
1161 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
1162 {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
1163
1164 user = insert(:user)
1165
1166 {:ok, activity} =
1167 CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
1168
1169 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1170
1171 assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
1172 end
1173
1174 test "it strips internal hashtag data" do
1175 user = insert(:user)
1176
1177 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1178
1179 expected_tag = %{
1180 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1181 "type" => "Hashtag",
1182 "name" => "#2hu"
1183 }
1184
1185 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1186
1187 assert modified["object"]["tag"] == [expected_tag]
1188 end
1189
1190 test "it strips internal fields" do
1191 user = insert(:user)
1192
1193 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1194
1195 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1196
1197 assert length(modified["object"]["tag"]) == 2
1198
1199 assert is_nil(modified["object"]["emoji"])
1200 assert is_nil(modified["object"]["like_count"])
1201 assert is_nil(modified["object"]["announcements"])
1202 assert is_nil(modified["object"]["announcement_count"])
1203 assert is_nil(modified["object"]["context_id"])
1204 end
1205
1206 test "it strips internal fields of article" do
1207 activity = insert(:article_activity)
1208
1209 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1210
1211 assert length(modified["object"]["tag"]) == 2
1212
1213 assert is_nil(modified["object"]["emoji"])
1214 assert is_nil(modified["object"]["like_count"])
1215 assert is_nil(modified["object"]["announcements"])
1216 assert is_nil(modified["object"]["announcement_count"])
1217 assert is_nil(modified["object"]["context_id"])
1218 assert is_nil(modified["object"]["likes"])
1219 end
1220
1221 test "the directMessage flag is present" do
1222 user = insert(:user)
1223 other_user = insert(:user)
1224
1225 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1226
1227 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1228
1229 assert modified["directMessage"] == false
1230
1231 {:ok, activity} =
1232 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1233
1234 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1235
1236 assert modified["directMessage"] == false
1237
1238 {:ok, activity} =
1239 CommonAPI.post(user, %{
1240 "status" => "@#{other_user.nickname} :moominmamma:",
1241 "visibility" => "direct"
1242 })
1243
1244 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1245
1246 assert modified["directMessage"] == true
1247 end
1248
1249 test "it strips BCC field" do
1250 user = insert(:user)
1251 {:ok, list} = Pleroma.List.create("foo", user)
1252
1253 {:ok, activity} =
1254 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1255
1256 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1257
1258 assert is_nil(modified["bcc"])
1259 end
1260
1261 test "it can handle Listen activities" do
1262 listen_activity = insert(:listen)
1263
1264 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1265
1266 assert modified["type"] == "Listen"
1267
1268 user = insert(:user)
1269
1270 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1271
1272 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1273 end
1274 end
1275
1276 describe "user upgrade" do
1277 test "it upgrades a user to activitypub" do
1278 user =
1279 insert(:user, %{
1280 nickname: "rye@niu.moe",
1281 local: false,
1282 ap_id: "https://niu.moe/users/rye",
1283 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1284 })
1285
1286 user_two = insert(:user, %{following: [user.follower_address]})
1287
1288 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1289 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1290 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1291
1292 user = User.get_cached_by_id(user.id)
1293 assert user.info.note_count == 1
1294
1295 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1296 ObanHelpers.perform_all()
1297
1298 assert user.info.ap_enabled
1299 assert user.info.note_count == 1
1300 assert user.follower_address == "https://niu.moe/users/rye/followers"
1301 assert user.following_address == "https://niu.moe/users/rye/following"
1302
1303 user = User.get_cached_by_id(user.id)
1304 assert user.info.note_count == 1
1305
1306 activity = Activity.get_by_id(activity.id)
1307 assert user.follower_address in activity.recipients
1308
1309 assert %{
1310 "url" => [
1311 %{
1312 "href" =>
1313 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1314 }
1315 ]
1316 } = user.avatar
1317
1318 assert %{
1319 "url" => [
1320 %{
1321 "href" =>
1322 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1323 }
1324 ]
1325 } = user.info.banner
1326
1327 refute "..." in activity.recipients
1328
1329 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1330 refute user.follower_address in unrelated_activity.recipients
1331
1332 user_two = User.get_cached_by_id(user_two.id)
1333 assert user.follower_address in user_two.following
1334 refute "..." in user_two.following
1335 end
1336 end
1337
1338 describe "maybe_retire_websub" do
1339 test "it deletes all websub client subscripitions with the user as topic" do
1340 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
1341 {:ok, ws} = Repo.insert(subscription)
1342
1343 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
1344 {:ok, ws2} = Repo.insert(subscription)
1345
1346 Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
1347
1348 refute Repo.get(WebsubClientSubscription, ws.id)
1349 assert Repo.get(WebsubClientSubscription, ws2.id)
1350 end
1351 end
1352
1353 describe "actor rewriting" do
1354 test "it fixes the actor URL property to be a proper URI" do
1355 data = %{
1356 "url" => %{"href" => "http://example.com"}
1357 }
1358
1359 rewritten = Transmogrifier.maybe_fix_user_object(data)
1360 assert rewritten["url"] == "http://example.com"
1361 end
1362 end
1363
1364 describe "actor origin containment" do
1365 test "it rejects activities which reference objects with bogus origins" do
1366 data = %{
1367 "@context" => "https://www.w3.org/ns/activitystreams",
1368 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1369 "actor" => "http://mastodon.example.org/users/admin",
1370 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1371 "object" => "https://info.pleroma.site/activity.json",
1372 "type" => "Announce"
1373 }
1374
1375 :error = Transmogrifier.handle_incoming(data)
1376 end
1377
1378 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1379 data = %{
1380 "@context" => "https://www.w3.org/ns/activitystreams",
1381 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1382 "actor" => "http://mastodon.example.org/users/admin",
1383 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1384 "object" => "https://info.pleroma.site/activity2.json",
1385 "type" => "Announce"
1386 }
1387
1388 :error = Transmogrifier.handle_incoming(data)
1389 end
1390
1391 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1392 data = %{
1393 "@context" => "https://www.w3.org/ns/activitystreams",
1394 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1395 "actor" => "http://mastodon.example.org/users/admin",
1396 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1397 "object" => "https://info.pleroma.site/activity3.json",
1398 "type" => "Announce"
1399 }
1400
1401 :error = Transmogrifier.handle_incoming(data)
1402 end
1403 end
1404
1405 describe "reserialization" do
1406 test "successfully reserializes a message with inReplyTo == nil" do
1407 user = insert(:user)
1408
1409 message = %{
1410 "@context" => "https://www.w3.org/ns/activitystreams",
1411 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1412 "cc" => [],
1413 "type" => "Create",
1414 "object" => %{
1415 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1416 "cc" => [],
1417 "type" => "Note",
1418 "content" => "Hi",
1419 "inReplyTo" => nil,
1420 "attributedTo" => user.ap_id
1421 },
1422 "actor" => user.ap_id
1423 }
1424
1425 {:ok, activity} = Transmogrifier.handle_incoming(message)
1426
1427 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1428 end
1429
1430 test "successfully reserializes a message with AS2 objects in IR" do
1431 user = insert(:user)
1432
1433 message = %{
1434 "@context" => "https://www.w3.org/ns/activitystreams",
1435 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1436 "cc" => [],
1437 "type" => "Create",
1438 "object" => %{
1439 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1440 "cc" => [],
1441 "type" => "Note",
1442 "content" => "Hi",
1443 "inReplyTo" => nil,
1444 "attributedTo" => user.ap_id,
1445 "tag" => [
1446 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1447 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1448 ]
1449 },
1450 "actor" => user.ap_id
1451 }
1452
1453 {:ok, activity} = Transmogrifier.handle_incoming(message)
1454
1455 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1456 end
1457 end
1458
1459 test "Rewrites Answers to Notes" do
1460 user = insert(:user)
1461
1462 {:ok, poll_activity} =
1463 CommonAPI.post(user, %{
1464 "status" => "suya...",
1465 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1466 })
1467
1468 poll_object = Object.normalize(poll_activity)
1469 # TODO: Replace with CommonAPI vote creation when implemented
1470 data =
1471 File.read!("test/fixtures/mastodon-vote.json")
1472 |> Poison.decode!()
1473 |> Kernel.put_in(["to"], user.ap_id)
1474 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1475 |> Kernel.put_in(["object", "to"], user.ap_id)
1476
1477 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1478 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1479
1480 assert data["object"]["type"] == "Note"
1481 end
1482
1483 describe "fix_explicit_addressing" do
1484 setup do
1485 user = insert(:user)
1486 [user: user]
1487 end
1488
1489 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1490 explicitly_mentioned_actors = [
1491 "https://pleroma.gold/users/user1",
1492 "https://pleroma.gold/user2"
1493 ]
1494
1495 object = %{
1496 "actor" => user.ap_id,
1497 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1498 "cc" => [],
1499 "tag" =>
1500 Enum.map(explicitly_mentioned_actors, fn href ->
1501 %{"type" => "Mention", "href" => href}
1502 end)
1503 }
1504
1505 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1506 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1507 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1508 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1509 end
1510
1511 test "does not move actor's follower collection to cc", %{user: user} do
1512 object = %{
1513 "actor" => user.ap_id,
1514 "to" => [user.follower_address],
1515 "cc" => []
1516 }
1517
1518 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1519 assert user.follower_address in fixed_object["to"]
1520 refute user.follower_address in fixed_object["cc"]
1521 end
1522
1523 test "removes recipient's follower collection from cc", %{user: user} do
1524 recipient = insert(:user)
1525
1526 object = %{
1527 "actor" => user.ap_id,
1528 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1529 "cc" => [user.follower_address, recipient.follower_address]
1530 }
1531
1532 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1533
1534 assert user.follower_address in fixed_object["cc"]
1535 refute recipient.follower_address in fixed_object["cc"]
1536 refute recipient.follower_address in fixed_object["to"]
1537 end
1538 end
1539
1540 describe "fix_summary/1" do
1541 test "returns fixed object" do
1542 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1543 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1544 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1545 end
1546 end
1547
1548 describe "fix_in_reply_to/2" do
1549 clear_config([:instance, :federation_incoming_replies_max_depth])
1550
1551 setup do
1552 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1553 [data: data]
1554 end
1555
1556 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1557 assert Transmogrifier.fix_in_reply_to(data) == data
1558 end
1559
1560 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1561 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1562
1563 object_with_reply =
1564 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1565
1566 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1567 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1568 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1569
1570 object_with_reply =
1571 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1572
1573 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1574 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1575 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1576
1577 object_with_reply =
1578 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1579
1580 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1581 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1582 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1583
1584 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1585 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1586 assert modified_object["inReplyTo"] == []
1587 assert modified_object["inReplyToAtomUri"] == ""
1588 end
1589
1590 test "returns modified object when allowed incoming reply", %{data: data} do
1591 object_with_reply =
1592 Map.put(
1593 data["object"],
1594 "inReplyTo",
1595 "https://shitposter.club/notice/2827873"
1596 )
1597
1598 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1599 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1600
1601 assert modified_object["inReplyTo"] ==
1602 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1603
1604 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1605
1606 assert modified_object["conversation"] ==
1607 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1608
1609 assert modified_object["context"] ==
1610 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1611 end
1612 end
1613
1614 describe "fix_url/1" do
1615 test "fixes data for object when url is map" do
1616 object = %{
1617 "url" => %{
1618 "type" => "Link",
1619 "mimeType" => "video/mp4",
1620 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1621 }
1622 }
1623
1624 assert Transmogrifier.fix_url(object) == %{
1625 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1626 }
1627 end
1628
1629 test "fixes data for video object" do
1630 object = %{
1631 "type" => "Video",
1632 "url" => [
1633 %{
1634 "type" => "Link",
1635 "mimeType" => "video/mp4",
1636 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1637 },
1638 %{
1639 "type" => "Link",
1640 "mimeType" => "video/mp4",
1641 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1642 },
1643 %{
1644 "type" => "Link",
1645 "mimeType" => "text/html",
1646 "href" => "https://peertube.-2d4c2d1630e3"
1647 },
1648 %{
1649 "type" => "Link",
1650 "mimeType" => "text/html",
1651 "href" => "https://peertube.-2d4c2d16377-42"
1652 }
1653 ]
1654 }
1655
1656 assert Transmogrifier.fix_url(object) == %{
1657 "attachment" => [
1658 %{
1659 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1660 "mimeType" => "video/mp4",
1661 "type" => "Link"
1662 }
1663 ],
1664 "type" => "Video",
1665 "url" => "https://peertube.-2d4c2d1630e3"
1666 }
1667 end
1668
1669 test "fixes url for not Video object" do
1670 object = %{
1671 "type" => "Text",
1672 "url" => [
1673 %{
1674 "type" => "Link",
1675 "mimeType" => "text/html",
1676 "href" => "https://peertube.-2d4c2d1630e3"
1677 },
1678 %{
1679 "type" => "Link",
1680 "mimeType" => "text/html",
1681 "href" => "https://peertube.-2d4c2d16377-42"
1682 }
1683 ]
1684 }
1685
1686 assert Transmogrifier.fix_url(object) == %{
1687 "type" => "Text",
1688 "url" => "https://peertube.-2d4c2d1630e3"
1689 }
1690
1691 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1692 "type" => "Text",
1693 "url" => ""
1694 }
1695 end
1696
1697 test "retunrs not modified object" do
1698 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1699 end
1700 end
1701
1702 describe "get_obj_helper/2" do
1703 test "returns nil when cannot normalize object" do
1704 refute Transmogrifier.get_obj_helper("test-obj-id")
1705 end
1706
1707 test "returns {:ok, %Object{}} for success case" do
1708 assert {:ok, %Object{}} =
1709 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1710 end
1711 end
1712
1713 describe "fix_attachments/1" do
1714 test "returns not modified object" do
1715 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1716 assert Transmogrifier.fix_attachments(data) == data
1717 end
1718
1719 test "returns modified object when attachment is map" do
1720 assert Transmogrifier.fix_attachments(%{
1721 "attachment" => %{
1722 "mediaType" => "video/mp4",
1723 "url" => "https://peertube.moe/stat-480.mp4"
1724 }
1725 }) == %{
1726 "attachment" => [
1727 %{
1728 "mediaType" => "video/mp4",
1729 "url" => [
1730 %{
1731 "href" => "https://peertube.moe/stat-480.mp4",
1732 "mediaType" => "video/mp4",
1733 "type" => "Link"
1734 }
1735 ]
1736 }
1737 ]
1738 }
1739 end
1740
1741 test "returns modified object when attachment is list" do
1742 assert Transmogrifier.fix_attachments(%{
1743 "attachment" => [
1744 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1745 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1746 ]
1747 }) == %{
1748 "attachment" => [
1749 %{
1750 "mediaType" => "video/mp4",
1751 "url" => [
1752 %{
1753 "href" => "https://pe.er/stat-480.mp4",
1754 "mediaType" => "video/mp4",
1755 "type" => "Link"
1756 }
1757 ]
1758 },
1759 %{
1760 "href" => "https://pe.er/stat-480.mp4",
1761 "mediaType" => "video/mp4",
1762 "mimeType" => "video/mp4",
1763 "url" => [
1764 %{
1765 "href" => "https://pe.er/stat-480.mp4",
1766 "mediaType" => "video/mp4",
1767 "type" => "Link"
1768 }
1769 ]
1770 }
1771 ]
1772 }
1773 end
1774 end
1775
1776 describe "fix_emoji/1" do
1777 test "returns not modified object when object not contains tags" do
1778 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1779 assert Transmogrifier.fix_emoji(data) == data
1780 end
1781
1782 test "returns object with emoji when object contains list tags" do
1783 assert Transmogrifier.fix_emoji(%{
1784 "tag" => [
1785 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1786 %{"type" => "Hashtag"}
1787 ]
1788 }) == %{
1789 "emoji" => %{"bib" => "/test"},
1790 "tag" => [
1791 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1792 %{"type" => "Hashtag"}
1793 ]
1794 }
1795 end
1796
1797 test "returns object with emoji when object contains map tag" do
1798 assert Transmogrifier.fix_emoji(%{
1799 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1800 }) == %{
1801 "emoji" => %{"bib" => "/test"},
1802 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1803 }
1804 end
1805 end
1806 end