AP C2S: Remove restrictions and make it go through pipeline
[akkoma] / test / pleroma / web / activity_pub / activity_pub_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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.Delivery
11 alias Pleroma.Instances
12 alias Pleroma.Object
13 alias Pleroma.Tests.ObanHelpers
14 alias Pleroma.User
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
23
24 import Pleroma.Factory
25
26 require Pleroma.Constants
27
28 setup_all do
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
30 :ok
31 end
32
33 setup do: clear_config([:instance, :federating], true)
34
35 describe "/relay" do
36 setup do: clear_config([:instance, :allow_relay])
37
38 test "with the relay active, it returns the relay user", %{conn: conn} do
39 res =
40 conn
41 |> get(activity_pub_path(conn, :relay))
42 |> json_response(200)
43
44 assert res["id"] =~ "/relay"
45 end
46
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 clear_config([:instance, :allow_relay], false)
49
50 conn
51 |> get(activity_pub_path(conn, :relay))
52 |> json_response(404)
53 end
54
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 clear_config([:instance, :federating], false)
57 user = insert(:user)
58
59 conn
60 |> assign(:user, user)
61 |> get(activity_pub_path(conn, :relay))
62 |> json_response(404)
63 end
64 end
65
66 describe "/internal/fetch" do
67 test "it returns the internal fetch user", %{conn: conn} do
68 res =
69 conn
70 |> get(activity_pub_path(conn, :internal_fetch))
71 |> json_response(200)
72
73 assert res["id"] =~ "/fetch"
74 end
75
76 test "on non-federating instance, it returns 404", %{conn: conn} do
77 clear_config([:instance, :federating], false)
78 user = insert(:user)
79
80 conn
81 |> assign(:user, user)
82 |> get(activity_pub_path(conn, :internal_fetch))
83 |> json_response(404)
84 end
85 end
86
87 describe "/users/:nickname" do
88 test "it returns a json representation of the user with accept application/json", %{
89 conn: conn
90 } do
91 user = insert(:user)
92
93 conn =
94 conn
95 |> put_req_header("accept", "application/json")
96 |> get("/users/#{user.nickname}")
97
98 user = User.get_cached_by_id(user.id)
99
100 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
101 end
102
103 test "it returns a json representation of the user with accept application/activity+json", %{
104 conn: conn
105 } do
106 user = insert(:user)
107
108 conn =
109 conn
110 |> put_req_header("accept", "application/activity+json")
111 |> get("/users/#{user.nickname}")
112
113 user = User.get_cached_by_id(user.id)
114
115 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
116 end
117
118 test "it returns a json representation of the user with accept application/ld+json", %{
119 conn: conn
120 } do
121 user = insert(:user)
122
123 conn =
124 conn
125 |> put_req_header(
126 "accept",
127 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
128 )
129 |> get("/users/#{user.nickname}")
130
131 user = User.get_cached_by_id(user.id)
132
133 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
134 end
135
136 test "it returns 404 for remote users", %{
137 conn: conn
138 } do
139 user = insert(:user, local: false, nickname: "remoteuser@example.com")
140
141 conn =
142 conn
143 |> put_req_header("accept", "application/json")
144 |> get("/users/#{user.nickname}.json")
145
146 assert json_response(conn, 404)
147 end
148
149 test "it returns error when user is not found", %{conn: conn} do
150 response =
151 conn
152 |> put_req_header("accept", "application/json")
153 |> get("/users/jimm")
154 |> json_response(404)
155
156 assert response == "Not found"
157 end
158 end
159
160 describe "mastodon compatibility routes" do
161 test "it returns a json representation of the object with accept application/json", %{
162 conn: conn
163 } do
164 {:ok, object} =
165 %{
166 "type" => "Note",
167 "content" => "hey",
168 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
169 "actor" => Endpoint.url() <> "/users/raymoo",
170 "to" => [Pleroma.Constants.as_public()]
171 }
172 |> Object.create()
173
174 conn =
175 conn
176 |> put_req_header("accept", "application/json")
177 |> get("/users/raymoo/statuses/999999999")
178
179 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
180 end
181
182 test "it returns a json representation of the activity with accept application/json", %{
183 conn: conn
184 } do
185 {:ok, object} =
186 %{
187 "type" => "Note",
188 "content" => "hey",
189 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
190 "actor" => Endpoint.url() <> "/users/raymoo",
191 "to" => [Pleroma.Constants.as_public()]
192 }
193 |> Object.create()
194
195 {:ok, activity, _} =
196 %{
197 "id" => object.data["id"] <> "/activity",
198 "type" => "Create",
199 "object" => object.data["id"],
200 "actor" => object.data["actor"],
201 "to" => object.data["to"]
202 }
203 |> ActivityPub.persist(local: true)
204
205 conn =
206 conn
207 |> put_req_header("accept", "application/json")
208 |> get("/users/raymoo/statuses/999999999/activity")
209
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
211 end
212 end
213
214 describe "/objects/:uuid" do
215 test "it doesn't return a local-only object", %{conn: conn} do
216 user = insert(:user)
217 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
218
219 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
220
221 object = Object.normalize(post, fetch: false)
222 uuid = String.split(object.data["id"], "/") |> List.last()
223
224 conn =
225 conn
226 |> put_req_header("accept", "application/json")
227 |> get("/objects/#{uuid}")
228
229 assert json_response(conn, 404)
230 end
231
232 test "returns local-only objects when authenticated", %{conn: conn} do
233 user = insert(:user)
234 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
235
236 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
237
238 object = Object.normalize(post, fetch: false)
239 uuid = String.split(object.data["id"], "/") |> List.last()
240
241 assert response =
242 conn
243 |> assign(:user, user)
244 |> put_req_header("accept", "application/activity+json")
245 |> get("/objects/#{uuid}")
246
247 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
248 end
249
250 test "it returns a json representation of the object with accept application/json", %{
251 conn: conn
252 } do
253 note = insert(:note)
254 uuid = String.split(note.data["id"], "/") |> List.last()
255
256 conn =
257 conn
258 |> put_req_header("accept", "application/json")
259 |> get("/objects/#{uuid}")
260
261 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
262 end
263
264 test "it returns a json representation of the object with accept application/activity+json",
265 %{conn: conn} do
266 note = insert(:note)
267 uuid = String.split(note.data["id"], "/") |> List.last()
268
269 conn =
270 conn
271 |> put_req_header("accept", "application/activity+json")
272 |> get("/objects/#{uuid}")
273
274 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
275 end
276
277 test "it returns a json representation of the object with accept application/ld+json", %{
278 conn: conn
279 } do
280 note = insert(:note)
281 uuid = String.split(note.data["id"], "/") |> List.last()
282
283 conn =
284 conn
285 |> put_req_header(
286 "accept",
287 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
288 )
289 |> get("/objects/#{uuid}")
290
291 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
292 end
293
294 test "it returns 404 for non-public messages", %{conn: conn} do
295 note = insert(:direct_note)
296 uuid = String.split(note.data["id"], "/") |> List.last()
297
298 conn =
299 conn
300 |> put_req_header("accept", "application/activity+json")
301 |> get("/objects/#{uuid}")
302
303 assert json_response(conn, 404)
304 end
305
306 test "returns visible non-public messages when authenticated", %{conn: conn} do
307 note = insert(:direct_note)
308 uuid = String.split(note.data["id"], "/") |> List.last()
309 user = User.get_by_ap_id(note.data["actor"])
310 marisa = insert(:user)
311
312 assert conn
313 |> assign(:user, marisa)
314 |> put_req_header("accept", "application/activity+json")
315 |> get("/objects/#{uuid}")
316 |> json_response(404)
317
318 assert response =
319 conn
320 |> assign(:user, user)
321 |> put_req_header("accept", "application/activity+json")
322 |> get("/objects/#{uuid}")
323 |> json_response(200)
324
325 assert response == ObjectView.render("object.json", %{object: note})
326 end
327
328 test "it returns 404 for tombstone objects", %{conn: conn} do
329 tombstone = insert(:tombstone)
330 uuid = String.split(tombstone.data["id"], "/") |> List.last()
331
332 conn =
333 conn
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
336
337 assert json_response(conn, 404)
338 end
339
340 test "it caches a response", %{conn: conn} do
341 note = insert(:note)
342 uuid = String.split(note.data["id"], "/") |> List.last()
343
344 conn1 =
345 conn
346 |> put_req_header("accept", "application/activity+json")
347 |> get("/objects/#{uuid}")
348
349 assert json_response(conn1, :ok)
350 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
351
352 conn2 =
353 conn
354 |> put_req_header("accept", "application/activity+json")
355 |> get("/objects/#{uuid}")
356
357 assert json_response(conn1, :ok) == json_response(conn2, :ok)
358 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
359 end
360
361 test "cached purged after object deletion", %{conn: conn} do
362 note = insert(:note)
363 uuid = String.split(note.data["id"], "/") |> List.last()
364
365 conn1 =
366 conn
367 |> put_req_header("accept", "application/activity+json")
368 |> get("/objects/#{uuid}")
369
370 assert json_response(conn1, :ok)
371 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
372
373 Object.delete(note)
374
375 conn2 =
376 conn
377 |> put_req_header("accept", "application/activity+json")
378 |> get("/objects/#{uuid}")
379
380 assert "Not found" == json_response(conn2, :not_found)
381 end
382 end
383
384 describe "/activities/:uuid" do
385 test "it doesn't return a local-only activity", %{conn: conn} do
386 user = insert(:user)
387 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
388
389 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
390
391 uuid = String.split(post.data["id"], "/") |> List.last()
392
393 conn =
394 conn
395 |> put_req_header("accept", "application/json")
396 |> get("/activities/#{uuid}")
397
398 assert json_response(conn, 404)
399 end
400
401 test "returns local-only activities when authenticated", %{conn: conn} do
402 user = insert(:user)
403 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
404
405 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
406
407 uuid = String.split(post.data["id"], "/") |> List.last()
408
409 assert response =
410 conn
411 |> assign(:user, user)
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/activities/#{uuid}")
414
415 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
416 end
417
418 test "it returns a json representation of the activity", %{conn: conn} do
419 activity = insert(:note_activity)
420 uuid = String.split(activity.data["id"], "/") |> List.last()
421
422 conn =
423 conn
424 |> put_req_header("accept", "application/activity+json")
425 |> get("/activities/#{uuid}")
426
427 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
428 end
429
430 test "it returns 404 for non-public activities", %{conn: conn} do
431 activity = insert(:direct_note_activity)
432 uuid = String.split(activity.data["id"], "/") |> List.last()
433
434 conn =
435 conn
436 |> put_req_header("accept", "application/activity+json")
437 |> get("/activities/#{uuid}")
438
439 assert json_response(conn, 404)
440 end
441
442 test "returns visible non-public messages when authenticated", %{conn: conn} do
443 note = insert(:direct_note_activity)
444 uuid = String.split(note.data["id"], "/") |> List.last()
445 user = User.get_by_ap_id(note.data["actor"])
446 marisa = insert(:user)
447
448 assert conn
449 |> assign(:user, marisa)
450 |> put_req_header("accept", "application/activity+json")
451 |> get("/activities/#{uuid}")
452 |> json_response(404)
453
454 assert response =
455 conn
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
459 |> json_response(200)
460
461 assert response == ObjectView.render("object.json", %{object: note})
462 end
463
464 test "it caches a response", %{conn: conn} do
465 activity = insert(:note_activity)
466 uuid = String.split(activity.data["id"], "/") |> List.last()
467
468 conn1 =
469 conn
470 |> put_req_header("accept", "application/activity+json")
471 |> get("/activities/#{uuid}")
472
473 assert json_response(conn1, :ok)
474 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
475
476 conn2 =
477 conn
478 |> put_req_header("accept", "application/activity+json")
479 |> get("/activities/#{uuid}")
480
481 assert json_response(conn1, :ok) == json_response(conn2, :ok)
482 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
483 end
484
485 test "cached purged after activity deletion", %{conn: conn} do
486 user = insert(:user)
487 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
488
489 uuid = String.split(activity.data["id"], "/") |> List.last()
490
491 conn1 =
492 conn
493 |> put_req_header("accept", "application/activity+json")
494 |> get("/activities/#{uuid}")
495
496 assert json_response(conn1, :ok)
497 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
498
499 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
500
501 conn2 =
502 conn
503 |> put_req_header("accept", "application/activity+json")
504 |> get("/activities/#{uuid}")
505
506 assert "Not found" == json_response(conn2, :not_found)
507 end
508 end
509
510 describe "/inbox" do
511 test "it inserts an incoming activity into the database", %{conn: conn} do
512 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
513
514 conn =
515 conn
516 |> assign(:valid_signature, true)
517 |> put_req_header("content-type", "application/activity+json")
518 |> post("/inbox", data)
519
520 assert "ok" == json_response(conn, 200)
521
522 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
523 assert Activity.get_by_ap_id(data["id"])
524 end
525
526 @tag capture_log: true
527 test "it inserts an incoming activity into the database" <>
528 "even if we can't fetch the user but have it in our db",
529 %{conn: conn} do
530 user =
531 insert(:user,
532 ap_id: "https://mastodon.example.org/users/raymoo",
533 ap_enabled: true,
534 local: false,
535 last_refreshed_at: nil
536 )
537
538 data =
539 File.read!("test/fixtures/mastodon-post-activity.json")
540 |> Jason.decode!()
541 |> Map.put("actor", user.ap_id)
542 |> put_in(["object", "attributedTo"], user.ap_id)
543
544 conn =
545 conn
546 |> assign(:valid_signature, true)
547 |> put_req_header("content-type", "application/activity+json")
548 |> post("/inbox", data)
549
550 assert "ok" == json_response(conn, 200)
551
552 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
553 assert Activity.get_by_ap_id(data["id"])
554 end
555
556 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
558
559 sender_url = data["actor"]
560 Instances.set_consistently_unreachable(sender_url)
561 refute Instances.reachable?(sender_url)
562
563 conn =
564 conn
565 |> assign(:valid_signature, true)
566 |> put_req_header("content-type", "application/activity+json")
567 |> post("/inbox", data)
568
569 assert "ok" == json_response(conn, 200)
570 assert Instances.reachable?(sender_url)
571 end
572
573 test "accept follow activity", %{conn: conn} do
574 clear_config([:instance, :federating], true)
575 relay = Relay.get_actor()
576
577 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
578
579 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
580 relay = refresh_record(relay)
581
582 accept =
583 File.read!("test/fixtures/relay/accept-follow.json")
584 |> String.replace("{{ap_id}}", relay.ap_id)
585 |> String.replace("{{activity_id}}", activity.data["id"])
586
587 assert "ok" ==
588 conn
589 |> assign(:valid_signature, true)
590 |> put_req_header("content-type", "application/activity+json")
591 |> post("/inbox", accept)
592 |> json_response(200)
593
594 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
595
596 assert Pleroma.FollowingRelationship.following?(
597 relay,
598 followed_relay
599 )
600
601 Mix.shell(Mix.Shell.Process)
602
603 on_exit(fn ->
604 Mix.shell(Mix.Shell.IO)
605 end)
606
607 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
608 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
609 end
610
611 @tag capture_log: true
612 test "without valid signature, " <>
613 "it only accepts Create activities and requires enabled federation",
614 %{conn: conn} do
615 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
616 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
617
618 conn = put_req_header(conn, "content-type", "application/activity+json")
619
620 clear_config([:instance, :federating], false)
621
622 conn
623 |> post("/inbox", data)
624 |> json_response(403)
625
626 conn
627 |> post("/inbox", non_create_data)
628 |> json_response(403)
629
630 clear_config([:instance, :federating], true)
631
632 ret_conn = post(conn, "/inbox", data)
633 assert "ok" == json_response(ret_conn, 200)
634
635 conn
636 |> post("/inbox", non_create_data)
637 |> json_response(400)
638 end
639
640 test "accepts Add/Remove activities", %{conn: conn} do
641 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
642
643 status =
644 File.read!("test/fixtures/statuses/note.json")
645 |> String.replace("{{nickname}}", "lain")
646 |> String.replace("{{object_id}}", object_id)
647
648 object_url = "https://example.com/objects/#{object_id}"
649
650 user =
651 File.read!("test/fixtures/users_mock/user.json")
652 |> String.replace("{{nickname}}", "lain")
653
654 actor = "https://example.com/users/lain"
655
656 Tesla.Mock.mock(fn
657 %{
658 method: :get,
659 url: ^object_url
660 } ->
661 %Tesla.Env{
662 status: 200,
663 body: status,
664 headers: [{"content-type", "application/activity+json"}]
665 }
666
667 %{
668 method: :get,
669 url: ^actor
670 } ->
671 %Tesla.Env{
672 status: 200,
673 body: user,
674 headers: [{"content-type", "application/activity+json"}]
675 }
676
677 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
678 %Tesla.Env{
679 status: 200,
680 body:
681 "test/fixtures/users_mock/masto_featured.json"
682 |> File.read!()
683 |> String.replace("{{domain}}", "example.com")
684 |> String.replace("{{nickname}}", "lain"),
685 headers: [{"content-type", "application/activity+json"}]
686 }
687 end)
688
689 data = %{
690 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
691 "actor" => actor,
692 "object" => object_url,
693 "target" => "https://example.com/users/lain/collections/featured",
694 "type" => "Add",
695 "to" => [Pleroma.Constants.as_public()]
696 }
697
698 assert "ok" ==
699 conn
700 |> assign(:valid_signature, true)
701 |> put_req_header("content-type", "application/activity+json")
702 |> post("/inbox", data)
703 |> json_response(200)
704
705 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
706 assert Activity.get_by_ap_id(data["id"])
707 user = User.get_cached_by_ap_id(data["actor"])
708 assert user.pinned_objects[data["object"]]
709
710 data = %{
711 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
712 "actor" => actor,
713 "object" => object_url,
714 "target" => "https://example.com/users/lain/collections/featured",
715 "type" => "Remove",
716 "to" => [Pleroma.Constants.as_public()]
717 }
718
719 assert "ok" ==
720 conn
721 |> assign(:valid_signature, true)
722 |> put_req_header("content-type", "application/activity+json")
723 |> post("/inbox", data)
724 |> json_response(200)
725
726 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
727 user = refresh_record(user)
728 refute user.pinned_objects[data["object"]]
729 end
730
731 test "mastodon pin/unpin", %{conn: conn} do
732 status_id = "105786274556060421"
733
734 status =
735 File.read!("test/fixtures/statuses/masto-note.json")
736 |> String.replace("{{nickname}}", "lain")
737 |> String.replace("{{status_id}}", status_id)
738
739 status_url = "https://example.com/users/lain/statuses/#{status_id}"
740
741 user =
742 File.read!("test/fixtures/users_mock/user.json")
743 |> String.replace("{{nickname}}", "lain")
744
745 actor = "https://example.com/users/lain"
746
747 Tesla.Mock.mock(fn
748 %{
749 method: :get,
750 url: ^status_url
751 } ->
752 %Tesla.Env{
753 status: 200,
754 body: status,
755 headers: [{"content-type", "application/activity+json"}]
756 }
757
758 %{
759 method: :get,
760 url: ^actor
761 } ->
762 %Tesla.Env{
763 status: 200,
764 body: user,
765 headers: [{"content-type", "application/activity+json"}]
766 }
767
768 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
769 %Tesla.Env{
770 status: 200,
771 body:
772 "test/fixtures/users_mock/masto_featured.json"
773 |> File.read!()
774 |> String.replace("{{domain}}", "example.com")
775 |> String.replace("{{nickname}}", "lain"),
776 headers: [{"content-type", "application/activity+json"}]
777 }
778 end)
779
780 data = %{
781 "@context" => "https://www.w3.org/ns/activitystreams",
782 "actor" => actor,
783 "object" => status_url,
784 "target" => "https://example.com/users/lain/collections/featured",
785 "type" => "Add"
786 }
787
788 assert "ok" ==
789 conn
790 |> assign(:valid_signature, true)
791 |> put_req_header("content-type", "application/activity+json")
792 |> post("/inbox", data)
793 |> json_response(200)
794
795 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
796 assert Activity.get_by_object_ap_id_with_object(data["object"])
797 user = User.get_cached_by_ap_id(data["actor"])
798 assert user.pinned_objects[data["object"]]
799
800 data = %{
801 "actor" => actor,
802 "object" => status_url,
803 "target" => "https://example.com/users/lain/collections/featured",
804 "type" => "Remove"
805 }
806
807 assert "ok" ==
808 conn
809 |> assign(:valid_signature, true)
810 |> put_req_header("content-type", "application/activity+json")
811 |> post("/inbox", data)
812 |> json_response(200)
813
814 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
815 assert Activity.get_by_object_ap_id_with_object(data["object"])
816 user = refresh_record(user)
817 refute user.pinned_objects[data["object"]]
818 end
819 end
820
821 describe "/users/:nickname/inbox" do
822 setup do
823 data =
824 File.read!("test/fixtures/mastodon-post-activity.json")
825 |> Jason.decode!()
826
827 [data: data]
828 end
829
830 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
831 user = insert(:user)
832
833 data =
834 data
835 |> Map.put("bcc", [user.ap_id])
836 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
837
838 conn =
839 conn
840 |> assign(:valid_signature, true)
841 |> put_req_header("content-type", "application/activity+json")
842 |> post("/users/#{user.nickname}/inbox", data)
843
844 assert "ok" == json_response(conn, 200)
845 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
846 assert Activity.get_by_ap_id(data["id"])
847 end
848
849 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
850 user = insert(:user)
851
852 data =
853 data
854 |> Map.put("to", user.ap_id)
855 |> Map.put("cc", [])
856 |> Kernel.put_in(["object", "to"], user.ap_id)
857 |> Kernel.put_in(["object", "cc"], [])
858
859 conn =
860 conn
861 |> assign(:valid_signature, true)
862 |> put_req_header("content-type", "application/activity+json")
863 |> post("/users/#{user.nickname}/inbox", data)
864
865 assert "ok" == json_response(conn, 200)
866 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
867 assert Activity.get_by_ap_id(data["id"])
868 end
869
870 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
871 user = insert(:user)
872
873 data =
874 data
875 |> Map.put("to", [])
876 |> Map.put("cc", user.ap_id)
877 |> Kernel.put_in(["object", "to"], [])
878 |> Kernel.put_in(["object", "cc"], user.ap_id)
879
880 conn =
881 conn
882 |> assign(:valid_signature, true)
883 |> put_req_header("content-type", "application/activity+json")
884 |> post("/users/#{user.nickname}/inbox", data)
885
886 assert "ok" == json_response(conn, 200)
887 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
888 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
889 assert user.ap_id in activity.recipients
890 end
891
892 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
893 user = insert(:user)
894
895 data =
896 data
897 |> Map.put("to", [])
898 |> Map.put("cc", [])
899 |> Map.put("bcc", user.ap_id)
900 |> Kernel.put_in(["object", "to"], [])
901 |> Kernel.put_in(["object", "cc"], [])
902 |> Kernel.put_in(["object", "bcc"], user.ap_id)
903
904 conn =
905 conn
906 |> assign(:valid_signature, true)
907 |> put_req_header("content-type", "application/activity+json")
908 |> post("/users/#{user.nickname}/inbox", data)
909
910 assert "ok" == json_response(conn, 200)
911 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
912 assert Activity.get_by_ap_id(data["id"])
913 end
914
915 test "it accepts announces with to as string instead of array", %{conn: conn} do
916 user = insert(:user)
917
918 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
919 announcer = insert(:user, local: false)
920
921 data = %{
922 "@context" => "https://www.w3.org/ns/activitystreams",
923 "actor" => announcer.ap_id,
924 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
925 "object" => post.data["object"],
926 "to" => "https://www.w3.org/ns/activitystreams#Public",
927 "cc" => [user.ap_id],
928 "type" => "Announce"
929 }
930
931 conn =
932 conn
933 |> assign(:valid_signature, true)
934 |> put_req_header("content-type", "application/activity+json")
935 |> post("/users/#{user.nickname}/inbox", data)
936
937 assert "ok" == json_response(conn, 200)
938 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
939 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
940 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
941 end
942
943 test "it accepts messages from actors that are followed by the user", %{
944 conn: conn,
945 data: data
946 } do
947 recipient = insert(:user)
948 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
949
950 {:ok, recipient, actor} = User.follow(recipient, actor)
951
952 object =
953 data["object"]
954 |> Map.put("attributedTo", actor.ap_id)
955
956 data =
957 data
958 |> Map.put("actor", actor.ap_id)
959 |> Map.put("object", object)
960
961 conn =
962 conn
963 |> assign(:valid_signature, true)
964 |> put_req_header("content-type", "application/activity+json")
965 |> post("/users/#{recipient.nickname}/inbox", data)
966
967 assert "ok" == json_response(conn, 200)
968 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
969 assert Activity.get_by_ap_id(data["id"])
970 end
971
972 test "it rejects reads from other users", %{conn: conn} do
973 user = insert(:user)
974 other_user = insert(:user)
975
976 conn =
977 conn
978 |> assign(:user, other_user)
979 |> put_req_header("accept", "application/activity+json")
980 |> get("/users/#{user.nickname}/inbox")
981
982 assert json_response(conn, 403)
983 end
984
985 test "it returns a note activity in a collection", %{conn: conn} do
986 note_activity = insert(:direct_note_activity)
987 note_object = Object.normalize(note_activity, fetch: false)
988 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
989
990 conn =
991 conn
992 |> assign(:user, user)
993 |> put_req_header("accept", "application/activity+json")
994 |> get("/users/#{user.nickname}/inbox?page=true")
995
996 assert response(conn, 200) =~ note_object.data["content"]
997 end
998
999 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1000 user = insert(:user)
1001 data = Map.put(data, "bcc", [user.ap_id])
1002
1003 sender_host = URI.parse(data["actor"]).host
1004 Instances.set_consistently_unreachable(sender_host)
1005 refute Instances.reachable?(sender_host)
1006
1007 conn =
1008 conn
1009 |> assign(:valid_signature, true)
1010 |> put_req_header("content-type", "application/activity+json")
1011 |> post("/users/#{user.nickname}/inbox", data)
1012
1013 assert "ok" == json_response(conn, 200)
1014 assert Instances.reachable?(sender_host)
1015 end
1016
1017 @tag capture_log: true
1018 test "it removes all follower collections but actor's", %{conn: conn} do
1019 [actor, recipient] = insert_pair(:user)
1020
1021 to = [
1022 recipient.ap_id,
1023 recipient.follower_address,
1024 "https://www.w3.org/ns/activitystreams#Public"
1025 ]
1026
1027 cc = [recipient.follower_address, actor.follower_address]
1028
1029 data = %{
1030 "@context" => ["https://www.w3.org/ns/activitystreams"],
1031 "type" => "Create",
1032 "id" => Utils.generate_activity_id(),
1033 "to" => to,
1034 "cc" => cc,
1035 "actor" => actor.ap_id,
1036 "object" => %{
1037 "type" => "Note",
1038 "to" => to,
1039 "cc" => cc,
1040 "content" => "It's a note",
1041 "attributedTo" => actor.ap_id,
1042 "id" => Utils.generate_object_id()
1043 }
1044 }
1045
1046 conn
1047 |> assign(:valid_signature, true)
1048 |> put_req_header("content-type", "application/activity+json")
1049 |> post("/users/#{recipient.nickname}/inbox", data)
1050 |> json_response(200)
1051
1052 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1053
1054 assert activity = Activity.get_by_ap_id(data["id"])
1055
1056 assert activity.id
1057 assert actor.follower_address in activity.recipients
1058 assert actor.follower_address in activity.data["cc"]
1059
1060 refute recipient.follower_address in activity.recipients
1061 refute recipient.follower_address in activity.data["cc"]
1062 refute recipient.follower_address in activity.data["to"]
1063 end
1064
1065 test "it requires authentication", %{conn: conn} do
1066 user = insert(:user)
1067 conn = put_req_header(conn, "accept", "application/activity+json")
1068
1069 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1070 assert json_response(ret_conn, 403)
1071
1072 ret_conn =
1073 conn
1074 |> assign(:user, user)
1075 |> get("/users/#{user.nickname}/inbox")
1076
1077 assert json_response(ret_conn, 200)
1078 end
1079
1080 @tag capture_log: true
1081 test "forwarded report", %{conn: conn} do
1082 admin = insert(:user, is_admin: true)
1083 actor = insert(:user, local: false)
1084 remote_domain = URI.parse(actor.ap_id).host
1085 reported_user = insert(:user)
1086
1087 note = insert(:note_activity, user: reported_user)
1088
1089 data = %{
1090 "@context" => [
1091 "https://www.w3.org/ns/activitystreams",
1092 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1093 %{
1094 "@language" => "und"
1095 }
1096 ],
1097 "actor" => actor.ap_id,
1098 "cc" => [
1099 reported_user.ap_id
1100 ],
1101 "content" => "test",
1102 "context" => "context",
1103 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1104 "nickname" => reported_user.nickname,
1105 "object" => [
1106 reported_user.ap_id,
1107 %{
1108 "actor" => %{
1109 "actor_type" => "Person",
1110 "approval_pending" => false,
1111 "avatar" => "",
1112 "confirmation_pending" => false,
1113 "deactivated" => false,
1114 "display_name" => "test user",
1115 "id" => reported_user.id,
1116 "local" => false,
1117 "nickname" => reported_user.nickname,
1118 "registration_reason" => nil,
1119 "roles" => %{
1120 "admin" => false,
1121 "moderator" => false
1122 },
1123 "tags" => [],
1124 "url" => reported_user.ap_id
1125 },
1126 "content" => "",
1127 "id" => note.data["id"],
1128 "published" => note.data["published"],
1129 "type" => "Note"
1130 }
1131 ],
1132 "published" => note.data["published"],
1133 "state" => "open",
1134 "to" => [],
1135 "type" => "Flag"
1136 }
1137
1138 conn
1139 |> assign(:valid_signature, true)
1140 |> put_req_header("content-type", "application/activity+json")
1141 |> post("/users/#{reported_user.nickname}/inbox", data)
1142 |> json_response(200)
1143
1144 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1145
1146 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1147
1148 ObanHelpers.perform_all()
1149
1150 Swoosh.TestAssertions.assert_email_sent(
1151 to: {admin.name, admin.email},
1152 html_body: ~r/Reported Account:/i
1153 )
1154 end
1155
1156 @tag capture_log: true
1157 test "forwarded report from mastodon", %{conn: conn} do
1158 admin = insert(:user, is_admin: true)
1159 actor = insert(:user, local: false)
1160 remote_domain = URI.parse(actor.ap_id).host
1161 remote_actor = "https://#{remote_domain}/actor"
1162 [reported_user, another] = insert_list(2, :user)
1163
1164 note = insert(:note_activity, user: reported_user)
1165
1166 Pleroma.Web.CommonAPI.favorite(another, note.id)
1167
1168 mock_json_body =
1169 "test/fixtures/mastodon/application_actor.json"
1170 |> File.read!()
1171 |> String.replace("{{DOMAIN}}", remote_domain)
1172
1173 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1174 %Tesla.Env{
1175 status: 200,
1176 body: mock_json_body,
1177 headers: [{"content-type", "application/activity+json"}]
1178 }
1179 end)
1180
1181 data = %{
1182 "@context" => "https://www.w3.org/ns/activitystreams",
1183 "actor" => remote_actor,
1184 "content" => "test report",
1185 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1186 "object" => [
1187 reported_user.ap_id,
1188 note.data["object"]
1189 ],
1190 "type" => "Flag"
1191 }
1192
1193 conn
1194 |> assign(:valid_signature, true)
1195 |> put_req_header("content-type", "application/activity+json")
1196 |> post("/users/#{reported_user.nickname}/inbox", data)
1197 |> json_response(200)
1198
1199 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1200
1201 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1202 reported_user_ap_id = reported_user.ap_id
1203
1204 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1205
1206 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1207 ObanHelpers.perform_all()
1208
1209 Swoosh.TestAssertions.assert_email_sent(
1210 to: {admin.name, admin.email},
1211 html_body: ~r/#{note.data["object"]}/i
1212 )
1213 end
1214 end
1215
1216 describe "GET /users/:nickname/outbox" do
1217 test "it paginates correctly", %{conn: conn} do
1218 user = insert(:user)
1219 conn = assign(conn, :user, user)
1220 outbox_endpoint = user.ap_id <> "/outbox"
1221
1222 _posts =
1223 for i <- 0..25 do
1224 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1225 activity
1226 end
1227
1228 result =
1229 conn
1230 |> put_req_header("accept", "application/activity+json")
1231 |> get(outbox_endpoint <> "?page=true")
1232 |> json_response(200)
1233
1234 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1235 assert length(result["orderedItems"]) == 20
1236 assert length(result_ids) == 20
1237 assert result["next"]
1238 assert String.starts_with?(result["next"], outbox_endpoint)
1239
1240 result_next =
1241 conn
1242 |> put_req_header("accept", "application/activity+json")
1243 |> get(result["next"])
1244 |> json_response(200)
1245
1246 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1247 assert length(result_next["orderedItems"]) == 6
1248 assert length(result_next_ids) == 6
1249 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1250 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1251 assert String.starts_with?(result["id"], outbox_endpoint)
1252
1253 result_next_again =
1254 conn
1255 |> put_req_header("accept", "application/activity+json")
1256 |> get(result_next["id"])
1257 |> json_response(200)
1258
1259 assert result_next == result_next_again
1260 end
1261
1262 test "it returns 200 even if there're no activities", %{conn: conn} do
1263 user = insert(:user)
1264 outbox_endpoint = user.ap_id <> "/outbox"
1265
1266 conn =
1267 conn
1268 |> assign(:user, user)
1269 |> put_req_header("accept", "application/activity+json")
1270 |> get(outbox_endpoint)
1271
1272 result = json_response(conn, 200)
1273 assert outbox_endpoint == result["id"]
1274 end
1275
1276 test "it returns a note activity in a collection", %{conn: conn} do
1277 note_activity = insert(:note_activity)
1278 note_object = Object.normalize(note_activity, fetch: false)
1279 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1280
1281 conn =
1282 conn
1283 |> assign(:user, user)
1284 |> put_req_header("accept", "application/activity+json")
1285 |> get("/users/#{user.nickname}/outbox?page=true")
1286
1287 assert response(conn, 200) =~ note_object.data["content"]
1288 end
1289
1290 test "it returns an announce activity in a collection", %{conn: conn} do
1291 announce_activity = insert(:announce_activity)
1292 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1293
1294 conn =
1295 conn
1296 |> assign(:user, user)
1297 |> put_req_header("accept", "application/activity+json")
1298 |> get("/users/#{user.nickname}/outbox?page=true")
1299
1300 assert response(conn, 200) =~ announce_activity.data["object"]
1301 end
1302
1303 test "It returns poll Answers when authenticated", %{conn: conn} do
1304 poller = insert(:user)
1305 voter = insert(:user)
1306
1307 {:ok, activity} =
1308 CommonAPI.post(poller, %{
1309 status: "suya...",
1310 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1311 })
1312
1313 assert question = Object.normalize(activity, fetch: false)
1314
1315 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1316
1317 assert outbox_get =
1318 conn
1319 |> assign(:user, voter)
1320 |> put_req_header("accept", "application/activity+json")
1321 |> get(voter.ap_id <> "/outbox?page=true")
1322 |> json_response(200)
1323
1324 assert [answer_outbox] = outbox_get["orderedItems"]
1325 assert answer_outbox["id"] == activity.data["id"]
1326 end
1327 end
1328
1329 describe "POST /users/:nickname/outbox (C2S)" do
1330 setup do: clear_config([:instance, :limit])
1331
1332 setup do
1333 [
1334 activity: %{
1335 "@context" => "https://www.w3.org/ns/activitystreams",
1336 "type" => "Create",
1337 "object" => %{
1338 "type" => "Note",
1339 "content" => "AP C2S test",
1340 "to" => "https://www.w3.org/ns/activitystreams#Public",
1341 "cc" => []
1342 }
1343 }
1344 ]
1345 end
1346
1347 test "it rejects posts from other users / unauthenticated users", %{
1348 conn: conn,
1349 activity: activity
1350 } do
1351 user = insert(:user)
1352 other_user = insert(:user)
1353 conn = put_req_header(conn, "content-type", "application/activity+json")
1354
1355 conn
1356 |> post("/users/#{user.nickname}/outbox", activity)
1357 |> json_response(403)
1358
1359 conn
1360 |> assign(:user, other_user)
1361 |> post("/users/#{user.nickname}/outbox", activity)
1362 |> json_response(403)
1363 end
1364
1365 test "it inserts an incoming create activity into the database", %{
1366 conn: conn,
1367 activity: activity
1368 } do
1369 user = insert(:user)
1370
1371 result =
1372 conn
1373 |> assign(:user, user)
1374 |> put_req_header("content-type", "application/activity+json")
1375 |> post("/users/#{user.nickname}/outbox", activity)
1376 |> json_response(201)
1377
1378 assert Activity.get_by_ap_id(result["id"])
1379 assert result["object"]
1380 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1381 assert object["content"] == activity["object"]["content"]
1382 end
1383
1384 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1385 user = insert(:user)
1386
1387 activity =
1388 activity
1389 |> put_in(["object", "type"], "Benis")
1390
1391 _result =
1392 conn
1393 |> assign(:user, user)
1394 |> put_req_header("content-type", "application/activity+json")
1395 |> post("/users/#{user.nickname}/outbox", activity)
1396 |> json_response(400)
1397 end
1398
1399 test "it inserts an incoming sensitive activity into the database", %{
1400 conn: conn,
1401 activity: activity
1402 } do
1403 user = insert(:user)
1404 conn = assign(conn, :user, user)
1405 object = Map.put(activity["object"], "sensitive", true)
1406 activity = Map.put(activity, "object", object)
1407
1408 response =
1409 conn
1410 |> put_req_header("content-type", "application/activity+json")
1411 |> post("/users/#{user.nickname}/outbox", activity)
1412 |> json_response(201)
1413
1414 assert Activity.get_by_ap_id(response["id"])
1415 assert response["object"]
1416 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1417 assert response_object["sensitive"] == true
1418 assert response_object["content"] == activity["object"]["content"]
1419
1420 representation =
1421 conn
1422 |> put_req_header("accept", "application/activity+json")
1423 |> get(response["id"])
1424 |> json_response(200)
1425
1426 assert representation["object"]["sensitive"] == true
1427 end
1428
1429 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1430 user = insert(:user)
1431 activity = Map.put(activity, "type", "BadType")
1432
1433 conn =
1434 conn
1435 |> assign(:user, user)
1436 |> put_req_header("content-type", "application/activity+json")
1437 |> post("/users/#{user.nickname}/outbox", activity)
1438
1439 assert json_response(conn, 400)
1440 end
1441
1442 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1443 note_activity = insert(:note_activity)
1444 note_object = Object.normalize(note_activity, fetch: false)
1445 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1446
1447 data = %{
1448 "type" => "Delete",
1449 "object" => %{
1450 "id" => note_object.data["id"]
1451 }
1452 }
1453
1454 result =
1455 conn
1456 |> assign(:user, user)
1457 |> put_req_header("content-type", "application/activity+json")
1458 |> post("/users/#{user.nickname}/outbox", data)
1459 |> json_response(201)
1460
1461 assert Activity.get_by_ap_id(result["id"])
1462
1463 assert object = Object.get_by_ap_id(note_object.data["id"])
1464 assert object.data["type"] == "Tombstone"
1465 end
1466
1467 test "it rejects delete activity of object from other actor", %{conn: conn} do
1468 note_activity = insert(:note_activity)
1469 note_object = Object.normalize(note_activity, fetch: false)
1470 user = insert(:user)
1471
1472 data = %{
1473 type: "Delete",
1474 object: %{
1475 id: note_object.data["id"]
1476 }
1477 }
1478
1479 conn =
1480 conn
1481 |> assign(:user, user)
1482 |> put_req_header("content-type", "application/activity+json")
1483 |> post("/users/#{user.nickname}/outbox", data)
1484
1485 assert json_response(conn, 403)
1486 end
1487
1488 test "it increases like count when receiving a like action", %{conn: conn} do
1489 note_activity = insert(:note_activity)
1490 note_object = Object.normalize(note_activity, fetch: false)
1491 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1492
1493 data = %{
1494 type: "Like",
1495 object: %{
1496 id: note_object.data["id"]
1497 }
1498 }
1499
1500 conn =
1501 conn
1502 |> assign(:user, user)
1503 |> put_req_header("content-type", "application/activity+json")
1504 |> post("/users/#{user.nickname}/outbox", data)
1505
1506 result = json_response(conn, 201)
1507 assert Activity.get_by_ap_id(result["id"])
1508
1509 assert object = Object.get_by_ap_id(note_object.data["id"])
1510 assert object.data["like_count"] == 1
1511 end
1512
1513 test "it doesn't spreads faulty attributedTo or actor fields", %{
1514 conn: conn,
1515 activity: activity
1516 } do
1517 reimu = insert(:user, nickname: "reimu")
1518 cirno = insert(:user, nickname: "cirno")
1519
1520 assert reimu.ap_id
1521 assert cirno.ap_id
1522
1523 activity =
1524 activity
1525 |> put_in(["object", "actor"], reimu.ap_id)
1526 |> put_in(["object", "attributedTo"], reimu.ap_id)
1527 |> put_in(["actor"], reimu.ap_id)
1528 |> put_in(["attributedTo"], reimu.ap_id)
1529
1530 _reimu_outbox =
1531 conn
1532 |> assign(:user, cirno)
1533 |> put_req_header("content-type", "application/activity+json")
1534 |> post("/users/#{reimu.nickname}/outbox", activity)
1535 |> json_response(403)
1536
1537 cirno_outbox =
1538 conn
1539 |> assign(:user, cirno)
1540 |> put_req_header("content-type", "application/activity+json")
1541 |> post("/users/#{cirno.nickname}/outbox", activity)
1542 |> json_response(201)
1543
1544 assert cirno_outbox["attributedTo"] == nil
1545 assert cirno_outbox["actor"] == cirno.ap_id
1546
1547 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1548 assert cirno_object.data["actor"] == cirno.ap_id
1549 assert cirno_object.data["attributedTo"] == cirno.ap_id
1550 end
1551
1552 test "Character limitation", %{conn: conn, activity: activity} do
1553 clear_config([:instance, :limit], 5)
1554 user = insert(:user)
1555
1556 result =
1557 conn
1558 |> assign(:user, user)
1559 |> put_req_header("content-type", "application/activity+json")
1560 |> post("/users/#{user.nickname}/outbox", activity)
1561 |> json_response(400)
1562
1563 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1564 end
1565 end
1566
1567 describe "/relay/followers" do
1568 test "it returns relay followers", %{conn: conn} do
1569 relay_actor = Relay.get_actor()
1570 user = insert(:user)
1571 User.follow(user, relay_actor)
1572
1573 result =
1574 conn
1575 |> get("/relay/followers")
1576 |> json_response(200)
1577
1578 assert result["first"]["orderedItems"] == [user.ap_id]
1579 end
1580
1581 test "on non-federating instance, it returns 404", %{conn: conn} do
1582 clear_config([:instance, :federating], false)
1583 user = insert(:user)
1584
1585 conn
1586 |> assign(:user, user)
1587 |> get("/relay/followers")
1588 |> json_response(404)
1589 end
1590 end
1591
1592 describe "/relay/following" do
1593 test "it returns relay following", %{conn: conn} do
1594 result =
1595 conn
1596 |> get("/relay/following")
1597 |> json_response(200)
1598
1599 assert result["first"]["orderedItems"] == []
1600 end
1601
1602 test "on non-federating instance, it returns 404", %{conn: conn} do
1603 clear_config([:instance, :federating], false)
1604 user = insert(:user)
1605
1606 conn
1607 |> assign(:user, user)
1608 |> get("/relay/following")
1609 |> json_response(404)
1610 end
1611 end
1612
1613 describe "/users/:nickname/followers" do
1614 test "it returns the followers in a collection", %{conn: conn} do
1615 user = insert(:user)
1616 user_two = insert(:user)
1617 User.follow(user, user_two)
1618
1619 result =
1620 conn
1621 |> assign(:user, user_two)
1622 |> get("/users/#{user_two.nickname}/followers")
1623 |> json_response(200)
1624
1625 assert result["first"]["orderedItems"] == [user.ap_id]
1626 end
1627
1628 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1629 user = insert(:user)
1630 user_two = insert(:user, hide_followers: true)
1631 User.follow(user, user_two)
1632
1633 result =
1634 conn
1635 |> assign(:user, user)
1636 |> get("/users/#{user_two.nickname}/followers")
1637 |> json_response(200)
1638
1639 assert is_binary(result["first"])
1640 end
1641
1642 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1643 %{conn: conn} do
1644 user = insert(:user)
1645 other_user = insert(:user, hide_followers: true)
1646
1647 result =
1648 conn
1649 |> assign(:user, user)
1650 |> get("/users/#{other_user.nickname}/followers?page=1")
1651
1652 assert result.status == 403
1653 assert result.resp_body == ""
1654 end
1655
1656 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1657 %{conn: conn} do
1658 user = insert(:user, hide_followers: true)
1659 other_user = insert(:user)
1660 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1661
1662 result =
1663 conn
1664 |> assign(:user, user)
1665 |> get("/users/#{user.nickname}/followers?page=1")
1666 |> json_response(200)
1667
1668 assert result["totalItems"] == 1
1669 assert result["orderedItems"] == [other_user.ap_id]
1670 end
1671
1672 test "it works for more than 10 users", %{conn: conn} do
1673 user = insert(:user)
1674
1675 Enum.each(1..15, fn _ ->
1676 other_user = insert(:user)
1677 User.follow(other_user, user)
1678 end)
1679
1680 result =
1681 conn
1682 |> assign(:user, user)
1683 |> get("/users/#{user.nickname}/followers")
1684 |> json_response(200)
1685
1686 assert length(result["first"]["orderedItems"]) == 10
1687 assert result["first"]["totalItems"] == 15
1688 assert result["totalItems"] == 15
1689
1690 result =
1691 conn
1692 |> assign(:user, user)
1693 |> get("/users/#{user.nickname}/followers?page=2")
1694 |> json_response(200)
1695
1696 assert length(result["orderedItems"]) == 5
1697 assert result["totalItems"] == 15
1698 end
1699
1700 test "does not require authentication", %{conn: conn} do
1701 user = insert(:user)
1702
1703 conn
1704 |> get("/users/#{user.nickname}/followers")
1705 |> json_response(200)
1706 end
1707 end
1708
1709 describe "/users/:nickname/following" do
1710 test "it returns the following in a collection", %{conn: conn} do
1711 user = insert(:user)
1712 user_two = insert(:user)
1713 User.follow(user, user_two)
1714
1715 result =
1716 conn
1717 |> assign(:user, user)
1718 |> get("/users/#{user.nickname}/following")
1719 |> json_response(200)
1720
1721 assert result["first"]["orderedItems"] == [user_two.ap_id]
1722 end
1723
1724 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1725 user = insert(:user)
1726 user_two = insert(:user, hide_follows: true)
1727 User.follow(user, user_two)
1728
1729 result =
1730 conn
1731 |> assign(:user, user)
1732 |> get("/users/#{user_two.nickname}/following")
1733 |> json_response(200)
1734
1735 assert is_binary(result["first"])
1736 end
1737
1738 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1739 %{conn: conn} do
1740 user = insert(:user)
1741 user_two = insert(:user, hide_follows: true)
1742
1743 result =
1744 conn
1745 |> assign(:user, user)
1746 |> get("/users/#{user_two.nickname}/following?page=1")
1747
1748 assert result.status == 403
1749 assert result.resp_body == ""
1750 end
1751
1752 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1753 %{conn: conn} do
1754 user = insert(:user, hide_follows: true)
1755 other_user = insert(:user)
1756 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1757
1758 result =
1759 conn
1760 |> assign(:user, user)
1761 |> get("/users/#{user.nickname}/following?page=1")
1762 |> json_response(200)
1763
1764 assert result["totalItems"] == 1
1765 assert result["orderedItems"] == [other_user.ap_id]
1766 end
1767
1768 test "it works for more than 10 users", %{conn: conn} do
1769 user = insert(:user)
1770
1771 Enum.each(1..15, fn _ ->
1772 user = User.get_cached_by_id(user.id)
1773 other_user = insert(:user)
1774 User.follow(user, other_user)
1775 end)
1776
1777 result =
1778 conn
1779 |> assign(:user, user)
1780 |> get("/users/#{user.nickname}/following")
1781 |> json_response(200)
1782
1783 assert length(result["first"]["orderedItems"]) == 10
1784 assert result["first"]["totalItems"] == 15
1785 assert result["totalItems"] == 15
1786
1787 result =
1788 conn
1789 |> assign(:user, user)
1790 |> get("/users/#{user.nickname}/following?page=2")
1791 |> json_response(200)
1792
1793 assert length(result["orderedItems"]) == 5
1794 assert result["totalItems"] == 15
1795 end
1796
1797 test "does not require authentication", %{conn: conn} do
1798 user = insert(:user)
1799
1800 conn
1801 |> get("/users/#{user.nickname}/following")
1802 |> json_response(200)
1803 end
1804 end
1805
1806 describe "delivery tracking" do
1807 test "it tracks a signed object fetch", %{conn: conn} do
1808 user = insert(:user, local: false)
1809 activity = insert(:note_activity)
1810 object = Object.normalize(activity, fetch: false)
1811
1812 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1813
1814 conn
1815 |> put_req_header("accept", "application/activity+json")
1816 |> assign(:user, user)
1817 |> get(object_path)
1818 |> json_response(200)
1819
1820 assert Delivery.get(object.id, user.id)
1821 end
1822
1823 test "it tracks a signed activity fetch", %{conn: conn} do
1824 user = insert(:user, local: false)
1825 activity = insert(:note_activity)
1826 object = Object.normalize(activity, fetch: false)
1827
1828 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1829
1830 conn
1831 |> put_req_header("accept", "application/activity+json")
1832 |> assign(:user, user)
1833 |> get(activity_path)
1834 |> json_response(200)
1835
1836 assert Delivery.get(object.id, user.id)
1837 end
1838
1839 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1840 user = insert(:user, local: false)
1841 other_user = insert(:user, local: false)
1842 activity = insert(:note_activity)
1843 object = Object.normalize(activity, fetch: false)
1844
1845 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1846
1847 conn
1848 |> put_req_header("accept", "application/activity+json")
1849 |> assign(:user, user)
1850 |> get(object_path)
1851 |> json_response(200)
1852
1853 build_conn()
1854 |> put_req_header("accept", "application/activity+json")
1855 |> assign(:user, other_user)
1856 |> get(object_path)
1857 |> json_response(200)
1858
1859 assert Delivery.get(object.id, user.id)
1860 assert Delivery.get(object.id, other_user.id)
1861 end
1862
1863 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1864 user = insert(:user, local: false)
1865 other_user = insert(:user, local: false)
1866 activity = insert(:note_activity)
1867 object = Object.normalize(activity, fetch: false)
1868
1869 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1870
1871 conn
1872 |> put_req_header("accept", "application/activity+json")
1873 |> assign(:user, user)
1874 |> get(activity_path)
1875 |> json_response(200)
1876
1877 build_conn()
1878 |> put_req_header("accept", "application/activity+json")
1879 |> assign(:user, other_user)
1880 |> get(activity_path)
1881 |> json_response(200)
1882
1883 assert Delivery.get(object.id, user.id)
1884 assert Delivery.get(object.id, other_user.id)
1885 end
1886 end
1887
1888 describe "Additional ActivityPub C2S endpoints" do
1889 test "GET /api/ap/whoami", %{conn: conn} do
1890 user = insert(:user)
1891
1892 conn =
1893 conn
1894 |> assign(:user, user)
1895 |> get("/api/ap/whoami")
1896
1897 user = User.get_cached_by_id(user.id)
1898
1899 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1900
1901 conn
1902 |> get("/api/ap/whoami")
1903 |> json_response(403)
1904 end
1905
1906 setup do: clear_config([:media_proxy])
1907 setup do: clear_config([Pleroma.Upload])
1908
1909 test "POST /api/ap/upload_media", %{conn: conn} do
1910 user = insert(:user)
1911
1912 desc = "Description of the image"
1913
1914 image = %Plug.Upload{
1915 content_type: "image/jpeg",
1916 path: Path.absname("test/fixtures/image.jpg"),
1917 filename: "an_image.jpg"
1918 }
1919
1920 object =
1921 conn
1922 |> assign(:user, user)
1923 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1924 |> json_response(:created)
1925
1926 assert object["name"] == desc
1927 assert object["type"] == "Document"
1928 assert object["actor"] == user.ap_id
1929 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1930 assert is_binary(object_href)
1931 assert object_mediatype == "image/jpeg"
1932 assert String.ends_with?(object_href, ".jpg")
1933
1934 activity_request = %{
1935 "@context" => "https://www.w3.org/ns/activitystreams",
1936 "type" => "Create",
1937 "object" => %{
1938 "type" => "Note",
1939 "content" => "AP C2S test, attachment",
1940 "attachment" => [object],
1941 "to" => "https://www.w3.org/ns/activitystreams#Public",
1942 "cc" => []
1943 }
1944 }
1945
1946 activity_response =
1947 conn
1948 |> assign(:user, user)
1949 |> post("/users/#{user.nickname}/outbox", activity_request)
1950 |> json_response(:created)
1951
1952 assert activity_response["id"]
1953 assert activity_response["object"]
1954 assert activity_response["actor"] == user.ap_id
1955
1956 assert %Object{data: %{"attachment" => [attachment]}} =
1957 Object.normalize(activity_response["object"], fetch: false)
1958
1959 assert attachment["type"] == "Document"
1960 assert attachment["name"] == desc
1961
1962 assert [
1963 %{
1964 "href" => ^object_href,
1965 "type" => "Link",
1966 "mediaType" => ^object_mediatype
1967 }
1968 ] = attachment["url"]
1969
1970 # Fails if unauthenticated
1971 conn
1972 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1973 |> json_response(403)
1974 end
1975 end
1976
1977 test "pinned collection", %{conn: conn} do
1978 clear_config([:instance, :max_pinned_statuses], 2)
1979 user = insert(:user)
1980 objects = insert_list(2, :note, user: user)
1981
1982 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
1983 {:ok, updated} = User.add_pinned_object_id(user, object_id)
1984 updated
1985 end)
1986
1987 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
1988 refresh_record(user)
1989
1990 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
1991 conn
1992 |> get("/users/#{nickname}/collections/featured")
1993 |> json_response(200)
1994
1995 object_ids = Enum.map(items, & &1["id"])
1996
1997 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
1998 obj_id in object_ids
1999 end)
2000 end
2001 end