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