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