Merge branch 'feature/1739-account-endpoints' 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 @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 paginates correctly", %{conn: conn} do
808 user = insert(:user)
809 conn = assign(conn, :user, user)
810 outbox_endpoint = user.ap_id <> "/outbox"
811
812 _posts =
813 for i <- 0..25 do
814 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
815 activity
816 end
817
818 result =
819 conn
820 |> put_req_header("accept", "application/activity+json")
821 |> get(outbox_endpoint <> "?page=true")
822 |> json_response(200)
823
824 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
825 assert length(result["orderedItems"]) == 20
826 assert length(result_ids) == 20
827 assert result["next"]
828 assert String.starts_with?(result["next"], outbox_endpoint)
829
830 result_next =
831 conn
832 |> put_req_header("accept", "application/activity+json")
833 |> get(result["next"])
834 |> json_response(200)
835
836 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
837 assert length(result_next["orderedItems"]) == 6
838 assert length(result_next_ids) == 6
839 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
840 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
841 assert String.starts_with?(result["id"], outbox_endpoint)
842
843 result_next_again =
844 conn
845 |> put_req_header("accept", "application/activity+json")
846 |> get(result_next["id"])
847 |> json_response(200)
848
849 assert result_next == result_next_again
850 end
851
852 test "it returns 200 even if there're no activities", %{conn: conn} do
853 user = insert(:user)
854 outbox_endpoint = user.ap_id <> "/outbox"
855
856 conn =
857 conn
858 |> assign(:user, user)
859 |> put_req_header("accept", "application/activity+json")
860 |> get(outbox_endpoint)
861
862 result = json_response(conn, 200)
863 assert outbox_endpoint == result["id"]
864 end
865
866 test "it returns a note activity in a collection", %{conn: conn} do
867 note_activity = insert(:note_activity)
868 note_object = Object.normalize(note_activity)
869 user = User.get_cached_by_ap_id(note_activity.data["actor"])
870
871 conn =
872 conn
873 |> assign(:user, user)
874 |> put_req_header("accept", "application/activity+json")
875 |> get("/users/#{user.nickname}/outbox?page=true")
876
877 assert response(conn, 200) =~ note_object.data["content"]
878 end
879
880 test "it returns an announce activity in a collection", %{conn: conn} do
881 announce_activity = insert(:announce_activity)
882 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
883
884 conn =
885 conn
886 |> assign(:user, user)
887 |> put_req_header("accept", "application/activity+json")
888 |> get("/users/#{user.nickname}/outbox?page=true")
889
890 assert response(conn, 200) =~ announce_activity.data["object"]
891 end
892
893 test "it requires authentication if instance is NOT federating", %{
894 conn: conn
895 } do
896 user = insert(:user)
897 conn = put_req_header(conn, "accept", "application/activity+json")
898
899 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
900 end
901 end
902
903 describe "POST /users/:nickname/outbox (C2S)" do
904 setup do
905 [
906 activity: %{
907 "@context" => "https://www.w3.org/ns/activitystreams",
908 "type" => "Create",
909 "object" => %{"type" => "Note", "content" => "AP C2S test"},
910 "to" => "https://www.w3.org/ns/activitystreams#Public",
911 "cc" => []
912 }
913 ]
914 end
915
916 test "it rejects posts from other users / unauthenticated users", %{
917 conn: conn,
918 activity: activity
919 } do
920 user = insert(:user)
921 other_user = insert(:user)
922 conn = put_req_header(conn, "content-type", "application/activity+json")
923
924 conn
925 |> post("/users/#{user.nickname}/outbox", activity)
926 |> json_response(403)
927
928 conn
929 |> assign(:user, other_user)
930 |> post("/users/#{user.nickname}/outbox", activity)
931 |> json_response(403)
932 end
933
934 test "it inserts an incoming create activity into the database", %{
935 conn: conn,
936 activity: activity
937 } do
938 user = insert(:user)
939
940 result =
941 conn
942 |> assign(:user, user)
943 |> put_req_header("content-type", "application/activity+json")
944 |> post("/users/#{user.nickname}/outbox", activity)
945 |> json_response(201)
946
947 assert Activity.get_by_ap_id(result["id"])
948 assert result["object"]
949 assert %Object{data: object} = Object.normalize(result["object"])
950 assert object["content"] == activity["object"]["content"]
951 end
952
953 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
954 user = insert(:user)
955
956 activity =
957 activity
958 |> put_in(["object", "type"], "Benis")
959
960 _result =
961 conn
962 |> assign(:user, user)
963 |> put_req_header("content-type", "application/activity+json")
964 |> post("/users/#{user.nickname}/outbox", activity)
965 |> json_response(400)
966 end
967
968 test "it inserts an incoming sensitive activity into the database", %{
969 conn: conn,
970 activity: activity
971 } do
972 user = insert(:user)
973 conn = assign(conn, :user, user)
974 object = Map.put(activity["object"], "sensitive", true)
975 activity = Map.put(activity, "object", object)
976
977 response =
978 conn
979 |> put_req_header("content-type", "application/activity+json")
980 |> post("/users/#{user.nickname}/outbox", activity)
981 |> json_response(201)
982
983 assert Activity.get_by_ap_id(response["id"])
984 assert response["object"]
985 assert %Object{data: response_object} = Object.normalize(response["object"])
986 assert response_object["sensitive"] == true
987 assert response_object["content"] == activity["object"]["content"]
988
989 representation =
990 conn
991 |> put_req_header("accept", "application/activity+json")
992 |> get(response["id"])
993 |> json_response(200)
994
995 assert representation["object"]["sensitive"] == true
996 end
997
998 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
999 user = insert(:user)
1000 activity = Map.put(activity, "type", "BadType")
1001
1002 conn =
1003 conn
1004 |> assign(:user, user)
1005 |> put_req_header("content-type", "application/activity+json")
1006 |> post("/users/#{user.nickname}/outbox", activity)
1007
1008 assert json_response(conn, 400)
1009 end
1010
1011 test "it erects a tombstone when receiving a delete activity", %{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: "Delete",
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["type"] == "Tombstone"
1034 end
1035
1036 test "it rejects delete activity of object from other actor", %{conn: conn} do
1037 note_activity = insert(:note_activity)
1038 note_object = Object.normalize(note_activity)
1039 user = insert(:user)
1040
1041 data = %{
1042 type: "Delete",
1043 object: %{
1044 id: note_object.data["id"]
1045 }
1046 }
1047
1048 conn =
1049 conn
1050 |> assign(:user, user)
1051 |> put_req_header("content-type", "application/activity+json")
1052 |> post("/users/#{user.nickname}/outbox", data)
1053
1054 assert json_response(conn, 400)
1055 end
1056
1057 test "it increases like count when receiving a like action", %{conn: conn} do
1058 note_activity = insert(:note_activity)
1059 note_object = Object.normalize(note_activity)
1060 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1061
1062 data = %{
1063 type: "Like",
1064 object: %{
1065 id: note_object.data["id"]
1066 }
1067 }
1068
1069 conn =
1070 conn
1071 |> assign(:user, user)
1072 |> put_req_header("content-type", "application/activity+json")
1073 |> post("/users/#{user.nickname}/outbox", data)
1074
1075 result = json_response(conn, 201)
1076 assert Activity.get_by_ap_id(result["id"])
1077
1078 assert object = Object.get_by_ap_id(note_object.data["id"])
1079 assert object.data["like_count"] == 1
1080 end
1081 end
1082
1083 describe "/relay/followers" do
1084 test "it returns relay followers", %{conn: conn} do
1085 relay_actor = Relay.get_actor()
1086 user = insert(:user)
1087 User.follow(user, relay_actor)
1088
1089 result =
1090 conn
1091 |> get("/relay/followers")
1092 |> json_response(200)
1093
1094 assert result["first"]["orderedItems"] == [user.ap_id]
1095 end
1096
1097 test "on non-federating instance, it returns 404", %{conn: conn} do
1098 Config.put([:instance, :federating], false)
1099 user = insert(:user)
1100
1101 conn
1102 |> assign(:user, user)
1103 |> get("/relay/followers")
1104 |> json_response(404)
1105 end
1106 end
1107
1108 describe "/relay/following" do
1109 test "it returns relay following", %{conn: conn} do
1110 result =
1111 conn
1112 |> get("/relay/following")
1113 |> json_response(200)
1114
1115 assert result["first"]["orderedItems"] == []
1116 end
1117
1118 test "on non-federating instance, it returns 404", %{conn: conn} do
1119 Config.put([:instance, :federating], false)
1120 user = insert(:user)
1121
1122 conn
1123 |> assign(:user, user)
1124 |> get("/relay/following")
1125 |> json_response(404)
1126 end
1127 end
1128
1129 describe "/users/:nickname/followers" do
1130 test "it returns the followers in a collection", %{conn: conn} do
1131 user = insert(:user)
1132 user_two = insert(:user)
1133 User.follow(user, user_two)
1134
1135 result =
1136 conn
1137 |> assign(:user, user_two)
1138 |> get("/users/#{user_two.nickname}/followers")
1139 |> json_response(200)
1140
1141 assert result["first"]["orderedItems"] == [user.ap_id]
1142 end
1143
1144 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1145 user = insert(:user)
1146 user_two = insert(:user, hide_followers: true)
1147 User.follow(user, user_two)
1148
1149 result =
1150 conn
1151 |> assign(:user, user)
1152 |> get("/users/#{user_two.nickname}/followers")
1153 |> json_response(200)
1154
1155 assert is_binary(result["first"])
1156 end
1157
1158 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1159 %{conn: conn} do
1160 user = insert(:user)
1161 other_user = insert(:user, hide_followers: true)
1162
1163 result =
1164 conn
1165 |> assign(:user, user)
1166 |> get("/users/#{other_user.nickname}/followers?page=1")
1167
1168 assert result.status == 403
1169 assert result.resp_body == ""
1170 end
1171
1172 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1173 %{conn: conn} do
1174 user = insert(:user, hide_followers: true)
1175 other_user = insert(:user)
1176 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1177
1178 result =
1179 conn
1180 |> assign(:user, user)
1181 |> get("/users/#{user.nickname}/followers?page=1")
1182 |> json_response(200)
1183
1184 assert result["totalItems"] == 1
1185 assert result["orderedItems"] == [other_user.ap_id]
1186 end
1187
1188 test "it works for more than 10 users", %{conn: conn} do
1189 user = insert(:user)
1190
1191 Enum.each(1..15, fn _ ->
1192 other_user = insert(:user)
1193 User.follow(other_user, user)
1194 end)
1195
1196 result =
1197 conn
1198 |> assign(:user, user)
1199 |> get("/users/#{user.nickname}/followers")
1200 |> json_response(200)
1201
1202 assert length(result["first"]["orderedItems"]) == 10
1203 assert result["first"]["totalItems"] == 15
1204 assert result["totalItems"] == 15
1205
1206 result =
1207 conn
1208 |> assign(:user, user)
1209 |> get("/users/#{user.nickname}/followers?page=2")
1210 |> json_response(200)
1211
1212 assert length(result["orderedItems"]) == 5
1213 assert result["totalItems"] == 15
1214 end
1215
1216 test "does not require authentication", %{conn: conn} do
1217 user = insert(:user)
1218
1219 conn
1220 |> get("/users/#{user.nickname}/followers")
1221 |> json_response(200)
1222 end
1223 end
1224
1225 describe "/users/:nickname/following" do
1226 test "it returns the following in a collection", %{conn: conn} do
1227 user = insert(:user)
1228 user_two = insert(:user)
1229 User.follow(user, user_two)
1230
1231 result =
1232 conn
1233 |> assign(:user, user)
1234 |> get("/users/#{user.nickname}/following")
1235 |> json_response(200)
1236
1237 assert result["first"]["orderedItems"] == [user_two.ap_id]
1238 end
1239
1240 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1241 user = insert(:user)
1242 user_two = insert(:user, hide_follows: true)
1243 User.follow(user, user_two)
1244
1245 result =
1246 conn
1247 |> assign(:user, user)
1248 |> get("/users/#{user_two.nickname}/following")
1249 |> json_response(200)
1250
1251 assert is_binary(result["first"])
1252 end
1253
1254 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1255 %{conn: conn} do
1256 user = insert(:user)
1257 user_two = insert(:user, hide_follows: true)
1258
1259 result =
1260 conn
1261 |> assign(:user, user)
1262 |> get("/users/#{user_two.nickname}/following?page=1")
1263
1264 assert result.status == 403
1265 assert result.resp_body == ""
1266 end
1267
1268 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1269 %{conn: conn} do
1270 user = insert(:user, hide_follows: true)
1271 other_user = insert(:user)
1272 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1273
1274 result =
1275 conn
1276 |> assign(:user, user)
1277 |> get("/users/#{user.nickname}/following?page=1")
1278 |> json_response(200)
1279
1280 assert result["totalItems"] == 1
1281 assert result["orderedItems"] == [other_user.ap_id]
1282 end
1283
1284 test "it works for more than 10 users", %{conn: conn} do
1285 user = insert(:user)
1286
1287 Enum.each(1..15, fn _ ->
1288 user = User.get_cached_by_id(user.id)
1289 other_user = insert(:user)
1290 User.follow(user, other_user)
1291 end)
1292
1293 result =
1294 conn
1295 |> assign(:user, user)
1296 |> get("/users/#{user.nickname}/following")
1297 |> json_response(200)
1298
1299 assert length(result["first"]["orderedItems"]) == 10
1300 assert result["first"]["totalItems"] == 15
1301 assert result["totalItems"] == 15
1302
1303 result =
1304 conn
1305 |> assign(:user, user)
1306 |> get("/users/#{user.nickname}/following?page=2")
1307 |> json_response(200)
1308
1309 assert length(result["orderedItems"]) == 5
1310 assert result["totalItems"] == 15
1311 end
1312
1313 test "does not require authentication", %{conn: conn} do
1314 user = insert(:user)
1315
1316 conn
1317 |> get("/users/#{user.nickname}/following")
1318 |> json_response(200)
1319 end
1320 end
1321
1322 describe "delivery tracking" do
1323 test "it tracks a signed object fetch", %{conn: conn} do
1324 user = insert(:user, local: false)
1325 activity = insert(:note_activity)
1326 object = Object.normalize(activity)
1327
1328 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1329
1330 conn
1331 |> put_req_header("accept", "application/activity+json")
1332 |> assign(:user, user)
1333 |> get(object_path)
1334 |> json_response(200)
1335
1336 assert Delivery.get(object.id, user.id)
1337 end
1338
1339 test "it tracks a signed activity fetch", %{conn: conn} do
1340 user = insert(:user, local: false)
1341 activity = insert(:note_activity)
1342 object = Object.normalize(activity)
1343
1344 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1345
1346 conn
1347 |> put_req_header("accept", "application/activity+json")
1348 |> assign(:user, user)
1349 |> get(activity_path)
1350 |> json_response(200)
1351
1352 assert Delivery.get(object.id, user.id)
1353 end
1354
1355 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1356 user = insert(:user, local: false)
1357 other_user = insert(:user, local: false)
1358 activity = insert(:note_activity)
1359 object = Object.normalize(activity)
1360
1361 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1362
1363 conn
1364 |> put_req_header("accept", "application/activity+json")
1365 |> assign(:user, user)
1366 |> get(object_path)
1367 |> json_response(200)
1368
1369 build_conn()
1370 |> put_req_header("accept", "application/activity+json")
1371 |> assign(:user, other_user)
1372 |> get(object_path)
1373 |> json_response(200)
1374
1375 assert Delivery.get(object.id, user.id)
1376 assert Delivery.get(object.id, other_user.id)
1377 end
1378
1379 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1380 user = insert(:user, local: false)
1381 other_user = insert(:user, local: false)
1382 activity = insert(:note_activity)
1383 object = Object.normalize(activity)
1384
1385 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1386
1387 conn
1388 |> put_req_header("accept", "application/activity+json")
1389 |> assign(:user, user)
1390 |> get(activity_path)
1391 |> json_response(200)
1392
1393 build_conn()
1394 |> put_req_header("accept", "application/activity+json")
1395 |> assign(:user, other_user)
1396 |> get(activity_path)
1397 |> json_response(200)
1398
1399 assert Delivery.get(object.id, user.id)
1400 assert Delivery.get(object.id, other_user.id)
1401 end
1402 end
1403
1404 describe "Additional ActivityPub C2S endpoints" do
1405 test "GET /api/ap/whoami", %{conn: conn} do
1406 user = insert(:user)
1407
1408 conn =
1409 conn
1410 |> assign(:user, user)
1411 |> get("/api/ap/whoami")
1412
1413 user = User.get_cached_by_id(user.id)
1414
1415 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1416
1417 conn
1418 |> get("/api/ap/whoami")
1419 |> json_response(403)
1420 end
1421
1422 setup do: clear_config([:media_proxy])
1423 setup do: clear_config([Pleroma.Upload])
1424
1425 test "POST /api/ap/upload_media", %{conn: conn} do
1426 user = insert(:user)
1427
1428 desc = "Description of the image"
1429
1430 image = %Plug.Upload{
1431 content_type: "image/jpg",
1432 path: Path.absname("test/fixtures/image.jpg"),
1433 filename: "an_image.jpg"
1434 }
1435
1436 object =
1437 conn
1438 |> assign(:user, user)
1439 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1440 |> json_response(:created)
1441
1442 assert object["name"] == desc
1443 assert object["type"] == "Document"
1444 assert object["actor"] == user.ap_id
1445 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1446 assert is_binary(object_href)
1447 assert object_mediatype == "image/jpeg"
1448
1449 activity_request = %{
1450 "@context" => "https://www.w3.org/ns/activitystreams",
1451 "type" => "Create",
1452 "object" => %{
1453 "type" => "Note",
1454 "content" => "AP C2S test, attachment",
1455 "attachment" => [object]
1456 },
1457 "to" => "https://www.w3.org/ns/activitystreams#Public",
1458 "cc" => []
1459 }
1460
1461 activity_response =
1462 conn
1463 |> assign(:user, user)
1464 |> post("/users/#{user.nickname}/outbox", activity_request)
1465 |> json_response(:created)
1466
1467 assert activity_response["id"]
1468 assert activity_response["object"]
1469 assert activity_response["actor"] == user.ap_id
1470
1471 assert %Object{data: %{"attachment" => [attachment]}} =
1472 Object.normalize(activity_response["object"])
1473
1474 assert attachment["type"] == "Document"
1475 assert attachment["name"] == desc
1476
1477 assert [
1478 %{
1479 "href" => ^object_href,
1480 "type" => "Link",
1481 "mediaType" => ^object_mediatype
1482 }
1483 ] = attachment["url"]
1484
1485 # Fails if unauthenticated
1486 conn
1487 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1488 |> json_response(403)
1489 end
1490 end
1491 end