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