Merge pull request 'allow quote-inline span class' (#152) from allow-quote-inline...
[akkoma] / test / pleroma / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Tests.ObanHelpers
12 alias Pleroma.User
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.CommonAPI
17
18 import Mock
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
21
22 setup_all do
23 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
24 :ok
25 end
26
27 setup do: clear_config([:instance, :max_remote_account_fields])
28
29 describe "handle_incoming" do
30 test "it works for incoming unfollows with an existing follow" do
31 user = insert(:user)
32
33 follow_data =
34 File.read!("test/fixtures/mastodon-follow-activity.json")
35 |> Jason.decode!()
36 |> Map.put("object", user.ap_id)
37
38 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
39
40 data =
41 File.read!("test/fixtures/mastodon-unfollow-activity.json")
42 |> Jason.decode!()
43 |> Map.put("object", follow_data)
44
45 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
46
47 assert data["type"] == "Undo"
48 assert data["object"]["type"] == "Follow"
49 assert data["object"]["object"] == user.ap_id
50 assert data["actor"] == "http://mastodon.example.org/users/admin"
51
52 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
53 end
54
55 test "it accepts Flag activities" do
56 user = insert(:user)
57 other_user = insert(:user)
58
59 {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
60 object = Object.normalize(activity, fetch: false)
61
62 note_obj = %{
63 "type" => "Note",
64 "id" => activity.data["id"],
65 "content" => "test post",
66 "published" => object.data["published"],
67 "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
68 }
69
70 message = %{
71 "@context" => "https://www.w3.org/ns/activitystreams",
72 "cc" => [user.ap_id],
73 "object" => [user.ap_id, activity.data["id"]],
74 "type" => "Flag",
75 "content" => "blocked AND reported!!!",
76 "actor" => other_user.ap_id
77 }
78
79 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
80
81 assert activity.data["object"] == [user.ap_id, note_obj]
82 assert activity.data["content"] == "blocked AND reported!!!"
83 assert activity.data["actor"] == other_user.ap_id
84 assert activity.data["cc"] == [user.ap_id]
85 end
86
87 test "it accepts Move activities" do
88 old_user = insert(:user)
89 new_user = insert(:user)
90
91 message = %{
92 "@context" => "https://www.w3.org/ns/activitystreams",
93 "type" => "Move",
94 "actor" => old_user.ap_id,
95 "object" => old_user.ap_id,
96 "target" => new_user.ap_id
97 }
98
99 assert :error = Transmogrifier.handle_incoming(message)
100
101 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
102
103 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
104 assert activity.actor == old_user.ap_id
105 assert activity.data["actor"] == old_user.ap_id
106 assert activity.data["object"] == old_user.ap_id
107 assert activity.data["target"] == new_user.ap_id
108 assert activity.data["type"] == "Move"
109 end
110
111 test "it fixes both the Create and object contexts in a reply" do
112 insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
113 insert(:user, ap_id: "https://p.helene.moe/users/helene")
114
115 create_activity =
116 "test/fixtures/create-pleroma-reply-to-misskey-thread.json"
117 |> File.read!()
118 |> Jason.decode!()
119
120 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(create_activity)
121
122 object = Object.normalize(activity, fetch: false)
123
124 assert activity.data["context"] == object.data["context"]
125 end
126 end
127
128 describe "prepare outgoing" do
129 test "it inlines private announced objects" do
130 user = insert(:user)
131
132 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
133
134 {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
135
136 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
137
138 assert modified["object"]["content"] == "hey"
139 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
140 end
141
142 test "it turns mentions into tags" do
143 user = insert(:user)
144 other_user = insert(:user)
145
146 {:ok, activity} =
147 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
148
149 with_mock Pleroma.Notification,
150 get_notified_from_activity: fn _, _ -> [] end do
151 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
152
153 object = modified["object"]
154
155 expected_mention = %{
156 "href" => other_user.ap_id,
157 "name" => "@#{other_user.nickname}",
158 "type" => "Mention"
159 }
160
161 expected_tag = %{
162 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
163 "type" => "Hashtag",
164 "name" => "#2hu"
165 }
166
167 refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
168 assert Enum.member?(object["tag"], expected_tag)
169 assert Enum.member?(object["tag"], expected_mention)
170 end
171 end
172
173 test "it adds the json-ld context and the conversation property" do
174 user = insert(:user)
175
176 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
177 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
178
179 assert modified["@context"] == Utils.make_json_ld_header()["@context"]
180
181 assert modified["object"]["conversation"] == modified["context"]
182 end
183
184 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
185 user = insert(:user)
186
187 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
188 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
189
190 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
191 end
192
193 test "it strips internal hashtag data" do
194 user = insert(:user)
195
196 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
197
198 expected_tag = %{
199 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
200 "type" => "Hashtag",
201 "name" => "#2hu"
202 }
203
204 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
205
206 assert modified["object"]["tag"] == [expected_tag]
207 end
208
209 test "it strips internal fields" do
210 user = insert(:user)
211
212 {:ok, activity} =
213 CommonAPI.post(user, %{
214 status: "#2hu :firefox:",
215 generator: %{type: "Application", name: "TestClient", url: "https://pleroma.social"}
216 })
217
218 # Ensure injected application data made it into the activity
219 # as we don't have a Token to derive it from, otherwise it will
220 # be nil and the test will pass
221 assert %{
222 type: "Application",
223 name: "TestClient",
224 url: "https://pleroma.social"
225 } == activity.object.data["generator"]
226
227 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
228
229 assert length(modified["object"]["tag"]) == 2
230
231 assert is_nil(modified["object"]["emoji"])
232 assert is_nil(modified["object"]["like_count"])
233 assert is_nil(modified["object"]["announcements"])
234 assert is_nil(modified["object"]["announcement_count"])
235 assert is_nil(modified["object"]["context_id"])
236 assert is_nil(modified["object"]["generator"])
237 end
238
239 test "it strips internal fields of article" do
240 activity = insert(:article_activity)
241
242 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
243
244 assert length(modified["object"]["tag"]) == 2
245
246 assert is_nil(modified["object"]["emoji"])
247 assert is_nil(modified["object"]["like_count"])
248 assert is_nil(modified["object"]["announcements"])
249 assert is_nil(modified["object"]["announcement_count"])
250 assert is_nil(modified["object"]["context_id"])
251 assert is_nil(modified["object"]["likes"])
252 end
253
254 test "the directMessage flag is present" do
255 user = insert(:user)
256 other_user = insert(:user)
257
258 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
259
260 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
261
262 assert modified["directMessage"] == false
263
264 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
265
266 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
267
268 assert modified["directMessage"] == false
269
270 {:ok, activity} =
271 CommonAPI.post(user, %{
272 status: "@#{other_user.nickname} :moominmamma:",
273 visibility: "direct"
274 })
275
276 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
277
278 assert modified["directMessage"] == true
279 end
280
281 test "it strips BCC field" do
282 user = insert(:user)
283 {:ok, list} = Pleroma.List.create("foo", user)
284
285 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
286
287 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
288
289 assert is_nil(modified["bcc"])
290 end
291
292 test "custom emoji urls are URI encoded" do
293 # :dinosaur: filename has a space -> dino walking.gif
294 user = insert(:user)
295
296 {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"})
297
298 {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
299
300 assert length(prepared["object"]["tag"]) == 1
301
302 url = prepared["object"]["tag"] |> List.first() |> Map.get("icon") |> Map.get("url")
303
304 assert url == "http://localhost:4001/emoji/dino%20walking.gif"
305 end
306 end
307
308 describe "user upgrade" do
309 test "it upgrades a user to activitypub" do
310 user =
311 insert(:user, %{
312 nickname: "rye@niu.moe",
313 local: false,
314 ap_id: "https://niu.moe/users/rye",
315 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
316 })
317
318 user_two = insert(:user)
319 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
320
321 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
322 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
323 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
324
325 user = User.get_cached_by_id(user.id)
326 assert user.note_count == 1
327
328 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
329 ObanHelpers.perform_all()
330
331 assert user.ap_enabled
332 assert user.note_count == 1
333 assert user.follower_address == "https://niu.moe/users/rye/followers"
334 assert user.following_address == "https://niu.moe/users/rye/following"
335
336 user = User.get_cached_by_id(user.id)
337 assert user.note_count == 1
338
339 activity = Activity.get_by_id(activity.id)
340 assert user.follower_address in activity.recipients
341
342 assert %{
343 "url" => [
344 %{
345 "href" =>
346 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
347 }
348 ]
349 } = user.avatar
350
351 assert %{
352 "url" => [
353 %{
354 "href" =>
355 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
356 }
357 ]
358 } = user.banner
359
360 refute "..." in activity.recipients
361
362 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
363 refute user.follower_address in unrelated_activity.recipients
364
365 user_two = User.get_cached_by_id(user_two.id)
366 assert User.following?(user_two, user)
367 refute "..." in User.following(user_two)
368 end
369 end
370
371 describe "actor rewriting" do
372 test "it fixes the actor URL property to be a proper URI" do
373 data = %{
374 "url" => %{"href" => "http://example.com"}
375 }
376
377 rewritten = Transmogrifier.maybe_fix_user_object(data)
378 assert rewritten["url"] == "http://example.com"
379 end
380 end
381
382 describe "actor origin containment" do
383 test "it rejects activities which reference objects with bogus origins" do
384 data = %{
385 "@context" => "https://www.w3.org/ns/activitystreams",
386 "id" => "http://mastodon.example.org/users/admin/activities/1234",
387 "actor" => "http://mastodon.example.org/users/admin",
388 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
389 "object" => "https://info.pleroma.site/activity.json",
390 "type" => "Announce"
391 }
392
393 assert capture_log(fn ->
394 {:error, _} = Transmogrifier.handle_incoming(data)
395 end) =~ "Object containment failed"
396 end
397
398 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
399 data = %{
400 "@context" => "https://www.w3.org/ns/activitystreams",
401 "id" => "http://mastodon.example.org/users/admin/activities/1234",
402 "actor" => "http://mastodon.example.org/users/admin",
403 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
404 "object" => "https://info.pleroma.site/activity2.json",
405 "type" => "Announce"
406 }
407
408 assert capture_log(fn ->
409 {:error, _} = Transmogrifier.handle_incoming(data)
410 end) =~ "Object containment failed"
411 end
412
413 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
414 data = %{
415 "@context" => "https://www.w3.org/ns/activitystreams",
416 "id" => "http://mastodon.example.org/users/admin/activities/1234",
417 "actor" => "http://mastodon.example.org/users/admin",
418 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
419 "object" => "https://info.pleroma.site/activity3.json",
420 "type" => "Announce"
421 }
422
423 assert capture_log(fn ->
424 {:error, _} = Transmogrifier.handle_incoming(data)
425 end) =~ "Object containment failed"
426 end
427 end
428
429 describe "fix_explicit_addressing" do
430 setup do
431 user = insert(:user)
432 [user: user]
433 end
434
435 test "moves non-explicitly mentioned actors to cc", %{user: user} do
436 explicitly_mentioned_actors = [
437 "https://pleroma.gold/users/user1",
438 "https://pleroma.gold/user2"
439 ]
440
441 object = %{
442 "actor" => user.ap_id,
443 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
444 "cc" => [],
445 "tag" =>
446 Enum.map(explicitly_mentioned_actors, fn href ->
447 %{"type" => "Mention", "href" => href}
448 end)
449 }
450
451 fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
452 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
453 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
454 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
455 end
456
457 test "does not move actor's follower collection to cc", %{user: user} do
458 object = %{
459 "actor" => user.ap_id,
460 "to" => [user.follower_address],
461 "cc" => []
462 }
463
464 fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
465 assert user.follower_address in fixed_object["to"]
466 refute user.follower_address in fixed_object["cc"]
467 end
468
469 test "removes recipient's follower collection from cc", %{user: user} do
470 recipient = insert(:user)
471
472 object = %{
473 "actor" => user.ap_id,
474 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
475 "cc" => [user.follower_address, recipient.follower_address]
476 }
477
478 fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
479
480 assert user.follower_address in fixed_object["cc"]
481 refute recipient.follower_address in fixed_object["cc"]
482 refute recipient.follower_address in fixed_object["to"]
483 end
484 end
485
486 describe "fix_summary/1" do
487 test "returns fixed object" do
488 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
489 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
490 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
491 end
492 end
493
494 describe "fix_url/1" do
495 test "fixes data for object when url is map" do
496 object = %{
497 "url" => %{
498 "type" => "Link",
499 "mimeType" => "video/mp4",
500 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
501 }
502 }
503
504 assert Transmogrifier.fix_url(object) == %{
505 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
506 }
507 end
508
509 test "returns non-modified object" do
510 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
511 end
512 end
513
514 describe "get_obj_helper/2" do
515 test "returns nil when cannot normalize object" do
516 assert capture_log(fn ->
517 refute Transmogrifier.get_obj_helper("test-obj-id")
518 end) =~ "Unsupported URI scheme"
519 end
520
521 @tag capture_log: true
522 test "returns {:ok, %Object{}} for success case" do
523 assert {:ok, %Object{}} =
524 Transmogrifier.get_obj_helper(
525 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
526 )
527 end
528 end
529
530 describe "fix_attachments/1" do
531 test "puts dimensions into attachment url field" do
532 object = %{
533 "attachment" => [
534 %{
535 "type" => "Document",
536 "name" => "Hello world",
537 "url" => "https://media.example.tld/1.jpg",
538 "width" => 880,
539 "height" => 960,
540 "mediaType" => "image/jpeg",
541 "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
542 }
543 ]
544 }
545
546 expected = %{
547 "attachment" => [
548 %{
549 "type" => "Document",
550 "name" => "Hello world",
551 "url" => [
552 %{
553 "type" => "Link",
554 "mediaType" => "image/jpeg",
555 "href" => "https://media.example.tld/1.jpg",
556 "width" => 880,
557 "height" => 960
558 }
559 ],
560 "mediaType" => "image/jpeg",
561 "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
562 }
563 ]
564 }
565
566 assert Transmogrifier.fix_attachments(object) == expected
567 end
568 end
569 end