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