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