Revert adding extra alert types here
[akkoma] / test / pleroma / 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 end
160
161 describe "mastodon compatibility routes" do
162 test "it returns a json representation of the object with accept application/json", %{
163 conn: conn
164 } do
165 {:ok, object} =
166 %{
167 "type" => "Note",
168 "content" => "hey",
169 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
170 "actor" => Endpoint.url() <> "/users/raymoo",
171 "to" => [Pleroma.Constants.as_public()]
172 }
173 |> Object.create()
174
175 conn =
176 conn
177 |> put_req_header("accept", "application/json")
178 |> get("/users/raymoo/statuses/999999999")
179
180 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
181 end
182
183 test "it returns a json representation of the activity with accept application/json", %{
184 conn: conn
185 } do
186 {:ok, object} =
187 %{
188 "type" => "Note",
189 "content" => "hey",
190 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
191 "actor" => Endpoint.url() <> "/users/raymoo",
192 "to" => [Pleroma.Constants.as_public()]
193 }
194 |> Object.create()
195
196 {:ok, activity, _} =
197 %{
198 "id" => object.data["id"] <> "/activity",
199 "type" => "Create",
200 "object" => object.data["id"],
201 "actor" => object.data["actor"],
202 "to" => object.data["to"]
203 }
204 |> ActivityPub.persist(local: true)
205
206 conn =
207 conn
208 |> put_req_header("accept", "application/json")
209 |> get("/users/raymoo/statuses/999999999/activity")
210
211 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
212 end
213 end
214
215 describe "/objects/:uuid" do
216 test "it doesn't return a local-only object", %{conn: conn} do
217 user = insert(:user)
218 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
219
220 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
221
222 object = Object.normalize(post, false)
223 uuid = String.split(object.data["id"], "/") |> List.last()
224
225 conn =
226 conn
227 |> put_req_header("accept", "application/json")
228 |> get("/objects/#{uuid}")
229
230 assert json_response(conn, 404)
231 end
232
233 test "it returns a json representation of the object with accept application/json", %{
234 conn: conn
235 } do
236 note = insert(:note)
237 uuid = String.split(note.data["id"], "/") |> List.last()
238
239 conn =
240 conn
241 |> put_req_header("accept", "application/json")
242 |> get("/objects/#{uuid}")
243
244 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
245 end
246
247 test "it returns a json representation of the object with accept application/activity+json",
248 %{conn: conn} do
249 note = insert(:note)
250 uuid = String.split(note.data["id"], "/") |> List.last()
251
252 conn =
253 conn
254 |> put_req_header("accept", "application/activity+json")
255 |> get("/objects/#{uuid}")
256
257 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
258 end
259
260 test "it returns a json representation of the object with accept application/ld+json", %{
261 conn: conn
262 } do
263 note = insert(:note)
264 uuid = String.split(note.data["id"], "/") |> List.last()
265
266 conn =
267 conn
268 |> put_req_header(
269 "accept",
270 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
271 )
272 |> get("/objects/#{uuid}")
273
274 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
275 end
276
277 test "it returns 404 for non-public messages", %{conn: conn} do
278 note = insert(:direct_note)
279 uuid = String.split(note.data["id"], "/") |> List.last()
280
281 conn =
282 conn
283 |> put_req_header("accept", "application/activity+json")
284 |> get("/objects/#{uuid}")
285
286 assert json_response(conn, 404)
287 end
288
289 test "it returns 404 for tombstone objects", %{conn: conn} do
290 tombstone = insert(:tombstone)
291 uuid = String.split(tombstone.data["id"], "/") |> List.last()
292
293 conn =
294 conn
295 |> put_req_header("accept", "application/activity+json")
296 |> get("/objects/#{uuid}")
297
298 assert json_response(conn, 404)
299 end
300
301 test "it caches a response", %{conn: conn} do
302 note = insert(:note)
303 uuid = String.split(note.data["id"], "/") |> List.last()
304
305 conn1 =
306 conn
307 |> put_req_header("accept", "application/activity+json")
308 |> get("/objects/#{uuid}")
309
310 assert json_response(conn1, :ok)
311 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
312
313 conn2 =
314 conn
315 |> put_req_header("accept", "application/activity+json")
316 |> get("/objects/#{uuid}")
317
318 assert json_response(conn1, :ok) == json_response(conn2, :ok)
319 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
320 end
321
322 test "cached purged after object deletion", %{conn: conn} do
323 note = insert(:note)
324 uuid = String.split(note.data["id"], "/") |> List.last()
325
326 conn1 =
327 conn
328 |> put_req_header("accept", "application/activity+json")
329 |> get("/objects/#{uuid}")
330
331 assert json_response(conn1, :ok)
332 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
333
334 Object.delete(note)
335
336 conn2 =
337 conn
338 |> put_req_header("accept", "application/activity+json")
339 |> get("/objects/#{uuid}")
340
341 assert "Not found" == json_response(conn2, :not_found)
342 end
343 end
344
345 describe "/activities/:uuid" do
346 test "it doesn't return a local-only activity", %{conn: conn} do
347 user = insert(:user)
348 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
349
350 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
351
352 uuid = String.split(post.data["id"], "/") |> List.last()
353
354 conn =
355 conn
356 |> put_req_header("accept", "application/json")
357 |> get("/activities/#{uuid}")
358
359 assert json_response(conn, 404)
360 end
361
362 test "it returns a json representation of the activity", %{conn: conn} do
363 activity = insert(:note_activity)
364 uuid = String.split(activity.data["id"], "/") |> List.last()
365
366 conn =
367 conn
368 |> put_req_header("accept", "application/activity+json")
369 |> get("/activities/#{uuid}")
370
371 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
372 end
373
374 test "it returns 404 for non-public activities", %{conn: conn} do
375 activity = insert(:direct_note_activity)
376 uuid = String.split(activity.data["id"], "/") |> List.last()
377
378 conn =
379 conn
380 |> put_req_header("accept", "application/activity+json")
381 |> get("/activities/#{uuid}")
382
383 assert json_response(conn, 404)
384 end
385
386 test "it caches a response", %{conn: conn} do
387 activity = insert(:note_activity)
388 uuid = String.split(activity.data["id"], "/") |> List.last()
389
390 conn1 =
391 conn
392 |> put_req_header("accept", "application/activity+json")
393 |> get("/activities/#{uuid}")
394
395 assert json_response(conn1, :ok)
396 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
397
398 conn2 =
399 conn
400 |> put_req_header("accept", "application/activity+json")
401 |> get("/activities/#{uuid}")
402
403 assert json_response(conn1, :ok) == json_response(conn2, :ok)
404 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
405 end
406
407 test "cached purged after activity deletion", %{conn: conn} do
408 user = insert(:user)
409 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
410
411 uuid = String.split(activity.data["id"], "/") |> List.last()
412
413 conn1 =
414 conn
415 |> put_req_header("accept", "application/activity+json")
416 |> get("/activities/#{uuid}")
417
418 assert json_response(conn1, :ok)
419 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
420
421 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
422
423 conn2 =
424 conn
425 |> put_req_header("accept", "application/activity+json")
426 |> get("/activities/#{uuid}")
427
428 assert "Not found" == json_response(conn2, :not_found)
429 end
430 end
431
432 describe "/inbox" do
433 test "it inserts an incoming activity into the database", %{conn: conn} do
434 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
435
436 conn =
437 conn
438 |> assign(:valid_signature, true)
439 |> put_req_header("content-type", "application/activity+json")
440 |> post("/inbox", data)
441
442 assert "ok" == json_response(conn, 200)
443
444 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
445 assert Activity.get_by_ap_id(data["id"])
446 end
447
448 @tag capture_log: true
449 test "it inserts an incoming activity into the database" <>
450 "even if we can't fetch the user but have it in our db",
451 %{conn: conn} do
452 user =
453 insert(:user,
454 ap_id: "https://mastodon.example.org/users/raymoo",
455 ap_enabled: true,
456 local: false,
457 last_refreshed_at: nil
458 )
459
460 data =
461 File.read!("test/fixtures/mastodon-post-activity.json")
462 |> Poison.decode!()
463 |> Map.put("actor", user.ap_id)
464 |> put_in(["object", "attridbutedTo"], user.ap_id)
465
466 conn =
467 conn
468 |> assign(:valid_signature, true)
469 |> put_req_header("content-type", "application/activity+json")
470 |> post("/inbox", data)
471
472 assert "ok" == json_response(conn, 200)
473
474 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
475 assert Activity.get_by_ap_id(data["id"])
476 end
477
478 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
479 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
480
481 sender_url = data["actor"]
482 Instances.set_consistently_unreachable(sender_url)
483 refute Instances.reachable?(sender_url)
484
485 conn =
486 conn
487 |> assign(:valid_signature, true)
488 |> put_req_header("content-type", "application/activity+json")
489 |> post("/inbox", data)
490
491 assert "ok" == json_response(conn, 200)
492 assert Instances.reachable?(sender_url)
493 end
494
495 test "accept follow activity", %{conn: conn} do
496 Pleroma.Config.put([:instance, :federating], true)
497 relay = Relay.get_actor()
498
499 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
500
501 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
502 relay = refresh_record(relay)
503
504 accept =
505 File.read!("test/fixtures/relay/accept-follow.json")
506 |> String.replace("{{ap_id}}", relay.ap_id)
507 |> String.replace("{{activity_id}}", activity.data["id"])
508
509 assert "ok" ==
510 conn
511 |> assign(:valid_signature, true)
512 |> put_req_header("content-type", "application/activity+json")
513 |> post("/inbox", accept)
514 |> json_response(200)
515
516 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
517
518 assert Pleroma.FollowingRelationship.following?(
519 relay,
520 followed_relay
521 )
522
523 Mix.shell(Mix.Shell.Process)
524
525 on_exit(fn ->
526 Mix.shell(Mix.Shell.IO)
527 end)
528
529 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
530 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
531 end
532
533 @tag capture_log: true
534 test "without valid signature, " <>
535 "it only accepts Create activities and requires enabled federation",
536 %{conn: conn} do
537 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
538 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
539
540 conn = put_req_header(conn, "content-type", "application/activity+json")
541
542 Config.put([:instance, :federating], false)
543
544 conn
545 |> post("/inbox", data)
546 |> json_response(403)
547
548 conn
549 |> post("/inbox", non_create_data)
550 |> json_response(403)
551
552 Config.put([:instance, :federating], true)
553
554 ret_conn = post(conn, "/inbox", data)
555 assert "ok" == json_response(ret_conn, 200)
556
557 conn
558 |> post("/inbox", non_create_data)
559 |> json_response(400)
560 end
561 end
562
563 describe "/users/:nickname/inbox" do
564 setup do
565 data =
566 File.read!("test/fixtures/mastodon-post-activity.json")
567 |> Poison.decode!()
568
569 [data: data]
570 end
571
572 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
573 user = insert(:user)
574 data = Map.put(data, "bcc", [user.ap_id])
575
576 conn =
577 conn
578 |> assign(:valid_signature, true)
579 |> put_req_header("content-type", "application/activity+json")
580 |> post("/users/#{user.nickname}/inbox", data)
581
582 assert "ok" == json_response(conn, 200)
583 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
584 assert Activity.get_by_ap_id(data["id"])
585 end
586
587 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
588 user = insert(:user)
589
590 data =
591 Map.put(data, "to", user.ap_id)
592 |> Map.delete("cc")
593
594 conn =
595 conn
596 |> assign(:valid_signature, true)
597 |> put_req_header("content-type", "application/activity+json")
598 |> post("/users/#{user.nickname}/inbox", data)
599
600 assert "ok" == json_response(conn, 200)
601 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
602 assert Activity.get_by_ap_id(data["id"])
603 end
604
605 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
606 user = insert(:user)
607
608 data =
609 Map.put(data, "cc", user.ap_id)
610 |> Map.delete("to")
611
612 conn =
613 conn
614 |> assign(:valid_signature, true)
615 |> put_req_header("content-type", "application/activity+json")
616 |> post("/users/#{user.nickname}/inbox", data)
617
618 assert "ok" == json_response(conn, 200)
619 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
620 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
621 assert user.ap_id in activity.recipients
622 end
623
624 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
625 user = insert(:user)
626
627 data =
628 Map.put(data, "bcc", user.ap_id)
629 |> Map.delete("to")
630 |> Map.delete("cc")
631
632 conn =
633 conn
634 |> assign(:valid_signature, true)
635 |> put_req_header("content-type", "application/activity+json")
636 |> post("/users/#{user.nickname}/inbox", data)
637
638 assert "ok" == json_response(conn, 200)
639 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
640 assert Activity.get_by_ap_id(data["id"])
641 end
642
643 test "it accepts announces with to as string instead of array", %{conn: conn} do
644 user = insert(:user)
645
646 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
647 announcer = insert(:user, local: false)
648
649 data = %{
650 "@context" => "https://www.w3.org/ns/activitystreams",
651 "actor" => announcer.ap_id,
652 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
653 "object" => post.data["object"],
654 "to" => "https://www.w3.org/ns/activitystreams#Public",
655 "cc" => [user.ap_id],
656 "type" => "Announce"
657 }
658
659 conn =
660 conn
661 |> assign(:valid_signature, true)
662 |> put_req_header("content-type", "application/activity+json")
663 |> post("/users/#{user.nickname}/inbox", data)
664
665 assert "ok" == json_response(conn, 200)
666 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
667 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
668 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
669 end
670
671 test "it accepts messages from actors that are followed by the user", %{
672 conn: conn,
673 data: data
674 } do
675 recipient = insert(:user)
676 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
677
678 {:ok, recipient} = User.follow(recipient, actor)
679
680 object =
681 data["object"]
682 |> Map.put("attributedTo", actor.ap_id)
683
684 data =
685 data
686 |> Map.put("actor", actor.ap_id)
687 |> Map.put("object", object)
688
689 conn =
690 conn
691 |> assign(:valid_signature, true)
692 |> put_req_header("content-type", "application/activity+json")
693 |> post("/users/#{recipient.nickname}/inbox", data)
694
695 assert "ok" == json_response(conn, 200)
696 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
697 assert Activity.get_by_ap_id(data["id"])
698 end
699
700 test "it rejects reads from other users", %{conn: conn} do
701 user = insert(:user)
702 other_user = insert(:user)
703
704 conn =
705 conn
706 |> assign(:user, other_user)
707 |> put_req_header("accept", "application/activity+json")
708 |> get("/users/#{user.nickname}/inbox")
709
710 assert json_response(conn, 403)
711 end
712
713 test "it returns a note activity in a collection", %{conn: conn} do
714 note_activity = insert(:direct_note_activity)
715 note_object = Object.normalize(note_activity)
716 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
717
718 conn =
719 conn
720 |> assign(:user, user)
721 |> put_req_header("accept", "application/activity+json")
722 |> get("/users/#{user.nickname}/inbox?page=true")
723
724 assert response(conn, 200) =~ note_object.data["content"]
725 end
726
727 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
728 user = insert(:user)
729 data = Map.put(data, "bcc", [user.ap_id])
730
731 sender_host = URI.parse(data["actor"]).host
732 Instances.set_consistently_unreachable(sender_host)
733 refute Instances.reachable?(sender_host)
734
735 conn =
736 conn
737 |> assign(:valid_signature, true)
738 |> put_req_header("content-type", "application/activity+json")
739 |> post("/users/#{user.nickname}/inbox", data)
740
741 assert "ok" == json_response(conn, 200)
742 assert Instances.reachable?(sender_host)
743 end
744
745 test "it removes all follower collections but actor's", %{conn: conn} do
746 [actor, recipient] = insert_pair(:user)
747
748 data =
749 File.read!("test/fixtures/activitypub-client-post-activity.json")
750 |> Poison.decode!()
751
752 object = Map.put(data["object"], "attributedTo", actor.ap_id)
753
754 data =
755 data
756 |> Map.put("id", Utils.generate_object_id())
757 |> Map.put("actor", actor.ap_id)
758 |> Map.put("object", object)
759 |> Map.put("cc", [
760 recipient.follower_address,
761 actor.follower_address
762 ])
763 |> Map.put("to", [
764 recipient.ap_id,
765 recipient.follower_address,
766 "https://www.w3.org/ns/activitystreams#Public"
767 ])
768
769 conn
770 |> assign(:valid_signature, true)
771 |> put_req_header("content-type", "application/activity+json")
772 |> post("/users/#{recipient.nickname}/inbox", data)
773 |> json_response(200)
774
775 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
776
777 activity = Activity.get_by_ap_id(data["id"])
778
779 assert activity.id
780 assert actor.follower_address in activity.recipients
781 assert actor.follower_address in activity.data["cc"]
782
783 refute recipient.follower_address in activity.recipients
784 refute recipient.follower_address in activity.data["cc"]
785 refute recipient.follower_address in activity.data["to"]
786 end
787
788 test "it requires authentication", %{conn: conn} do
789 user = insert(:user)
790 conn = put_req_header(conn, "accept", "application/activity+json")
791
792 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
793 assert json_response(ret_conn, 403)
794
795 ret_conn =
796 conn
797 |> assign(:user, user)
798 |> get("/users/#{user.nickname}/inbox")
799
800 assert json_response(ret_conn, 200)
801 end
802 end
803
804 describe "GET /users/:nickname/outbox" do
805 test "it paginates correctly", %{conn: conn} do
806 user = insert(:user)
807 conn = assign(conn, :user, user)
808 outbox_endpoint = user.ap_id <> "/outbox"
809
810 _posts =
811 for i <- 0..25 do
812 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
813 activity
814 end
815
816 result =
817 conn
818 |> put_req_header("accept", "application/activity+json")
819 |> get(outbox_endpoint <> "?page=true")
820 |> json_response(200)
821
822 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
823 assert length(result["orderedItems"]) == 20
824 assert length(result_ids) == 20
825 assert result["next"]
826 assert String.starts_with?(result["next"], outbox_endpoint)
827
828 result_next =
829 conn
830 |> put_req_header("accept", "application/activity+json")
831 |> get(result["next"])
832 |> json_response(200)
833
834 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
835 assert length(result_next["orderedItems"]) == 6
836 assert length(result_next_ids) == 6
837 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
838 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
839 assert String.starts_with?(result["id"], outbox_endpoint)
840
841 result_next_again =
842 conn
843 |> put_req_header("accept", "application/activity+json")
844 |> get(result_next["id"])
845 |> json_response(200)
846
847 assert result_next == result_next_again
848 end
849
850 test "it returns 200 even if there're no activities", %{conn: conn} do
851 user = insert(:user)
852 outbox_endpoint = user.ap_id <> "/outbox"
853
854 conn =
855 conn
856 |> assign(:user, user)
857 |> put_req_header("accept", "application/activity+json")
858 |> get(outbox_endpoint)
859
860 result = json_response(conn, 200)
861 assert outbox_endpoint == result["id"]
862 end
863
864 test "it returns a note activity in a collection", %{conn: conn} do
865 note_activity = insert(:note_activity)
866 note_object = Object.normalize(note_activity)
867 user = User.get_cached_by_ap_id(note_activity.data["actor"])
868
869 conn =
870 conn
871 |> assign(:user, user)
872 |> put_req_header("accept", "application/activity+json")
873 |> get("/users/#{user.nickname}/outbox?page=true")
874
875 assert response(conn, 200) =~ note_object.data["content"]
876 end
877
878 test "it returns an announce activity in a collection", %{conn: conn} do
879 announce_activity = insert(:announce_activity)
880 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
881
882 conn =
883 conn
884 |> assign(:user, user)
885 |> put_req_header("accept", "application/activity+json")
886 |> get("/users/#{user.nickname}/outbox?page=true")
887
888 assert response(conn, 200) =~ announce_activity.data["object"]
889 end
890 end
891
892 describe "POST /users/:nickname/outbox (C2S)" do
893 setup do: clear_config([:instance, :limit])
894
895 setup do
896 [
897 activity: %{
898 "@context" => "https://www.w3.org/ns/activitystreams",
899 "type" => "Create",
900 "object" => %{"type" => "Note", "content" => "AP C2S test"},
901 "to" => "https://www.w3.org/ns/activitystreams#Public",
902 "cc" => []
903 }
904 ]
905 end
906
907 test "it rejects posts from other users / unauthenticated users", %{
908 conn: conn,
909 activity: activity
910 } do
911 user = insert(:user)
912 other_user = insert(:user)
913 conn = put_req_header(conn, "content-type", "application/activity+json")
914
915 conn
916 |> post("/users/#{user.nickname}/outbox", activity)
917 |> json_response(403)
918
919 conn
920 |> assign(:user, other_user)
921 |> post("/users/#{user.nickname}/outbox", activity)
922 |> json_response(403)
923 end
924
925 test "it inserts an incoming create activity into the database", %{
926 conn: conn,
927 activity: activity
928 } do
929 user = insert(:user)
930
931 result =
932 conn
933 |> assign(:user, user)
934 |> put_req_header("content-type", "application/activity+json")
935 |> post("/users/#{user.nickname}/outbox", activity)
936 |> json_response(201)
937
938 assert Activity.get_by_ap_id(result["id"])
939 assert result["object"]
940 assert %Object{data: object} = Object.normalize(result["object"])
941 assert object["content"] == activity["object"]["content"]
942 end
943
944 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
945 user = insert(:user)
946
947 activity =
948 activity
949 |> put_in(["object", "type"], "Benis")
950
951 _result =
952 conn
953 |> assign(:user, user)
954 |> put_req_header("content-type", "application/activity+json")
955 |> post("/users/#{user.nickname}/outbox", activity)
956 |> json_response(400)
957 end
958
959 test "it inserts an incoming sensitive activity into the database", %{
960 conn: conn,
961 activity: activity
962 } do
963 user = insert(:user)
964 conn = assign(conn, :user, user)
965 object = Map.put(activity["object"], "sensitive", true)
966 activity = Map.put(activity, "object", object)
967
968 response =
969 conn
970 |> put_req_header("content-type", "application/activity+json")
971 |> post("/users/#{user.nickname}/outbox", activity)
972 |> json_response(201)
973
974 assert Activity.get_by_ap_id(response["id"])
975 assert response["object"]
976 assert %Object{data: response_object} = Object.normalize(response["object"])
977 assert response_object["sensitive"] == true
978 assert response_object["content"] == activity["object"]["content"]
979
980 representation =
981 conn
982 |> put_req_header("accept", "application/activity+json")
983 |> get(response["id"])
984 |> json_response(200)
985
986 assert representation["object"]["sensitive"] == true
987 end
988
989 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
990 user = insert(:user)
991 activity = Map.put(activity, "type", "BadType")
992
993 conn =
994 conn
995 |> assign(:user, user)
996 |> put_req_header("content-type", "application/activity+json")
997 |> post("/users/#{user.nickname}/outbox", activity)
998
999 assert json_response(conn, 400)
1000 end
1001
1002 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1003 note_activity = insert(:note_activity)
1004 note_object = Object.normalize(note_activity)
1005 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1006
1007 data = %{
1008 type: "Delete",
1009 object: %{
1010 id: note_object.data["id"]
1011 }
1012 }
1013
1014 conn =
1015 conn
1016 |> assign(:user, user)
1017 |> put_req_header("content-type", "application/activity+json")
1018 |> post("/users/#{user.nickname}/outbox", data)
1019
1020 result = json_response(conn, 201)
1021 assert Activity.get_by_ap_id(result["id"])
1022
1023 assert object = Object.get_by_ap_id(note_object.data["id"])
1024 assert object.data["type"] == "Tombstone"
1025 end
1026
1027 test "it rejects delete activity of object from other actor", %{conn: conn} do
1028 note_activity = insert(:note_activity)
1029 note_object = Object.normalize(note_activity)
1030 user = insert(:user)
1031
1032 data = %{
1033 type: "Delete",
1034 object: %{
1035 id: note_object.data["id"]
1036 }
1037 }
1038
1039 conn =
1040 conn
1041 |> assign(:user, user)
1042 |> put_req_header("content-type", "application/activity+json")
1043 |> post("/users/#{user.nickname}/outbox", data)
1044
1045 assert json_response(conn, 400)
1046 end
1047
1048 test "it increases like count when receiving a like action", %{conn: conn} do
1049 note_activity = insert(:note_activity)
1050 note_object = Object.normalize(note_activity)
1051 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1052
1053 data = %{
1054 type: "Like",
1055 object: %{
1056 id: note_object.data["id"]
1057 }
1058 }
1059
1060 conn =
1061 conn
1062 |> assign(:user, user)
1063 |> put_req_header("content-type", "application/activity+json")
1064 |> post("/users/#{user.nickname}/outbox", data)
1065
1066 result = json_response(conn, 201)
1067 assert Activity.get_by_ap_id(result["id"])
1068
1069 assert object = Object.get_by_ap_id(note_object.data["id"])
1070 assert object.data["like_count"] == 1
1071 end
1072
1073 test "it doesn't spreads faulty attributedTo or actor fields", %{
1074 conn: conn,
1075 activity: activity
1076 } do
1077 reimu = insert(:user, nickname: "reimu")
1078 cirno = insert(:user, nickname: "cirno")
1079
1080 assert reimu.ap_id
1081 assert cirno.ap_id
1082
1083 activity =
1084 activity
1085 |> put_in(["object", "actor"], reimu.ap_id)
1086 |> put_in(["object", "attributedTo"], reimu.ap_id)
1087 |> put_in(["actor"], reimu.ap_id)
1088 |> put_in(["attributedTo"], reimu.ap_id)
1089
1090 _reimu_outbox =
1091 conn
1092 |> assign(:user, cirno)
1093 |> put_req_header("content-type", "application/activity+json")
1094 |> post("/users/#{reimu.nickname}/outbox", activity)
1095 |> json_response(403)
1096
1097 cirno_outbox =
1098 conn
1099 |> assign(:user, cirno)
1100 |> put_req_header("content-type", "application/activity+json")
1101 |> post("/users/#{cirno.nickname}/outbox", activity)
1102 |> json_response(201)
1103
1104 assert cirno_outbox["attributedTo"] == nil
1105 assert cirno_outbox["actor"] == cirno.ap_id
1106
1107 assert cirno_object = Object.normalize(cirno_outbox["object"])
1108 assert cirno_object.data["actor"] == cirno.ap_id
1109 assert cirno_object.data["attributedTo"] == cirno.ap_id
1110 end
1111
1112 test "Character limitation", %{conn: conn, activity: activity} do
1113 Pleroma.Config.put([:instance, :limit], 5)
1114 user = insert(:user)
1115
1116 result =
1117 conn
1118 |> assign(:user, user)
1119 |> put_req_header("content-type", "application/activity+json")
1120 |> post("/users/#{user.nickname}/outbox", activity)
1121 |> json_response(400)
1122
1123 assert result == "Note is over the character limit"
1124 end
1125 end
1126
1127 describe "/relay/followers" do
1128 test "it returns relay followers", %{conn: conn} do
1129 relay_actor = Relay.get_actor()
1130 user = insert(:user)
1131 User.follow(user, relay_actor)
1132
1133 result =
1134 conn
1135 |> get("/relay/followers")
1136 |> json_response(200)
1137
1138 assert result["first"]["orderedItems"] == [user.ap_id]
1139 end
1140
1141 test "on non-federating instance, it returns 404", %{conn: conn} do
1142 Config.put([:instance, :federating], false)
1143 user = insert(:user)
1144
1145 conn
1146 |> assign(:user, user)
1147 |> get("/relay/followers")
1148 |> json_response(404)
1149 end
1150 end
1151
1152 describe "/relay/following" do
1153 test "it returns relay following", %{conn: conn} do
1154 result =
1155 conn
1156 |> get("/relay/following")
1157 |> json_response(200)
1158
1159 assert result["first"]["orderedItems"] == []
1160 end
1161
1162 test "on non-federating instance, it returns 404", %{conn: conn} do
1163 Config.put([:instance, :federating], false)
1164 user = insert(:user)
1165
1166 conn
1167 |> assign(:user, user)
1168 |> get("/relay/following")
1169 |> json_response(404)
1170 end
1171 end
1172
1173 describe "/users/:nickname/followers" do
1174 test "it returns the followers in a collection", %{conn: conn} do
1175 user = insert(:user)
1176 user_two = insert(:user)
1177 User.follow(user, user_two)
1178
1179 result =
1180 conn
1181 |> assign(:user, user_two)
1182 |> get("/users/#{user_two.nickname}/followers")
1183 |> json_response(200)
1184
1185 assert result["first"]["orderedItems"] == [user.ap_id]
1186 end
1187
1188 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1189 user = insert(:user)
1190 user_two = insert(:user, hide_followers: true)
1191 User.follow(user, user_two)
1192
1193 result =
1194 conn
1195 |> assign(:user, user)
1196 |> get("/users/#{user_two.nickname}/followers")
1197 |> json_response(200)
1198
1199 assert is_binary(result["first"])
1200 end
1201
1202 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1203 %{conn: conn} do
1204 user = insert(:user)
1205 other_user = insert(:user, hide_followers: true)
1206
1207 result =
1208 conn
1209 |> assign(:user, user)
1210 |> get("/users/#{other_user.nickname}/followers?page=1")
1211
1212 assert result.status == 403
1213 assert result.resp_body == ""
1214 end
1215
1216 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1217 %{conn: conn} do
1218 user = insert(:user, hide_followers: true)
1219 other_user = insert(:user)
1220 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1221
1222 result =
1223 conn
1224 |> assign(:user, user)
1225 |> get("/users/#{user.nickname}/followers?page=1")
1226 |> json_response(200)
1227
1228 assert result["totalItems"] == 1
1229 assert result["orderedItems"] == [other_user.ap_id]
1230 end
1231
1232 test "it works for more than 10 users", %{conn: conn} do
1233 user = insert(:user)
1234
1235 Enum.each(1..15, fn _ ->
1236 other_user = insert(:user)
1237 User.follow(other_user, user)
1238 end)
1239
1240 result =
1241 conn
1242 |> assign(:user, user)
1243 |> get("/users/#{user.nickname}/followers")
1244 |> json_response(200)
1245
1246 assert length(result["first"]["orderedItems"]) == 10
1247 assert result["first"]["totalItems"] == 15
1248 assert result["totalItems"] == 15
1249
1250 result =
1251 conn
1252 |> assign(:user, user)
1253 |> get("/users/#{user.nickname}/followers?page=2")
1254 |> json_response(200)
1255
1256 assert length(result["orderedItems"]) == 5
1257 assert result["totalItems"] == 15
1258 end
1259
1260 test "does not require authentication", %{conn: conn} do
1261 user = insert(:user)
1262
1263 conn
1264 |> get("/users/#{user.nickname}/followers")
1265 |> json_response(200)
1266 end
1267 end
1268
1269 describe "/users/:nickname/following" do
1270 test "it returns the following in a collection", %{conn: conn} do
1271 user = insert(:user)
1272 user_two = insert(:user)
1273 User.follow(user, user_two)
1274
1275 result =
1276 conn
1277 |> assign(:user, user)
1278 |> get("/users/#{user.nickname}/following")
1279 |> json_response(200)
1280
1281 assert result["first"]["orderedItems"] == [user_two.ap_id]
1282 end
1283
1284 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1285 user = insert(:user)
1286 user_two = insert(:user, hide_follows: true)
1287 User.follow(user, user_two)
1288
1289 result =
1290 conn
1291 |> assign(:user, user)
1292 |> get("/users/#{user_two.nickname}/following")
1293 |> json_response(200)
1294
1295 assert is_binary(result["first"])
1296 end
1297
1298 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1299 %{conn: conn} do
1300 user = insert(:user)
1301 user_two = insert(:user, hide_follows: true)
1302
1303 result =
1304 conn
1305 |> assign(:user, user)
1306 |> get("/users/#{user_two.nickname}/following?page=1")
1307
1308 assert result.status == 403
1309 assert result.resp_body == ""
1310 end
1311
1312 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1313 %{conn: conn} do
1314 user = insert(:user, hide_follows: true)
1315 other_user = insert(:user)
1316 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1317
1318 result =
1319 conn
1320 |> assign(:user, user)
1321 |> get("/users/#{user.nickname}/following?page=1")
1322 |> json_response(200)
1323
1324 assert result["totalItems"] == 1
1325 assert result["orderedItems"] == [other_user.ap_id]
1326 end
1327
1328 test "it works for more than 10 users", %{conn: conn} do
1329 user = insert(:user)
1330
1331 Enum.each(1..15, fn _ ->
1332 user = User.get_cached_by_id(user.id)
1333 other_user = insert(:user)
1334 User.follow(user, other_user)
1335 end)
1336
1337 result =
1338 conn
1339 |> assign(:user, user)
1340 |> get("/users/#{user.nickname}/following")
1341 |> json_response(200)
1342
1343 assert length(result["first"]["orderedItems"]) == 10
1344 assert result["first"]["totalItems"] == 15
1345 assert result["totalItems"] == 15
1346
1347 result =
1348 conn
1349 |> assign(:user, user)
1350 |> get("/users/#{user.nickname}/following?page=2")
1351 |> json_response(200)
1352
1353 assert length(result["orderedItems"]) == 5
1354 assert result["totalItems"] == 15
1355 end
1356
1357 test "does not require authentication", %{conn: conn} do
1358 user = insert(:user)
1359
1360 conn
1361 |> get("/users/#{user.nickname}/following")
1362 |> json_response(200)
1363 end
1364 end
1365
1366 describe "delivery tracking" do
1367 test "it tracks a signed object fetch", %{conn: conn} do
1368 user = insert(:user, local: false)
1369 activity = insert(:note_activity)
1370 object = Object.normalize(activity)
1371
1372 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1373
1374 conn
1375 |> put_req_header("accept", "application/activity+json")
1376 |> assign(:user, user)
1377 |> get(object_path)
1378 |> json_response(200)
1379
1380 assert Delivery.get(object.id, user.id)
1381 end
1382
1383 test "it tracks a signed activity fetch", %{conn: conn} do
1384 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 assert Delivery.get(object.id, user.id)
1397 end
1398
1399 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1400 user = insert(:user, local: false)
1401 other_user = insert(:user, local: false)
1402 activity = insert(:note_activity)
1403 object = Object.normalize(activity)
1404
1405 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1406
1407 conn
1408 |> put_req_header("accept", "application/activity+json")
1409 |> assign(:user, user)
1410 |> get(object_path)
1411 |> json_response(200)
1412
1413 build_conn()
1414 |> put_req_header("accept", "application/activity+json")
1415 |> assign(:user, other_user)
1416 |> get(object_path)
1417 |> json_response(200)
1418
1419 assert Delivery.get(object.id, user.id)
1420 assert Delivery.get(object.id, other_user.id)
1421 end
1422
1423 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1424 user = insert(:user, local: false)
1425 other_user = insert(:user, local: false)
1426 activity = insert(:note_activity)
1427 object = Object.normalize(activity)
1428
1429 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1430
1431 conn
1432 |> put_req_header("accept", "application/activity+json")
1433 |> assign(:user, user)
1434 |> get(activity_path)
1435 |> json_response(200)
1436
1437 build_conn()
1438 |> put_req_header("accept", "application/activity+json")
1439 |> assign(:user, other_user)
1440 |> get(activity_path)
1441 |> json_response(200)
1442
1443 assert Delivery.get(object.id, user.id)
1444 assert Delivery.get(object.id, other_user.id)
1445 end
1446 end
1447
1448 describe "Additional ActivityPub C2S endpoints" do
1449 test "GET /api/ap/whoami", %{conn: conn} do
1450 user = insert(:user)
1451
1452 conn =
1453 conn
1454 |> assign(:user, user)
1455 |> get("/api/ap/whoami")
1456
1457 user = User.get_cached_by_id(user.id)
1458
1459 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1460
1461 conn
1462 |> get("/api/ap/whoami")
1463 |> json_response(403)
1464 end
1465
1466 setup do: clear_config([:media_proxy])
1467 setup do: clear_config([Pleroma.Upload])
1468
1469 test "POST /api/ap/upload_media", %{conn: conn} do
1470 user = insert(:user)
1471
1472 desc = "Description of the image"
1473
1474 image = %Plug.Upload{
1475 content_type: "bad/content-type",
1476 path: Path.absname("test/fixtures/image.jpg"),
1477 filename: "an_image.png"
1478 }
1479
1480 object =
1481 conn
1482 |> assign(:user, user)
1483 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1484 |> json_response(:created)
1485
1486 assert object["name"] == desc
1487 assert object["type"] == "Document"
1488 assert object["actor"] == user.ap_id
1489 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1490 assert is_binary(object_href)
1491 assert object_mediatype == "image/jpeg"
1492 assert String.ends_with?(object_href, ".jpg")
1493
1494 activity_request = %{
1495 "@context" => "https://www.w3.org/ns/activitystreams",
1496 "type" => "Create",
1497 "object" => %{
1498 "type" => "Note",
1499 "content" => "AP C2S test, attachment",
1500 "attachment" => [object]
1501 },
1502 "to" => "https://www.w3.org/ns/activitystreams#Public",
1503 "cc" => []
1504 }
1505
1506 activity_response =
1507 conn
1508 |> assign(:user, user)
1509 |> post("/users/#{user.nickname}/outbox", activity_request)
1510 |> json_response(:created)
1511
1512 assert activity_response["id"]
1513 assert activity_response["object"]
1514 assert activity_response["actor"] == user.ap_id
1515
1516 assert %Object{data: %{"attachment" => [attachment]}} =
1517 Object.normalize(activity_response["object"])
1518
1519 assert attachment["type"] == "Document"
1520 assert attachment["name"] == desc
1521
1522 assert [
1523 %{
1524 "href" => ^object_href,
1525 "type" => "Link",
1526 "mediaType" => ^object_mediatype
1527 }
1528 ] = attachment["url"]
1529
1530 # Fails if unauthenticated
1531 conn
1532 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1533 |> json_response(403)
1534 end
1535 end
1536 end