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