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