utils: Fix maybe_splice_recipient when "object" isn’t a map
[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 "object" => [
1007 reported_user.ap_id,
1008 note.data["object"]
1009 ],
1010 "type" => "Flag"
1011 }
1012
1013 conn
1014 |> assign(:valid_signature, true)
1015 |> put_req_header("content-type", "application/activity+json")
1016 |> post("/users/#{reported_user.nickname}/inbox", data)
1017 |> json_response(200)
1018
1019 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1020
1021 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1022 reported_user_ap_id = reported_user.ap_id
1023
1024 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1025
1026 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1027 ObanHelpers.perform_all()
1028
1029 Swoosh.TestAssertions.assert_email_sent(
1030 to: {admin.name, admin.email},
1031 html_body: ~r/#{note.data["object"]}/i
1032 )
1033 end
1034 end
1035
1036 describe "GET /users/:nickname/outbox" do
1037 test "it paginates correctly", %{conn: conn} do
1038 user = insert(:user)
1039 conn = assign(conn, :user, user)
1040 outbox_endpoint = user.ap_id <> "/outbox"
1041
1042 _posts =
1043 for i <- 0..25 do
1044 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1045 activity
1046 end
1047
1048 result =
1049 conn
1050 |> put_req_header("accept", "application/activity+json")
1051 |> get(outbox_endpoint <> "?page=true")
1052 |> json_response(200)
1053
1054 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1055 assert length(result["orderedItems"]) == 20
1056 assert length(result_ids) == 20
1057 assert result["next"]
1058 assert String.starts_with?(result["next"], outbox_endpoint)
1059
1060 result_next =
1061 conn
1062 |> put_req_header("accept", "application/activity+json")
1063 |> get(result["next"])
1064 |> json_response(200)
1065
1066 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1067 assert length(result_next["orderedItems"]) == 6
1068 assert length(result_next_ids) == 6
1069 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1070 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1071 assert String.starts_with?(result["id"], outbox_endpoint)
1072
1073 result_next_again =
1074 conn
1075 |> put_req_header("accept", "application/activity+json")
1076 |> get(result_next["id"])
1077 |> json_response(200)
1078
1079 assert result_next == result_next_again
1080 end
1081
1082 test "it returns 200 even if there're no activities", %{conn: conn} do
1083 user = insert(:user)
1084 outbox_endpoint = user.ap_id <> "/outbox"
1085
1086 conn =
1087 conn
1088 |> assign(:user, user)
1089 |> put_req_header("accept", "application/activity+json")
1090 |> get(outbox_endpoint)
1091
1092 result = json_response(conn, 200)
1093 assert outbox_endpoint == result["id"]
1094 end
1095
1096 test "it returns a note activity in a collection", %{conn: conn} do
1097 note_activity = insert(:note_activity)
1098 note_object = Object.normalize(note_activity, fetch: false)
1099 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1100
1101 conn =
1102 conn
1103 |> assign(:user, user)
1104 |> put_req_header("accept", "application/activity+json")
1105 |> get("/users/#{user.nickname}/outbox?page=true")
1106
1107 assert response(conn, 200) =~ note_object.data["content"]
1108 end
1109
1110 test "it returns an announce activity in a collection", %{conn: conn} do
1111 announce_activity = insert(:announce_activity)
1112 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1113
1114 conn =
1115 conn
1116 |> assign(:user, user)
1117 |> put_req_header("accept", "application/activity+json")
1118 |> get("/users/#{user.nickname}/outbox?page=true")
1119
1120 assert response(conn, 200) =~ announce_activity.data["object"]
1121 end
1122
1123 test "It returns poll Answers when authenticated", %{conn: conn} do
1124 poller = insert(:user)
1125 voter = insert(:user)
1126
1127 {:ok, activity} =
1128 CommonAPI.post(poller, %{
1129 status: "suya...",
1130 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1131 })
1132
1133 assert question = Object.normalize(activity, fetch: false)
1134
1135 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1136
1137 assert outbox_get =
1138 conn
1139 |> assign(:user, voter)
1140 |> put_req_header("accept", "application/activity+json")
1141 |> get(voter.ap_id <> "/outbox?page=true")
1142 |> json_response(200)
1143
1144 assert [answer_outbox] = outbox_get["orderedItems"]
1145 assert answer_outbox["id"] == activity.data["id"]
1146 end
1147 end
1148
1149 describe "POST /users/:nickname/outbox (C2S)" do
1150 setup do: clear_config([:instance, :limit])
1151
1152 setup do
1153 [
1154 activity: %{
1155 "@context" => "https://www.w3.org/ns/activitystreams",
1156 "type" => "Create",
1157 "object" => %{"type" => "Note", "content" => "AP C2S test"},
1158 "to" => "https://www.w3.org/ns/activitystreams#Public",
1159 "cc" => []
1160 }
1161 ]
1162 end
1163
1164 test "it rejects posts from other users / unauthenticated users", %{
1165 conn: conn,
1166 activity: activity
1167 } do
1168 user = insert(:user)
1169 other_user = insert(:user)
1170 conn = put_req_header(conn, "content-type", "application/activity+json")
1171
1172 conn
1173 |> post("/users/#{user.nickname}/outbox", activity)
1174 |> json_response(403)
1175
1176 conn
1177 |> assign(:user, other_user)
1178 |> post("/users/#{user.nickname}/outbox", activity)
1179 |> json_response(403)
1180 end
1181
1182 test "it inserts an incoming create activity into the database", %{
1183 conn: conn,
1184 activity: activity
1185 } do
1186 user = insert(:user)
1187
1188 result =
1189 conn
1190 |> assign(:user, user)
1191 |> put_req_header("content-type", "application/activity+json")
1192 |> post("/users/#{user.nickname}/outbox", activity)
1193 |> json_response(201)
1194
1195 assert Activity.get_by_ap_id(result["id"])
1196 assert result["object"]
1197 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1198 assert object["content"] == activity["object"]["content"]
1199 end
1200
1201 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1202 user = insert(:user)
1203
1204 activity =
1205 activity
1206 |> put_in(["object", "type"], "Benis")
1207
1208 _result =
1209 conn
1210 |> assign(:user, user)
1211 |> put_req_header("content-type", "application/activity+json")
1212 |> post("/users/#{user.nickname}/outbox", activity)
1213 |> json_response(400)
1214 end
1215
1216 test "it inserts an incoming sensitive activity into the database", %{
1217 conn: conn,
1218 activity: activity
1219 } do
1220 user = insert(:user)
1221 conn = assign(conn, :user, user)
1222 object = Map.put(activity["object"], "sensitive", true)
1223 activity = Map.put(activity, "object", object)
1224
1225 response =
1226 conn
1227 |> put_req_header("content-type", "application/activity+json")
1228 |> post("/users/#{user.nickname}/outbox", activity)
1229 |> json_response(201)
1230
1231 assert Activity.get_by_ap_id(response["id"])
1232 assert response["object"]
1233 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1234 assert response_object["sensitive"] == true
1235 assert response_object["content"] == activity["object"]["content"]
1236
1237 representation =
1238 conn
1239 |> put_req_header("accept", "application/activity+json")
1240 |> get(response["id"])
1241 |> json_response(200)
1242
1243 assert representation["object"]["sensitive"] == true
1244 end
1245
1246 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1247 user = insert(:user)
1248 activity = Map.put(activity, "type", "BadType")
1249
1250 conn =
1251 conn
1252 |> assign(:user, user)
1253 |> put_req_header("content-type", "application/activity+json")
1254 |> post("/users/#{user.nickname}/outbox", activity)
1255
1256 assert json_response(conn, 400)
1257 end
1258
1259 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1260 note_activity = insert(:note_activity)
1261 note_object = Object.normalize(note_activity, fetch: false)
1262 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1263
1264 data = %{
1265 type: "Delete",
1266 object: %{
1267 id: note_object.data["id"]
1268 }
1269 }
1270
1271 conn =
1272 conn
1273 |> assign(:user, user)
1274 |> put_req_header("content-type", "application/activity+json")
1275 |> post("/users/#{user.nickname}/outbox", data)
1276
1277 result = json_response(conn, 201)
1278 assert Activity.get_by_ap_id(result["id"])
1279
1280 assert object = Object.get_by_ap_id(note_object.data["id"])
1281 assert object.data["type"] == "Tombstone"
1282 end
1283
1284 test "it rejects delete activity of object from other actor", %{conn: conn} do
1285 note_activity = insert(:note_activity)
1286 note_object = Object.normalize(note_activity, fetch: false)
1287 user = insert(:user)
1288
1289 data = %{
1290 type: "Delete",
1291 object: %{
1292 id: note_object.data["id"]
1293 }
1294 }
1295
1296 conn =
1297 conn
1298 |> assign(:user, user)
1299 |> put_req_header("content-type", "application/activity+json")
1300 |> post("/users/#{user.nickname}/outbox", data)
1301
1302 assert json_response(conn, 400)
1303 end
1304
1305 test "it increases like count when receiving a like action", %{conn: conn} do
1306 note_activity = insert(:note_activity)
1307 note_object = Object.normalize(note_activity, fetch: false)
1308 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1309
1310 data = %{
1311 type: "Like",
1312 object: %{
1313 id: note_object.data["id"]
1314 }
1315 }
1316
1317 conn =
1318 conn
1319 |> assign(:user, user)
1320 |> put_req_header("content-type", "application/activity+json")
1321 |> post("/users/#{user.nickname}/outbox", data)
1322
1323 result = json_response(conn, 201)
1324 assert Activity.get_by_ap_id(result["id"])
1325
1326 assert object = Object.get_by_ap_id(note_object.data["id"])
1327 assert object.data["like_count"] == 1
1328 end
1329
1330 test "it doesn't spreads faulty attributedTo or actor fields", %{
1331 conn: conn,
1332 activity: activity
1333 } do
1334 reimu = insert(:user, nickname: "reimu")
1335 cirno = insert(:user, nickname: "cirno")
1336
1337 assert reimu.ap_id
1338 assert cirno.ap_id
1339
1340 activity =
1341 activity
1342 |> put_in(["object", "actor"], reimu.ap_id)
1343 |> put_in(["object", "attributedTo"], reimu.ap_id)
1344 |> put_in(["actor"], reimu.ap_id)
1345 |> put_in(["attributedTo"], reimu.ap_id)
1346
1347 _reimu_outbox =
1348 conn
1349 |> assign(:user, cirno)
1350 |> put_req_header("content-type", "application/activity+json")
1351 |> post("/users/#{reimu.nickname}/outbox", activity)
1352 |> json_response(403)
1353
1354 cirno_outbox =
1355 conn
1356 |> assign(:user, cirno)
1357 |> put_req_header("content-type", "application/activity+json")
1358 |> post("/users/#{cirno.nickname}/outbox", activity)
1359 |> json_response(201)
1360
1361 assert cirno_outbox["attributedTo"] == nil
1362 assert cirno_outbox["actor"] == cirno.ap_id
1363
1364 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1365 assert cirno_object.data["actor"] == cirno.ap_id
1366 assert cirno_object.data["attributedTo"] == cirno.ap_id
1367 end
1368
1369 test "Character limitation", %{conn: conn, activity: activity} do
1370 clear_config([:instance, :limit], 5)
1371 user = insert(:user)
1372
1373 result =
1374 conn
1375 |> assign(:user, user)
1376 |> put_req_header("content-type", "application/activity+json")
1377 |> post("/users/#{user.nickname}/outbox", activity)
1378 |> json_response(400)
1379
1380 assert result == "Note is over the character limit"
1381 end
1382 end
1383
1384 describe "/relay/followers" do
1385 test "it returns relay followers", %{conn: conn} do
1386 relay_actor = Relay.get_actor()
1387 user = insert(:user)
1388 User.follow(user, relay_actor)
1389
1390 result =
1391 conn
1392 |> get("/relay/followers")
1393 |> json_response(200)
1394
1395 assert result["first"]["orderedItems"] == [user.ap_id]
1396 end
1397
1398 test "on non-federating instance, it returns 404", %{conn: conn} do
1399 clear_config([:instance, :federating], false)
1400 user = insert(:user)
1401
1402 conn
1403 |> assign(:user, user)
1404 |> get("/relay/followers")
1405 |> json_response(404)
1406 end
1407 end
1408
1409 describe "/relay/following" do
1410 test "it returns relay following", %{conn: conn} do
1411 result =
1412 conn
1413 |> get("/relay/following")
1414 |> json_response(200)
1415
1416 assert result["first"]["orderedItems"] == []
1417 end
1418
1419 test "on non-federating instance, it returns 404", %{conn: conn} do
1420 clear_config([:instance, :federating], false)
1421 user = insert(:user)
1422
1423 conn
1424 |> assign(:user, user)
1425 |> get("/relay/following")
1426 |> json_response(404)
1427 end
1428 end
1429
1430 describe "/users/:nickname/followers" do
1431 test "it returns the followers in a collection", %{conn: conn} do
1432 user = insert(:user)
1433 user_two = insert(:user)
1434 User.follow(user, user_two)
1435
1436 result =
1437 conn
1438 |> assign(:user, user_two)
1439 |> get("/users/#{user_two.nickname}/followers")
1440 |> json_response(200)
1441
1442 assert result["first"]["orderedItems"] == [user.ap_id]
1443 end
1444
1445 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1446 user = insert(:user)
1447 user_two = insert(:user, hide_followers: true)
1448 User.follow(user, user_two)
1449
1450 result =
1451 conn
1452 |> assign(:user, user)
1453 |> get("/users/#{user_two.nickname}/followers")
1454 |> json_response(200)
1455
1456 assert is_binary(result["first"])
1457 end
1458
1459 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1460 %{conn: conn} do
1461 user = insert(:user)
1462 other_user = insert(:user, hide_followers: true)
1463
1464 result =
1465 conn
1466 |> assign(:user, user)
1467 |> get("/users/#{other_user.nickname}/followers?page=1")
1468
1469 assert result.status == 403
1470 assert result.resp_body == ""
1471 end
1472
1473 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1474 %{conn: conn} do
1475 user = insert(:user, hide_followers: true)
1476 other_user = insert(:user)
1477 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1478
1479 result =
1480 conn
1481 |> assign(:user, user)
1482 |> get("/users/#{user.nickname}/followers?page=1")
1483 |> json_response(200)
1484
1485 assert result["totalItems"] == 1
1486 assert result["orderedItems"] == [other_user.ap_id]
1487 end
1488
1489 test "it works for more than 10 users", %{conn: conn} do
1490 user = insert(:user)
1491
1492 Enum.each(1..15, fn _ ->
1493 other_user = insert(:user)
1494 User.follow(other_user, user)
1495 end)
1496
1497 result =
1498 conn
1499 |> assign(:user, user)
1500 |> get("/users/#{user.nickname}/followers")
1501 |> json_response(200)
1502
1503 assert length(result["first"]["orderedItems"]) == 10
1504 assert result["first"]["totalItems"] == 15
1505 assert result["totalItems"] == 15
1506
1507 result =
1508 conn
1509 |> assign(:user, user)
1510 |> get("/users/#{user.nickname}/followers?page=2")
1511 |> json_response(200)
1512
1513 assert length(result["orderedItems"]) == 5
1514 assert result["totalItems"] == 15
1515 end
1516
1517 test "does not require authentication", %{conn: conn} do
1518 user = insert(:user)
1519
1520 conn
1521 |> get("/users/#{user.nickname}/followers")
1522 |> json_response(200)
1523 end
1524 end
1525
1526 describe "/users/:nickname/following" do
1527 test "it returns the following in a collection", %{conn: conn} do
1528 user = insert(:user)
1529 user_two = insert(:user)
1530 User.follow(user, user_two)
1531
1532 result =
1533 conn
1534 |> assign(:user, user)
1535 |> get("/users/#{user.nickname}/following")
1536 |> json_response(200)
1537
1538 assert result["first"]["orderedItems"] == [user_two.ap_id]
1539 end
1540
1541 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1542 user = insert(:user)
1543 user_two = insert(:user, hide_follows: true)
1544 User.follow(user, user_two)
1545
1546 result =
1547 conn
1548 |> assign(:user, user)
1549 |> get("/users/#{user_two.nickname}/following")
1550 |> json_response(200)
1551
1552 assert is_binary(result["first"])
1553 end
1554
1555 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1556 %{conn: conn} do
1557 user = insert(:user)
1558 user_two = insert(:user, hide_follows: true)
1559
1560 result =
1561 conn
1562 |> assign(:user, user)
1563 |> get("/users/#{user_two.nickname}/following?page=1")
1564
1565 assert result.status == 403
1566 assert result.resp_body == ""
1567 end
1568
1569 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1570 %{conn: conn} do
1571 user = insert(:user, hide_follows: true)
1572 other_user = insert(:user)
1573 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1574
1575 result =
1576 conn
1577 |> assign(:user, user)
1578 |> get("/users/#{user.nickname}/following?page=1")
1579 |> json_response(200)
1580
1581 assert result["totalItems"] == 1
1582 assert result["orderedItems"] == [other_user.ap_id]
1583 end
1584
1585 test "it works for more than 10 users", %{conn: conn} do
1586 user = insert(:user)
1587
1588 Enum.each(1..15, fn _ ->
1589 user = User.get_cached_by_id(user.id)
1590 other_user = insert(:user)
1591 User.follow(user, other_user)
1592 end)
1593
1594 result =
1595 conn
1596 |> assign(:user, user)
1597 |> get("/users/#{user.nickname}/following")
1598 |> json_response(200)
1599
1600 assert length(result["first"]["orderedItems"]) == 10
1601 assert result["first"]["totalItems"] == 15
1602 assert result["totalItems"] == 15
1603
1604 result =
1605 conn
1606 |> assign(:user, user)
1607 |> get("/users/#{user.nickname}/following?page=2")
1608 |> json_response(200)
1609
1610 assert length(result["orderedItems"]) == 5
1611 assert result["totalItems"] == 15
1612 end
1613
1614 test "does not require authentication", %{conn: conn} do
1615 user = insert(:user)
1616
1617 conn
1618 |> get("/users/#{user.nickname}/following")
1619 |> json_response(200)
1620 end
1621 end
1622
1623 describe "delivery tracking" do
1624 test "it tracks a signed object fetch", %{conn: conn} do
1625 user = insert(:user, local: false)
1626 activity = insert(:note_activity)
1627 object = Object.normalize(activity, fetch: false)
1628
1629 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1630
1631 conn
1632 |> put_req_header("accept", "application/activity+json")
1633 |> assign(:user, user)
1634 |> get(object_path)
1635 |> json_response(200)
1636
1637 assert Delivery.get(object.id, user.id)
1638 end
1639
1640 test "it tracks a signed activity fetch", %{conn: conn} do
1641 user = insert(:user, local: false)
1642 activity = insert(:note_activity)
1643 object = Object.normalize(activity, fetch: false)
1644
1645 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1646
1647 conn
1648 |> put_req_header("accept", "application/activity+json")
1649 |> assign(:user, user)
1650 |> get(activity_path)
1651 |> json_response(200)
1652
1653 assert Delivery.get(object.id, user.id)
1654 end
1655
1656 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1657 user = insert(:user, local: false)
1658 other_user = insert(:user, local: false)
1659 activity = insert(:note_activity)
1660 object = Object.normalize(activity, fetch: false)
1661
1662 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1663
1664 conn
1665 |> put_req_header("accept", "application/activity+json")
1666 |> assign(:user, user)
1667 |> get(object_path)
1668 |> json_response(200)
1669
1670 build_conn()
1671 |> put_req_header("accept", "application/activity+json")
1672 |> assign(:user, other_user)
1673 |> get(object_path)
1674 |> json_response(200)
1675
1676 assert Delivery.get(object.id, user.id)
1677 assert Delivery.get(object.id, other_user.id)
1678 end
1679
1680 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1681 user = insert(:user, local: false)
1682 other_user = insert(:user, local: false)
1683 activity = insert(:note_activity)
1684 object = Object.normalize(activity, fetch: false)
1685
1686 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1687
1688 conn
1689 |> put_req_header("accept", "application/activity+json")
1690 |> assign(:user, user)
1691 |> get(activity_path)
1692 |> json_response(200)
1693
1694 build_conn()
1695 |> put_req_header("accept", "application/activity+json")
1696 |> assign(:user, other_user)
1697 |> get(activity_path)
1698 |> json_response(200)
1699
1700 assert Delivery.get(object.id, user.id)
1701 assert Delivery.get(object.id, other_user.id)
1702 end
1703 end
1704
1705 describe "Additional ActivityPub C2S endpoints" do
1706 test "GET /api/ap/whoami", %{conn: conn} do
1707 user = insert(:user)
1708
1709 conn =
1710 conn
1711 |> assign(:user, user)
1712 |> get("/api/ap/whoami")
1713
1714 user = User.get_cached_by_id(user.id)
1715
1716 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1717
1718 conn
1719 |> get("/api/ap/whoami")
1720 |> json_response(403)
1721 end
1722
1723 setup do: clear_config([:media_proxy])
1724 setup do: clear_config([Pleroma.Upload])
1725
1726 test "POST /api/ap/upload_media", %{conn: conn} do
1727 user = insert(:user)
1728
1729 desc = "Description of the image"
1730
1731 image = %Plug.Upload{
1732 content_type: "image/jpeg",
1733 path: Path.absname("test/fixtures/image.jpg"),
1734 filename: "an_image.jpg"
1735 }
1736
1737 object =
1738 conn
1739 |> assign(:user, user)
1740 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1741 |> json_response(:created)
1742
1743 assert object["name"] == desc
1744 assert object["type"] == "Document"
1745 assert object["actor"] == user.ap_id
1746 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1747 assert is_binary(object_href)
1748 assert object_mediatype == "image/jpeg"
1749 assert String.ends_with?(object_href, ".jpg")
1750
1751 activity_request = %{
1752 "@context" => "https://www.w3.org/ns/activitystreams",
1753 "type" => "Create",
1754 "object" => %{
1755 "type" => "Note",
1756 "content" => "AP C2S test, attachment",
1757 "attachment" => [object]
1758 },
1759 "to" => "https://www.w3.org/ns/activitystreams#Public",
1760 "cc" => []
1761 }
1762
1763 activity_response =
1764 conn
1765 |> assign(:user, user)
1766 |> post("/users/#{user.nickname}/outbox", activity_request)
1767 |> json_response(:created)
1768
1769 assert activity_response["id"]
1770 assert activity_response["object"]
1771 assert activity_response["actor"] == user.ap_id
1772
1773 assert %Object{data: %{"attachment" => [attachment]}} =
1774 Object.normalize(activity_response["object"], fetch: false)
1775
1776 assert attachment["type"] == "Document"
1777 assert attachment["name"] == desc
1778
1779 assert [
1780 %{
1781 "href" => ^object_href,
1782 "type" => "Link",
1783 "mediaType" => ^object_mediatype
1784 }
1785 ] = attachment["url"]
1786
1787 # Fails if unauthenticated
1788 conn
1789 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1790 |> json_response(403)
1791 end
1792 end
1793 end