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