ActivityPubControllerTest: Testing changes.
[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 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
652 announcer = insert(:user, local: false)
653
654 data = %{
655 "@context" => "https://www.w3.org/ns/activitystreams",
656 "actor" => announcer.ap_id,
657 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
658 "object" => post.data["object"],
659 "to" => "https://www.w3.org/ns/activitystreams#Public",
660 "cc" => [user.ap_id],
661 "type" => "Announce"
662 }
663
664 conn =
665 conn
666 |> assign(:valid_signature, true)
667 |> put_req_header("content-type", "application/activity+json")
668 |> post("/users/#{user.nickname}/inbox", data)
669
670 assert "ok" == json_response(conn, 200)
671 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
672 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
673 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
674 end
675
676 test "it accepts messages from actors that are followed by the user", %{
677 conn: conn,
678 data: data
679 } do
680 recipient = insert(:user)
681 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
682
683 {:ok, recipient} = User.follow(recipient, actor)
684
685 object =
686 data["object"]
687 |> Map.put("attributedTo", actor.ap_id)
688
689 data =
690 data
691 |> Map.put("actor", actor.ap_id)
692 |> Map.put("object", object)
693
694 conn =
695 conn
696 |> assign(:valid_signature, true)
697 |> put_req_header("content-type", "application/activity+json")
698 |> post("/users/#{recipient.nickname}/inbox", data)
699
700 assert "ok" == json_response(conn, 200)
701 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
702 assert Activity.get_by_ap_id(data["id"])
703 end
704
705 test "it rejects reads from other users", %{conn: conn} do
706 user = insert(:user)
707 other_user = insert(:user)
708
709 conn =
710 conn
711 |> assign(:user, other_user)
712 |> put_req_header("accept", "application/activity+json")
713 |> get("/users/#{user.nickname}/inbox")
714
715 assert json_response(conn, 403)
716 end
717
718 test "it returns a note activity in a collection", %{conn: conn} do
719 note_activity = insert(:direct_note_activity)
720 note_object = Object.normalize(note_activity)
721 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
722
723 conn =
724 conn
725 |> assign(:user, user)
726 |> put_req_header("accept", "application/activity+json")
727 |> get("/users/#{user.nickname}/inbox?page=true")
728
729 assert response(conn, 200) =~ note_object.data["content"]
730 end
731
732 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
733 user = insert(:user)
734 data = Map.put(data, "bcc", [user.ap_id])
735
736 sender_host = URI.parse(data["actor"]).host
737 Instances.set_consistently_unreachable(sender_host)
738 refute Instances.reachable?(sender_host)
739
740 conn =
741 conn
742 |> assign(:valid_signature, true)
743 |> put_req_header("content-type", "application/activity+json")
744 |> post("/users/#{user.nickname}/inbox", data)
745
746 assert "ok" == json_response(conn, 200)
747 assert Instances.reachable?(sender_host)
748 end
749
750 test "it removes all follower collections but actor's", %{conn: conn} do
751 [actor, recipient] = insert_pair(:user)
752
753 data =
754 File.read!("test/fixtures/activitypub-client-post-activity.json")
755 |> Poison.decode!()
756
757 object = Map.put(data["object"], "attributedTo", actor.ap_id)
758
759 data =
760 data
761 |> Map.put("id", Utils.generate_object_id())
762 |> Map.put("actor", actor.ap_id)
763 |> Map.put("object", object)
764 |> Map.put("cc", [
765 recipient.follower_address,
766 actor.follower_address
767 ])
768 |> Map.put("to", [
769 recipient.ap_id,
770 recipient.follower_address,
771 "https://www.w3.org/ns/activitystreams#Public"
772 ])
773
774 conn
775 |> assign(:valid_signature, true)
776 |> put_req_header("content-type", "application/activity+json")
777 |> post("/users/#{recipient.nickname}/inbox", data)
778 |> json_response(200)
779
780 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
781
782 activity = Activity.get_by_ap_id(data["id"])
783
784 assert activity.id
785 assert actor.follower_address in activity.recipients
786 assert actor.follower_address in activity.data["cc"]
787
788 refute recipient.follower_address in activity.recipients
789 refute recipient.follower_address in activity.data["cc"]
790 refute recipient.follower_address in activity.data["to"]
791 end
792
793 test "it requires authentication", %{conn: conn} do
794 user = insert(:user)
795 conn = put_req_header(conn, "accept", "application/activity+json")
796
797 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
798 assert json_response(ret_conn, 403)
799
800 ret_conn =
801 conn
802 |> assign(:user, user)
803 |> get("/users/#{user.nickname}/inbox")
804
805 assert json_response(ret_conn, 200)
806 end
807 end
808
809 describe "GET /users/:nickname/outbox" do
810 test "it paginates correctly", %{conn: conn} do
811 user = insert(:user)
812 conn = assign(conn, :user, user)
813 outbox_endpoint = user.ap_id <> "/outbox"
814
815 _posts =
816 for i <- 0..25 do
817 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
818 activity
819 end
820
821 result =
822 conn
823 |> put_req_header("accept", "application/activity+json")
824 |> get(outbox_endpoint <> "?page=true")
825 |> json_response(200)
826
827 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
828 assert length(result["orderedItems"]) == 20
829 assert length(result_ids) == 20
830 assert result["next"]
831 assert String.starts_with?(result["next"], outbox_endpoint)
832
833 result_next =
834 conn
835 |> put_req_header("accept", "application/activity+json")
836 |> get(result["next"])
837 |> json_response(200)
838
839 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
840 assert length(result_next["orderedItems"]) == 6
841 assert length(result_next_ids) == 6
842 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
843 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
844 assert String.starts_with?(result["id"], outbox_endpoint)
845
846 result_next_again =
847 conn
848 |> put_req_header("accept", "application/activity+json")
849 |> get(result_next["id"])
850 |> json_response(200)
851
852 assert result_next == result_next_again
853 end
854
855 test "it returns 200 even if there're no activities", %{conn: conn} do
856 user = insert(:user)
857 outbox_endpoint = user.ap_id <> "/outbox"
858
859 conn =
860 conn
861 |> assign(:user, user)
862 |> put_req_header("accept", "application/activity+json")
863 |> get(outbox_endpoint)
864
865 result = json_response(conn, 200)
866 assert outbox_endpoint == result["id"]
867 end
868
869 test "it returns a note activity in a collection", %{conn: conn} do
870 note_activity = insert(:note_activity)
871 note_object = Object.normalize(note_activity)
872 user = User.get_cached_by_ap_id(note_activity.data["actor"])
873
874 conn =
875 conn
876 |> assign(:user, user)
877 |> put_req_header("accept", "application/activity+json")
878 |> get("/users/#{user.nickname}/outbox?page=true")
879
880 assert response(conn, 200) =~ note_object.data["content"]
881 end
882
883 test "it returns an announce activity in a collection", %{conn: conn} do
884 announce_activity = insert(:announce_activity)
885 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
886
887 conn =
888 conn
889 |> assign(:user, user)
890 |> put_req_header("accept", "application/activity+json")
891 |> get("/users/#{user.nickname}/outbox?page=true")
892
893 assert response(conn, 200) =~ announce_activity.data["object"]
894 end
895
896 test "it requires authentication if instance is NOT federating", %{
897 conn: conn
898 } do
899 user = insert(:user)
900 conn = put_req_header(conn, "accept", "application/activity+json")
901
902 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
903 end
904 end
905
906 describe "POST /users/:nickname/outbox (C2S)" do
907 setup do
908 [
909 activity: %{
910 "@context" => "https://www.w3.org/ns/activitystreams",
911 "type" => "Create",
912 "object" => %{"type" => "Note", "content" => "AP C2S test"},
913 "to" => "https://www.w3.org/ns/activitystreams#Public",
914 "cc" => []
915 }
916 ]
917 end
918
919 test "it rejects posts from other users / unauthenticated users", %{
920 conn: conn,
921 activity: activity
922 } do
923 user = insert(:user)
924 other_user = insert(:user)
925 conn = put_req_header(conn, "content-type", "application/activity+json")
926
927 conn
928 |> post("/users/#{user.nickname}/outbox", activity)
929 |> json_response(403)
930
931 conn
932 |> assign(:user, other_user)
933 |> post("/users/#{user.nickname}/outbox", activity)
934 |> json_response(403)
935 end
936
937 test "it inserts an incoming create activity into the database", %{
938 conn: conn,
939 activity: activity
940 } do
941 user = insert(:user)
942
943 result =
944 conn
945 |> assign(:user, user)
946 |> put_req_header("content-type", "application/activity+json")
947 |> post("/users/#{user.nickname}/outbox", activity)
948 |> json_response(201)
949
950 assert Activity.get_by_ap_id(result["id"])
951 assert result["object"]
952 assert %Object{data: object} = Object.normalize(result["object"])
953 assert object["content"] == activity["object"]["content"]
954 end
955
956 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
957 user = insert(:user)
958
959 activity =
960 activity
961 |> put_in(["object", "type"], "Benis")
962
963 _result =
964 conn
965 |> assign(:user, user)
966 |> put_req_header("content-type", "application/activity+json")
967 |> post("/users/#{user.nickname}/outbox", activity)
968 |> json_response(400)
969 end
970
971 test "it inserts an incoming sensitive activity into the database", %{
972 conn: conn,
973 activity: activity
974 } do
975 user = insert(:user)
976 conn = assign(conn, :user, user)
977 object = Map.put(activity["object"], "sensitive", true)
978 activity = Map.put(activity, "object", object)
979
980 response =
981 conn
982 |> put_req_header("content-type", "application/activity+json")
983 |> post("/users/#{user.nickname}/outbox", activity)
984 |> json_response(201)
985
986 assert Activity.get_by_ap_id(response["id"])
987 assert response["object"]
988 assert %Object{data: response_object} = Object.normalize(response["object"])
989 assert response_object["sensitive"] == true
990 assert response_object["content"] == activity["object"]["content"]
991
992 representation =
993 conn
994 |> put_req_header("accept", "application/activity+json")
995 |> get(response["id"])
996 |> json_response(200)
997
998 assert representation["object"]["sensitive"] == true
999 end
1000
1001 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1002 user = insert(:user)
1003 activity = Map.put(activity, "type", "BadType")
1004
1005 conn =
1006 conn
1007 |> assign(:user, user)
1008 |> put_req_header("content-type", "application/activity+json")
1009 |> post("/users/#{user.nickname}/outbox", activity)
1010
1011 assert json_response(conn, 400)
1012 end
1013
1014 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1015 note_activity = insert(:note_activity)
1016 note_object = Object.normalize(note_activity)
1017 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1018
1019 data = %{
1020 type: "Delete",
1021 object: %{
1022 id: note_object.data["id"]
1023 }
1024 }
1025
1026 conn =
1027 conn
1028 |> assign(:user, user)
1029 |> put_req_header("content-type", "application/activity+json")
1030 |> post("/users/#{user.nickname}/outbox", data)
1031
1032 result = json_response(conn, 201)
1033 assert Activity.get_by_ap_id(result["id"])
1034
1035 assert object = Object.get_by_ap_id(note_object.data["id"])
1036 assert object.data["type"] == "Tombstone"
1037 end
1038
1039 test "it rejects delete activity of object from other actor", %{conn: conn} do
1040 note_activity = insert(:note_activity)
1041 note_object = Object.normalize(note_activity)
1042 user = insert(:user)
1043
1044 data = %{
1045 type: "Delete",
1046 object: %{
1047 id: note_object.data["id"]
1048 }
1049 }
1050
1051 conn =
1052 conn
1053 |> assign(:user, user)
1054 |> put_req_header("content-type", "application/activity+json")
1055 |> post("/users/#{user.nickname}/outbox", data)
1056
1057 assert json_response(conn, 400)
1058 end
1059
1060 test "it increases like count when receiving a like action", %{conn: conn} do
1061 note_activity = insert(:note_activity)
1062 note_object = Object.normalize(note_activity)
1063 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1064
1065 data = %{
1066 type: "Like",
1067 object: %{
1068 id: note_object.data["id"]
1069 }
1070 }
1071
1072 conn =
1073 conn
1074 |> assign(:user, user)
1075 |> put_req_header("content-type", "application/activity+json")
1076 |> post("/users/#{user.nickname}/outbox", data)
1077
1078 result = json_response(conn, 201)
1079 assert Activity.get_by_ap_id(result["id"])
1080
1081 assert object = Object.get_by_ap_id(note_object.data["id"])
1082 assert object.data["like_count"] == 1
1083 end
1084 end
1085
1086 describe "/relay/followers" do
1087 test "it returns relay followers", %{conn: conn} do
1088 relay_actor = Relay.get_actor()
1089 user = insert(:user)
1090 User.follow(user, relay_actor)
1091
1092 result =
1093 conn
1094 |> get("/relay/followers")
1095 |> json_response(200)
1096
1097 assert result["first"]["orderedItems"] == [user.ap_id]
1098 end
1099
1100 test "on non-federating instance, it returns 404", %{conn: conn} do
1101 Config.put([:instance, :federating], false)
1102 user = insert(:user)
1103
1104 conn
1105 |> assign(:user, user)
1106 |> get("/relay/followers")
1107 |> json_response(404)
1108 end
1109 end
1110
1111 describe "/relay/following" do
1112 test "it returns relay following", %{conn: conn} do
1113 result =
1114 conn
1115 |> get("/relay/following")
1116 |> json_response(200)
1117
1118 assert result["first"]["orderedItems"] == []
1119 end
1120
1121 test "on non-federating instance, it returns 404", %{conn: conn} do
1122 Config.put([:instance, :federating], false)
1123 user = insert(:user)
1124
1125 conn
1126 |> assign(:user, user)
1127 |> get("/relay/following")
1128 |> json_response(404)
1129 end
1130 end
1131
1132 describe "/users/:nickname/followers" do
1133 test "it returns the followers in a collection", %{conn: conn} do
1134 user = insert(:user)
1135 user_two = insert(:user)
1136 User.follow(user, user_two)
1137
1138 result =
1139 conn
1140 |> assign(:user, user_two)
1141 |> get("/users/#{user_two.nickname}/followers")
1142 |> json_response(200)
1143
1144 assert result["first"]["orderedItems"] == [user.ap_id]
1145 end
1146
1147 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1148 user = insert(:user)
1149 user_two = insert(:user, hide_followers: true)
1150 User.follow(user, user_two)
1151
1152 result =
1153 conn
1154 |> assign(:user, user)
1155 |> get("/users/#{user_two.nickname}/followers")
1156 |> json_response(200)
1157
1158 assert is_binary(result["first"])
1159 end
1160
1161 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1162 %{conn: conn} do
1163 user = insert(:user)
1164 other_user = insert(:user, hide_followers: true)
1165
1166 result =
1167 conn
1168 |> assign(:user, user)
1169 |> get("/users/#{other_user.nickname}/followers?page=1")
1170
1171 assert result.status == 403
1172 assert result.resp_body == ""
1173 end
1174
1175 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1176 %{conn: conn} do
1177 user = insert(:user, hide_followers: true)
1178 other_user = insert(:user)
1179 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1180
1181 result =
1182 conn
1183 |> assign(:user, user)
1184 |> get("/users/#{user.nickname}/followers?page=1")
1185 |> json_response(200)
1186
1187 assert result["totalItems"] == 1
1188 assert result["orderedItems"] == [other_user.ap_id]
1189 end
1190
1191 test "it works for more than 10 users", %{conn: conn} do
1192 user = insert(:user)
1193
1194 Enum.each(1..15, fn _ ->
1195 other_user = insert(:user)
1196 User.follow(other_user, user)
1197 end)
1198
1199 result =
1200 conn
1201 |> assign(:user, user)
1202 |> get("/users/#{user.nickname}/followers")
1203 |> json_response(200)
1204
1205 assert length(result["first"]["orderedItems"]) == 10
1206 assert result["first"]["totalItems"] == 15
1207 assert result["totalItems"] == 15
1208
1209 result =
1210 conn
1211 |> assign(:user, user)
1212 |> get("/users/#{user.nickname}/followers?page=2")
1213 |> json_response(200)
1214
1215 assert length(result["orderedItems"]) == 5
1216 assert result["totalItems"] == 15
1217 end
1218
1219 test "does not require authentication", %{conn: conn} do
1220 user = insert(:user)
1221
1222 conn
1223 |> get("/users/#{user.nickname}/followers")
1224 |> json_response(200)
1225 end
1226 end
1227
1228 describe "/users/:nickname/following" do
1229 test "it returns the following in a collection", %{conn: conn} do
1230 user = insert(:user)
1231 user_two = insert(:user)
1232 User.follow(user, user_two)
1233
1234 result =
1235 conn
1236 |> assign(:user, user)
1237 |> get("/users/#{user.nickname}/following")
1238 |> json_response(200)
1239
1240 assert result["first"]["orderedItems"] == [user_two.ap_id]
1241 end
1242
1243 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1244 user = insert(:user)
1245 user_two = insert(:user, hide_follows: true)
1246 User.follow(user, user_two)
1247
1248 result =
1249 conn
1250 |> assign(:user, user)
1251 |> get("/users/#{user_two.nickname}/following")
1252 |> json_response(200)
1253
1254 assert is_binary(result["first"])
1255 end
1256
1257 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1258 %{conn: conn} do
1259 user = insert(:user)
1260 user_two = insert(:user, hide_follows: true)
1261
1262 result =
1263 conn
1264 |> assign(:user, user)
1265 |> get("/users/#{user_two.nickname}/following?page=1")
1266
1267 assert result.status == 403
1268 assert result.resp_body == ""
1269 end
1270
1271 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1272 %{conn: conn} do
1273 user = insert(:user, hide_follows: true)
1274 other_user = insert(:user)
1275 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1276
1277 result =
1278 conn
1279 |> assign(:user, user)
1280 |> get("/users/#{user.nickname}/following?page=1")
1281 |> json_response(200)
1282
1283 assert result["totalItems"] == 1
1284 assert result["orderedItems"] == [other_user.ap_id]
1285 end
1286
1287 test "it works for more than 10 users", %{conn: conn} do
1288 user = insert(:user)
1289
1290 Enum.each(1..15, fn _ ->
1291 user = User.get_cached_by_id(user.id)
1292 other_user = insert(:user)
1293 User.follow(user, other_user)
1294 end)
1295
1296 result =
1297 conn
1298 |> assign(:user, user)
1299 |> get("/users/#{user.nickname}/following")
1300 |> json_response(200)
1301
1302 assert length(result["first"]["orderedItems"]) == 10
1303 assert result["first"]["totalItems"] == 15
1304 assert result["totalItems"] == 15
1305
1306 result =
1307 conn
1308 |> assign(:user, user)
1309 |> get("/users/#{user.nickname}/following?page=2")
1310 |> json_response(200)
1311
1312 assert length(result["orderedItems"]) == 5
1313 assert result["totalItems"] == 15
1314 end
1315
1316 test "does not require authentication", %{conn: conn} do
1317 user = insert(:user)
1318
1319 conn
1320 |> get("/users/#{user.nickname}/following")
1321 |> json_response(200)
1322 end
1323 end
1324
1325 describe "delivery tracking" do
1326 test "it tracks a signed object fetch", %{conn: conn} do
1327 user = insert(:user, local: false)
1328 activity = insert(:note_activity)
1329 object = Object.normalize(activity)
1330
1331 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1332
1333 conn
1334 |> put_req_header("accept", "application/activity+json")
1335 |> assign(:user, user)
1336 |> get(object_path)
1337 |> json_response(200)
1338
1339 assert Delivery.get(object.id, user.id)
1340 end
1341
1342 test "it tracks a signed activity fetch", %{conn: conn} do
1343 user = insert(:user, local: false)
1344 activity = insert(:note_activity)
1345 object = Object.normalize(activity)
1346
1347 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1348
1349 conn
1350 |> put_req_header("accept", "application/activity+json")
1351 |> assign(:user, user)
1352 |> get(activity_path)
1353 |> json_response(200)
1354
1355 assert Delivery.get(object.id, user.id)
1356 end
1357
1358 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1359 user = insert(:user, local: false)
1360 other_user = insert(:user, local: false)
1361 activity = insert(:note_activity)
1362 object = Object.normalize(activity)
1363
1364 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1365
1366 conn
1367 |> put_req_header("accept", "application/activity+json")
1368 |> assign(:user, user)
1369 |> get(object_path)
1370 |> json_response(200)
1371
1372 build_conn()
1373 |> put_req_header("accept", "application/activity+json")
1374 |> assign(:user, other_user)
1375 |> get(object_path)
1376 |> json_response(200)
1377
1378 assert Delivery.get(object.id, user.id)
1379 assert Delivery.get(object.id, other_user.id)
1380 end
1381
1382 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1383 user = insert(:user, local: false)
1384 other_user = insert(:user, local: false)
1385 activity = insert(:note_activity)
1386 object = Object.normalize(activity)
1387
1388 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1389
1390 conn
1391 |> put_req_header("accept", "application/activity+json")
1392 |> assign(:user, user)
1393 |> get(activity_path)
1394 |> json_response(200)
1395
1396 build_conn()
1397 |> put_req_header("accept", "application/activity+json")
1398 |> assign(:user, other_user)
1399 |> get(activity_path)
1400 |> json_response(200)
1401
1402 assert Delivery.get(object.id, user.id)
1403 assert Delivery.get(object.id, other_user.id)
1404 end
1405 end
1406
1407 describe "Additional ActivityPub C2S endpoints" do
1408 test "GET /api/ap/whoami", %{conn: conn} do
1409 user = insert(:user)
1410
1411 conn =
1412 conn
1413 |> assign(:user, user)
1414 |> get("/api/ap/whoami")
1415
1416 user = User.get_cached_by_id(user.id)
1417
1418 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1419
1420 conn
1421 |> get("/api/ap/whoami")
1422 |> json_response(403)
1423 end
1424
1425 setup do: clear_config([:media_proxy])
1426 setup do: clear_config([Pleroma.Upload])
1427
1428 test "POST /api/ap/upload_media", %{conn: conn} do
1429 user = insert(:user)
1430
1431 desc = "Description of the image"
1432
1433 image = %Plug.Upload{
1434 content_type: "image/jpg",
1435 path: Path.absname("test/fixtures/image.jpg"),
1436 filename: "an_image.jpg"
1437 }
1438
1439 object =
1440 conn
1441 |> assign(:user, user)
1442 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1443 |> json_response(:created)
1444
1445 assert object["name"] == desc
1446 assert object["type"] == "Document"
1447 assert object["actor"] == user.ap_id
1448 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1449 assert is_binary(object_href)
1450 assert object_mediatype == "image/jpeg"
1451
1452 activity_request = %{
1453 "@context" => "https://www.w3.org/ns/activitystreams",
1454 "type" => "Create",
1455 "object" => %{
1456 "type" => "Note",
1457 "content" => "AP C2S test, attachment",
1458 "attachment" => [object]
1459 },
1460 "to" => "https://www.w3.org/ns/activitystreams#Public",
1461 "cc" => []
1462 }
1463
1464 activity_response =
1465 conn
1466 |> assign(:user, user)
1467 |> post("/users/#{user.nickname}/outbox", activity_request)
1468 |> json_response(:created)
1469
1470 assert activity_response["id"]
1471 assert activity_response["object"]
1472 assert activity_response["actor"] == user.ap_id
1473
1474 assert %Object{data: %{"attachment" => [attachment]}} =
1475 Object.normalize(activity_response["object"])
1476
1477 assert attachment["type"] == "Document"
1478 assert attachment["name"] == desc
1479
1480 assert [
1481 %{
1482 "href" => ^object_href,
1483 "type" => "Link",
1484 "mediaType" => ^object_mediatype
1485 }
1486 ] = attachment["url"]
1487
1488 # Fails if unauthenticated
1489 conn
1490 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1491 |> json_response(403)
1492 end
1493 end
1494 end