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