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