7c97fa8f880a3faf92aa130d1fee2a3865c1cb01
[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} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
206
207 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
208
209 assert length(modified["object"]["tag"]) == 2
210
211 assert is_nil(modified["object"]["emoji"])
212 assert is_nil(modified["object"]["like_count"])
213 assert is_nil(modified["object"]["announcements"])
214 assert is_nil(modified["object"]["announcement_count"])
215 assert is_nil(modified["object"]["context_id"])
216 end
217
218 test "it strips internal fields of article" do
219 activity = insert(:article_activity)
220
221 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
222
223 assert length(modified["object"]["tag"]) == 2
224
225 assert is_nil(modified["object"]["emoji"])
226 assert is_nil(modified["object"]["like_count"])
227 assert is_nil(modified["object"]["announcements"])
228 assert is_nil(modified["object"]["announcement_count"])
229 assert is_nil(modified["object"]["context_id"])
230 assert is_nil(modified["object"]["likes"])
231 end
232
233 test "the directMessage flag is present" do
234 user = insert(:user)
235 other_user = insert(:user)
236
237 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
238
239 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
240
241 assert modified["directMessage"] == false
242
243 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
244
245 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
246
247 assert modified["directMessage"] == false
248
249 {:ok, activity} =
250 CommonAPI.post(user, %{
251 status: "@#{other_user.nickname} :moominmamma:",
252 visibility: "direct"
253 })
254
255 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
256
257 assert modified["directMessage"] == true
258 end
259
260 test "it strips BCC field" do
261 user = insert(:user)
262 {:ok, list} = Pleroma.List.create("foo", user)
263
264 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
265
266 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
267
268 assert is_nil(modified["bcc"])
269 end
270
271 test "it can handle Listen activities" do
272 listen_activity = insert(:listen)
273
274 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
275
276 assert modified["type"] == "Listen"
277
278 user = insert(:user)
279
280 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
281
282 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
283 end
284
285 test "custom emoji urls are URI encoded" do
286 # :dinosaur: filename has a space -> dino walking.gif
287 user = insert(:user)
288
289 {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"})
290
291 {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
292
293 assert length(prepared["object"]["tag"]) == 1
294
295 url = prepared["object"]["tag"] |> List.first() |> Map.get("icon") |> Map.get("url")
296
297 assert url == "http://localhost:4001/emoji/dino%20walking.gif"
298 end
299 end
300
301 describe "user upgrade" do
302 test "it upgrades a user to activitypub" do
303 user =
304 insert(:user, %{
305 nickname: "rye@niu.moe",
306 local: false,
307 ap_id: "https://niu.moe/users/rye",
308 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
309 })
310
311 user_two = insert(:user)
312 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
313
314 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
315 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
316 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
317
318 user = User.get_cached_by_id(user.id)
319 assert user.note_count == 1
320
321 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
322 ObanHelpers.perform_all()
323
324 assert user.ap_enabled
325 assert user.note_count == 1
326 assert user.follower_address == "https://niu.moe/users/rye/followers"
327 assert user.following_address == "https://niu.moe/users/rye/following"
328
329 user = User.get_cached_by_id(user.id)
330 assert user.note_count == 1
331
332 activity = Activity.get_by_id(activity.id)
333 assert user.follower_address in activity.recipients
334
335 assert %{
336 "url" => [
337 %{
338 "href" =>
339 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
340 }
341 ]
342 } = user.avatar
343
344 assert %{
345 "url" => [
346 %{
347 "href" =>
348 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
349 }
350 ]
351 } = user.banner
352
353 refute "..." in activity.recipients
354
355 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
356 refute user.follower_address in unrelated_activity.recipients
357
358 user_two = User.get_cached_by_id(user_two.id)
359 assert User.following?(user_two, user)
360 refute "..." in User.following(user_two)
361 end
362 end
363
364 describe "actor rewriting" do
365 test "it fixes the actor URL property to be a proper URI" do
366 data = %{
367 "url" => %{"href" => "http://example.com"}
368 }
369
370 rewritten = Transmogrifier.maybe_fix_user_object(data)
371 assert rewritten["url"] == "http://example.com"
372 end
373 end
374
375 describe "actor origin containment" do
376 test "it rejects activities which reference objects with bogus origins" do
377 data = %{
378 "@context" => "https://www.w3.org/ns/activitystreams",
379 "id" => "http://mastodon.example.org/users/admin/activities/1234",
380 "actor" => "http://mastodon.example.org/users/admin",
381 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
382 "object" => "https://info.pleroma.site/activity.json",
383 "type" => "Announce"
384 }
385
386 assert capture_log(fn ->
387 {:error, _} = Transmogrifier.handle_incoming(data)
388 end) =~ "Object containment failed"
389 end
390
391 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
392 data = %{
393 "@context" => "https://www.w3.org/ns/activitystreams",
394 "id" => "http://mastodon.example.org/users/admin/activities/1234",
395 "actor" => "http://mastodon.example.org/users/admin",
396 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
397 "object" => "https://info.pleroma.site/activity2.json",
398 "type" => "Announce"
399 }
400
401 assert capture_log(fn ->
402 {:error, _} = Transmogrifier.handle_incoming(data)
403 end) =~ "Object containment failed"
404 end
405
406 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
407 data = %{
408 "@context" => "https://www.w3.org/ns/activitystreams",
409 "id" => "http://mastodon.example.org/users/admin/activities/1234",
410 "actor" => "http://mastodon.example.org/users/admin",
411 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
412 "object" => "https://info.pleroma.site/activity3.json",
413 "type" => "Announce"
414 }
415
416 assert capture_log(fn ->
417 {:error, _} = Transmogrifier.handle_incoming(data)
418 end) =~ "Object containment failed"
419 end
420 end
421
422 describe "fix_explicit_addressing" do
423 setup do
424 user = insert(:user)
425 [user: user]
426 end
427
428 test "moves non-explicitly mentioned actors to cc", %{user: user} do
429 explicitly_mentioned_actors = [
430 "https://pleroma.gold/users/user1",
431 "https://pleroma.gold/user2"
432 ]
433
434 object = %{
435 "actor" => user.ap_id,
436 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
437 "cc" => [],
438 "tag" =>
439 Enum.map(explicitly_mentioned_actors, fn href ->
440 %{"type" => "Mention", "href" => href}
441 end)
442 }
443
444 fixed_object = Transmogrifier.fix_explicit_addressing(object)
445 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
446 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
447 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
448 end
449
450 test "does not move actor's follower collection to cc", %{user: user} do
451 object = %{
452 "actor" => user.ap_id,
453 "to" => [user.follower_address],
454 "cc" => []
455 }
456
457 fixed_object = Transmogrifier.fix_explicit_addressing(object)
458 assert user.follower_address in fixed_object["to"]
459 refute user.follower_address in fixed_object["cc"]
460 end
461
462 test "removes recipient's follower collection from cc", %{user: user} do
463 recipient = insert(:user)
464
465 object = %{
466 "actor" => user.ap_id,
467 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
468 "cc" => [user.follower_address, recipient.follower_address]
469 }
470
471 fixed_object = Transmogrifier.fix_explicit_addressing(object)
472
473 assert user.follower_address in fixed_object["cc"]
474 refute recipient.follower_address in fixed_object["cc"]
475 refute recipient.follower_address in fixed_object["to"]
476 end
477 end
478
479 describe "fix_summary/1" do
480 test "returns fixed object" do
481 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
482 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
483 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
484 end
485 end
486
487 describe "fix_url/1" do
488 test "fixes data for object when url is map" do
489 object = %{
490 "url" => %{
491 "type" => "Link",
492 "mimeType" => "video/mp4",
493 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
494 }
495 }
496
497 assert Transmogrifier.fix_url(object) == %{
498 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
499 }
500 end
501
502 test "returns non-modified object" do
503 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
504 end
505 end
506
507 describe "get_obj_helper/2" do
508 test "returns nil when cannot normalize object" do
509 assert capture_log(fn ->
510 refute Transmogrifier.get_obj_helper("test-obj-id")
511 end) =~ "Unsupported URI scheme"
512 end
513
514 @tag capture_log: true
515 test "returns {:ok, %Object{}} for success case" do
516 assert {:ok, %Object{}} =
517 Transmogrifier.get_obj_helper(
518 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
519 )
520 end
521 end
522 end