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