GTS: cherry-picks and collection usage (#186)
[akkoma] / test / pleroma / web / activity_pub / activity_pub_controller_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.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 use Oban.Testing, repo: Pleroma.Repo
8
9 alias Pleroma.Activity
10 alias Pleroma.Delivery
11 alias Pleroma.Instances
12 alias Pleroma.Object
13 alias Pleroma.Tests.ObanHelpers
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
23
24 import Pleroma.Factory
25
26 require Pleroma.Constants
27
28 setup_all do
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
30 :ok
31 end
32
33 setup do: clear_config([:instance, :federating], true)
34
35 describe "/relay" do
36 setup do: clear_config([:instance, :allow_relay])
37
38 test "with the relay active, it returns the relay user", %{conn: conn} do
39 res =
40 conn
41 |> get(activity_pub_path(conn, :relay))
42 |> json_response(200)
43
44 assert res["id"] =~ "/relay"
45 end
46
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 clear_config([:instance, :allow_relay], false)
49
50 conn
51 |> get(activity_pub_path(conn, :relay))
52 |> json_response(404)
53 end
54
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 clear_config([:instance, :federating], false)
57 user = insert(:user)
58
59 conn
60 |> assign(:user, user)
61 |> get(activity_pub_path(conn, :relay))
62 |> json_response(404)
63 end
64 end
65
66 describe "/internal/fetch" do
67 test "it returns the internal fetch user", %{conn: conn} do
68 res =
69 conn
70 |> get(activity_pub_path(conn, :internal_fetch))
71 |> json_response(200)
72
73 assert res["id"] =~ "/fetch"
74 end
75
76 test "on non-federating instance, it returns 404", %{conn: conn} do
77 clear_config([:instance, :federating], false)
78 user = insert(:user)
79
80 conn
81 |> assign(:user, user)
82 |> get(activity_pub_path(conn, :internal_fetch))
83 |> json_response(404)
84 end
85 end
86
87 describe "/users/:nickname" do
88 test "it returns a json representation of the user with accept application/json", %{
89 conn: conn
90 } do
91 user = insert(:user)
92
93 conn =
94 conn
95 |> put_req_header("accept", "application/json")
96 |> get("/users/#{user.nickname}")
97
98 user = User.get_cached_by_id(user.id)
99
100 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
101 end
102
103 test "it returns a json representation of the user with accept application/activity+json", %{
104 conn: conn
105 } do
106 user = insert(:user)
107
108 conn =
109 conn
110 |> put_req_header("accept", "application/activity+json")
111 |> get("/users/#{user.nickname}")
112
113 user = User.get_cached_by_id(user.id)
114
115 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
116 end
117
118 test "it returns a json representation of the user with accept application/ld+json", %{
119 conn: conn
120 } do
121 user = insert(:user)
122
123 conn =
124 conn
125 |> put_req_header(
126 "accept",
127 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
128 )
129 |> get("/users/#{user.nickname}")
130
131 user = User.get_cached_by_id(user.id)
132
133 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
134 end
135
136 test "it returns 404 for remote users", %{
137 conn: conn
138 } do
139 user = insert(:user, local: false, nickname: "remoteuser@example.com")
140
141 conn =
142 conn
143 |> put_req_header("accept", "application/json")
144 |> get("/users/#{user.nickname}.json")
145
146 assert json_response(conn, 404)
147 end
148
149 test "it returns error when user is not found", %{conn: conn} do
150 response =
151 conn
152 |> put_req_header("accept", "application/json")
153 |> get("/users/jimm")
154 |> json_response(404)
155
156 assert response == "Not found"
157 end
158 end
159
160 describe "mastodon compatibility routes" do
161 test "it returns a json representation of the object with accept application/json", %{
162 conn: conn
163 } do
164 {:ok, object} =
165 %{
166 "type" => "Note",
167 "content" => "hey",
168 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
169 "actor" => Endpoint.url() <> "/users/raymoo",
170 "to" => [Pleroma.Constants.as_public()]
171 }
172 |> Object.create()
173
174 conn =
175 conn
176 |> put_req_header("accept", "application/json")
177 |> get("/users/raymoo/statuses/999999999")
178
179 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
180 end
181
182 test "it returns a json representation of the activity with accept application/json", %{
183 conn: conn
184 } do
185 {:ok, object} =
186 %{
187 "type" => "Note",
188 "content" => "hey",
189 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
190 "actor" => Endpoint.url() <> "/users/raymoo",
191 "to" => [Pleroma.Constants.as_public()]
192 }
193 |> Object.create()
194
195 {:ok, activity, _} =
196 %{
197 "id" => object.data["id"] <> "/activity",
198 "type" => "Create",
199 "object" => object.data["id"],
200 "actor" => object.data["actor"],
201 "to" => object.data["to"]
202 }
203 |> ActivityPub.persist(local: true)
204
205 conn =
206 conn
207 |> put_req_header("accept", "application/json")
208 |> get("/users/raymoo/statuses/999999999/activity")
209
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
211 end
212 end
213
214 describe "/objects/:uuid" do
215 test "it doesn't return a local-only object", %{conn: conn} do
216 user = insert(:user)
217 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
218
219 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
220
221 object = Object.normalize(post, fetch: false)
222 uuid = String.split(object.data["id"], "/") |> List.last()
223
224 conn =
225 conn
226 |> put_req_header("accept", "application/json")
227 |> get("/objects/#{uuid}")
228
229 assert json_response(conn, 404)
230 end
231
232 test "returns local-only objects when authenticated", %{conn: conn} do
233 user = insert(:user)
234 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
235
236 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
237
238 object = Object.normalize(post, fetch: false)
239 uuid = String.split(object.data["id"], "/") |> List.last()
240
241 assert response =
242 conn
243 |> assign(:user, user)
244 |> put_req_header("accept", "application/activity+json")
245 |> get("/objects/#{uuid}")
246
247 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
248 end
249
250 test "does not return local-only objects for remote users", %{conn: conn} do
251 user = insert(:user)
252 reader = insert(:user, local: false)
253
254 {:ok, post} =
255 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
256
257 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
258
259 object = Object.normalize(post, fetch: false)
260 uuid = String.split(object.data["id"], "/") |> List.last()
261
262 assert response =
263 conn
264 |> assign(:user, reader)
265 |> put_req_header("accept", "application/activity+json")
266 |> get("/objects/#{uuid}")
267
268 json_response(response, 404)
269 end
270
271 test "it returns a json representation of the object with accept application/json", %{
272 conn: conn
273 } do
274 note = insert(:note)
275 uuid = String.split(note.data["id"], "/") |> List.last()
276
277 conn =
278 conn
279 |> put_req_header("accept", "application/json")
280 |> get("/objects/#{uuid}")
281
282 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
283 end
284
285 test "it returns a json representation of the object with accept application/activity+json",
286 %{conn: conn} do
287 note = insert(:note)
288 uuid = String.split(note.data["id"], "/") |> List.last()
289
290 conn =
291 conn
292 |> put_req_header("accept", "application/activity+json")
293 |> get("/objects/#{uuid}")
294
295 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
296 end
297
298 test "it returns a json representation of the object with accept application/ld+json", %{
299 conn: conn
300 } do
301 note = insert(:note)
302 uuid = String.split(note.data["id"], "/") |> List.last()
303
304 conn =
305 conn
306 |> put_req_header(
307 "accept",
308 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
309 )
310 |> get("/objects/#{uuid}")
311
312 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
313 end
314
315 test "does not cache authenticated response", %{conn: conn} do
316 user = insert(:user)
317 reader = insert(:user)
318
319 {:ok, post} =
320 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
321
322 object = Object.normalize(post, fetch: false)
323 uuid = String.split(object.data["id"], "/") |> List.last()
324
325 assert response =
326 conn
327 |> assign(:user, reader)
328 |> put_req_header("accept", "application/activity+json")
329 |> get("/objects/#{uuid}")
330
331 json_response(response, 200)
332
333 conn
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
336 |> json_response(404)
337 end
338
339 test "it returns 404 for non-public messages", %{conn: conn} do
340 note = insert(:direct_note)
341 uuid = String.split(note.data["id"], "/") |> List.last()
342
343 conn =
344 conn
345 |> put_req_header("accept", "application/activity+json")
346 |> get("/objects/#{uuid}")
347
348 assert json_response(conn, 404)
349 end
350
351 test "returns visible non-public messages when authenticated", %{conn: conn} do
352 note = insert(:direct_note)
353 uuid = String.split(note.data["id"], "/") |> List.last()
354 user = User.get_by_ap_id(note.data["actor"])
355 marisa = insert(:user)
356
357 assert conn
358 |> assign(:user, marisa)
359 |> put_req_header("accept", "application/activity+json")
360 |> get("/objects/#{uuid}")
361 |> json_response(404)
362
363 assert response =
364 conn
365 |> assign(:user, user)
366 |> put_req_header("accept", "application/activity+json")
367 |> get("/objects/#{uuid}")
368 |> json_response(200)
369
370 assert response == ObjectView.render("object.json", %{object: note})
371 end
372
373 test "it returns 404 for tombstone objects", %{conn: conn} do
374 tombstone = insert(:tombstone)
375 uuid = String.split(tombstone.data["id"], "/") |> List.last()
376
377 conn =
378 conn
379 |> put_req_header("accept", "application/activity+json")
380 |> get("/objects/#{uuid}")
381
382 assert json_response(conn, 404)
383 end
384
385 test "it caches a response", %{conn: conn} do
386 note = insert(:note)
387 uuid = String.split(note.data["id"], "/") |> List.last()
388
389 conn1 =
390 conn
391 |> put_req_header("accept", "application/activity+json")
392 |> get("/objects/#{uuid}")
393
394 assert json_response(conn1, :ok)
395 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
396
397 conn2 =
398 conn
399 |> put_req_header("accept", "application/activity+json")
400 |> get("/objects/#{uuid}")
401
402 assert json_response(conn1, :ok) == json_response(conn2, :ok)
403 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
404 end
405
406 test "cached purged after object deletion", %{conn: conn} do
407 note = insert(:note)
408 uuid = String.split(note.data["id"], "/") |> List.last()
409
410 conn1 =
411 conn
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/objects/#{uuid}")
414
415 assert json_response(conn1, :ok)
416 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
417
418 Object.delete(note)
419
420 conn2 =
421 conn
422 |> put_req_header("accept", "application/activity+json")
423 |> get("/objects/#{uuid}")
424
425 assert "Not found" == json_response(conn2, :not_found)
426 end
427 end
428
429 describe "/activities/:uuid" do
430 test "it doesn't return a local-only activity", %{conn: conn} do
431 user = insert(:user)
432 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
433
434 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
435
436 uuid = String.split(post.data["id"], "/") |> List.last()
437
438 conn =
439 conn
440 |> put_req_header("accept", "application/json")
441 |> get("/activities/#{uuid}")
442
443 assert json_response(conn, 404)
444 end
445
446 test "returns local-only activities when authenticated", %{conn: conn} do
447 user = insert(:user)
448 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
449
450 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
451
452 uuid = String.split(post.data["id"], "/") |> List.last()
453
454 assert response =
455 conn
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
459
460 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
461 end
462
463 test "it returns a json representation of the activity", %{conn: conn} do
464 activity = insert(:note_activity)
465 uuid = String.split(activity.data["id"], "/") |> List.last()
466
467 conn =
468 conn
469 |> put_req_header("accept", "application/activity+json")
470 |> get("/activities/#{uuid}")
471
472 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
473 end
474
475 test "it returns 404 for non-public activities", %{conn: conn} do
476 activity = insert(:direct_note_activity)
477 uuid = String.split(activity.data["id"], "/") |> List.last()
478
479 conn =
480 conn
481 |> put_req_header("accept", "application/activity+json")
482 |> get("/activities/#{uuid}")
483
484 assert json_response(conn, 404)
485 end
486
487 test "returns visible non-public messages when authenticated", %{conn: conn} do
488 note = insert(:direct_note_activity)
489 uuid = String.split(note.data["id"], "/") |> List.last()
490 user = User.get_by_ap_id(note.data["actor"])
491 marisa = insert(:user)
492
493 assert conn
494 |> assign(:user, marisa)
495 |> put_req_header("accept", "application/activity+json")
496 |> get("/activities/#{uuid}")
497 |> json_response(404)
498
499 assert response =
500 conn
501 |> assign(:user, user)
502 |> put_req_header("accept", "application/activity+json")
503 |> get("/activities/#{uuid}")
504 |> json_response(200)
505
506 assert response == ObjectView.render("object.json", %{object: note})
507 end
508
509 test "it caches a response", %{conn: conn} do
510 activity = insert(:note_activity)
511 uuid = String.split(activity.data["id"], "/") |> List.last()
512
513 conn1 =
514 conn
515 |> put_req_header("accept", "application/activity+json")
516 |> get("/activities/#{uuid}")
517
518 assert json_response(conn1, :ok)
519 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
520
521 conn2 =
522 conn
523 |> put_req_header("accept", "application/activity+json")
524 |> get("/activities/#{uuid}")
525
526 assert json_response(conn1, :ok) == json_response(conn2, :ok)
527 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
528 end
529
530 test "cached purged after activity deletion", %{conn: conn} do
531 user = insert(:user)
532 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
533
534 uuid = String.split(activity.data["id"], "/") |> List.last()
535
536 conn1 =
537 conn
538 |> put_req_header("accept", "application/activity+json")
539 |> get("/activities/#{uuid}")
540
541 assert json_response(conn1, :ok)
542 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
543
544 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
545
546 conn2 =
547 conn
548 |> put_req_header("accept", "application/activity+json")
549 |> get("/activities/#{uuid}")
550
551 assert "Not found" == json_response(conn2, :not_found)
552 end
553 end
554
555 describe "/inbox" do
556 test "it inserts an incoming activity into the database", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
558
559 conn =
560 conn
561 |> assign(:valid_signature, true)
562 |> put_req_header("content-type", "application/activity+json")
563 |> post("/inbox", data)
564
565 assert "ok" == json_response(conn, 200)
566
567 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
568 assert Activity.get_by_ap_id(data["id"])
569 end
570
571 @tag capture_log: true
572 test "it inserts an incoming activity into the database" <>
573 "even if we can't fetch the user but have it in our db",
574 %{conn: conn} do
575 user =
576 insert(:user,
577 ap_id: "https://mastodon.example.org/users/raymoo",
578 ap_enabled: true,
579 local: false,
580 last_refreshed_at: nil
581 )
582
583 data =
584 File.read!("test/fixtures/mastodon-post-activity.json")
585 |> Jason.decode!()
586 |> Map.put("actor", user.ap_id)
587 |> put_in(["object", "attributedTo"], user.ap_id)
588
589 conn =
590 conn
591 |> assign(:valid_signature, true)
592 |> put_req_header("content-type", "application/activity+json")
593 |> post("/inbox", data)
594
595 assert "ok" == json_response(conn, 200)
596
597 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
598 assert Activity.get_by_ap_id(data["id"])
599 end
600
601 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
602 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
603
604 sender_url = data["actor"]
605 Instances.set_consistently_unreachable(sender_url)
606 refute Instances.reachable?(sender_url)
607
608 conn =
609 conn
610 |> assign(:valid_signature, true)
611 |> put_req_header("content-type", "application/activity+json")
612 |> post("/inbox", data)
613
614 assert "ok" == json_response(conn, 200)
615 assert Instances.reachable?(sender_url)
616 end
617
618 test "accept follow activity", %{conn: conn} do
619 clear_config([:instance, :federating], true)
620 relay = Relay.get_actor()
621
622 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
623
624 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
625 relay = refresh_record(relay)
626
627 accept =
628 File.read!("test/fixtures/relay/accept-follow.json")
629 |> String.replace("{{ap_id}}", relay.ap_id)
630 |> String.replace("{{activity_id}}", activity.data["id"])
631
632 assert "ok" ==
633 conn
634 |> assign(:valid_signature, true)
635 |> put_req_header("content-type", "application/activity+json")
636 |> post("/inbox", accept)
637 |> json_response(200)
638
639 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
640
641 assert Pleroma.FollowingRelationship.following?(
642 relay,
643 followed_relay
644 )
645
646 Mix.shell(Mix.Shell.Process)
647
648 on_exit(fn ->
649 Mix.shell(Mix.Shell.IO)
650 end)
651
652 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
653 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
654 end
655
656 @tag capture_log: true
657 test "without valid signature, " <>
658 "it only accepts Create activities and requires enabled federation",
659 %{conn: conn} do
660 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
661 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
662
663 conn = put_req_header(conn, "content-type", "application/activity+json")
664
665 clear_config([:instance, :federating], false)
666
667 conn
668 |> post("/inbox", data)
669 |> json_response(403)
670
671 conn
672 |> post("/inbox", non_create_data)
673 |> json_response(403)
674
675 clear_config([:instance, :federating], true)
676
677 ret_conn = post(conn, "/inbox", data)
678 assert "ok" == json_response(ret_conn, 200)
679
680 conn
681 |> post("/inbox", non_create_data)
682 |> json_response(400)
683 end
684
685 test "accepts Add/Remove activities", %{conn: conn} do
686 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
687
688 status =
689 File.read!("test/fixtures/statuses/note.json")
690 |> String.replace("{{nickname}}", "lain")
691 |> String.replace("{{object_id}}", object_id)
692
693 object_url = "https://example.com/objects/#{object_id}"
694
695 user =
696 File.read!("test/fixtures/users_mock/user.json")
697 |> String.replace("{{nickname}}", "lain")
698
699 actor = "https://example.com/users/lain"
700
701 Tesla.Mock.mock(fn
702 %{
703 method: :get,
704 url: ^object_url
705 } ->
706 %Tesla.Env{
707 status: 200,
708 body: status,
709 headers: [{"content-type", "application/activity+json"}]
710 }
711
712 %{
713 method: :get,
714 url: ^actor
715 } ->
716 %Tesla.Env{
717 status: 200,
718 body: user,
719 headers: [{"content-type", "application/activity+json"}]
720 }
721
722 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
723 %Tesla.Env{
724 status: 200,
725 body:
726 "test/fixtures/users_mock/masto_featured.json"
727 |> File.read!()
728 |> String.replace("{{domain}}", "example.com")
729 |> String.replace("{{nickname}}", "lain"),
730 headers: [{"content-type", "application/activity+json"}]
731 }
732 end)
733
734 data = %{
735 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
736 "actor" => actor,
737 "object" => object_url,
738 "target" => "https://example.com/users/lain/collections/featured",
739 "type" => "Add",
740 "to" => [Pleroma.Constants.as_public()]
741 }
742
743 assert "ok" ==
744 conn
745 |> assign(:valid_signature, true)
746 |> put_req_header("content-type", "application/activity+json")
747 |> post("/inbox", data)
748 |> json_response(200)
749
750 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
751 assert Activity.get_by_ap_id(data["id"])
752 user = User.get_cached_by_ap_id(data["actor"])
753 assert user.pinned_objects[data["object"]]
754
755 data = %{
756 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
757 "actor" => actor,
758 "object" => object_url,
759 "target" => "https://example.com/users/lain/collections/featured",
760 "type" => "Remove",
761 "to" => [Pleroma.Constants.as_public()]
762 }
763
764 assert "ok" ==
765 conn
766 |> assign(:valid_signature, true)
767 |> put_req_header("content-type", "application/activity+json")
768 |> post("/inbox", data)
769 |> json_response(200)
770
771 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
772 user = refresh_record(user)
773 refute user.pinned_objects[data["object"]]
774 end
775
776 test "mastodon pin/unpin", %{conn: conn} do
777 status_id = "105786274556060421"
778
779 status =
780 File.read!("test/fixtures/statuses/masto-note.json")
781 |> String.replace("{{nickname}}", "lain")
782 |> String.replace("{{status_id}}", status_id)
783
784 status_url = "https://example.com/users/lain/statuses/#{status_id}"
785 replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
786
787 user =
788 File.read!("test/fixtures/users_mock/user.json")
789 |> String.replace("{{nickname}}", "lain")
790
791 actor = "https://example.com/users/lain"
792
793 Tesla.Mock.mock(fn
794 %{
795 method: :get,
796 url: ^status_url
797 } ->
798 %Tesla.Env{
799 status: 200,
800 body: status,
801 headers: [{"content-type", "application/activity+json"}]
802 }
803
804 %{
805 method: :get,
806 url: ^actor
807 } ->
808 %Tesla.Env{
809 status: 200,
810 body: user,
811 headers: [{"content-type", "application/activity+json"}]
812 }
813
814 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
815 %Tesla.Env{
816 status: 200,
817 body:
818 "test/fixtures/users_mock/masto_featured.json"
819 |> File.read!()
820 |> String.replace("{{domain}}", "example.com")
821 |> String.replace("{{nickname}}", "lain"),
822 headers: [{"content-type", "application/activity+json"}]
823 }
824
825 %{
826 method: :get,
827 url: ^replies_url
828 } ->
829 %Tesla.Env{
830 status: 404,
831 body: "",
832 headers: [{"content-type", "application/activity+json"}]
833 }
834 end)
835
836 data = %{
837 "@context" => "https://www.w3.org/ns/activitystreams",
838 "actor" => actor,
839 "object" => status_url,
840 "target" => "https://example.com/users/lain/collections/featured",
841 "type" => "Add"
842 }
843
844 assert "ok" ==
845 conn
846 |> assign(:valid_signature, true)
847 |> put_req_header("content-type", "application/activity+json")
848 |> post("/inbox", data)
849 |> json_response(200)
850
851 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
852 assert Activity.get_by_object_ap_id_with_object(data["object"])
853 user = User.get_cached_by_ap_id(data["actor"])
854 assert user.pinned_objects[data["object"]]
855
856 data = %{
857 "actor" => actor,
858 "object" => status_url,
859 "target" => "https://example.com/users/lain/collections/featured",
860 "type" => "Remove"
861 }
862
863 assert "ok" ==
864 conn
865 |> assign(:valid_signature, true)
866 |> put_req_header("content-type", "application/activity+json")
867 |> post("/inbox", data)
868 |> json_response(200)
869
870 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
871 assert Activity.get_by_object_ap_id_with_object(data["object"])
872 user = refresh_record(user)
873 refute user.pinned_objects[data["object"]]
874 end
875 end
876
877 describe "/users/:nickname/inbox" do
878 setup do
879 data =
880 File.read!("test/fixtures/mastodon-post-activity.json")
881 |> Jason.decode!()
882
883 [data: data]
884 end
885
886 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
887 user = insert(:user)
888
889 data =
890 data
891 |> Map.put("bcc", [user.ap_id])
892 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
893
894 conn =
895 conn
896 |> assign(:valid_signature, true)
897 |> put_req_header("content-type", "application/activity+json")
898 |> post("/users/#{user.nickname}/inbox", data)
899
900 assert "ok" == json_response(conn, 200)
901 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
902 assert Activity.get_by_ap_id(data["id"])
903 end
904
905 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
906 user = insert(:user)
907
908 data =
909 data
910 |> Map.put("to", user.ap_id)
911 |> Map.put("cc", [])
912 |> Kernel.put_in(["object", "to"], user.ap_id)
913 |> Kernel.put_in(["object", "cc"], [])
914
915 conn =
916 conn
917 |> assign(:valid_signature, true)
918 |> put_req_header("content-type", "application/activity+json")
919 |> post("/users/#{user.nickname}/inbox", data)
920
921 assert "ok" == json_response(conn, 200)
922 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
923 assert Activity.get_by_ap_id(data["id"])
924 end
925
926 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
927 user = insert(:user)
928
929 data =
930 data
931 |> Map.put("to", [])
932 |> Map.put("cc", user.ap_id)
933 |> Kernel.put_in(["object", "to"], [])
934 |> Kernel.put_in(["object", "cc"], user.ap_id)
935
936 conn =
937 conn
938 |> assign(:valid_signature, true)
939 |> put_req_header("content-type", "application/activity+json")
940 |> post("/users/#{user.nickname}/inbox", data)
941
942 assert "ok" == json_response(conn, 200)
943 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
944 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
945 assert user.ap_id in activity.recipients
946 end
947
948 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
949 user = insert(:user)
950
951 data =
952 data
953 |> Map.put("to", [])
954 |> Map.put("cc", [])
955 |> Map.put("bcc", user.ap_id)
956 |> Kernel.put_in(["object", "to"], [])
957 |> Kernel.put_in(["object", "cc"], [])
958 |> Kernel.put_in(["object", "bcc"], user.ap_id)
959
960 conn =
961 conn
962 |> assign(:valid_signature, true)
963 |> put_req_header("content-type", "application/activity+json")
964 |> post("/users/#{user.nickname}/inbox", data)
965
966 assert "ok" == json_response(conn, 200)
967 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
968 assert Activity.get_by_ap_id(data["id"])
969 end
970
971 test "it accepts announces with to as string instead of array", %{conn: conn} do
972 user = insert(:user)
973
974 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
975 announcer = insert(:user, local: false)
976
977 data = %{
978 "@context" => "https://www.w3.org/ns/activitystreams",
979 "actor" => announcer.ap_id,
980 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
981 "object" => post.data["object"],
982 "to" => "https://www.w3.org/ns/activitystreams#Public",
983 "cc" => [user.ap_id],
984 "type" => "Announce"
985 }
986
987 conn =
988 conn
989 |> assign(:valid_signature, true)
990 |> put_req_header("content-type", "application/activity+json")
991 |> post("/users/#{user.nickname}/inbox", data)
992
993 assert "ok" == json_response(conn, 200)
994 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
995 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
996 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
997 end
998
999 test "it accepts messages from actors that are followed by the user", %{
1000 conn: conn,
1001 data: data
1002 } do
1003 recipient = insert(:user)
1004 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
1005
1006 {:ok, recipient, actor} = User.follow(recipient, actor)
1007
1008 object =
1009 data["object"]
1010 |> Map.put("attributedTo", actor.ap_id)
1011
1012 data =
1013 data
1014 |> Map.put("actor", actor.ap_id)
1015 |> Map.put("object", object)
1016
1017 conn =
1018 conn
1019 |> assign(:valid_signature, true)
1020 |> put_req_header("content-type", "application/activity+json")
1021 |> post("/users/#{recipient.nickname}/inbox", data)
1022
1023 assert "ok" == json_response(conn, 200)
1024 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1025 assert Activity.get_by_ap_id(data["id"])
1026 end
1027
1028 test "it rejects reads from other users", %{conn: conn} do
1029 user = insert(:user)
1030 other_user = insert(:user)
1031
1032 conn =
1033 conn
1034 |> assign(:user, other_user)
1035 |> put_req_header("accept", "application/activity+json")
1036 |> get("/users/#{user.nickname}/inbox")
1037
1038 assert json_response(conn, 403)
1039 end
1040
1041 test "it returns a note activity in a collection", %{conn: conn} do
1042 note_activity = insert(:direct_note_activity)
1043 note_object = Object.normalize(note_activity, fetch: false)
1044 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1045
1046 conn =
1047 conn
1048 |> assign(:user, user)
1049 |> put_req_header("accept", "application/activity+json")
1050 |> get("/users/#{user.nickname}/inbox?page=true")
1051
1052 assert response(conn, 200) =~ note_object.data["content"]
1053 end
1054
1055 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1056 user = insert(:user)
1057 data = Map.put(data, "bcc", [user.ap_id])
1058
1059 sender_host = URI.parse(data["actor"]).host
1060 Instances.set_consistently_unreachable(sender_host)
1061 refute Instances.reachable?(sender_host)
1062
1063 conn =
1064 conn
1065 |> assign(:valid_signature, true)
1066 |> put_req_header("content-type", "application/activity+json")
1067 |> post("/users/#{user.nickname}/inbox", data)
1068
1069 assert "ok" == json_response(conn, 200)
1070 assert Instances.reachable?(sender_host)
1071 end
1072
1073 @tag capture_log: true
1074 test "it removes all follower collections but actor's", %{conn: conn} do
1075 [actor, recipient] = insert_pair(:user)
1076
1077 to = [
1078 recipient.ap_id,
1079 recipient.follower_address,
1080 "https://www.w3.org/ns/activitystreams#Public"
1081 ]
1082
1083 cc = [recipient.follower_address, actor.follower_address]
1084
1085 data = %{
1086 "@context" => ["https://www.w3.org/ns/activitystreams"],
1087 "type" => "Create",
1088 "id" => Utils.generate_activity_id(),
1089 "to" => to,
1090 "cc" => cc,
1091 "actor" => actor.ap_id,
1092 "object" => %{
1093 "type" => "Note",
1094 "to" => to,
1095 "cc" => cc,
1096 "content" => "It's a note",
1097 "attributedTo" => actor.ap_id,
1098 "id" => Utils.generate_object_id()
1099 }
1100 }
1101
1102 conn
1103 |> assign(:valid_signature, true)
1104 |> put_req_header("content-type", "application/activity+json")
1105 |> post("/users/#{recipient.nickname}/inbox", data)
1106 |> json_response(200)
1107
1108 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1109
1110 assert activity = Activity.get_by_ap_id(data["id"])
1111
1112 assert activity.id
1113 assert actor.follower_address in activity.recipients
1114 assert actor.follower_address in activity.data["cc"]
1115
1116 refute recipient.follower_address in activity.recipients
1117 refute recipient.follower_address in activity.data["cc"]
1118 refute recipient.follower_address in activity.data["to"]
1119 end
1120
1121 test "it requires authentication", %{conn: conn} do
1122 user = insert(:user)
1123 conn = put_req_header(conn, "accept", "application/activity+json")
1124
1125 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1126 assert json_response(ret_conn, 403)
1127
1128 ret_conn =
1129 conn
1130 |> assign(:user, user)
1131 |> get("/users/#{user.nickname}/inbox")
1132
1133 assert json_response(ret_conn, 200)
1134 end
1135
1136 @tag capture_log: true
1137 test "forwarded report", %{conn: conn} do
1138 admin = insert(:user, is_admin: true)
1139 actor = insert(:user, local: false)
1140 remote_domain = URI.parse(actor.ap_id).host
1141 reported_user = insert(:user)
1142
1143 note = insert(:note_activity, user: reported_user)
1144
1145 data = %{
1146 "@context" => [
1147 "https://www.w3.org/ns/activitystreams",
1148 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1149 %{
1150 "@language" => "und"
1151 }
1152 ],
1153 "actor" => actor.ap_id,
1154 "cc" => [
1155 reported_user.ap_id
1156 ],
1157 "content" => "test",
1158 "context" => "context",
1159 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1160 "nickname" => reported_user.nickname,
1161 "object" => [
1162 reported_user.ap_id,
1163 %{
1164 "actor" => %{
1165 "actor_type" => "Person",
1166 "approval_pending" => false,
1167 "avatar" => "",
1168 "confirmation_pending" => false,
1169 "deactivated" => false,
1170 "display_name" => "test user",
1171 "id" => reported_user.id,
1172 "local" => false,
1173 "nickname" => reported_user.nickname,
1174 "registration_reason" => nil,
1175 "roles" => %{
1176 "admin" => false,
1177 "moderator" => false
1178 },
1179 "tags" => [],
1180 "url" => reported_user.ap_id
1181 },
1182 "content" => "",
1183 "id" => note.data["id"],
1184 "published" => note.data["published"],
1185 "type" => "Note"
1186 }
1187 ],
1188 "published" => note.data["published"],
1189 "state" => "open",
1190 "to" => [],
1191 "type" => "Flag"
1192 }
1193
1194 conn
1195 |> assign(:valid_signature, true)
1196 |> put_req_header("content-type", "application/activity+json")
1197 |> post("/users/#{reported_user.nickname}/inbox", data)
1198 |> json_response(200)
1199
1200 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1201
1202 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1203
1204 ObanHelpers.perform_all()
1205
1206 Swoosh.TestAssertions.assert_email_sent(
1207 to: {admin.name, admin.email},
1208 html_body: ~r/Reported Account:/i
1209 )
1210 end
1211
1212 @tag capture_log: true
1213 test "forwarded report from mastodon", %{conn: conn} do
1214 admin = insert(:user, is_admin: true)
1215 actor = insert(:user, local: false)
1216 remote_domain = URI.parse(actor.ap_id).host
1217 remote_actor = "https://#{remote_domain}/actor"
1218 [reported_user, another] = insert_list(2, :user)
1219
1220 note = insert(:note_activity, user: reported_user)
1221
1222 Pleroma.Web.CommonAPI.favorite(another, note.id)
1223
1224 mock_json_body =
1225 "test/fixtures/mastodon/application_actor.json"
1226 |> File.read!()
1227 |> String.replace("{{DOMAIN}}", remote_domain)
1228
1229 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1230 %Tesla.Env{
1231 status: 200,
1232 body: mock_json_body,
1233 headers: [{"content-type", "application/activity+json"}]
1234 }
1235 end)
1236
1237 data = %{
1238 "@context" => "https://www.w3.org/ns/activitystreams",
1239 "actor" => remote_actor,
1240 "content" => "test report",
1241 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1242 "object" => [
1243 reported_user.ap_id,
1244 note.data["object"]
1245 ],
1246 "type" => "Flag"
1247 }
1248
1249 conn
1250 |> assign(:valid_signature, true)
1251 |> put_req_header("content-type", "application/activity+json")
1252 |> post("/users/#{reported_user.nickname}/inbox", data)
1253 |> json_response(200)
1254
1255 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1256
1257 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1258 reported_user_ap_id = reported_user.ap_id
1259
1260 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1261
1262 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1263 ObanHelpers.perform_all()
1264
1265 Swoosh.TestAssertions.assert_email_sent(
1266 to: {admin.name, admin.email},
1267 html_body: ~r/#{note.data["object"]}/i
1268 )
1269 end
1270 end
1271
1272 describe "GET /users/:nickname/outbox" do
1273 test "it paginates correctly", %{conn: conn} do
1274 user = insert(:user)
1275 conn = assign(conn, :user, user)
1276 outbox_endpoint = user.ap_id <> "/outbox"
1277
1278 _posts =
1279 for i <- 0..25 do
1280 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1281 activity
1282 end
1283
1284 result =
1285 conn
1286 |> put_req_header("accept", "application/activity+json")
1287 |> get(outbox_endpoint <> "?page=true")
1288 |> json_response(200)
1289
1290 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1291 assert length(result["orderedItems"]) == 20
1292 assert length(result_ids) == 20
1293 assert result["next"]
1294 assert String.starts_with?(result["next"], outbox_endpoint)
1295
1296 result_next =
1297 conn
1298 |> put_req_header("accept", "application/activity+json")
1299 |> get(result["next"])
1300 |> json_response(200)
1301
1302 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1303 assert length(result_next["orderedItems"]) == 6
1304 assert length(result_next_ids) == 6
1305 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1306 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1307 assert String.starts_with?(result["id"], outbox_endpoint)
1308
1309 result_next_again =
1310 conn
1311 |> put_req_header("accept", "application/activity+json")
1312 |> get(result_next["id"])
1313 |> json_response(200)
1314
1315 assert result_next == result_next_again
1316 end
1317
1318 test "it returns 200 even if there're no activities", %{conn: conn} do
1319 user = insert(:user)
1320 outbox_endpoint = user.ap_id <> "/outbox"
1321
1322 conn =
1323 conn
1324 |> assign(:user, user)
1325 |> put_req_header("accept", "application/activity+json")
1326 |> get(outbox_endpoint)
1327
1328 result = json_response(conn, 200)
1329 assert outbox_endpoint == result["id"]
1330 end
1331
1332 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1333 user = insert(:user)
1334 reader = insert(:user)
1335 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1336 ap_id = note_activity.data["id"]
1337
1338 resp =
1339 conn
1340 |> assign(:user, reader)
1341 |> put_req_header("accept", "application/activity+json")
1342 |> get("/users/#{user.nickname}/outbox?page=true")
1343 |> json_response(200)
1344
1345 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1346 end
1347
1348 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1349 user = insert(:user)
1350 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1351
1352 resp =
1353 conn
1354 |> put_req_header("accept", "application/activity+json")
1355 |> get("/users/#{user.nickname}/outbox?page=true")
1356 |> json_response(200)
1357
1358 assert %{"orderedItems" => []} = resp
1359 end
1360
1361 test "it returns a note activity in a collection", %{conn: conn} do
1362 note_activity = insert(:note_activity)
1363 note_object = Object.normalize(note_activity, fetch: false)
1364 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1365
1366 conn =
1367 conn
1368 |> assign(:user, user)
1369 |> put_req_header("accept", "application/activity+json")
1370 |> get("/users/#{user.nickname}/outbox?page=true")
1371
1372 assert response(conn, 200) =~ note_object.data["content"]
1373 end
1374
1375 test "it returns an announce activity in a collection", %{conn: conn} do
1376 announce_activity = insert(:announce_activity)
1377 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1378
1379 conn =
1380 conn
1381 |> assign(:user, user)
1382 |> put_req_header("accept", "application/activity+json")
1383 |> get("/users/#{user.nickname}/outbox?page=true")
1384
1385 assert response(conn, 200) =~ announce_activity.data["object"]
1386 end
1387
1388 test "It returns poll Answers when authenticated", %{conn: conn} do
1389 poller = insert(:user)
1390 voter = insert(:user)
1391
1392 {:ok, activity} =
1393 CommonAPI.post(poller, %{
1394 status: "suya...",
1395 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1396 })
1397
1398 assert question = Object.normalize(activity, fetch: false)
1399
1400 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1401
1402 assert outbox_get =
1403 conn
1404 |> assign(:user, voter)
1405 |> put_req_header("accept", "application/activity+json")
1406 |> get(voter.ap_id <> "/outbox?page=true")
1407 |> json_response(200)
1408
1409 assert [answer_outbox] = outbox_get["orderedItems"]
1410 assert answer_outbox["id"] == activity.data["id"]
1411 end
1412 end
1413
1414 describe "POST /users/:nickname/outbox (C2S)" do
1415 setup do: clear_config([:instance, :limit])
1416
1417 setup do
1418 [
1419 activity: %{
1420 "@context" => "https://www.w3.org/ns/activitystreams",
1421 "type" => "Create",
1422 "object" => %{
1423 "type" => "Note",
1424 "content" => "AP C2S test",
1425 "to" => "https://www.w3.org/ns/activitystreams#Public",
1426 "cc" => []
1427 }
1428 }
1429 ]
1430 end
1431
1432 test "it rejects posts from other users / unauthenticated users", %{
1433 conn: conn,
1434 activity: activity
1435 } do
1436 user = insert(:user)
1437 other_user = insert(:user)
1438 conn = put_req_header(conn, "content-type", "application/activity+json")
1439
1440 conn
1441 |> post("/users/#{user.nickname}/outbox", activity)
1442 |> json_response(403)
1443
1444 conn
1445 |> assign(:user, other_user)
1446 |> post("/users/#{user.nickname}/outbox", activity)
1447 |> json_response(403)
1448 end
1449
1450 test "it inserts an incoming create activity into the database", %{
1451 conn: conn,
1452 activity: activity
1453 } do
1454 user = insert(:user)
1455
1456 result =
1457 conn
1458 |> assign(:user, user)
1459 |> put_req_header("content-type", "application/activity+json")
1460 |> post("/users/#{user.nickname}/outbox", activity)
1461 |> json_response(201)
1462
1463 assert Activity.get_by_ap_id(result["id"])
1464 assert result["object"]
1465 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1466 assert object["content"] == activity["object"]["content"]
1467 end
1468
1469 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1470 user = insert(:user)
1471
1472 activity =
1473 activity
1474 |> put_in(["object", "type"], "Benis")
1475
1476 _result =
1477 conn
1478 |> assign(:user, user)
1479 |> put_req_header("content-type", "application/activity+json")
1480 |> post("/users/#{user.nickname}/outbox", activity)
1481 |> json_response(400)
1482 end
1483
1484 test "it inserts an incoming sensitive activity into the database", %{
1485 conn: conn,
1486 activity: activity
1487 } do
1488 user = insert(:user)
1489 conn = assign(conn, :user, user)
1490 object = Map.put(activity["object"], "sensitive", true)
1491 activity = Map.put(activity, "object", object)
1492
1493 response =
1494 conn
1495 |> put_req_header("content-type", "application/activity+json")
1496 |> post("/users/#{user.nickname}/outbox", activity)
1497 |> json_response(201)
1498
1499 assert Activity.get_by_ap_id(response["id"])
1500 assert response["object"]
1501 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1502 assert response_object["sensitive"] == true
1503 assert response_object["content"] == activity["object"]["content"]
1504
1505 representation =
1506 conn
1507 |> put_req_header("accept", "application/activity+json")
1508 |> get(response["id"])
1509 |> json_response(200)
1510
1511 assert representation["object"]["sensitive"] == true
1512 end
1513
1514 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1515 user = insert(:user)
1516 activity = Map.put(activity, "type", "BadType")
1517
1518 conn =
1519 conn
1520 |> assign(:user, user)
1521 |> put_req_header("content-type", "application/activity+json")
1522 |> post("/users/#{user.nickname}/outbox", activity)
1523
1524 assert json_response(conn, 400)
1525 end
1526
1527 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1528 note_activity = insert(:note_activity)
1529 note_object = Object.normalize(note_activity, fetch: false)
1530 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1531
1532 data = %{
1533 "type" => "Delete",
1534 "object" => %{
1535 "id" => note_object.data["id"]
1536 }
1537 }
1538
1539 result =
1540 conn
1541 |> assign(:user, user)
1542 |> put_req_header("content-type", "application/activity+json")
1543 |> post("/users/#{user.nickname}/outbox", data)
1544 |> json_response(201)
1545
1546 assert Activity.get_by_ap_id(result["id"])
1547
1548 assert object = Object.get_by_ap_id(note_object.data["id"])
1549 assert object.data["type"] == "Tombstone"
1550 end
1551
1552 test "it rejects delete activity of object from other actor", %{conn: conn} do
1553 note_activity = insert(:note_activity)
1554 note_object = Object.normalize(note_activity, fetch: false)
1555 user = insert(:user)
1556
1557 data = %{
1558 type: "Delete",
1559 object: %{
1560 id: note_object.data["id"]
1561 }
1562 }
1563
1564 conn =
1565 conn
1566 |> assign(:user, user)
1567 |> put_req_header("content-type", "application/activity+json")
1568 |> post("/users/#{user.nickname}/outbox", data)
1569
1570 assert json_response(conn, 403)
1571 end
1572
1573 test "it increases like count when receiving a like action", %{conn: conn} do
1574 note_activity = insert(:note_activity)
1575 note_object = Object.normalize(note_activity, fetch: false)
1576 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1577
1578 data = %{
1579 type: "Like",
1580 object: %{
1581 id: note_object.data["id"]
1582 }
1583 }
1584
1585 conn =
1586 conn
1587 |> assign(:user, user)
1588 |> put_req_header("content-type", "application/activity+json")
1589 |> post("/users/#{user.nickname}/outbox", data)
1590
1591 result = json_response(conn, 201)
1592 assert Activity.get_by_ap_id(result["id"])
1593
1594 assert object = Object.get_by_ap_id(note_object.data["id"])
1595 assert object.data["like_count"] == 1
1596 end
1597
1598 test "it doesn't spreads faulty attributedTo or actor fields", %{
1599 conn: conn,
1600 activity: activity
1601 } do
1602 reimu = insert(:user, nickname: "reimu")
1603 cirno = insert(:user, nickname: "cirno")
1604
1605 assert reimu.ap_id
1606 assert cirno.ap_id
1607
1608 activity =
1609 activity
1610 |> put_in(["object", "actor"], reimu.ap_id)
1611 |> put_in(["object", "attributedTo"], reimu.ap_id)
1612 |> put_in(["actor"], reimu.ap_id)
1613 |> put_in(["attributedTo"], reimu.ap_id)
1614
1615 _reimu_outbox =
1616 conn
1617 |> assign(:user, cirno)
1618 |> put_req_header("content-type", "application/activity+json")
1619 |> post("/users/#{reimu.nickname}/outbox", activity)
1620 |> json_response(403)
1621
1622 cirno_outbox =
1623 conn
1624 |> assign(:user, cirno)
1625 |> put_req_header("content-type", "application/activity+json")
1626 |> post("/users/#{cirno.nickname}/outbox", activity)
1627 |> json_response(201)
1628
1629 assert cirno_outbox["attributedTo"] == nil
1630 assert cirno_outbox["actor"] == cirno.ap_id
1631
1632 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1633 assert cirno_object.data["actor"] == cirno.ap_id
1634 assert cirno_object.data["attributedTo"] == cirno.ap_id
1635 end
1636
1637 test "Character limitation", %{conn: conn, activity: activity} do
1638 clear_config([:instance, :limit], 5)
1639 user = insert(:user)
1640
1641 result =
1642 conn
1643 |> assign(:user, user)
1644 |> put_req_header("content-type", "application/activity+json")
1645 |> post("/users/#{user.nickname}/outbox", activity)
1646 |> json_response(400)
1647
1648 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1649 end
1650 end
1651
1652 describe "/relay/followers" do
1653 test "it returns relay followers", %{conn: conn} do
1654 relay_actor = Relay.get_actor()
1655 user = insert(:user)
1656 User.follow(user, relay_actor)
1657
1658 result =
1659 conn
1660 |> get("/relay/followers")
1661 |> json_response(200)
1662
1663 assert result["first"]["orderedItems"] == [user.ap_id]
1664 end
1665
1666 test "on non-federating instance, it returns 404", %{conn: conn} do
1667 clear_config([:instance, :federating], false)
1668 user = insert(:user)
1669
1670 conn
1671 |> assign(:user, user)
1672 |> get("/relay/followers")
1673 |> json_response(404)
1674 end
1675 end
1676
1677 describe "/relay/following" do
1678 test "it returns relay following", %{conn: conn} do
1679 result =
1680 conn
1681 |> get("/relay/following")
1682 |> json_response(200)
1683
1684 assert result["first"]["orderedItems"] == []
1685 end
1686
1687 test "on non-federating instance, it returns 404", %{conn: conn} do
1688 clear_config([:instance, :federating], false)
1689 user = insert(:user)
1690
1691 conn
1692 |> assign(:user, user)
1693 |> get("/relay/following")
1694 |> json_response(404)
1695 end
1696 end
1697
1698 describe "/users/:nickname/followers" do
1699 test "it returns the followers in a collection", %{conn: conn} do
1700 user = insert(:user)
1701 user_two = insert(:user)
1702 User.follow(user, user_two)
1703
1704 result =
1705 conn
1706 |> assign(:user, user_two)
1707 |> get("/users/#{user_two.nickname}/followers")
1708 |> json_response(200)
1709
1710 assert result["first"]["orderedItems"] == [user.ap_id]
1711 end
1712
1713 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1714 user = insert(:user)
1715 user_two = insert(:user, hide_followers: true)
1716 User.follow(user, user_two)
1717
1718 result =
1719 conn
1720 |> assign(:user, user)
1721 |> get("/users/#{user_two.nickname}/followers")
1722 |> json_response(200)
1723
1724 assert is_binary(result["first"])
1725 end
1726
1727 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1728 %{conn: conn} do
1729 user = insert(:user)
1730 other_user = insert(:user, hide_followers: true)
1731
1732 result =
1733 conn
1734 |> assign(:user, user)
1735 |> get("/users/#{other_user.nickname}/followers?page=1")
1736
1737 assert result.status == 403
1738 assert result.resp_body == ""
1739 end
1740
1741 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1742 %{conn: conn} do
1743 user = insert(:user, hide_followers: true)
1744 other_user = insert(:user)
1745 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1746
1747 result =
1748 conn
1749 |> assign(:user, user)
1750 |> get("/users/#{user.nickname}/followers?page=1")
1751 |> json_response(200)
1752
1753 assert result["totalItems"] == 1
1754 assert result["orderedItems"] == [other_user.ap_id]
1755 end
1756
1757 test "it works for more than 10 users", %{conn: conn} do
1758 user = insert(:user)
1759
1760 Enum.each(1..15, fn _ ->
1761 other_user = insert(:user)
1762 User.follow(other_user, user)
1763 end)
1764
1765 result =
1766 conn
1767 |> assign(:user, user)
1768 |> get("/users/#{user.nickname}/followers")
1769 |> json_response(200)
1770
1771 assert length(result["first"]["orderedItems"]) == 10
1772 assert result["first"]["totalItems"] == 15
1773 assert result["totalItems"] == 15
1774
1775 result =
1776 conn
1777 |> assign(:user, user)
1778 |> get("/users/#{user.nickname}/followers?page=2")
1779 |> json_response(200)
1780
1781 assert length(result["orderedItems"]) == 5
1782 assert result["totalItems"] == 15
1783 end
1784
1785 test "does not require authentication", %{conn: conn} do
1786 user = insert(:user)
1787
1788 conn
1789 |> get("/users/#{user.nickname}/followers")
1790 |> json_response(200)
1791 end
1792 end
1793
1794 describe "/users/:nickname/following" do
1795 test "it returns the following in a collection", %{conn: conn} do
1796 user = insert(:user)
1797 user_two = insert(:user)
1798 User.follow(user, user_two)
1799
1800 result =
1801 conn
1802 |> assign(:user, user)
1803 |> get("/users/#{user.nickname}/following")
1804 |> json_response(200)
1805
1806 assert result["first"]["orderedItems"] == [user_two.ap_id]
1807 end
1808
1809 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1810 user = insert(:user)
1811 user_two = insert(:user, hide_follows: true)
1812 User.follow(user, user_two)
1813
1814 result =
1815 conn
1816 |> assign(:user, user)
1817 |> get("/users/#{user_two.nickname}/following")
1818 |> json_response(200)
1819
1820 assert is_binary(result["first"])
1821 end
1822
1823 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1824 %{conn: conn} do
1825 user = insert(:user)
1826 user_two = insert(:user, hide_follows: true)
1827
1828 result =
1829 conn
1830 |> assign(:user, user)
1831 |> get("/users/#{user_two.nickname}/following?page=1")
1832
1833 assert result.status == 403
1834 assert result.resp_body == ""
1835 end
1836
1837 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1838 %{conn: conn} do
1839 user = insert(:user, hide_follows: true)
1840 other_user = insert(:user)
1841 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1842
1843 result =
1844 conn
1845 |> assign(:user, user)
1846 |> get("/users/#{user.nickname}/following?page=1")
1847 |> json_response(200)
1848
1849 assert result["totalItems"] == 1
1850 assert result["orderedItems"] == [other_user.ap_id]
1851 end
1852
1853 test "it works for more than 10 users", %{conn: conn} do
1854 user = insert(:user)
1855
1856 Enum.each(1..15, fn _ ->
1857 user = User.get_cached_by_id(user.id)
1858 other_user = insert(:user)
1859 User.follow(user, other_user)
1860 end)
1861
1862 result =
1863 conn
1864 |> assign(:user, user)
1865 |> get("/users/#{user.nickname}/following")
1866 |> json_response(200)
1867
1868 assert length(result["first"]["orderedItems"]) == 10
1869 assert result["first"]["totalItems"] == 15
1870 assert result["totalItems"] == 15
1871
1872 result =
1873 conn
1874 |> assign(:user, user)
1875 |> get("/users/#{user.nickname}/following?page=2")
1876 |> json_response(200)
1877
1878 assert length(result["orderedItems"]) == 5
1879 assert result["totalItems"] == 15
1880 end
1881
1882 test "does not require authentication", %{conn: conn} do
1883 user = insert(:user)
1884
1885 conn
1886 |> get("/users/#{user.nickname}/following")
1887 |> json_response(200)
1888 end
1889 end
1890
1891 describe "delivery tracking" do
1892 test "it tracks a signed object fetch", %{conn: conn} do
1893 user = insert(:user, local: false)
1894 activity = insert(:note_activity)
1895 object = Object.normalize(activity, fetch: false)
1896
1897 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1898
1899 conn
1900 |> put_req_header("accept", "application/activity+json")
1901 |> assign(:user, user)
1902 |> get(object_path)
1903 |> json_response(200)
1904
1905 assert Delivery.get(object.id, user.id)
1906 end
1907
1908 test "it tracks a signed activity fetch", %{conn: conn} do
1909 user = insert(:user, local: false)
1910 activity = insert(:note_activity)
1911 object = Object.normalize(activity, fetch: false)
1912
1913 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1914
1915 conn
1916 |> put_req_header("accept", "application/activity+json")
1917 |> assign(:user, user)
1918 |> get(activity_path)
1919 |> json_response(200)
1920
1921 assert Delivery.get(object.id, user.id)
1922 end
1923
1924 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1925 user = insert(:user, local: false)
1926 other_user = insert(:user, local: false)
1927 activity = insert(:note_activity)
1928 object = Object.normalize(activity, fetch: false)
1929
1930 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1931
1932 conn
1933 |> put_req_header("accept", "application/activity+json")
1934 |> assign(:user, user)
1935 |> get(object_path)
1936 |> json_response(200)
1937
1938 build_conn()
1939 |> put_req_header("accept", "application/activity+json")
1940 |> assign(:user, other_user)
1941 |> get(object_path)
1942 |> json_response(200)
1943
1944 assert Delivery.get(object.id, user.id)
1945 assert Delivery.get(object.id, other_user.id)
1946 end
1947
1948 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1949 user = insert(:user, local: false)
1950 other_user = insert(:user, local: false)
1951 activity = insert(:note_activity)
1952 object = Object.normalize(activity, fetch: false)
1953
1954 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1955
1956 conn
1957 |> put_req_header("accept", "application/activity+json")
1958 |> assign(:user, user)
1959 |> get(activity_path)
1960 |> json_response(200)
1961
1962 build_conn()
1963 |> put_req_header("accept", "application/activity+json")
1964 |> assign(:user, other_user)
1965 |> get(activity_path)
1966 |> json_response(200)
1967
1968 assert Delivery.get(object.id, user.id)
1969 assert Delivery.get(object.id, other_user.id)
1970 end
1971 end
1972
1973 describe "Additional ActivityPub C2S endpoints" do
1974 test "GET /api/ap/whoami", %{conn: conn} do
1975 user = insert(:user)
1976
1977 conn =
1978 conn
1979 |> assign(:user, user)
1980 |> get("/api/ap/whoami")
1981
1982 user = User.get_cached_by_id(user.id)
1983
1984 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1985
1986 conn
1987 |> get("/api/ap/whoami")
1988 |> json_response(403)
1989 end
1990
1991 setup do: clear_config([:media_proxy])
1992 setup do: clear_config([Pleroma.Upload])
1993
1994 test "POST /api/ap/upload_media", %{conn: conn} do
1995 user = insert(:user)
1996
1997 desc = "Description of the image"
1998
1999 image = %Plug.Upload{
2000 content_type: "image/jpeg",
2001 path: Path.absname("test/fixtures/image.jpg"),
2002 filename: "an_image.jpg"
2003 }
2004
2005 object =
2006 conn
2007 |> assign(:user, user)
2008 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2009 |> json_response(:created)
2010
2011 assert object["name"] == desc
2012 assert object["type"] == "Document"
2013 assert object["actor"] == user.ap_id
2014 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2015 assert is_binary(object_href)
2016 assert object_mediatype == "image/jpeg"
2017 assert String.ends_with?(object_href, ".jpg")
2018
2019 activity_request = %{
2020 "@context" => "https://www.w3.org/ns/activitystreams",
2021 "type" => "Create",
2022 "object" => %{
2023 "type" => "Note",
2024 "content" => "AP C2S test, attachment",
2025 "attachment" => [object],
2026 "to" => "https://www.w3.org/ns/activitystreams#Public",
2027 "cc" => []
2028 }
2029 }
2030
2031 activity_response =
2032 conn
2033 |> assign(:user, user)
2034 |> post("/users/#{user.nickname}/outbox", activity_request)
2035 |> json_response(:created)
2036
2037 assert activity_response["id"]
2038 assert activity_response["object"]
2039 assert activity_response["actor"] == user.ap_id
2040
2041 assert %Object{data: %{"attachment" => [attachment]}} =
2042 Object.normalize(activity_response["object"], fetch: false)
2043
2044 assert attachment["type"] == "Document"
2045 assert attachment["name"] == desc
2046
2047 assert [
2048 %{
2049 "href" => ^object_href,
2050 "type" => "Link",
2051 "mediaType" => ^object_mediatype
2052 }
2053 ] = attachment["url"]
2054
2055 # Fails if unauthenticated
2056 conn
2057 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2058 |> json_response(403)
2059 end
2060 end
2061
2062 test "pinned collection", %{conn: conn} do
2063 clear_config([:instance, :max_pinned_statuses], 2)
2064 user = insert(:user)
2065 objects = insert_list(2, :note, user: user)
2066
2067 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2068 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2069 updated
2070 end)
2071
2072 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2073 refresh_record(user)
2074
2075 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2076 conn
2077 |> get("/users/#{nickname}/collections/featured")
2078 |> json_response(200)
2079
2080 object_ids = Enum.map(items, & &1["id"])
2081
2082 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2083 obj_id in object_ids
2084 end)
2085 end
2086 end