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