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