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