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