Let pins federate
[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 end
720
721 describe "/users/:nickname/inbox" do
722 setup do
723 data =
724 File.read!("test/fixtures/mastodon-post-activity.json")
725 |> Jason.decode!()
726
727 [data: data]
728 end
729
730 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
731 user = insert(:user)
732 data = Map.put(data, "bcc", [user.ap_id])
733
734 conn =
735 conn
736 |> assign(:valid_signature, true)
737 |> put_req_header("content-type", "application/activity+json")
738 |> post("/users/#{user.nickname}/inbox", data)
739
740 assert "ok" == json_response(conn, 200)
741 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
742 assert Activity.get_by_ap_id(data["id"])
743 end
744
745 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
746 user = insert(:user)
747
748 data =
749 Map.put(data, "to", user.ap_id)
750 |> Map.delete("cc")
751
752 conn =
753 conn
754 |> assign(:valid_signature, true)
755 |> put_req_header("content-type", "application/activity+json")
756 |> post("/users/#{user.nickname}/inbox", data)
757
758 assert "ok" == json_response(conn, 200)
759 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
760 assert Activity.get_by_ap_id(data["id"])
761 end
762
763 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
764 user = insert(:user)
765
766 data =
767 Map.put(data, "cc", user.ap_id)
768 |> Map.delete("to")
769
770 conn =
771 conn
772 |> assign(:valid_signature, true)
773 |> put_req_header("content-type", "application/activity+json")
774 |> post("/users/#{user.nickname}/inbox", data)
775
776 assert "ok" == json_response(conn, 200)
777 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
778 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
779 assert user.ap_id in activity.recipients
780 end
781
782 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
783 user = insert(:user)
784
785 data =
786 Map.put(data, "bcc", user.ap_id)
787 |> Map.delete("to")
788 |> Map.delete("cc")
789
790 conn =
791 conn
792 |> assign(:valid_signature, true)
793 |> put_req_header("content-type", "application/activity+json")
794 |> post("/users/#{user.nickname}/inbox", data)
795
796 assert "ok" == json_response(conn, 200)
797 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
798 assert Activity.get_by_ap_id(data["id"])
799 end
800
801 test "it accepts announces with to as string instead of array", %{conn: conn} do
802 user = insert(:user)
803
804 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
805 announcer = insert(:user, local: false)
806
807 data = %{
808 "@context" => "https://www.w3.org/ns/activitystreams",
809 "actor" => announcer.ap_id,
810 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
811 "object" => post.data["object"],
812 "to" => "https://www.w3.org/ns/activitystreams#Public",
813 "cc" => [user.ap_id],
814 "type" => "Announce"
815 }
816
817 conn =
818 conn
819 |> assign(:valid_signature, true)
820 |> put_req_header("content-type", "application/activity+json")
821 |> post("/users/#{user.nickname}/inbox", data)
822
823 assert "ok" == json_response(conn, 200)
824 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
825 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
826 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
827 end
828
829 test "it accepts messages from actors that are followed by the user", %{
830 conn: conn,
831 data: data
832 } do
833 recipient = insert(:user)
834 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
835
836 {:ok, recipient, actor} = User.follow(recipient, actor)
837
838 object =
839 data["object"]
840 |> Map.put("attributedTo", actor.ap_id)
841
842 data =
843 data
844 |> Map.put("actor", actor.ap_id)
845 |> Map.put("object", object)
846
847 conn =
848 conn
849 |> assign(:valid_signature, true)
850 |> put_req_header("content-type", "application/activity+json")
851 |> post("/users/#{recipient.nickname}/inbox", data)
852
853 assert "ok" == json_response(conn, 200)
854 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
855 assert Activity.get_by_ap_id(data["id"])
856 end
857
858 test "it rejects reads from other users", %{conn: conn} do
859 user = insert(:user)
860 other_user = insert(:user)
861
862 conn =
863 conn
864 |> assign(:user, other_user)
865 |> put_req_header("accept", "application/activity+json")
866 |> get("/users/#{user.nickname}/inbox")
867
868 assert json_response(conn, 403)
869 end
870
871 test "it returns a note activity in a collection", %{conn: conn} do
872 note_activity = insert(:direct_note_activity)
873 note_object = Object.normalize(note_activity, fetch: false)
874 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
875
876 conn =
877 conn
878 |> assign(:user, user)
879 |> put_req_header("accept", "application/activity+json")
880 |> get("/users/#{user.nickname}/inbox?page=true")
881
882 assert response(conn, 200) =~ note_object.data["content"]
883 end
884
885 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
886 user = insert(:user)
887 data = Map.put(data, "bcc", [user.ap_id])
888
889 sender_host = URI.parse(data["actor"]).host
890 Instances.set_consistently_unreachable(sender_host)
891 refute Instances.reachable?(sender_host)
892
893 conn =
894 conn
895 |> assign(:valid_signature, true)
896 |> put_req_header("content-type", "application/activity+json")
897 |> post("/users/#{user.nickname}/inbox", data)
898
899 assert "ok" == json_response(conn, 200)
900 assert Instances.reachable?(sender_host)
901 end
902
903 test "it removes all follower collections but actor's", %{conn: conn} do
904 [actor, recipient] = insert_pair(:user)
905
906 data =
907 File.read!("test/fixtures/activitypub-client-post-activity.json")
908 |> Jason.decode!()
909
910 object = Map.put(data["object"], "attributedTo", actor.ap_id)
911
912 data =
913 data
914 |> Map.put("id", Utils.generate_object_id())
915 |> Map.put("actor", actor.ap_id)
916 |> Map.put("object", object)
917 |> Map.put("cc", [
918 recipient.follower_address,
919 actor.follower_address
920 ])
921 |> Map.put("to", [
922 recipient.ap_id,
923 recipient.follower_address,
924 "https://www.w3.org/ns/activitystreams#Public"
925 ])
926
927 conn
928 |> assign(:valid_signature, true)
929 |> put_req_header("content-type", "application/activity+json")
930 |> post("/users/#{recipient.nickname}/inbox", data)
931 |> json_response(200)
932
933 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
934
935 activity = Activity.get_by_ap_id(data["id"])
936
937 assert activity.id
938 assert actor.follower_address in activity.recipients
939 assert actor.follower_address in activity.data["cc"]
940
941 refute recipient.follower_address in activity.recipients
942 refute recipient.follower_address in activity.data["cc"]
943 refute recipient.follower_address in activity.data["to"]
944 end
945
946 test "it requires authentication", %{conn: conn} do
947 user = insert(:user)
948 conn = put_req_header(conn, "accept", "application/activity+json")
949
950 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
951 assert json_response(ret_conn, 403)
952
953 ret_conn =
954 conn
955 |> assign(:user, user)
956 |> get("/users/#{user.nickname}/inbox")
957
958 assert json_response(ret_conn, 200)
959 end
960
961 @tag capture_log: true
962 test "forwarded report", %{conn: conn} do
963 admin = insert(:user, is_admin: true)
964 actor = insert(:user, local: false)
965 remote_domain = URI.parse(actor.ap_id).host
966 reported_user = insert(:user)
967
968 note = insert(:note_activity, user: reported_user)
969
970 data = %{
971 "@context" => [
972 "https://www.w3.org/ns/activitystreams",
973 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
974 %{
975 "@language" => "und"
976 }
977 ],
978 "actor" => actor.ap_id,
979 "cc" => [
980 reported_user.ap_id
981 ],
982 "content" => "test",
983 "context" => "context",
984 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
985 "nickname" => reported_user.nickname,
986 "object" => [
987 reported_user.ap_id,
988 %{
989 "actor" => %{
990 "actor_type" => "Person",
991 "approval_pending" => false,
992 "avatar" => "",
993 "confirmation_pending" => false,
994 "deactivated" => false,
995 "display_name" => "test user",
996 "id" => reported_user.id,
997 "local" => false,
998 "nickname" => reported_user.nickname,
999 "registration_reason" => nil,
1000 "roles" => %{
1001 "admin" => false,
1002 "moderator" => false
1003 },
1004 "tags" => [],
1005 "url" => reported_user.ap_id
1006 },
1007 "content" => "",
1008 "id" => note.data["id"],
1009 "published" => note.data["published"],
1010 "type" => "Note"
1011 }
1012 ],
1013 "published" => note.data["published"],
1014 "state" => "open",
1015 "to" => [],
1016 "type" => "Flag"
1017 }
1018
1019 conn
1020 |> assign(:valid_signature, true)
1021 |> put_req_header("content-type", "application/activity+json")
1022 |> post("/users/#{reported_user.nickname}/inbox", data)
1023 |> json_response(200)
1024
1025 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1026
1027 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1028
1029 ObanHelpers.perform_all()
1030
1031 Swoosh.TestAssertions.assert_email_sent(
1032 to: {admin.name, admin.email},
1033 html_body: ~r/Reported Account:/i
1034 )
1035 end
1036
1037 @tag capture_log: true
1038 test "forwarded report from mastodon", %{conn: conn} do
1039 admin = insert(:user, is_admin: true)
1040 actor = insert(:user, local: false)
1041 remote_domain = URI.parse(actor.ap_id).host
1042 remote_actor = "https://#{remote_domain}/actor"
1043 [reported_user, another] = insert_list(2, :user)
1044
1045 note = insert(:note_activity, user: reported_user)
1046
1047 Pleroma.Web.CommonAPI.favorite(another, note.id)
1048
1049 mock_json_body =
1050 "test/fixtures/mastodon/application_actor.json"
1051 |> File.read!()
1052 |> String.replace("{{DOMAIN}}", remote_domain)
1053
1054 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1055 %Tesla.Env{
1056 status: 200,
1057 body: mock_json_body,
1058 headers: [{"content-type", "application/activity+json"}]
1059 }
1060 end)
1061
1062 data = %{
1063 "@context" => "https://www.w3.org/ns/activitystreams",
1064 "actor" => remote_actor,
1065 "content" => "test report",
1066 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1067 "nickname" => reported_user.nickname,
1068 "object" => [
1069 reported_user.ap_id,
1070 note.data["object"]
1071 ],
1072 "type" => "Flag"
1073 }
1074
1075 conn
1076 |> assign(:valid_signature, true)
1077 |> put_req_header("content-type", "application/activity+json")
1078 |> post("/users/#{reported_user.nickname}/inbox", data)
1079 |> json_response(200)
1080
1081 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1082
1083 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1084 reported_user_ap_id = reported_user.ap_id
1085
1086 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1087
1088 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1089 ObanHelpers.perform_all()
1090
1091 Swoosh.TestAssertions.assert_email_sent(
1092 to: {admin.name, admin.email},
1093 html_body: ~r/#{note.data["object"]}/i
1094 )
1095 end
1096 end
1097
1098 describe "GET /users/:nickname/outbox" do
1099 test "it paginates correctly", %{conn: conn} do
1100 user = insert(:user)
1101 conn = assign(conn, :user, user)
1102 outbox_endpoint = user.ap_id <> "/outbox"
1103
1104 _posts =
1105 for i <- 0..25 do
1106 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1107 activity
1108 end
1109
1110 result =
1111 conn
1112 |> put_req_header("accept", "application/activity+json")
1113 |> get(outbox_endpoint <> "?page=true")
1114 |> json_response(200)
1115
1116 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1117 assert length(result["orderedItems"]) == 20
1118 assert length(result_ids) == 20
1119 assert result["next"]
1120 assert String.starts_with?(result["next"], outbox_endpoint)
1121
1122 result_next =
1123 conn
1124 |> put_req_header("accept", "application/activity+json")
1125 |> get(result["next"])
1126 |> json_response(200)
1127
1128 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1129 assert length(result_next["orderedItems"]) == 6
1130 assert length(result_next_ids) == 6
1131 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1132 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1133 assert String.starts_with?(result["id"], outbox_endpoint)
1134
1135 result_next_again =
1136 conn
1137 |> put_req_header("accept", "application/activity+json")
1138 |> get(result_next["id"])
1139 |> json_response(200)
1140
1141 assert result_next == result_next_again
1142 end
1143
1144 test "it returns 200 even if there're no activities", %{conn: conn} do
1145 user = insert(:user)
1146 outbox_endpoint = user.ap_id <> "/outbox"
1147
1148 conn =
1149 conn
1150 |> assign(:user, user)
1151 |> put_req_header("accept", "application/activity+json")
1152 |> get(outbox_endpoint)
1153
1154 result = json_response(conn, 200)
1155 assert outbox_endpoint == result["id"]
1156 end
1157
1158 test "it returns a note activity in a collection", %{conn: conn} do
1159 note_activity = insert(:note_activity)
1160 note_object = Object.normalize(note_activity, fetch: false)
1161 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1162
1163 conn =
1164 conn
1165 |> assign(:user, user)
1166 |> put_req_header("accept", "application/activity+json")
1167 |> get("/users/#{user.nickname}/outbox?page=true")
1168
1169 assert response(conn, 200) =~ note_object.data["content"]
1170 end
1171
1172 test "it returns an announce activity in a collection", %{conn: conn} do
1173 announce_activity = insert(:announce_activity)
1174 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1175
1176 conn =
1177 conn
1178 |> assign(:user, user)
1179 |> put_req_header("accept", "application/activity+json")
1180 |> get("/users/#{user.nickname}/outbox?page=true")
1181
1182 assert response(conn, 200) =~ announce_activity.data["object"]
1183 end
1184
1185 test "It returns poll Answers when authenticated", %{conn: conn} do
1186 poller = insert(:user)
1187 voter = insert(:user)
1188
1189 {:ok, activity} =
1190 CommonAPI.post(poller, %{
1191 status: "suya...",
1192 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1193 })
1194
1195 assert question = Object.normalize(activity, fetch: false)
1196
1197 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1198
1199 assert outbox_get =
1200 conn
1201 |> assign(:user, voter)
1202 |> put_req_header("accept", "application/activity+json")
1203 |> get(voter.ap_id <> "/outbox?page=true")
1204 |> json_response(200)
1205
1206 assert [answer_outbox] = outbox_get["orderedItems"]
1207 assert answer_outbox["id"] == activity.data["id"]
1208 end
1209 end
1210
1211 describe "POST /users/:nickname/outbox (C2S)" do
1212 setup do: clear_config([:instance, :limit])
1213
1214 setup do
1215 [
1216 activity: %{
1217 "@context" => "https://www.w3.org/ns/activitystreams",
1218 "type" => "Create",
1219 "object" => %{"type" => "Note", "content" => "AP C2S test"},
1220 "to" => "https://www.w3.org/ns/activitystreams#Public",
1221 "cc" => []
1222 }
1223 ]
1224 end
1225
1226 test "it rejects posts from other users / unauthenticated users", %{
1227 conn: conn,
1228 activity: activity
1229 } do
1230 user = insert(:user)
1231 other_user = insert(:user)
1232 conn = put_req_header(conn, "content-type", "application/activity+json")
1233
1234 conn
1235 |> post("/users/#{user.nickname}/outbox", activity)
1236 |> json_response(403)
1237
1238 conn
1239 |> assign(:user, other_user)
1240 |> post("/users/#{user.nickname}/outbox", activity)
1241 |> json_response(403)
1242 end
1243
1244 test "it inserts an incoming create activity into the database", %{
1245 conn: conn,
1246 activity: activity
1247 } do
1248 user = insert(:user)
1249
1250 result =
1251 conn
1252 |> assign(:user, user)
1253 |> put_req_header("content-type", "application/activity+json")
1254 |> post("/users/#{user.nickname}/outbox", activity)
1255 |> json_response(201)
1256
1257 assert Activity.get_by_ap_id(result["id"])
1258 assert result["object"]
1259 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1260 assert object["content"] == activity["object"]["content"]
1261 end
1262
1263 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1264 user = insert(:user)
1265
1266 activity =
1267 activity
1268 |> put_in(["object", "type"], "Benis")
1269
1270 _result =
1271 conn
1272 |> assign(:user, user)
1273 |> put_req_header("content-type", "application/activity+json")
1274 |> post("/users/#{user.nickname}/outbox", activity)
1275 |> json_response(400)
1276 end
1277
1278 test "it inserts an incoming sensitive activity into the database", %{
1279 conn: conn,
1280 activity: activity
1281 } do
1282 user = insert(:user)
1283 conn = assign(conn, :user, user)
1284 object = Map.put(activity["object"], "sensitive", true)
1285 activity = Map.put(activity, "object", object)
1286
1287 response =
1288 conn
1289 |> put_req_header("content-type", "application/activity+json")
1290 |> post("/users/#{user.nickname}/outbox", activity)
1291 |> json_response(201)
1292
1293 assert Activity.get_by_ap_id(response["id"])
1294 assert response["object"]
1295 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1296 assert response_object["sensitive"] == true
1297 assert response_object["content"] == activity["object"]["content"]
1298
1299 representation =
1300 conn
1301 |> put_req_header("accept", "application/activity+json")
1302 |> get(response["id"])
1303 |> json_response(200)
1304
1305 assert representation["object"]["sensitive"] == true
1306 end
1307
1308 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1309 user = insert(:user)
1310 activity = Map.put(activity, "type", "BadType")
1311
1312 conn =
1313 conn
1314 |> assign(:user, user)
1315 |> put_req_header("content-type", "application/activity+json")
1316 |> post("/users/#{user.nickname}/outbox", activity)
1317
1318 assert json_response(conn, 400)
1319 end
1320
1321 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1322 note_activity = insert(:note_activity)
1323 note_object = Object.normalize(note_activity, fetch: false)
1324 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1325
1326 data = %{
1327 type: "Delete",
1328 object: %{
1329 id: note_object.data["id"]
1330 }
1331 }
1332
1333 conn =
1334 conn
1335 |> assign(:user, user)
1336 |> put_req_header("content-type", "application/activity+json")
1337 |> post("/users/#{user.nickname}/outbox", data)
1338
1339 result = json_response(conn, 201)
1340 assert Activity.get_by_ap_id(result["id"])
1341
1342 assert object = Object.get_by_ap_id(note_object.data["id"])
1343 assert object.data["type"] == "Tombstone"
1344 end
1345
1346 test "it rejects delete activity of object from other actor", %{conn: conn} do
1347 note_activity = insert(:note_activity)
1348 note_object = Object.normalize(note_activity, fetch: false)
1349 user = insert(:user)
1350
1351 data = %{
1352 type: "Delete",
1353 object: %{
1354 id: note_object.data["id"]
1355 }
1356 }
1357
1358 conn =
1359 conn
1360 |> assign(:user, user)
1361 |> put_req_header("content-type", "application/activity+json")
1362 |> post("/users/#{user.nickname}/outbox", data)
1363
1364 assert json_response(conn, 400)
1365 end
1366
1367 test "it increases like count when receiving a like action", %{conn: conn} do
1368 note_activity = insert(:note_activity)
1369 note_object = Object.normalize(note_activity, fetch: false)
1370 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1371
1372 data = %{
1373 type: "Like",
1374 object: %{
1375 id: note_object.data["id"]
1376 }
1377 }
1378
1379 conn =
1380 conn
1381 |> assign(:user, user)
1382 |> put_req_header("content-type", "application/activity+json")
1383 |> post("/users/#{user.nickname}/outbox", data)
1384
1385 result = json_response(conn, 201)
1386 assert Activity.get_by_ap_id(result["id"])
1387
1388 assert object = Object.get_by_ap_id(note_object.data["id"])
1389 assert object.data["like_count"] == 1
1390 end
1391
1392 test "it doesn't spreads faulty attributedTo or actor fields", %{
1393 conn: conn,
1394 activity: activity
1395 } do
1396 reimu = insert(:user, nickname: "reimu")
1397 cirno = insert(:user, nickname: "cirno")
1398
1399 assert reimu.ap_id
1400 assert cirno.ap_id
1401
1402 activity =
1403 activity
1404 |> put_in(["object", "actor"], reimu.ap_id)
1405 |> put_in(["object", "attributedTo"], reimu.ap_id)
1406 |> put_in(["actor"], reimu.ap_id)
1407 |> put_in(["attributedTo"], reimu.ap_id)
1408
1409 _reimu_outbox =
1410 conn
1411 |> assign(:user, cirno)
1412 |> put_req_header("content-type", "application/activity+json")
1413 |> post("/users/#{reimu.nickname}/outbox", activity)
1414 |> json_response(403)
1415
1416 cirno_outbox =
1417 conn
1418 |> assign(:user, cirno)
1419 |> put_req_header("content-type", "application/activity+json")
1420 |> post("/users/#{cirno.nickname}/outbox", activity)
1421 |> json_response(201)
1422
1423 assert cirno_outbox["attributedTo"] == nil
1424 assert cirno_outbox["actor"] == cirno.ap_id
1425
1426 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1427 assert cirno_object.data["actor"] == cirno.ap_id
1428 assert cirno_object.data["attributedTo"] == cirno.ap_id
1429 end
1430
1431 test "Character limitation", %{conn: conn, activity: activity} do
1432 clear_config([:instance, :limit], 5)
1433 user = insert(:user)
1434
1435 result =
1436 conn
1437 |> assign(:user, user)
1438 |> put_req_header("content-type", "application/activity+json")
1439 |> post("/users/#{user.nickname}/outbox", activity)
1440 |> json_response(400)
1441
1442 assert result == "Note is over the character limit"
1443 end
1444 end
1445
1446 describe "/relay/followers" do
1447 test "it returns relay followers", %{conn: conn} do
1448 relay_actor = Relay.get_actor()
1449 user = insert(:user)
1450 User.follow(user, relay_actor)
1451
1452 result =
1453 conn
1454 |> get("/relay/followers")
1455 |> json_response(200)
1456
1457 assert result["first"]["orderedItems"] == [user.ap_id]
1458 end
1459
1460 test "on non-federating instance, it returns 404", %{conn: conn} do
1461 clear_config([:instance, :federating], false)
1462 user = insert(:user)
1463
1464 conn
1465 |> assign(:user, user)
1466 |> get("/relay/followers")
1467 |> json_response(404)
1468 end
1469 end
1470
1471 describe "/relay/following" do
1472 test "it returns relay following", %{conn: conn} do
1473 result =
1474 conn
1475 |> get("/relay/following")
1476 |> json_response(200)
1477
1478 assert result["first"]["orderedItems"] == []
1479 end
1480
1481 test "on non-federating instance, it returns 404", %{conn: conn} do
1482 clear_config([:instance, :federating], false)
1483 user = insert(:user)
1484
1485 conn
1486 |> assign(:user, user)
1487 |> get("/relay/following")
1488 |> json_response(404)
1489 end
1490 end
1491
1492 describe "/users/:nickname/followers" do
1493 test "it returns the followers in a collection", %{conn: conn} do
1494 user = insert(:user)
1495 user_two = insert(:user)
1496 User.follow(user, user_two)
1497
1498 result =
1499 conn
1500 |> assign(:user, user_two)
1501 |> get("/users/#{user_two.nickname}/followers")
1502 |> json_response(200)
1503
1504 assert result["first"]["orderedItems"] == [user.ap_id]
1505 end
1506
1507 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1508 user = insert(:user)
1509 user_two = insert(:user, hide_followers: true)
1510 User.follow(user, user_two)
1511
1512 result =
1513 conn
1514 |> assign(:user, user)
1515 |> get("/users/#{user_two.nickname}/followers")
1516 |> json_response(200)
1517
1518 assert is_binary(result["first"])
1519 end
1520
1521 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1522 %{conn: conn} do
1523 user = insert(:user)
1524 other_user = insert(:user, hide_followers: true)
1525
1526 result =
1527 conn
1528 |> assign(:user, user)
1529 |> get("/users/#{other_user.nickname}/followers?page=1")
1530
1531 assert result.status == 403
1532 assert result.resp_body == ""
1533 end
1534
1535 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1536 %{conn: conn} do
1537 user = insert(:user, hide_followers: true)
1538 other_user = insert(:user)
1539 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1540
1541 result =
1542 conn
1543 |> assign(:user, user)
1544 |> get("/users/#{user.nickname}/followers?page=1")
1545 |> json_response(200)
1546
1547 assert result["totalItems"] == 1
1548 assert result["orderedItems"] == [other_user.ap_id]
1549 end
1550
1551 test "it works for more than 10 users", %{conn: conn} do
1552 user = insert(:user)
1553
1554 Enum.each(1..15, fn _ ->
1555 other_user = insert(:user)
1556 User.follow(other_user, user)
1557 end)
1558
1559 result =
1560 conn
1561 |> assign(:user, user)
1562 |> get("/users/#{user.nickname}/followers")
1563 |> json_response(200)
1564
1565 assert length(result["first"]["orderedItems"]) == 10
1566 assert result["first"]["totalItems"] == 15
1567 assert result["totalItems"] == 15
1568
1569 result =
1570 conn
1571 |> assign(:user, user)
1572 |> get("/users/#{user.nickname}/followers?page=2")
1573 |> json_response(200)
1574
1575 assert length(result["orderedItems"]) == 5
1576 assert result["totalItems"] == 15
1577 end
1578
1579 test "does not require authentication", %{conn: conn} do
1580 user = insert(:user)
1581
1582 conn
1583 |> get("/users/#{user.nickname}/followers")
1584 |> json_response(200)
1585 end
1586 end
1587
1588 describe "/users/:nickname/following" do
1589 test "it returns the following in a collection", %{conn: conn} do
1590 user = insert(:user)
1591 user_two = insert(:user)
1592 User.follow(user, user_two)
1593
1594 result =
1595 conn
1596 |> assign(:user, user)
1597 |> get("/users/#{user.nickname}/following")
1598 |> json_response(200)
1599
1600 assert result["first"]["orderedItems"] == [user_two.ap_id]
1601 end
1602
1603 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1604 user = insert(:user)
1605 user_two = insert(:user, hide_follows: true)
1606 User.follow(user, user_two)
1607
1608 result =
1609 conn
1610 |> assign(:user, user)
1611 |> get("/users/#{user_two.nickname}/following")
1612 |> json_response(200)
1613
1614 assert is_binary(result["first"])
1615 end
1616
1617 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1618 %{conn: conn} do
1619 user = insert(:user)
1620 user_two = insert(:user, hide_follows: true)
1621
1622 result =
1623 conn
1624 |> assign(:user, user)
1625 |> get("/users/#{user_two.nickname}/following?page=1")
1626
1627 assert result.status == 403
1628 assert result.resp_body == ""
1629 end
1630
1631 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1632 %{conn: conn} do
1633 user = insert(:user, hide_follows: true)
1634 other_user = insert(:user)
1635 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1636
1637 result =
1638 conn
1639 |> assign(:user, user)
1640 |> get("/users/#{user.nickname}/following?page=1")
1641 |> json_response(200)
1642
1643 assert result["totalItems"] == 1
1644 assert result["orderedItems"] == [other_user.ap_id]
1645 end
1646
1647 test "it works for more than 10 users", %{conn: conn} do
1648 user = insert(:user)
1649
1650 Enum.each(1..15, fn _ ->
1651 user = User.get_cached_by_id(user.id)
1652 other_user = insert(:user)
1653 User.follow(user, other_user)
1654 end)
1655
1656 result =
1657 conn
1658 |> assign(:user, user)
1659 |> get("/users/#{user.nickname}/following")
1660 |> json_response(200)
1661
1662 assert length(result["first"]["orderedItems"]) == 10
1663 assert result["first"]["totalItems"] == 15
1664 assert result["totalItems"] == 15
1665
1666 result =
1667 conn
1668 |> assign(:user, user)
1669 |> get("/users/#{user.nickname}/following?page=2")
1670 |> json_response(200)
1671
1672 assert length(result["orderedItems"]) == 5
1673 assert result["totalItems"] == 15
1674 end
1675
1676 test "does not require authentication", %{conn: conn} do
1677 user = insert(:user)
1678
1679 conn
1680 |> get("/users/#{user.nickname}/following")
1681 |> json_response(200)
1682 end
1683 end
1684
1685 describe "delivery tracking" do
1686 test "it tracks a signed object fetch", %{conn: conn} do
1687 user = insert(:user, local: false)
1688 activity = insert(:note_activity)
1689 object = Object.normalize(activity, fetch: false)
1690
1691 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1692
1693 conn
1694 |> put_req_header("accept", "application/activity+json")
1695 |> assign(:user, user)
1696 |> get(object_path)
1697 |> json_response(200)
1698
1699 assert Delivery.get(object.id, user.id)
1700 end
1701
1702 test "it tracks a signed activity fetch", %{conn: conn} do
1703 user = insert(:user, local: false)
1704 activity = insert(:note_activity)
1705 object = Object.normalize(activity, fetch: false)
1706
1707 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1708
1709 conn
1710 |> put_req_header("accept", "application/activity+json")
1711 |> assign(:user, user)
1712 |> get(activity_path)
1713 |> json_response(200)
1714
1715 assert Delivery.get(object.id, user.id)
1716 end
1717
1718 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1719 user = insert(:user, local: false)
1720 other_user = insert(:user, local: false)
1721 activity = insert(:note_activity)
1722 object = Object.normalize(activity, fetch: false)
1723
1724 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1725
1726 conn
1727 |> put_req_header("accept", "application/activity+json")
1728 |> assign(:user, user)
1729 |> get(object_path)
1730 |> json_response(200)
1731
1732 build_conn()
1733 |> put_req_header("accept", "application/activity+json")
1734 |> assign(:user, other_user)
1735 |> get(object_path)
1736 |> json_response(200)
1737
1738 assert Delivery.get(object.id, user.id)
1739 assert Delivery.get(object.id, other_user.id)
1740 end
1741
1742 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1743 user = insert(:user, local: false)
1744 other_user = insert(:user, local: false)
1745 activity = insert(:note_activity)
1746 object = Object.normalize(activity, fetch: false)
1747
1748 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1749
1750 conn
1751 |> put_req_header("accept", "application/activity+json")
1752 |> assign(:user, user)
1753 |> get(activity_path)
1754 |> json_response(200)
1755
1756 build_conn()
1757 |> put_req_header("accept", "application/activity+json")
1758 |> assign(:user, other_user)
1759 |> get(activity_path)
1760 |> json_response(200)
1761
1762 assert Delivery.get(object.id, user.id)
1763 assert Delivery.get(object.id, other_user.id)
1764 end
1765 end
1766
1767 describe "Additional ActivityPub C2S endpoints" do
1768 test "GET /api/ap/whoami", %{conn: conn} do
1769 user = insert(:user)
1770
1771 conn =
1772 conn
1773 |> assign(:user, user)
1774 |> get("/api/ap/whoami")
1775
1776 user = User.get_cached_by_id(user.id)
1777
1778 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1779
1780 conn
1781 |> get("/api/ap/whoami")
1782 |> json_response(403)
1783 end
1784
1785 setup do: clear_config([:media_proxy])
1786 setup do: clear_config([Pleroma.Upload])
1787
1788 test "POST /api/ap/upload_media", %{conn: conn} do
1789 user = insert(:user)
1790
1791 desc = "Description of the image"
1792
1793 image = %Plug.Upload{
1794 content_type: "image/jpeg",
1795 path: Path.absname("test/fixtures/image.jpg"),
1796 filename: "an_image.jpg"
1797 }
1798
1799 object =
1800 conn
1801 |> assign(:user, user)
1802 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1803 |> json_response(:created)
1804
1805 assert object["name"] == desc
1806 assert object["type"] == "Document"
1807 assert object["actor"] == user.ap_id
1808 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1809 assert is_binary(object_href)
1810 assert object_mediatype == "image/jpeg"
1811 assert String.ends_with?(object_href, ".jpg")
1812
1813 activity_request = %{
1814 "@context" => "https://www.w3.org/ns/activitystreams",
1815 "type" => "Create",
1816 "object" => %{
1817 "type" => "Note",
1818 "content" => "AP C2S test, attachment",
1819 "attachment" => [object]
1820 },
1821 "to" => "https://www.w3.org/ns/activitystreams#Public",
1822 "cc" => []
1823 }
1824
1825 activity_response =
1826 conn
1827 |> assign(:user, user)
1828 |> post("/users/#{user.nickname}/outbox", activity_request)
1829 |> json_response(:created)
1830
1831 assert activity_response["id"]
1832 assert activity_response["object"]
1833 assert activity_response["actor"] == user.ap_id
1834
1835 assert %Object{data: %{"attachment" => [attachment]}} =
1836 Object.normalize(activity_response["object"], fetch: false)
1837
1838 assert attachment["type"] == "Document"
1839 assert attachment["name"] == desc
1840
1841 assert [
1842 %{
1843 "href" => ^object_href,
1844 "type" => "Link",
1845 "mediaType" => ^object_mediatype
1846 }
1847 ] = attachment["url"]
1848
1849 # Fails if unauthenticated
1850 conn
1851 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1852 |> json_response(403)
1853 end
1854 end
1855
1856 test "pinned collection", %{conn: conn} do
1857 clear_config([:instance, :max_pinned_statuses], 2)
1858 user = insert(:user)
1859 objects = insert_list(2, :note, user: user)
1860
1861 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
1862 {:ok, updated} = User.add_pinned_object_id(user, object_id)
1863 updated
1864 end)
1865
1866 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
1867 refresh_record(user)
1868
1869 %{"id" => ^featured_address, "orderedItems" => items} =
1870 conn
1871 |> get("/users/#{nickname}/collections/featured")
1872 |> json_response(200)
1873
1874 object_ids = Enum.map(items, & &1["id"])
1875
1876 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
1877 obj_id in object_ids
1878 end)
1879 end
1880 end