Merge branch 'feature/restrict-c2s' into 'develop'
[akkoma] / test / web / activity_pub / activity_pub_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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 import Pleroma.Factory
10 alias Pleroma.Activity
11 alias Pleroma.Config
12 alias Pleroma.Delivery
13 alias Pleroma.Instances
14 alias Pleroma.Object
15 alias Pleroma.Tests.ObanHelpers
16 alias Pleroma.User
17 alias Pleroma.Web.ActivityPub.ObjectView
18 alias Pleroma.Web.ActivityPub.Relay
19 alias Pleroma.Web.ActivityPub.UserView
20 alias Pleroma.Web.ActivityPub.Utils
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Workers.ReceiverWorker
23
24 setup_all do
25 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
26 :ok
27 end
28
29 setup do: clear_config([:instance, :federating], true)
30
31 describe "/relay" do
32 setup do: clear_config([:instance, :allow_relay])
33
34 test "with the relay active, it returns the relay user", %{conn: conn} do
35 res =
36 conn
37 |> get(activity_pub_path(conn, :relay))
38 |> json_response(200)
39
40 assert res["id"] =~ "/relay"
41 end
42
43 test "with the relay disabled, it returns 404", %{conn: conn} do
44 Config.put([:instance, :allow_relay], false)
45
46 conn
47 |> get(activity_pub_path(conn, :relay))
48 |> json_response(404)
49 end
50
51 test "on non-federating instance, it returns 404", %{conn: conn} do
52 Config.put([:instance, :federating], false)
53 user = insert(:user)
54
55 conn
56 |> assign(:user, user)
57 |> get(activity_pub_path(conn, :relay))
58 |> json_response(404)
59 end
60 end
61
62 describe "/internal/fetch" do
63 test "it returns the internal fetch user", %{conn: conn} do
64 res =
65 conn
66 |> get(activity_pub_path(conn, :internal_fetch))
67 |> json_response(200)
68
69 assert res["id"] =~ "/fetch"
70 end
71
72 test "on non-federating instance, it returns 404", %{conn: conn} do
73 Config.put([:instance, :federating], false)
74 user = insert(:user)
75
76 conn
77 |> assign(:user, user)
78 |> get(activity_pub_path(conn, :internal_fetch))
79 |> json_response(404)
80 end
81 end
82
83 describe "/users/:nickname" do
84 test "it returns a json representation of the user with accept application/json", %{
85 conn: conn
86 } do
87 user = insert(:user)
88
89 conn =
90 conn
91 |> put_req_header("accept", "application/json")
92 |> get("/users/#{user.nickname}")
93
94 user = User.get_cached_by_id(user.id)
95
96 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
97 end
98
99 test "it returns a json representation of the user with accept application/activity+json", %{
100 conn: conn
101 } do
102 user = insert(:user)
103
104 conn =
105 conn
106 |> put_req_header("accept", "application/activity+json")
107 |> get("/users/#{user.nickname}")
108
109 user = User.get_cached_by_id(user.id)
110
111 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
112 end
113
114 test "it returns a json representation of the user with accept application/ld+json", %{
115 conn: conn
116 } do
117 user = insert(:user)
118
119 conn =
120 conn
121 |> put_req_header(
122 "accept",
123 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
124 )
125 |> get("/users/#{user.nickname}")
126
127 user = User.get_cached_by_id(user.id)
128
129 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
130 end
131
132 test "it returns 404 for remote users", %{
133 conn: conn
134 } do
135 user = insert(:user, local: false, nickname: "remoteuser@example.com")
136
137 conn =
138 conn
139 |> put_req_header("accept", "application/json")
140 |> get("/users/#{user.nickname}.json")
141
142 assert json_response(conn, 404)
143 end
144
145 test "it returns error when user is not found", %{conn: conn} do
146 response =
147 conn
148 |> put_req_header("accept", "application/json")
149 |> get("/users/jimm")
150 |> json_response(404)
151
152 assert response == "Not found"
153 end
154
155 test "it requires authentication if instance is NOT federating", %{
156 conn: conn
157 } do
158 user = insert(:user)
159
160 conn =
161 put_req_header(
162 conn,
163 "accept",
164 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
165 )
166
167 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
168 end
169 end
170
171 describe "/objects/:uuid" do
172 test "it returns a json representation of the object with accept application/json", %{
173 conn: conn
174 } do
175 note = insert(:note)
176 uuid = String.split(note.data["id"], "/") |> List.last()
177
178 conn =
179 conn
180 |> put_req_header("accept", "application/json")
181 |> get("/objects/#{uuid}")
182
183 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
184 end
185
186 test "it returns a json representation of the object with accept application/activity+json",
187 %{conn: conn} do
188 note = insert(:note)
189 uuid = String.split(note.data["id"], "/") |> List.last()
190
191 conn =
192 conn
193 |> put_req_header("accept", "application/activity+json")
194 |> get("/objects/#{uuid}")
195
196 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
197 end
198
199 test "it returns a json representation of the object with accept application/ld+json", %{
200 conn: conn
201 } do
202 note = insert(:note)
203 uuid = String.split(note.data["id"], "/") |> List.last()
204
205 conn =
206 conn
207 |> put_req_header(
208 "accept",
209 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
210 )
211 |> get("/objects/#{uuid}")
212
213 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
214 end
215
216 test "it returns 404 for non-public messages", %{conn: conn} do
217 note = insert(:direct_note)
218 uuid = String.split(note.data["id"], "/") |> List.last()
219
220 conn =
221 conn
222 |> put_req_header("accept", "application/activity+json")
223 |> get("/objects/#{uuid}")
224
225 assert json_response(conn, 404)
226 end
227
228 test "it returns 404 for tombstone objects", %{conn: conn} do
229 tombstone = insert(:tombstone)
230 uuid = String.split(tombstone.data["id"], "/") |> List.last()
231
232 conn =
233 conn
234 |> put_req_header("accept", "application/activity+json")
235 |> get("/objects/#{uuid}")
236
237 assert json_response(conn, 404)
238 end
239
240 test "it caches a response", %{conn: conn} do
241 note = insert(:note)
242 uuid = String.split(note.data["id"], "/") |> List.last()
243
244 conn1 =
245 conn
246 |> put_req_header("accept", "application/activity+json")
247 |> get("/objects/#{uuid}")
248
249 assert json_response(conn1, :ok)
250 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
251
252 conn2 =
253 conn
254 |> put_req_header("accept", "application/activity+json")
255 |> get("/objects/#{uuid}")
256
257 assert json_response(conn1, :ok) == json_response(conn2, :ok)
258 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
259 end
260
261 test "cached purged after object deletion", %{conn: conn} do
262 note = insert(:note)
263 uuid = String.split(note.data["id"], "/") |> List.last()
264
265 conn1 =
266 conn
267 |> put_req_header("accept", "application/activity+json")
268 |> get("/objects/#{uuid}")
269
270 assert json_response(conn1, :ok)
271 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
272
273 Object.delete(note)
274
275 conn2 =
276 conn
277 |> put_req_header("accept", "application/activity+json")
278 |> get("/objects/#{uuid}")
279
280 assert "Not found" == json_response(conn2, :not_found)
281 end
282
283 test "it requires authentication if instance is NOT federating", %{
284 conn: conn
285 } do
286 user = insert(:user)
287 note = insert(:note)
288 uuid = String.split(note.data["id"], "/") |> List.last()
289
290 conn = put_req_header(conn, "accept", "application/activity+json")
291
292 ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
293 end
294 end
295
296 describe "/activities/:uuid" do
297 test "it returns a json representation of the activity", %{conn: conn} do
298 activity = insert(:note_activity)
299 uuid = String.split(activity.data["id"], "/") |> List.last()
300
301 conn =
302 conn
303 |> put_req_header("accept", "application/activity+json")
304 |> get("/activities/#{uuid}")
305
306 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
307 end
308
309 test "it returns 404 for non-public activities", %{conn: conn} do
310 activity = insert(:direct_note_activity)
311 uuid = String.split(activity.data["id"], "/") |> List.last()
312
313 conn =
314 conn
315 |> put_req_header("accept", "application/activity+json")
316 |> get("/activities/#{uuid}")
317
318 assert json_response(conn, 404)
319 end
320
321 test "it caches a response", %{conn: conn} do
322 activity = insert(:note_activity)
323 uuid = String.split(activity.data["id"], "/") |> List.last()
324
325 conn1 =
326 conn
327 |> put_req_header("accept", "application/activity+json")
328 |> get("/activities/#{uuid}")
329
330 assert json_response(conn1, :ok)
331 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
332
333 conn2 =
334 conn
335 |> put_req_header("accept", "application/activity+json")
336 |> get("/activities/#{uuid}")
337
338 assert json_response(conn1, :ok) == json_response(conn2, :ok)
339 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
340 end
341
342 test "cached purged after activity deletion", %{conn: conn} do
343 user = insert(:user)
344 {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"})
345
346 uuid = String.split(activity.data["id"], "/") |> List.last()
347
348 conn1 =
349 conn
350 |> put_req_header("accept", "application/activity+json")
351 |> get("/activities/#{uuid}")
352
353 assert json_response(conn1, :ok)
354 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
355
356 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
357
358 conn2 =
359 conn
360 |> put_req_header("accept", "application/activity+json")
361 |> get("/activities/#{uuid}")
362
363 assert "Not found" == json_response(conn2, :not_found)
364 end
365
366 test "it requires authentication if instance is NOT federating", %{
367 conn: conn
368 } do
369 user = insert(:user)
370 activity = insert(:note_activity)
371 uuid = String.split(activity.data["id"], "/") |> List.last()
372
373 conn = put_req_header(conn, "accept", "application/activity+json")
374
375 ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
376 end
377 end
378
379 describe "/inbox" do
380 test "it inserts an incoming activity into the database", %{conn: conn} do
381 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
382
383 conn =
384 conn
385 |> assign(:valid_signature, true)
386 |> put_req_header("content-type", "application/activity+json")
387 |> post("/inbox", data)
388
389 assert "ok" == json_response(conn, 200)
390
391 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
392 assert Activity.get_by_ap_id(data["id"])
393 end
394
395 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
396 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
397
398 sender_url = data["actor"]
399 Instances.set_consistently_unreachable(sender_url)
400 refute Instances.reachable?(sender_url)
401
402 conn =
403 conn
404 |> assign(:valid_signature, true)
405 |> put_req_header("content-type", "application/activity+json")
406 |> post("/inbox", data)
407
408 assert "ok" == json_response(conn, 200)
409 assert Instances.reachable?(sender_url)
410 end
411
412 test "accept follow activity", %{conn: conn} do
413 Pleroma.Config.put([:instance, :federating], true)
414 relay = Relay.get_actor()
415
416 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
417
418 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
419 relay = refresh_record(relay)
420
421 accept =
422 File.read!("test/fixtures/relay/accept-follow.json")
423 |> String.replace("{{ap_id}}", relay.ap_id)
424 |> String.replace("{{activity_id}}", activity.data["id"])
425
426 assert "ok" ==
427 conn
428 |> assign(:valid_signature, true)
429 |> put_req_header("content-type", "application/activity+json")
430 |> post("/inbox", accept)
431 |> json_response(200)
432
433 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
434
435 assert Pleroma.FollowingRelationship.following?(
436 relay,
437 followed_relay
438 )
439
440 Mix.shell(Mix.Shell.Process)
441
442 on_exit(fn ->
443 Mix.shell(Mix.Shell.IO)
444 end)
445
446 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
447 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
448 end
449
450 test "without valid signature, " <>
451 "it only accepts Create activities and requires enabled federation",
452 %{conn: conn} do
453 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
454 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
455
456 conn = put_req_header(conn, "content-type", "application/activity+json")
457
458 Config.put([:instance, :federating], false)
459
460 conn
461 |> post("/inbox", data)
462 |> json_response(403)
463
464 conn
465 |> post("/inbox", non_create_data)
466 |> json_response(403)
467
468 Config.put([:instance, :federating], true)
469
470 ret_conn = post(conn, "/inbox", data)
471 assert "ok" == json_response(ret_conn, 200)
472
473 conn
474 |> post("/inbox", non_create_data)
475 |> json_response(400)
476 end
477 end
478
479 describe "/users/:nickname/inbox" do
480 setup do
481 data =
482 File.read!("test/fixtures/mastodon-post-activity.json")
483 |> Poison.decode!()
484
485 [data: data]
486 end
487
488 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
489 user = insert(:user)
490 data = Map.put(data, "bcc", [user.ap_id])
491
492 conn =
493 conn
494 |> assign(:valid_signature, true)
495 |> put_req_header("content-type", "application/activity+json")
496 |> post("/users/#{user.nickname}/inbox", data)
497
498 assert "ok" == json_response(conn, 200)
499 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
500 assert Activity.get_by_ap_id(data["id"])
501 end
502
503 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
504 user = insert(:user)
505
506 data =
507 Map.put(data, "to", user.ap_id)
508 |> Map.delete("cc")
509
510 conn =
511 conn
512 |> assign(:valid_signature, true)
513 |> put_req_header("content-type", "application/activity+json")
514 |> post("/users/#{user.nickname}/inbox", data)
515
516 assert "ok" == json_response(conn, 200)
517 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
518 assert Activity.get_by_ap_id(data["id"])
519 end
520
521 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
522 user = insert(:user)
523
524 data =
525 Map.put(data, "cc", user.ap_id)
526 |> Map.delete("to")
527
528 conn =
529 conn
530 |> assign(:valid_signature, true)
531 |> put_req_header("content-type", "application/activity+json")
532 |> post("/users/#{user.nickname}/inbox", data)
533
534 assert "ok" == json_response(conn, 200)
535 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
536 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
537 assert user.ap_id in activity.recipients
538 end
539
540 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
541 user = insert(:user)
542
543 data =
544 Map.put(data, "bcc", user.ap_id)
545 |> Map.delete("to")
546 |> Map.delete("cc")
547
548 conn =
549 conn
550 |> assign(:valid_signature, true)
551 |> put_req_header("content-type", "application/activity+json")
552 |> post("/users/#{user.nickname}/inbox", data)
553
554 assert "ok" == json_response(conn, 200)
555 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
556 assert Activity.get_by_ap_id(data["id"])
557 end
558
559 test "it accepts announces with to as string instead of array", %{conn: conn} do
560 user = insert(:user)
561
562 data = %{
563 "@context" => "https://www.w3.org/ns/activitystreams",
564 "actor" => "http://mastodon.example.org/users/admin",
565 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
566 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
567 "to" => "https://www.w3.org/ns/activitystreams#Public",
568 "cc" => [user.ap_id],
569 "type" => "Announce"
570 }
571
572 conn =
573 conn
574 |> assign(:valid_signature, true)
575 |> put_req_header("content-type", "application/activity+json")
576 |> post("/users/#{user.nickname}/inbox", data)
577
578 assert "ok" == json_response(conn, 200)
579 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
580 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
581 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
582 end
583
584 test "it accepts messages from actors that are followed by the user", %{
585 conn: conn,
586 data: data
587 } do
588 recipient = insert(:user)
589 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
590
591 {:ok, recipient} = User.follow(recipient, actor)
592
593 object =
594 data["object"]
595 |> Map.put("attributedTo", actor.ap_id)
596
597 data =
598 data
599 |> Map.put("actor", actor.ap_id)
600 |> Map.put("object", object)
601
602 conn =
603 conn
604 |> assign(:valid_signature, true)
605 |> put_req_header("content-type", "application/activity+json")
606 |> post("/users/#{recipient.nickname}/inbox", data)
607
608 assert "ok" == json_response(conn, 200)
609 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
610 assert Activity.get_by_ap_id(data["id"])
611 end
612
613 test "it rejects reads from other users", %{conn: conn} do
614 user = insert(:user)
615 other_user = insert(:user)
616
617 conn =
618 conn
619 |> assign(:user, other_user)
620 |> put_req_header("accept", "application/activity+json")
621 |> get("/users/#{user.nickname}/inbox")
622
623 assert json_response(conn, 403)
624 end
625
626 test "it returns a note activity in a collection", %{conn: conn} do
627 note_activity = insert(:direct_note_activity)
628 note_object = Object.normalize(note_activity)
629 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
630
631 conn =
632 conn
633 |> assign(:user, user)
634 |> put_req_header("accept", "application/activity+json")
635 |> get("/users/#{user.nickname}/inbox?page=true")
636
637 assert response(conn, 200) =~ note_object.data["content"]
638 end
639
640 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
641 user = insert(:user)
642 data = Map.put(data, "bcc", [user.ap_id])
643
644 sender_host = URI.parse(data["actor"]).host
645 Instances.set_consistently_unreachable(sender_host)
646 refute Instances.reachable?(sender_host)
647
648 conn =
649 conn
650 |> assign(:valid_signature, true)
651 |> put_req_header("content-type", "application/activity+json")
652 |> post("/users/#{user.nickname}/inbox", data)
653
654 assert "ok" == json_response(conn, 200)
655 assert Instances.reachable?(sender_host)
656 end
657
658 test "it removes all follower collections but actor's", %{conn: conn} do
659 [actor, recipient] = insert_pair(:user)
660
661 data =
662 File.read!("test/fixtures/activitypub-client-post-activity.json")
663 |> Poison.decode!()
664
665 object = Map.put(data["object"], "attributedTo", actor.ap_id)
666
667 data =
668 data
669 |> Map.put("id", Utils.generate_object_id())
670 |> Map.put("actor", actor.ap_id)
671 |> Map.put("object", object)
672 |> Map.put("cc", [
673 recipient.follower_address,
674 actor.follower_address
675 ])
676 |> Map.put("to", [
677 recipient.ap_id,
678 recipient.follower_address,
679 "https://www.w3.org/ns/activitystreams#Public"
680 ])
681
682 conn
683 |> assign(:valid_signature, true)
684 |> put_req_header("content-type", "application/activity+json")
685 |> post("/users/#{recipient.nickname}/inbox", data)
686 |> json_response(200)
687
688 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
689
690 activity = Activity.get_by_ap_id(data["id"])
691
692 assert activity.id
693 assert actor.follower_address in activity.recipients
694 assert actor.follower_address in activity.data["cc"]
695
696 refute recipient.follower_address in activity.recipients
697 refute recipient.follower_address in activity.data["cc"]
698 refute recipient.follower_address in activity.data["to"]
699 end
700
701 test "it requires authentication", %{conn: conn} do
702 user = insert(:user)
703 conn = put_req_header(conn, "accept", "application/activity+json")
704
705 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
706 assert json_response(ret_conn, 403)
707
708 ret_conn =
709 conn
710 |> assign(:user, user)
711 |> get("/users/#{user.nickname}/inbox")
712
713 assert json_response(ret_conn, 200)
714 end
715 end
716
717 describe "GET /users/:nickname/outbox" do
718 test "it returns 200 even if there're no activities", %{conn: conn} do
719 user = insert(:user)
720
721 conn =
722 conn
723 |> assign(:user, user)
724 |> put_req_header("accept", "application/activity+json")
725 |> get("/users/#{user.nickname}/outbox")
726
727 result = json_response(conn, 200)
728 assert user.ap_id <> "/outbox" == result["id"]
729 end
730
731 test "it returns a note activity in a collection", %{conn: conn} do
732 note_activity = insert(:note_activity)
733 note_object = Object.normalize(note_activity)
734 user = User.get_cached_by_ap_id(note_activity.data["actor"])
735
736 conn =
737 conn
738 |> assign(:user, user)
739 |> put_req_header("accept", "application/activity+json")
740 |> get("/users/#{user.nickname}/outbox?page=true")
741
742 assert response(conn, 200) =~ note_object.data["content"]
743 end
744
745 test "it returns an announce activity in a collection", %{conn: conn} do
746 announce_activity = insert(:announce_activity)
747 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
748
749 conn =
750 conn
751 |> assign(:user, user)
752 |> put_req_header("accept", "application/activity+json")
753 |> get("/users/#{user.nickname}/outbox?page=true")
754
755 assert response(conn, 200) =~ announce_activity.data["object"]
756 end
757
758 test "it requires authentication if instance is NOT federating", %{
759 conn: conn
760 } do
761 user = insert(:user)
762 conn = put_req_header(conn, "accept", "application/activity+json")
763
764 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
765 end
766 end
767
768 describe "POST /users/:nickname/outbox (C2S)" do
769 setup do
770 [
771 activity: %{
772 "@context" => "https://www.w3.org/ns/activitystreams",
773 "type" => "Create",
774 "object" => %{"type" => "Note", "content" => "AP C2S test"},
775 "to" => "https://www.w3.org/ns/activitystreams#Public",
776 "cc" => []
777 }
778 ]
779 end
780
781 test "it rejects posts from other users / unauthenticated users", %{
782 conn: conn,
783 activity: activity
784 } do
785 user = insert(:user)
786 other_user = insert(:user)
787 conn = put_req_header(conn, "content-type", "application/activity+json")
788
789 conn
790 |> post("/users/#{user.nickname}/outbox", activity)
791 |> json_response(403)
792
793 conn
794 |> assign(:user, other_user)
795 |> post("/users/#{user.nickname}/outbox", activity)
796 |> json_response(403)
797 end
798
799 test "it inserts an incoming create activity into the database", %{
800 conn: conn,
801 activity: activity
802 } do
803 user = insert(:user)
804
805 result =
806 conn
807 |> assign(:user, user)
808 |> put_req_header("content-type", "application/activity+json")
809 |> post("/users/#{user.nickname}/outbox", activity)
810 |> json_response(201)
811
812 assert Activity.get_by_ap_id(result["id"])
813 assert result["object"]
814 assert %Object{data: object} = Object.normalize(result["object"])
815 assert object["content"] == activity["object"]["content"]
816 end
817
818 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
819 user = insert(:user)
820
821 activity =
822 activity
823 |> put_in(["object", "type"], "Benis")
824
825 _result =
826 conn
827 |> assign(:user, user)
828 |> put_req_header("content-type", "application/activity+json")
829 |> post("/users/#{user.nickname}/outbox", activity)
830 |> json_response(400)
831 end
832
833 test "it inserts an incoming sensitive activity into the database", %{
834 conn: conn,
835 activity: activity
836 } do
837 user = insert(:user)
838 conn = assign(conn, :user, user)
839 object = Map.put(activity["object"], "sensitive", true)
840 activity = Map.put(activity, "object", object)
841
842 response =
843 conn
844 |> put_req_header("content-type", "application/activity+json")
845 |> post("/users/#{user.nickname}/outbox", activity)
846 |> json_response(201)
847
848 assert Activity.get_by_ap_id(response["id"])
849 assert response["object"]
850 assert %Object{data: response_object} = Object.normalize(response["object"])
851 assert response_object["sensitive"] == true
852 assert response_object["content"] == activity["object"]["content"]
853
854 representation =
855 conn
856 |> put_req_header("accept", "application/activity+json")
857 |> get(response["id"])
858 |> json_response(200)
859
860 assert representation["object"]["sensitive"] == true
861 end
862
863 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
864 user = insert(:user)
865 activity = Map.put(activity, "type", "BadType")
866
867 conn =
868 conn
869 |> assign(:user, user)
870 |> put_req_header("content-type", "application/activity+json")
871 |> post("/users/#{user.nickname}/outbox", activity)
872
873 assert json_response(conn, 400)
874 end
875
876 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
877 note_activity = insert(:note_activity)
878 note_object = Object.normalize(note_activity)
879 user = User.get_cached_by_ap_id(note_activity.data["actor"])
880
881 data = %{
882 type: "Delete",
883 object: %{
884 id: note_object.data["id"]
885 }
886 }
887
888 conn =
889 conn
890 |> assign(:user, user)
891 |> put_req_header("content-type", "application/activity+json")
892 |> post("/users/#{user.nickname}/outbox", data)
893
894 result = json_response(conn, 201)
895 assert Activity.get_by_ap_id(result["id"])
896
897 assert object = Object.get_by_ap_id(note_object.data["id"])
898 assert object.data["type"] == "Tombstone"
899 end
900
901 test "it rejects delete activity of object from other actor", %{conn: conn} do
902 note_activity = insert(:note_activity)
903 note_object = Object.normalize(note_activity)
904 user = insert(:user)
905
906 data = %{
907 type: "Delete",
908 object: %{
909 id: note_object.data["id"]
910 }
911 }
912
913 conn =
914 conn
915 |> assign(:user, user)
916 |> put_req_header("content-type", "application/activity+json")
917 |> post("/users/#{user.nickname}/outbox", data)
918
919 assert json_response(conn, 400)
920 end
921
922 test "it increases like count when receiving a like action", %{conn: conn} do
923 note_activity = insert(:note_activity)
924 note_object = Object.normalize(note_activity)
925 user = User.get_cached_by_ap_id(note_activity.data["actor"])
926
927 data = %{
928 type: "Like",
929 object: %{
930 id: note_object.data["id"]
931 }
932 }
933
934 conn =
935 conn
936 |> assign(:user, user)
937 |> put_req_header("content-type", "application/activity+json")
938 |> post("/users/#{user.nickname}/outbox", data)
939
940 result = json_response(conn, 201)
941 assert Activity.get_by_ap_id(result["id"])
942
943 assert object = Object.get_by_ap_id(note_object.data["id"])
944 assert object.data["like_count"] == 1
945 end
946 end
947
948 describe "/relay/followers" do
949 test "it returns relay followers", %{conn: conn} do
950 relay_actor = Relay.get_actor()
951 user = insert(:user)
952 User.follow(user, relay_actor)
953
954 result =
955 conn
956 |> get("/relay/followers")
957 |> json_response(200)
958
959 assert result["first"]["orderedItems"] == [user.ap_id]
960 end
961
962 test "on non-federating instance, it returns 404", %{conn: conn} do
963 Config.put([:instance, :federating], false)
964 user = insert(:user)
965
966 conn
967 |> assign(:user, user)
968 |> get("/relay/followers")
969 |> json_response(404)
970 end
971 end
972
973 describe "/relay/following" do
974 test "it returns relay following", %{conn: conn} do
975 result =
976 conn
977 |> get("/relay/following")
978 |> json_response(200)
979
980 assert result["first"]["orderedItems"] == []
981 end
982
983 test "on non-federating instance, it returns 404", %{conn: conn} do
984 Config.put([:instance, :federating], false)
985 user = insert(:user)
986
987 conn
988 |> assign(:user, user)
989 |> get("/relay/following")
990 |> json_response(404)
991 end
992 end
993
994 describe "/users/:nickname/followers" do
995 test "it returns the followers in a collection", %{conn: conn} do
996 user = insert(:user)
997 user_two = insert(:user)
998 User.follow(user, user_two)
999
1000 result =
1001 conn
1002 |> assign(:user, user_two)
1003 |> get("/users/#{user_two.nickname}/followers")
1004 |> json_response(200)
1005
1006 assert result["first"]["orderedItems"] == [user.ap_id]
1007 end
1008
1009 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1010 user = insert(:user)
1011 user_two = insert(:user, hide_followers: true)
1012 User.follow(user, user_two)
1013
1014 result =
1015 conn
1016 |> assign(:user, user)
1017 |> get("/users/#{user_two.nickname}/followers")
1018 |> json_response(200)
1019
1020 assert is_binary(result["first"])
1021 end
1022
1023 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1024 %{conn: conn} do
1025 user = insert(:user)
1026 other_user = insert(:user, hide_followers: true)
1027
1028 result =
1029 conn
1030 |> assign(:user, user)
1031 |> get("/users/#{other_user.nickname}/followers?page=1")
1032
1033 assert result.status == 403
1034 assert result.resp_body == ""
1035 end
1036
1037 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1038 %{conn: conn} do
1039 user = insert(:user, hide_followers: true)
1040 other_user = insert(:user)
1041 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1042
1043 result =
1044 conn
1045 |> assign(:user, user)
1046 |> get("/users/#{user.nickname}/followers?page=1")
1047 |> json_response(200)
1048
1049 assert result["totalItems"] == 1
1050 assert result["orderedItems"] == [other_user.ap_id]
1051 end
1052
1053 test "it works for more than 10 users", %{conn: conn} do
1054 user = insert(:user)
1055
1056 Enum.each(1..15, fn _ ->
1057 other_user = insert(:user)
1058 User.follow(other_user, user)
1059 end)
1060
1061 result =
1062 conn
1063 |> assign(:user, user)
1064 |> get("/users/#{user.nickname}/followers")
1065 |> json_response(200)
1066
1067 assert length(result["first"]["orderedItems"]) == 10
1068 assert result["first"]["totalItems"] == 15
1069 assert result["totalItems"] == 15
1070
1071 result =
1072 conn
1073 |> assign(:user, user)
1074 |> get("/users/#{user.nickname}/followers?page=2")
1075 |> json_response(200)
1076
1077 assert length(result["orderedItems"]) == 5
1078 assert result["totalItems"] == 15
1079 end
1080
1081 test "does not require authentication", %{conn: conn} do
1082 user = insert(:user)
1083
1084 conn
1085 |> get("/users/#{user.nickname}/followers")
1086 |> json_response(200)
1087 end
1088 end
1089
1090 describe "/users/:nickname/following" do
1091 test "it returns the following in a collection", %{conn: conn} do
1092 user = insert(:user)
1093 user_two = insert(:user)
1094 User.follow(user, user_two)
1095
1096 result =
1097 conn
1098 |> assign(:user, user)
1099 |> get("/users/#{user.nickname}/following")
1100 |> json_response(200)
1101
1102 assert result["first"]["orderedItems"] == [user_two.ap_id]
1103 end
1104
1105 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1106 user = insert(:user)
1107 user_two = insert(:user, hide_follows: true)
1108 User.follow(user, user_two)
1109
1110 result =
1111 conn
1112 |> assign(:user, user)
1113 |> get("/users/#{user_two.nickname}/following")
1114 |> json_response(200)
1115
1116 assert is_binary(result["first"])
1117 end
1118
1119 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1120 %{conn: conn} do
1121 user = insert(:user)
1122 user_two = insert(:user, hide_follows: true)
1123
1124 result =
1125 conn
1126 |> assign(:user, user)
1127 |> get("/users/#{user_two.nickname}/following?page=1")
1128
1129 assert result.status == 403
1130 assert result.resp_body == ""
1131 end
1132
1133 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1134 %{conn: conn} do
1135 user = insert(:user, hide_follows: true)
1136 other_user = insert(:user)
1137 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1138
1139 result =
1140 conn
1141 |> assign(:user, user)
1142 |> get("/users/#{user.nickname}/following?page=1")
1143 |> json_response(200)
1144
1145 assert result["totalItems"] == 1
1146 assert result["orderedItems"] == [other_user.ap_id]
1147 end
1148
1149 test "it works for more than 10 users", %{conn: conn} do
1150 user = insert(:user)
1151
1152 Enum.each(1..15, fn _ ->
1153 user = User.get_cached_by_id(user.id)
1154 other_user = insert(:user)
1155 User.follow(user, other_user)
1156 end)
1157
1158 result =
1159 conn
1160 |> assign(:user, user)
1161 |> get("/users/#{user.nickname}/following")
1162 |> json_response(200)
1163
1164 assert length(result["first"]["orderedItems"]) == 10
1165 assert result["first"]["totalItems"] == 15
1166 assert result["totalItems"] == 15
1167
1168 result =
1169 conn
1170 |> assign(:user, user)
1171 |> get("/users/#{user.nickname}/following?page=2")
1172 |> json_response(200)
1173
1174 assert length(result["orderedItems"]) == 5
1175 assert result["totalItems"] == 15
1176 end
1177
1178 test "does not require authentication", %{conn: conn} do
1179 user = insert(:user)
1180
1181 conn
1182 |> get("/users/#{user.nickname}/following")
1183 |> json_response(200)
1184 end
1185 end
1186
1187 describe "delivery tracking" do
1188 test "it tracks a signed object fetch", %{conn: conn} do
1189 user = insert(:user, local: false)
1190 activity = insert(:note_activity)
1191 object = Object.normalize(activity)
1192
1193 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1194
1195 conn
1196 |> put_req_header("accept", "application/activity+json")
1197 |> assign(:user, user)
1198 |> get(object_path)
1199 |> json_response(200)
1200
1201 assert Delivery.get(object.id, user.id)
1202 end
1203
1204 test "it tracks a signed activity fetch", %{conn: conn} do
1205 user = insert(:user, local: false)
1206 activity = insert(:note_activity)
1207 object = Object.normalize(activity)
1208
1209 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1210
1211 conn
1212 |> put_req_header("accept", "application/activity+json")
1213 |> assign(:user, user)
1214 |> get(activity_path)
1215 |> json_response(200)
1216
1217 assert Delivery.get(object.id, user.id)
1218 end
1219
1220 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1221 user = insert(:user, local: false)
1222 other_user = insert(:user, local: false)
1223 activity = insert(:note_activity)
1224 object = Object.normalize(activity)
1225
1226 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1227
1228 conn
1229 |> put_req_header("accept", "application/activity+json")
1230 |> assign(:user, user)
1231 |> get(object_path)
1232 |> json_response(200)
1233
1234 build_conn()
1235 |> put_req_header("accept", "application/activity+json")
1236 |> assign(:user, other_user)
1237 |> get(object_path)
1238 |> json_response(200)
1239
1240 assert Delivery.get(object.id, user.id)
1241 assert Delivery.get(object.id, other_user.id)
1242 end
1243
1244 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1245 user = insert(:user, local: false)
1246 other_user = insert(:user, local: false)
1247 activity = insert(:note_activity)
1248 object = Object.normalize(activity)
1249
1250 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1251
1252 conn
1253 |> put_req_header("accept", "application/activity+json")
1254 |> assign(:user, user)
1255 |> get(activity_path)
1256 |> json_response(200)
1257
1258 build_conn()
1259 |> put_req_header("accept", "application/activity+json")
1260 |> assign(:user, other_user)
1261 |> get(activity_path)
1262 |> json_response(200)
1263
1264 assert Delivery.get(object.id, user.id)
1265 assert Delivery.get(object.id, other_user.id)
1266 end
1267 end
1268
1269 describe "Additional ActivityPub C2S endpoints" do
1270 test "GET /api/ap/whoami", %{conn: conn} do
1271 user = insert(:user)
1272
1273 conn =
1274 conn
1275 |> assign(:user, user)
1276 |> get("/api/ap/whoami")
1277
1278 user = User.get_cached_by_id(user.id)
1279
1280 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1281
1282 conn
1283 |> get("/api/ap/whoami")
1284 |> json_response(403)
1285 end
1286
1287 setup do: clear_config([:media_proxy])
1288 setup do: clear_config([Pleroma.Upload])
1289
1290 test "POST /api/ap/upload_media", %{conn: conn} do
1291 user = insert(:user)
1292
1293 desc = "Description of the image"
1294
1295 image = %Plug.Upload{
1296 content_type: "image/jpg",
1297 path: Path.absname("test/fixtures/image.jpg"),
1298 filename: "an_image.jpg"
1299 }
1300
1301 object =
1302 conn
1303 |> assign(:user, user)
1304 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1305 |> json_response(:created)
1306
1307 assert object["name"] == desc
1308 assert object["type"] == "Document"
1309 assert object["actor"] == user.ap_id
1310 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1311 assert is_binary(object_href)
1312 assert object_mediatype == "image/jpeg"
1313
1314 activity_request = %{
1315 "@context" => "https://www.w3.org/ns/activitystreams",
1316 "type" => "Create",
1317 "object" => %{
1318 "type" => "Note",
1319 "content" => "AP C2S test, attachment",
1320 "attachment" => [object]
1321 },
1322 "to" => "https://www.w3.org/ns/activitystreams#Public",
1323 "cc" => []
1324 }
1325
1326 activity_response =
1327 conn
1328 |> assign(:user, user)
1329 |> post("/users/#{user.nickname}/outbox", activity_request)
1330 |> json_response(:created)
1331
1332 assert activity_response["id"]
1333 assert activity_response["object"]
1334 assert activity_response["actor"] == user.ap_id
1335
1336 assert %Object{data: %{"attachment" => [attachment]}} =
1337 Object.normalize(activity_response["object"])
1338
1339 assert attachment["type"] == "Document"
1340 assert attachment["name"] == desc
1341
1342 assert [
1343 %{
1344 "href" => ^object_href,
1345 "type" => "Link",
1346 "mediaType" => ^object_mediatype
1347 }
1348 ] = attachment["url"]
1349
1350 # Fails if unauthenticated
1351 conn
1352 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1353 |> json_response(403)
1354 end
1355 end
1356 end