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