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