Add `account_activation_required` to /api/v1/instance
[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 @tag capture_log: true
455 test "it inserts an incoming activity into the database" <>
456 "even if we can't fetch the user but have it in our db",
457 %{conn: conn} do
458 user =
459 insert(:user,
460 ap_id: "https://mastodon.example.org/users/raymoo",
461 ap_enabled: true,
462 local: false,
463 last_refreshed_at: nil
464 )
465
466 data =
467 File.read!("test/fixtures/mastodon-post-activity.json")
468 |> Poison.decode!()
469 |> Map.put("actor", user.ap_id)
470 |> put_in(["object", "attridbutedTo"], user.ap_id)
471
472 conn =
473 conn
474 |> assign(:valid_signature, true)
475 |> put_req_header("content-type", "application/activity+json")
476 |> post("/inbox", data)
477
478 assert "ok" == json_response(conn, 200)
479
480 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
481 assert Activity.get_by_ap_id(data["id"])
482 end
483
484 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
485 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
486
487 sender_url = data["actor"]
488 Instances.set_consistently_unreachable(sender_url)
489 refute Instances.reachable?(sender_url)
490
491 conn =
492 conn
493 |> assign(:valid_signature, true)
494 |> put_req_header("content-type", "application/activity+json")
495 |> post("/inbox", data)
496
497 assert "ok" == json_response(conn, 200)
498 assert Instances.reachable?(sender_url)
499 end
500
501 test "accept follow activity", %{conn: conn} do
502 Pleroma.Config.put([:instance, :federating], true)
503 relay = Relay.get_actor()
504
505 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
506
507 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
508 relay = refresh_record(relay)
509
510 accept =
511 File.read!("test/fixtures/relay/accept-follow.json")
512 |> String.replace("{{ap_id}}", relay.ap_id)
513 |> String.replace("{{activity_id}}", activity.data["id"])
514
515 assert "ok" ==
516 conn
517 |> assign(:valid_signature, true)
518 |> put_req_header("content-type", "application/activity+json")
519 |> post("/inbox", accept)
520 |> json_response(200)
521
522 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
523
524 assert Pleroma.FollowingRelationship.following?(
525 relay,
526 followed_relay
527 )
528
529 Mix.shell(Mix.Shell.Process)
530
531 on_exit(fn ->
532 Mix.shell(Mix.Shell.IO)
533 end)
534
535 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
536 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
537 end
538
539 test "without valid signature, " <>
540 "it only accepts Create activities and requires enabled federation",
541 %{conn: conn} do
542 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
543 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
544
545 conn = put_req_header(conn, "content-type", "application/activity+json")
546
547 Config.put([:instance, :federating], false)
548
549 conn
550 |> post("/inbox", data)
551 |> json_response(403)
552
553 conn
554 |> post("/inbox", non_create_data)
555 |> json_response(403)
556
557 Config.put([:instance, :federating], true)
558
559 ret_conn = post(conn, "/inbox", data)
560 assert "ok" == json_response(ret_conn, 200)
561
562 conn
563 |> post("/inbox", non_create_data)
564 |> json_response(400)
565 end
566 end
567
568 describe "/users/:nickname/inbox" do
569 setup do
570 data =
571 File.read!("test/fixtures/mastodon-post-activity.json")
572 |> Poison.decode!()
573
574 [data: data]
575 end
576
577 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
578 user = insert(:user)
579 data = Map.put(data, "bcc", [user.ap_id])
580
581 conn =
582 conn
583 |> assign(:valid_signature, true)
584 |> put_req_header("content-type", "application/activity+json")
585 |> post("/users/#{user.nickname}/inbox", data)
586
587 assert "ok" == json_response(conn, 200)
588 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
589 assert Activity.get_by_ap_id(data["id"])
590 end
591
592 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
593 user = insert(:user)
594
595 data =
596 Map.put(data, "to", user.ap_id)
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 messages with cc as string instead of array", %{conn: conn, data: data} do
611 user = insert(:user)
612
613 data =
614 Map.put(data, "cc", user.ap_id)
615 |> Map.delete("to")
616
617 conn =
618 conn
619 |> assign(:valid_signature, true)
620 |> put_req_header("content-type", "application/activity+json")
621 |> post("/users/#{user.nickname}/inbox", data)
622
623 assert "ok" == json_response(conn, 200)
624 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
625 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
626 assert user.ap_id in activity.recipients
627 end
628
629 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
630 user = insert(:user)
631
632 data =
633 Map.put(data, "bcc", user.ap_id)
634 |> Map.delete("to")
635 |> Map.delete("cc")
636
637 conn =
638 conn
639 |> assign(:valid_signature, true)
640 |> put_req_header("content-type", "application/activity+json")
641 |> post("/users/#{user.nickname}/inbox", data)
642
643 assert "ok" == json_response(conn, 200)
644 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
645 assert Activity.get_by_ap_id(data["id"])
646 end
647
648 test "it accepts announces with to as string instead of array", %{conn: conn} do
649 user = insert(:user)
650
651 data = %{
652 "@context" => "https://www.w3.org/ns/activitystreams",
653 "actor" => "http://mastodon.example.org/users/admin",
654 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
655 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
656 "to" => "https://www.w3.org/ns/activitystreams#Public",
657 "cc" => [user.ap_id],
658 "type" => "Announce"
659 }
660
661 conn =
662 conn
663 |> assign(:valid_signature, true)
664 |> put_req_header("content-type", "application/activity+json")
665 |> post("/users/#{user.nickname}/inbox", data)
666
667 assert "ok" == json_response(conn, 200)
668 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
669 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
670 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
671 end
672
673 test "it accepts messages from actors that are followed by the user", %{
674 conn: conn,
675 data: data
676 } do
677 recipient = insert(:user)
678 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
679
680 {:ok, recipient} = User.follow(recipient, actor)
681
682 object =
683 data["object"]
684 |> Map.put("attributedTo", actor.ap_id)
685
686 data =
687 data
688 |> Map.put("actor", actor.ap_id)
689 |> Map.put("object", object)
690
691 conn =
692 conn
693 |> assign(:valid_signature, true)
694 |> put_req_header("content-type", "application/activity+json")
695 |> post("/users/#{recipient.nickname}/inbox", data)
696
697 assert "ok" == json_response(conn, 200)
698 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
699 assert Activity.get_by_ap_id(data["id"])
700 end
701
702 test "it rejects reads from other users", %{conn: conn} do
703 user = insert(:user)
704 other_user = insert(:user)
705
706 conn =
707 conn
708 |> assign(:user, other_user)
709 |> put_req_header("accept", "application/activity+json")
710 |> get("/users/#{user.nickname}/inbox")
711
712 assert json_response(conn, 403)
713 end
714
715 test "it returns a note activity in a collection", %{conn: conn} do
716 note_activity = insert(:direct_note_activity)
717 note_object = Object.normalize(note_activity)
718 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
719
720 conn =
721 conn
722 |> assign(:user, user)
723 |> put_req_header("accept", "application/activity+json")
724 |> get("/users/#{user.nickname}/inbox?page=true")
725
726 assert response(conn, 200) =~ note_object.data["content"]
727 end
728
729 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
730 user = insert(:user)
731 data = Map.put(data, "bcc", [user.ap_id])
732
733 sender_host = URI.parse(data["actor"]).host
734 Instances.set_consistently_unreachable(sender_host)
735 refute Instances.reachable?(sender_host)
736
737 conn =
738 conn
739 |> assign(:valid_signature, true)
740 |> put_req_header("content-type", "application/activity+json")
741 |> post("/users/#{user.nickname}/inbox", data)
742
743 assert "ok" == json_response(conn, 200)
744 assert Instances.reachable?(sender_host)
745 end
746
747 test "it removes all follower collections but actor's", %{conn: conn} do
748 [actor, recipient] = insert_pair(:user)
749
750 data =
751 File.read!("test/fixtures/activitypub-client-post-activity.json")
752 |> Poison.decode!()
753
754 object = Map.put(data["object"], "attributedTo", actor.ap_id)
755
756 data =
757 data
758 |> Map.put("id", Utils.generate_object_id())
759 |> Map.put("actor", actor.ap_id)
760 |> Map.put("object", object)
761 |> Map.put("cc", [
762 recipient.follower_address,
763 actor.follower_address
764 ])
765 |> Map.put("to", [
766 recipient.ap_id,
767 recipient.follower_address,
768 "https://www.w3.org/ns/activitystreams#Public"
769 ])
770
771 conn
772 |> assign(:valid_signature, true)
773 |> put_req_header("content-type", "application/activity+json")
774 |> post("/users/#{recipient.nickname}/inbox", data)
775 |> json_response(200)
776
777 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
778
779 activity = Activity.get_by_ap_id(data["id"])
780
781 assert activity.id
782 assert actor.follower_address in activity.recipients
783 assert actor.follower_address in activity.data["cc"]
784
785 refute recipient.follower_address in activity.recipients
786 refute recipient.follower_address in activity.data["cc"]
787 refute recipient.follower_address in activity.data["to"]
788 end
789
790 test "it requires authentication", %{conn: conn} do
791 user = insert(:user)
792 conn = put_req_header(conn, "accept", "application/activity+json")
793
794 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
795 assert json_response(ret_conn, 403)
796
797 ret_conn =
798 conn
799 |> assign(:user, user)
800 |> get("/users/#{user.nickname}/inbox")
801
802 assert json_response(ret_conn, 200)
803 end
804 end
805
806 describe "GET /users/:nickname/outbox" do
807 test "it returns 200 even if there're no activities", %{conn: conn} do
808 user = insert(:user)
809
810 conn =
811 conn
812 |> assign(:user, user)
813 |> put_req_header("accept", "application/activity+json")
814 |> get("/users/#{user.nickname}/outbox")
815
816 result = json_response(conn, 200)
817 assert user.ap_id <> "/outbox" == result["id"]
818 end
819
820 test "it returns a note activity in a collection", %{conn: conn} do
821 note_activity = insert(:note_activity)
822 note_object = Object.normalize(note_activity)
823 user = User.get_cached_by_ap_id(note_activity.data["actor"])
824
825 conn =
826 conn
827 |> assign(:user, user)
828 |> put_req_header("accept", "application/activity+json")
829 |> get("/users/#{user.nickname}/outbox?page=true")
830
831 assert response(conn, 200) =~ note_object.data["content"]
832 end
833
834 test "it returns an announce activity in a collection", %{conn: conn} do
835 announce_activity = insert(:announce_activity)
836 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
837
838 conn =
839 conn
840 |> assign(:user, user)
841 |> put_req_header("accept", "application/activity+json")
842 |> get("/users/#{user.nickname}/outbox?page=true")
843
844 assert response(conn, 200) =~ announce_activity.data["object"]
845 end
846
847 test "it requires authentication if instance is NOT federating", %{
848 conn: conn
849 } do
850 user = insert(:user)
851 conn = put_req_header(conn, "accept", "application/activity+json")
852
853 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
854 end
855 end
856
857 describe "POST /users/:nickname/outbox (C2S)" do
858 setup do
859 [
860 activity: %{
861 "@context" => "https://www.w3.org/ns/activitystreams",
862 "type" => "Create",
863 "object" => %{"type" => "Note", "content" => "AP C2S test"},
864 "to" => "https://www.w3.org/ns/activitystreams#Public",
865 "cc" => []
866 }
867 ]
868 end
869
870 test "it rejects posts from other users / unauthenticated users", %{
871 conn: conn,
872 activity: activity
873 } do
874 user = insert(:user)
875 other_user = insert(:user)
876 conn = put_req_header(conn, "content-type", "application/activity+json")
877
878 conn
879 |> post("/users/#{user.nickname}/outbox", activity)
880 |> json_response(403)
881
882 conn
883 |> assign(:user, other_user)
884 |> post("/users/#{user.nickname}/outbox", activity)
885 |> json_response(403)
886 end
887
888 test "it inserts an incoming create activity into the database", %{
889 conn: conn,
890 activity: activity
891 } do
892 user = insert(:user)
893
894 result =
895 conn
896 |> assign(:user, user)
897 |> put_req_header("content-type", "application/activity+json")
898 |> post("/users/#{user.nickname}/outbox", activity)
899 |> json_response(201)
900
901 assert Activity.get_by_ap_id(result["id"])
902 assert result["object"]
903 assert %Object{data: object} = Object.normalize(result["object"])
904 assert object["content"] == activity["object"]["content"]
905 end
906
907 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
908 user = insert(:user)
909
910 activity =
911 activity
912 |> put_in(["object", "type"], "Benis")
913
914 _result =
915 conn
916 |> assign(:user, user)
917 |> put_req_header("content-type", "application/activity+json")
918 |> post("/users/#{user.nickname}/outbox", activity)
919 |> json_response(400)
920 end
921
922 test "it inserts an incoming sensitive activity into the database", %{
923 conn: conn,
924 activity: activity
925 } do
926 user = insert(:user)
927 conn = assign(conn, :user, user)
928 object = Map.put(activity["object"], "sensitive", true)
929 activity = Map.put(activity, "object", object)
930
931 response =
932 conn
933 |> put_req_header("content-type", "application/activity+json")
934 |> post("/users/#{user.nickname}/outbox", activity)
935 |> json_response(201)
936
937 assert Activity.get_by_ap_id(response["id"])
938 assert response["object"]
939 assert %Object{data: response_object} = Object.normalize(response["object"])
940 assert response_object["sensitive"] == true
941 assert response_object["content"] == activity["object"]["content"]
942
943 representation =
944 conn
945 |> put_req_header("accept", "application/activity+json")
946 |> get(response["id"])
947 |> json_response(200)
948
949 assert representation["object"]["sensitive"] == true
950 end
951
952 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
953 user = insert(:user)
954 activity = Map.put(activity, "type", "BadType")
955
956 conn =
957 conn
958 |> assign(:user, user)
959 |> put_req_header("content-type", "application/activity+json")
960 |> post("/users/#{user.nickname}/outbox", activity)
961
962 assert json_response(conn, 400)
963 end
964
965 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
966 note_activity = insert(:note_activity)
967 note_object = Object.normalize(note_activity)
968 user = User.get_cached_by_ap_id(note_activity.data["actor"])
969
970 data = %{
971 type: "Delete",
972 object: %{
973 id: note_object.data["id"]
974 }
975 }
976
977 conn =
978 conn
979 |> assign(:user, user)
980 |> put_req_header("content-type", "application/activity+json")
981 |> post("/users/#{user.nickname}/outbox", data)
982
983 result = json_response(conn, 201)
984 assert Activity.get_by_ap_id(result["id"])
985
986 assert object = Object.get_by_ap_id(note_object.data["id"])
987 assert object.data["type"] == "Tombstone"
988 end
989
990 test "it rejects delete activity of object from other actor", %{conn: conn} do
991 note_activity = insert(:note_activity)
992 note_object = Object.normalize(note_activity)
993 user = insert(:user)
994
995 data = %{
996 type: "Delete",
997 object: %{
998 id: note_object.data["id"]
999 }
1000 }
1001
1002 conn =
1003 conn
1004 |> assign(:user, user)
1005 |> put_req_header("content-type", "application/activity+json")
1006 |> post("/users/#{user.nickname}/outbox", data)
1007
1008 assert json_response(conn, 400)
1009 end
1010
1011 test "it increases like count when receiving a like action", %{conn: conn} do
1012 note_activity = insert(:note_activity)
1013 note_object = Object.normalize(note_activity)
1014 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1015
1016 data = %{
1017 type: "Like",
1018 object: %{
1019 id: note_object.data["id"]
1020 }
1021 }
1022
1023 conn =
1024 conn
1025 |> assign(:user, user)
1026 |> put_req_header("content-type", "application/activity+json")
1027 |> post("/users/#{user.nickname}/outbox", data)
1028
1029 result = json_response(conn, 201)
1030 assert Activity.get_by_ap_id(result["id"])
1031
1032 assert object = Object.get_by_ap_id(note_object.data["id"])
1033 assert object.data["like_count"] == 1
1034 end
1035 end
1036
1037 describe "/relay/followers" do
1038 test "it returns relay followers", %{conn: conn} do
1039 relay_actor = Relay.get_actor()
1040 user = insert(:user)
1041 User.follow(user, relay_actor)
1042
1043 result =
1044 conn
1045 |> get("/relay/followers")
1046 |> json_response(200)
1047
1048 assert result["first"]["orderedItems"] == [user.ap_id]
1049 end
1050
1051 test "on non-federating instance, it returns 404", %{conn: conn} do
1052 Config.put([:instance, :federating], false)
1053 user = insert(:user)
1054
1055 conn
1056 |> assign(:user, user)
1057 |> get("/relay/followers")
1058 |> json_response(404)
1059 end
1060 end
1061
1062 describe "/relay/following" do
1063 test "it returns relay following", %{conn: conn} do
1064 result =
1065 conn
1066 |> get("/relay/following")
1067 |> json_response(200)
1068
1069 assert result["first"]["orderedItems"] == []
1070 end
1071
1072 test "on non-federating instance, it returns 404", %{conn: conn} do
1073 Config.put([:instance, :federating], false)
1074 user = insert(:user)
1075
1076 conn
1077 |> assign(:user, user)
1078 |> get("/relay/following")
1079 |> json_response(404)
1080 end
1081 end
1082
1083 describe "/users/:nickname/followers" do
1084 test "it returns the followers in a collection", %{conn: conn} do
1085 user = insert(:user)
1086 user_two = insert(:user)
1087 User.follow(user, user_two)
1088
1089 result =
1090 conn
1091 |> assign(:user, user_two)
1092 |> get("/users/#{user_two.nickname}/followers")
1093 |> json_response(200)
1094
1095 assert result["first"]["orderedItems"] == [user.ap_id]
1096 end
1097
1098 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1099 user = insert(:user)
1100 user_two = insert(:user, hide_followers: true)
1101 User.follow(user, user_two)
1102
1103 result =
1104 conn
1105 |> assign(:user, user)
1106 |> get("/users/#{user_two.nickname}/followers")
1107 |> json_response(200)
1108
1109 assert is_binary(result["first"])
1110 end
1111
1112 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1113 %{conn: conn} do
1114 user = insert(:user)
1115 other_user = insert(:user, hide_followers: true)
1116
1117 result =
1118 conn
1119 |> assign(:user, user)
1120 |> get("/users/#{other_user.nickname}/followers?page=1")
1121
1122 assert result.status == 403
1123 assert result.resp_body == ""
1124 end
1125
1126 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1127 %{conn: conn} do
1128 user = insert(:user, hide_followers: true)
1129 other_user = insert(:user)
1130 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1131
1132 result =
1133 conn
1134 |> assign(:user, user)
1135 |> get("/users/#{user.nickname}/followers?page=1")
1136 |> json_response(200)
1137
1138 assert result["totalItems"] == 1
1139 assert result["orderedItems"] == [other_user.ap_id]
1140 end
1141
1142 test "it works for more than 10 users", %{conn: conn} do
1143 user = insert(:user)
1144
1145 Enum.each(1..15, fn _ ->
1146 other_user = insert(:user)
1147 User.follow(other_user, user)
1148 end)
1149
1150 result =
1151 conn
1152 |> assign(:user, user)
1153 |> get("/users/#{user.nickname}/followers")
1154 |> json_response(200)
1155
1156 assert length(result["first"]["orderedItems"]) == 10
1157 assert result["first"]["totalItems"] == 15
1158 assert result["totalItems"] == 15
1159
1160 result =
1161 conn
1162 |> assign(:user, user)
1163 |> get("/users/#{user.nickname}/followers?page=2")
1164 |> json_response(200)
1165
1166 assert length(result["orderedItems"]) == 5
1167 assert result["totalItems"] == 15
1168 end
1169
1170 test "does not require authentication", %{conn: conn} do
1171 user = insert(:user)
1172
1173 conn
1174 |> get("/users/#{user.nickname}/followers")
1175 |> json_response(200)
1176 end
1177 end
1178
1179 describe "/users/:nickname/following" do
1180 test "it returns the following in a collection", %{conn: conn} do
1181 user = insert(:user)
1182 user_two = insert(:user)
1183 User.follow(user, user_two)
1184
1185 result =
1186 conn
1187 |> assign(:user, user)
1188 |> get("/users/#{user.nickname}/following")
1189 |> json_response(200)
1190
1191 assert result["first"]["orderedItems"] == [user_two.ap_id]
1192 end
1193
1194 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1195 user = insert(:user)
1196 user_two = insert(:user, hide_follows: true)
1197 User.follow(user, user_two)
1198
1199 result =
1200 conn
1201 |> assign(:user, user)
1202 |> get("/users/#{user_two.nickname}/following")
1203 |> json_response(200)
1204
1205 assert is_binary(result["first"])
1206 end
1207
1208 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1209 %{conn: conn} do
1210 user = insert(:user)
1211 user_two = insert(:user, hide_follows: true)
1212
1213 result =
1214 conn
1215 |> assign(:user, user)
1216 |> get("/users/#{user_two.nickname}/following?page=1")
1217
1218 assert result.status == 403
1219 assert result.resp_body == ""
1220 end
1221
1222 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1223 %{conn: conn} do
1224 user = insert(:user, hide_follows: true)
1225 other_user = insert(:user)
1226 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1227
1228 result =
1229 conn
1230 |> assign(:user, user)
1231 |> get("/users/#{user.nickname}/following?page=1")
1232 |> json_response(200)
1233
1234 assert result["totalItems"] == 1
1235 assert result["orderedItems"] == [other_user.ap_id]
1236 end
1237
1238 test "it works for more than 10 users", %{conn: conn} do
1239 user = insert(:user)
1240
1241 Enum.each(1..15, fn _ ->
1242 user = User.get_cached_by_id(user.id)
1243 other_user = insert(:user)
1244 User.follow(user, other_user)
1245 end)
1246
1247 result =
1248 conn
1249 |> assign(:user, user)
1250 |> get("/users/#{user.nickname}/following")
1251 |> json_response(200)
1252
1253 assert length(result["first"]["orderedItems"]) == 10
1254 assert result["first"]["totalItems"] == 15
1255 assert result["totalItems"] == 15
1256
1257 result =
1258 conn
1259 |> assign(:user, user)
1260 |> get("/users/#{user.nickname}/following?page=2")
1261 |> json_response(200)
1262
1263 assert length(result["orderedItems"]) == 5
1264 assert result["totalItems"] == 15
1265 end
1266
1267 test "does not require authentication", %{conn: conn} do
1268 user = insert(:user)
1269
1270 conn
1271 |> get("/users/#{user.nickname}/following")
1272 |> json_response(200)
1273 end
1274 end
1275
1276 describe "delivery tracking" do
1277 test "it tracks a signed object fetch", %{conn: conn} do
1278 user = insert(:user, local: false)
1279 activity = insert(:note_activity)
1280 object = Object.normalize(activity)
1281
1282 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1283
1284 conn
1285 |> put_req_header("accept", "application/activity+json")
1286 |> assign(:user, user)
1287 |> get(object_path)
1288 |> json_response(200)
1289
1290 assert Delivery.get(object.id, user.id)
1291 end
1292
1293 test "it tracks a signed activity fetch", %{conn: conn} do
1294 user = insert(:user, local: false)
1295 activity = insert(:note_activity)
1296 object = Object.normalize(activity)
1297
1298 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1299
1300 conn
1301 |> put_req_header("accept", "application/activity+json")
1302 |> assign(:user, user)
1303 |> get(activity_path)
1304 |> json_response(200)
1305
1306 assert Delivery.get(object.id, user.id)
1307 end
1308
1309 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1310 user = insert(:user, local: false)
1311 other_user = insert(:user, local: false)
1312 activity = insert(:note_activity)
1313 object = Object.normalize(activity)
1314
1315 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1316
1317 conn
1318 |> put_req_header("accept", "application/activity+json")
1319 |> assign(:user, user)
1320 |> get(object_path)
1321 |> json_response(200)
1322
1323 build_conn()
1324 |> put_req_header("accept", "application/activity+json")
1325 |> assign(:user, other_user)
1326 |> get(object_path)
1327 |> json_response(200)
1328
1329 assert Delivery.get(object.id, user.id)
1330 assert Delivery.get(object.id, other_user.id)
1331 end
1332
1333 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1334 user = insert(:user, local: false)
1335 other_user = insert(:user, local: false)
1336 activity = insert(:note_activity)
1337 object = Object.normalize(activity)
1338
1339 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1340
1341 conn
1342 |> put_req_header("accept", "application/activity+json")
1343 |> assign(:user, user)
1344 |> get(activity_path)
1345 |> json_response(200)
1346
1347 build_conn()
1348 |> put_req_header("accept", "application/activity+json")
1349 |> assign(:user, other_user)
1350 |> get(activity_path)
1351 |> json_response(200)
1352
1353 assert Delivery.get(object.id, user.id)
1354 assert Delivery.get(object.id, other_user.id)
1355 end
1356 end
1357
1358 describe "Additional ActivityPub C2S endpoints" do
1359 test "GET /api/ap/whoami", %{conn: conn} do
1360 user = insert(:user)
1361
1362 conn =
1363 conn
1364 |> assign(:user, user)
1365 |> get("/api/ap/whoami")
1366
1367 user = User.get_cached_by_id(user.id)
1368
1369 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1370
1371 conn
1372 |> get("/api/ap/whoami")
1373 |> json_response(403)
1374 end
1375
1376 setup do: clear_config([:media_proxy])
1377 setup do: clear_config([Pleroma.Upload])
1378
1379 test "POST /api/ap/upload_media", %{conn: conn} do
1380 user = insert(:user)
1381
1382 desc = "Description of the image"
1383
1384 image = %Plug.Upload{
1385 content_type: "image/jpg",
1386 path: Path.absname("test/fixtures/image.jpg"),
1387 filename: "an_image.jpg"
1388 }
1389
1390 object =
1391 conn
1392 |> assign(:user, user)
1393 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1394 |> json_response(:created)
1395
1396 assert object["name"] == desc
1397 assert object["type"] == "Document"
1398 assert object["actor"] == user.ap_id
1399 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1400 assert is_binary(object_href)
1401 assert object_mediatype == "image/jpeg"
1402
1403 activity_request = %{
1404 "@context" => "https://www.w3.org/ns/activitystreams",
1405 "type" => "Create",
1406 "object" => %{
1407 "type" => "Note",
1408 "content" => "AP C2S test, attachment",
1409 "attachment" => [object]
1410 },
1411 "to" => "https://www.w3.org/ns/activitystreams#Public",
1412 "cc" => []
1413 }
1414
1415 activity_response =
1416 conn
1417 |> assign(:user, user)
1418 |> post("/users/#{user.nickname}/outbox", activity_request)
1419 |> json_response(:created)
1420
1421 assert activity_response["id"]
1422 assert activity_response["object"]
1423 assert activity_response["actor"] == user.ap_id
1424
1425 assert %Object{data: %{"attachment" => [attachment]}} =
1426 Object.normalize(activity_response["object"])
1427
1428 assert attachment["type"] == "Document"
1429 assert attachment["name"] == desc
1430
1431 assert [
1432 %{
1433 "href" => ^object_href,
1434 "type" => "Link",
1435 "mediaType" => ^object_mediatype
1436 }
1437 ] = attachment["url"]
1438
1439 # Fails if unauthenticated
1440 conn
1441 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1442 |> json_response(403)
1443 end
1444 end
1445 end