remove `unread_conversation_count` from User
[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, ["https://relay.mastodon.host/actor"]}
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
1086 test "it doesn't spreads faulty attributedTo or actor fields", %{
1087 conn: conn,
1088 activity: activity
1089 } do
1090 reimu = insert(:user, nickname: "reimu")
1091 cirno = insert(:user, nickname: "cirno")
1092
1093 assert reimu.ap_id
1094 assert cirno.ap_id
1095
1096 activity =
1097 activity
1098 |> put_in(["object", "actor"], reimu.ap_id)
1099 |> put_in(["object", "attributedTo"], reimu.ap_id)
1100 |> put_in(["actor"], reimu.ap_id)
1101 |> put_in(["attributedTo"], reimu.ap_id)
1102
1103 _reimu_outbox =
1104 conn
1105 |> assign(:user, cirno)
1106 |> put_req_header("content-type", "application/activity+json")
1107 |> post("/users/#{reimu.nickname}/outbox", activity)
1108 |> json_response(403)
1109
1110 cirno_outbox =
1111 conn
1112 |> assign(:user, cirno)
1113 |> put_req_header("content-type", "application/activity+json")
1114 |> post("/users/#{cirno.nickname}/outbox", activity)
1115 |> json_response(201)
1116
1117 assert cirno_outbox["attributedTo"] == nil
1118 assert cirno_outbox["actor"] == cirno.ap_id
1119
1120 assert cirno_object = Object.normalize(cirno_outbox["object"])
1121 assert cirno_object.data["actor"] == cirno.ap_id
1122 assert cirno_object.data["attributedTo"] == cirno.ap_id
1123 end
1124 end
1125
1126 describe "/relay/followers" do
1127 test "it returns relay followers", %{conn: conn} do
1128 relay_actor = Relay.get_actor()
1129 user = insert(:user)
1130 User.follow(user, relay_actor)
1131
1132 result =
1133 conn
1134 |> get("/relay/followers")
1135 |> json_response(200)
1136
1137 assert result["first"]["orderedItems"] == [user.ap_id]
1138 end
1139
1140 test "on non-federating instance, it returns 404", %{conn: conn} do
1141 Config.put([:instance, :federating], false)
1142 user = insert(:user)
1143
1144 conn
1145 |> assign(:user, user)
1146 |> get("/relay/followers")
1147 |> json_response(404)
1148 end
1149 end
1150
1151 describe "/relay/following" do
1152 test "it returns relay following", %{conn: conn} do
1153 result =
1154 conn
1155 |> get("/relay/following")
1156 |> json_response(200)
1157
1158 assert result["first"]["orderedItems"] == []
1159 end
1160
1161 test "on non-federating instance, it returns 404", %{conn: conn} do
1162 Config.put([:instance, :federating], false)
1163 user = insert(:user)
1164
1165 conn
1166 |> assign(:user, user)
1167 |> get("/relay/following")
1168 |> json_response(404)
1169 end
1170 end
1171
1172 describe "/users/:nickname/followers" do
1173 test "it returns the followers in a collection", %{conn: conn} do
1174 user = insert(:user)
1175 user_two = insert(:user)
1176 User.follow(user, user_two)
1177
1178 result =
1179 conn
1180 |> assign(:user, user_two)
1181 |> get("/users/#{user_two.nickname}/followers")
1182 |> json_response(200)
1183
1184 assert result["first"]["orderedItems"] == [user.ap_id]
1185 end
1186
1187 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1188 user = insert(:user)
1189 user_two = insert(:user, hide_followers: true)
1190 User.follow(user, user_two)
1191
1192 result =
1193 conn
1194 |> assign(:user, user)
1195 |> get("/users/#{user_two.nickname}/followers")
1196 |> json_response(200)
1197
1198 assert is_binary(result["first"])
1199 end
1200
1201 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1202 %{conn: conn} do
1203 user = insert(:user)
1204 other_user = insert(:user, hide_followers: true)
1205
1206 result =
1207 conn
1208 |> assign(:user, user)
1209 |> get("/users/#{other_user.nickname}/followers?page=1")
1210
1211 assert result.status == 403
1212 assert result.resp_body == ""
1213 end
1214
1215 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1216 %{conn: conn} do
1217 user = insert(:user, hide_followers: true)
1218 other_user = insert(:user)
1219 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1220
1221 result =
1222 conn
1223 |> assign(:user, user)
1224 |> get("/users/#{user.nickname}/followers?page=1")
1225 |> json_response(200)
1226
1227 assert result["totalItems"] == 1
1228 assert result["orderedItems"] == [other_user.ap_id]
1229 end
1230
1231 test "it works for more than 10 users", %{conn: conn} do
1232 user = insert(:user)
1233
1234 Enum.each(1..15, fn _ ->
1235 other_user = insert(:user)
1236 User.follow(other_user, user)
1237 end)
1238
1239 result =
1240 conn
1241 |> assign(:user, user)
1242 |> get("/users/#{user.nickname}/followers")
1243 |> json_response(200)
1244
1245 assert length(result["first"]["orderedItems"]) == 10
1246 assert result["first"]["totalItems"] == 15
1247 assert result["totalItems"] == 15
1248
1249 result =
1250 conn
1251 |> assign(:user, user)
1252 |> get("/users/#{user.nickname}/followers?page=2")
1253 |> json_response(200)
1254
1255 assert length(result["orderedItems"]) == 5
1256 assert result["totalItems"] == 15
1257 end
1258
1259 test "does not require authentication", %{conn: conn} do
1260 user = insert(:user)
1261
1262 conn
1263 |> get("/users/#{user.nickname}/followers")
1264 |> json_response(200)
1265 end
1266 end
1267
1268 describe "/users/:nickname/following" do
1269 test "it returns the following in a collection", %{conn: conn} do
1270 user = insert(:user)
1271 user_two = insert(:user)
1272 User.follow(user, user_two)
1273
1274 result =
1275 conn
1276 |> assign(:user, user)
1277 |> get("/users/#{user.nickname}/following")
1278 |> json_response(200)
1279
1280 assert result["first"]["orderedItems"] == [user_two.ap_id]
1281 end
1282
1283 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1284 user = insert(:user)
1285 user_two = insert(:user, hide_follows: true)
1286 User.follow(user, user_two)
1287
1288 result =
1289 conn
1290 |> assign(:user, user)
1291 |> get("/users/#{user_two.nickname}/following")
1292 |> json_response(200)
1293
1294 assert is_binary(result["first"])
1295 end
1296
1297 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1298 %{conn: conn} do
1299 user = insert(:user)
1300 user_two = insert(:user, hide_follows: true)
1301
1302 result =
1303 conn
1304 |> assign(:user, user)
1305 |> get("/users/#{user_two.nickname}/following?page=1")
1306
1307 assert result.status == 403
1308 assert result.resp_body == ""
1309 end
1310
1311 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1312 %{conn: conn} do
1313 user = insert(:user, hide_follows: true)
1314 other_user = insert(:user)
1315 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1316
1317 result =
1318 conn
1319 |> assign(:user, user)
1320 |> get("/users/#{user.nickname}/following?page=1")
1321 |> json_response(200)
1322
1323 assert result["totalItems"] == 1
1324 assert result["orderedItems"] == [other_user.ap_id]
1325 end
1326
1327 test "it works for more than 10 users", %{conn: conn} do
1328 user = insert(:user)
1329
1330 Enum.each(1..15, fn _ ->
1331 user = User.get_cached_by_id(user.id)
1332 other_user = insert(:user)
1333 User.follow(user, other_user)
1334 end)
1335
1336 result =
1337 conn
1338 |> assign(:user, user)
1339 |> get("/users/#{user.nickname}/following")
1340 |> json_response(200)
1341
1342 assert length(result["first"]["orderedItems"]) == 10
1343 assert result["first"]["totalItems"] == 15
1344 assert result["totalItems"] == 15
1345
1346 result =
1347 conn
1348 |> assign(:user, user)
1349 |> get("/users/#{user.nickname}/following?page=2")
1350 |> json_response(200)
1351
1352 assert length(result["orderedItems"]) == 5
1353 assert result["totalItems"] == 15
1354 end
1355
1356 test "does not require authentication", %{conn: conn} do
1357 user = insert(:user)
1358
1359 conn
1360 |> get("/users/#{user.nickname}/following")
1361 |> json_response(200)
1362 end
1363 end
1364
1365 describe "delivery tracking" do
1366 test "it tracks a signed object fetch", %{conn: conn} do
1367 user = insert(:user, local: false)
1368 activity = insert(:note_activity)
1369 object = Object.normalize(activity)
1370
1371 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1372
1373 conn
1374 |> put_req_header("accept", "application/activity+json")
1375 |> assign(:user, user)
1376 |> get(object_path)
1377 |> json_response(200)
1378
1379 assert Delivery.get(object.id, user.id)
1380 end
1381
1382 test "it tracks a signed activity fetch", %{conn: conn} do
1383 user = insert(:user, local: false)
1384 activity = insert(:note_activity)
1385 object = Object.normalize(activity)
1386
1387 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1388
1389 conn
1390 |> put_req_header("accept", "application/activity+json")
1391 |> assign(:user, user)
1392 |> get(activity_path)
1393 |> json_response(200)
1394
1395 assert Delivery.get(object.id, user.id)
1396 end
1397
1398 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1399 user = insert(:user, local: false)
1400 other_user = insert(:user, local: false)
1401 activity = insert(:note_activity)
1402 object = Object.normalize(activity)
1403
1404 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1405
1406 conn
1407 |> put_req_header("accept", "application/activity+json")
1408 |> assign(:user, user)
1409 |> get(object_path)
1410 |> json_response(200)
1411
1412 build_conn()
1413 |> put_req_header("accept", "application/activity+json")
1414 |> assign(:user, other_user)
1415 |> get(object_path)
1416 |> json_response(200)
1417
1418 assert Delivery.get(object.id, user.id)
1419 assert Delivery.get(object.id, other_user.id)
1420 end
1421
1422 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1423 user = insert(:user, local: false)
1424 other_user = insert(:user, local: false)
1425 activity = insert(:note_activity)
1426 object = Object.normalize(activity)
1427
1428 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1429
1430 conn
1431 |> put_req_header("accept", "application/activity+json")
1432 |> assign(:user, user)
1433 |> get(activity_path)
1434 |> json_response(200)
1435
1436 build_conn()
1437 |> put_req_header("accept", "application/activity+json")
1438 |> assign(:user, other_user)
1439 |> get(activity_path)
1440 |> json_response(200)
1441
1442 assert Delivery.get(object.id, user.id)
1443 assert Delivery.get(object.id, other_user.id)
1444 end
1445 end
1446
1447 describe "Additional ActivityPub C2S endpoints" do
1448 test "GET /api/ap/whoami", %{conn: conn} do
1449 user = insert(:user)
1450
1451 conn =
1452 conn
1453 |> assign(:user, user)
1454 |> get("/api/ap/whoami")
1455
1456 user = User.get_cached_by_id(user.id)
1457
1458 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1459
1460 conn
1461 |> get("/api/ap/whoami")
1462 |> json_response(403)
1463 end
1464
1465 setup do: clear_config([:media_proxy])
1466 setup do: clear_config([Pleroma.Upload])
1467
1468 test "POST /api/ap/upload_media", %{conn: conn} do
1469 user = insert(:user)
1470
1471 desc = "Description of the image"
1472
1473 image = %Plug.Upload{
1474 content_type: "image/jpg",
1475 path: Path.absname("test/fixtures/image.jpg"),
1476 filename: "an_image.jpg"
1477 }
1478
1479 object =
1480 conn
1481 |> assign(:user, user)
1482 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1483 |> json_response(:created)
1484
1485 assert object["name"] == desc
1486 assert object["type"] == "Document"
1487 assert object["actor"] == user.ap_id
1488 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1489 assert is_binary(object_href)
1490 assert object_mediatype == "image/jpeg"
1491
1492 activity_request = %{
1493 "@context" => "https://www.w3.org/ns/activitystreams",
1494 "type" => "Create",
1495 "object" => %{
1496 "type" => "Note",
1497 "content" => "AP C2S test, attachment",
1498 "attachment" => [object]
1499 },
1500 "to" => "https://www.w3.org/ns/activitystreams#Public",
1501 "cc" => []
1502 }
1503
1504 activity_response =
1505 conn
1506 |> assign(:user, user)
1507 |> post("/users/#{user.nickname}/outbox", activity_request)
1508 |> json_response(:created)
1509
1510 assert activity_response["id"]
1511 assert activity_response["object"]
1512 assert activity_response["actor"] == user.ap_id
1513
1514 assert %Object{data: %{"attachment" => [attachment]}} =
1515 Object.normalize(activity_response["object"])
1516
1517 assert attachment["type"] == "Document"
1518 assert attachment["name"] == desc
1519
1520 assert [
1521 %{
1522 "href" => ^object_href,
1523 "type" => "Link",
1524 "mediaType" => ^object_mediatype
1525 }
1526 ] = attachment["url"]
1527
1528 # Fails if unauthenticated
1529 conn
1530 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1531 |> json_response(403)
1532 end
1533 end
1534 end