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