Move TransmogrifierTest for Note to NoteHandlingTest
[akkoma] / test / pleroma / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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 |> Poison.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 |> Poison.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)
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 end
285
286 describe "user upgrade" do
287 test "it upgrades a user to activitypub" do
288 user =
289 insert(:user, %{
290 nickname: "rye@niu.moe",
291 local: false,
292 ap_id: "https://niu.moe/users/rye",
293 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
294 })
295
296 user_two = insert(:user)
297 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
298
299 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
300 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
301 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
302
303 user = User.get_cached_by_id(user.id)
304 assert user.note_count == 1
305
306 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
307 ObanHelpers.perform_all()
308
309 assert user.ap_enabled
310 assert user.note_count == 1
311 assert user.follower_address == "https://niu.moe/users/rye/followers"
312 assert user.following_address == "https://niu.moe/users/rye/following"
313
314 user = User.get_cached_by_id(user.id)
315 assert user.note_count == 1
316
317 activity = Activity.get_by_id(activity.id)
318 assert user.follower_address in activity.recipients
319
320 assert %{
321 "url" => [
322 %{
323 "href" =>
324 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
325 }
326 ]
327 } = user.avatar
328
329 assert %{
330 "url" => [
331 %{
332 "href" =>
333 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
334 }
335 ]
336 } = user.banner
337
338 refute "..." in activity.recipients
339
340 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
341 refute user.follower_address in unrelated_activity.recipients
342
343 user_two = User.get_cached_by_id(user_two.id)
344 assert User.following?(user_two, user)
345 refute "..." in User.following(user_two)
346 end
347 end
348
349 describe "actor rewriting" do
350 test "it fixes the actor URL property to be a proper URI" do
351 data = %{
352 "url" => %{"href" => "http://example.com"}
353 }
354
355 rewritten = Transmogrifier.maybe_fix_user_object(data)
356 assert rewritten["url"] == "http://example.com"
357 end
358 end
359
360 describe "actor origin containment" do
361 test "it rejects activities which reference objects with bogus origins" do
362 data = %{
363 "@context" => "https://www.w3.org/ns/activitystreams",
364 "id" => "http://mastodon.example.org/users/admin/activities/1234",
365 "actor" => "http://mastodon.example.org/users/admin",
366 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
367 "object" => "https://info.pleroma.site/activity.json",
368 "type" => "Announce"
369 }
370
371 assert capture_log(fn ->
372 {:error, _} = Transmogrifier.handle_incoming(data)
373 end) =~ "Object containment failed"
374 end
375
376 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" 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/activity2.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 2)" 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/activity3.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 end
406
407 describe "fix_explicit_addressing" do
408 setup do
409 user = insert(:user)
410 [user: user]
411 end
412
413 test "moves non-explicitly mentioned actors to cc", %{user: user} do
414 explicitly_mentioned_actors = [
415 "https://pleroma.gold/users/user1",
416 "https://pleroma.gold/user2"
417 ]
418
419 object = %{
420 "actor" => user.ap_id,
421 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
422 "cc" => [],
423 "tag" =>
424 Enum.map(explicitly_mentioned_actors, fn href ->
425 %{"type" => "Mention", "href" => href}
426 end)
427 }
428
429 fixed_object = Transmogrifier.fix_explicit_addressing(object)
430 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
431 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
432 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
433 end
434
435 test "does not move actor's follower collection to cc", %{user: user} do
436 object = %{
437 "actor" => user.ap_id,
438 "to" => [user.follower_address],
439 "cc" => []
440 }
441
442 fixed_object = Transmogrifier.fix_explicit_addressing(object)
443 assert user.follower_address in fixed_object["to"]
444 refute user.follower_address in fixed_object["cc"]
445 end
446
447 test "removes recipient's follower collection from cc", %{user: user} do
448 recipient = insert(:user)
449
450 object = %{
451 "actor" => user.ap_id,
452 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
453 "cc" => [user.follower_address, recipient.follower_address]
454 }
455
456 fixed_object = Transmogrifier.fix_explicit_addressing(object)
457
458 assert user.follower_address in fixed_object["cc"]
459 refute recipient.follower_address in fixed_object["cc"]
460 refute recipient.follower_address in fixed_object["to"]
461 end
462 end
463
464 describe "fix_summary/1" do
465 test "returns fixed object" do
466 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
467 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
468 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
469 end
470 end
471
472 describe "fix_url/1" do
473 test "fixes data for object when url is map" do
474 object = %{
475 "url" => %{
476 "type" => "Link",
477 "mimeType" => "video/mp4",
478 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
479 }
480 }
481
482 assert Transmogrifier.fix_url(object) == %{
483 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
484 }
485 end
486
487 test "returns non-modified object" do
488 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
489 end
490 end
491
492 describe "get_obj_helper/2" do
493 test "returns nil when cannot normalize object" do
494 assert capture_log(fn ->
495 refute Transmogrifier.get_obj_helper("test-obj-id")
496 end) =~ "Unsupported URI scheme"
497 end
498
499 @tag capture_log: true
500 test "returns {:ok, %Object{}} for success case" do
501 assert {:ok, %Object{}} =
502 Transmogrifier.get_obj_helper(
503 "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
504 )
505 end
506 end
507 end