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