First
[anni] / test / pleroma / web / activity_pub / publisher_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.PublisherTest do
6   use Pleroma.Web.ConnCase
7
8   import ExUnit.CaptureLog
9   import Pleroma.Factory
10   import Tesla.Mock
11   import Mock
12
13   alias Pleroma.Activity
14   alias Pleroma.Instances
15   alias Pleroma.Object
16   alias Pleroma.Web.ActivityPub.Publisher
17   alias Pleroma.Web.CommonAPI
18
19   @as_public "https://www.w3.org/ns/activitystreams#Public"
20
21   setup do
22     mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
23     :ok
24   end
25
26   setup_all do: clear_config([:instance, :federating], true)
27
28   describe "gather_webfinger_links/1" do
29     test "it returns links" do
30       user = insert(:user)
31
32       expected_links = [
33         %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"},
34         %{
35           "href" => user.ap_id,
36           "rel" => "self",
37           "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
38         },
39         %{
40           "rel" => "http://ostatus.org/schema/1.0/subscribe",
41           "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
42         }
43       ]
44
45       assert expected_links == Publisher.gather_webfinger_links(user)
46     end
47   end
48
49   describe "determine_inbox/2" do
50     test "it returns sharedInbox for messages involving as:Public in to" do
51       user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
52
53       activity = %Activity{
54         data: %{"to" => [@as_public], "cc" => [user.follower_address]}
55       }
56
57       assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
58     end
59
60     test "it returns sharedInbox for messages involving as:Public in cc" do
61       user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
62
63       activity = %Activity{
64         data: %{"cc" => [@as_public], "to" => [user.follower_address]}
65       }
66
67       assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
68     end
69
70     test "it returns sharedInbox for messages involving multiple recipients in to" do
71       user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
72       user_two = insert(:user)
73       user_three = insert(:user)
74
75       activity = %Activity{
76         data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
77       }
78
79       assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
80     end
81
82     test "it returns sharedInbox for messages involving multiple recipients in cc" do
83       user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
84       user_two = insert(:user)
85       user_three = insert(:user)
86
87       activity = %Activity{
88         data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
89       }
90
91       assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
92     end
93
94     test "it returns sharedInbox for messages involving multiple recipients in total" do
95       user =
96         insert(:user, %{
97           shared_inbox: "http://example.com/inbox",
98           inbox: "http://example.com/personal-inbox"
99         })
100
101       user_two = insert(:user)
102
103       activity = %Activity{
104         data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
105       }
106
107       assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
108     end
109
110     test "it returns inbox for messages involving single recipients in total" do
111       user =
112         insert(:user, %{
113           shared_inbox: "http://example.com/inbox",
114           inbox: "http://example.com/personal-inbox"
115         })
116
117       activity = %Activity{
118         data: %{"to" => [user.ap_id], "cc" => []}
119       }
120
121       assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
122     end
123   end
124
125   describe "publish_one/1" do
126     test "publish to url with with different ports" do
127       inbox80 = "http://42.site/users/nick1/inbox"
128       inbox42 = "http://42.site:42/users/nick1/inbox"
129
130       mock(fn
131         %{method: :post, url: "http://42.site:42/users/nick1/inbox"} ->
132           {:ok, %Tesla.Env{status: 200, body: "port 42"}}
133
134         %{method: :post, url: "http://42.site/users/nick1/inbox"} ->
135           {:ok, %Tesla.Env{status: 200, body: "port 80"}}
136       end)
137
138       actor = insert(:user)
139
140       assert {:ok, %{body: "port 42"}} =
141                Publisher.publish_one(%{
142                  inbox: inbox42,
143                  json: "{}",
144                  actor: actor,
145                  id: 1,
146                  unreachable_since: true
147                })
148
149       assert {:ok, %{body: "port 80"}} =
150                Publisher.publish_one(%{
151                  inbox: inbox80,
152                  json: "{}",
153                  actor: actor,
154                  id: 1,
155                  unreachable_since: true
156                })
157     end
158
159     test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
160                    Instances,
161                    [:passthrough],
162                    [] do
163       actor = insert(:user)
164       inbox = "http://200.site/users/nick1/inbox"
165
166       assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
167       assert called(Instances.set_reachable(inbox))
168     end
169
170     test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
171                    Instances,
172                    [:passthrough],
173                    [] do
174       actor = insert(:user)
175       inbox = "http://200.site/users/nick1/inbox"
176
177       assert {:ok, _} =
178                Publisher.publish_one(%{
179                  inbox: inbox,
180                  json: "{}",
181                  actor: actor,
182                  id: 1,
183                  unreachable_since: NaiveDateTime.utc_now()
184                })
185
186       assert called(Instances.set_reachable(inbox))
187     end
188
189     test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
190                    Instances,
191                    [:passthrough],
192                    [] do
193       actor = insert(:user)
194       inbox = "http://200.site/users/nick1/inbox"
195
196       assert {:ok, _} =
197                Publisher.publish_one(%{
198                  inbox: inbox,
199                  json: "{}",
200                  actor: actor,
201                  id: 1,
202                  unreachable_since: nil
203                })
204
205       refute called(Instances.set_reachable(inbox))
206     end
207
208     test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
209                    Instances,
210                    [:passthrough],
211                    [] do
212       actor = insert(:user)
213       inbox = "http://404.site/users/nick1/inbox"
214
215       assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
216
217       assert called(Instances.set_unreachable(inbox))
218     end
219
220     test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
221                    Instances,
222                    [:passthrough],
223                    [] do
224       actor = insert(:user)
225       inbox = "http://connrefused.site/users/nick1/inbox"
226
227       assert capture_log(fn ->
228                assert {:error, _} =
229                         Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
230              end) =~ "connrefused"
231
232       assert called(Instances.set_unreachable(inbox))
233     end
234
235     test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
236                    Instances,
237                    [:passthrough],
238                    [] do
239       actor = insert(:user)
240       inbox = "http://200.site/users/nick1/inbox"
241
242       assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
243
244       refute called(Instances.set_unreachable(inbox))
245     end
246
247     test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
248                    Instances,
249                    [:passthrough],
250                    [] do
251       actor = insert(:user)
252       inbox = "http://connrefused.site/users/nick1/inbox"
253
254       assert capture_log(fn ->
255                assert {:error, _} =
256                         Publisher.publish_one(%{
257                           inbox: inbox,
258                           json: "{}",
259                           actor: actor,
260                           id: 1,
261                           unreachable_since: NaiveDateTime.utc_now()
262                         })
263              end) =~ "connrefused"
264
265       refute called(Instances.set_unreachable(inbox))
266     end
267   end
268
269   describe "publish/2" do
270     test_with_mock "doesn't publish a non-public activity to quarantined instances.",
271                    Pleroma.Web.Federator.Publisher,
272                    [:passthrough],
273                    [] do
274       Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
275
276       follower =
277         insert(:user, %{
278           local: false,
279           inbox: "https://domain.com/users/nick1/inbox",
280           ap_enabled: true
281         })
282
283       actor = insert(:user, follower_address: follower.ap_id)
284
285       {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
286       actor = refresh_record(actor)
287
288       note_activity =
289         insert(:followers_only_note_activity,
290           user: actor,
291           recipients: [follower.ap_id]
292         )
293
294       res = Publisher.publish(actor, note_activity)
295
296       assert res == :ok
297
298       assert not called(
299                Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
300                  inbox: "https://domain.com/users/nick1/inbox",
301                  actor_id: actor.id,
302                  id: note_activity.data["id"]
303                })
304              )
305     end
306
307     test_with_mock "Publishes a non-public activity to non-quarantined instances.",
308                    Pleroma.Web.Federator.Publisher,
309                    [:passthrough],
310                    [] do
311       Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
312
313       follower =
314         insert(:user, %{
315           local: false,
316           inbox: "https://domain.com/users/nick1/inbox",
317           ap_enabled: true
318         })
319
320       actor = insert(:user, follower_address: follower.ap_id)
321
322       {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
323       actor = refresh_record(actor)
324
325       note_activity =
326         insert(:followers_only_note_activity,
327           user: actor,
328           recipients: [follower.ap_id]
329         )
330
331       res = Publisher.publish(actor, note_activity)
332
333       assert res == :ok
334
335       assert called(
336                Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
337                  inbox: "https://domain.com/users/nick1/inbox",
338                  actor_id: actor.id,
339                  id: note_activity.data["id"]
340                })
341              )
342     end
343
344     test_with_mock "publishes an activity with BCC to all relevant peers.",
345                    Pleroma.Web.Federator.Publisher,
346                    [:passthrough],
347                    [] do
348       follower =
349         insert(:user, %{
350           local: false,
351           inbox: "https://domain.com/users/nick1/inbox",
352           ap_enabled: true
353         })
354
355       actor = insert(:user, follower_address: follower.ap_id)
356       user = insert(:user)
357
358       {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
359
360       note_activity =
361         insert(:note_activity,
362           recipients: [follower.ap_id],
363           data_attrs: %{"bcc" => [user.ap_id]}
364         )
365
366       res = Publisher.publish(actor, note_activity)
367       assert res == :ok
368
369       assert called(
370                Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
371                  inbox: "https://domain.com/users/nick1/inbox",
372                  actor_id: actor.id,
373                  id: note_activity.data["id"]
374                })
375              )
376     end
377
378     test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
379                    Pleroma.Web.Federator.Publisher,
380                    [:passthrough],
381                    [] do
382       fetcher =
383         insert(:user,
384           local: false,
385           inbox: "https://domain.com/users/nick1/inbox",
386           ap_enabled: true
387         )
388
389       another_fetcher =
390         insert(:user,
391           local: false,
392           inbox: "https://domain2.com/users/nick1/inbox",
393           ap_enabled: true
394         )
395
396       actor = insert(:user)
397
398       note_activity = insert(:note_activity, user: actor)
399       object = Object.normalize(note_activity, fetch: false)
400
401       activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
402       object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
403
404       build_conn()
405       |> put_req_header("accept", "application/activity+json")
406       |> assign(:user, fetcher)
407       |> get(object_path)
408       |> json_response(200)
409
410       build_conn()
411       |> put_req_header("accept", "application/activity+json")
412       |> assign(:user, another_fetcher)
413       |> get(activity_path)
414       |> json_response(200)
415
416       {:ok, delete} = CommonAPI.delete(note_activity.id, actor)
417
418       res = Publisher.publish(actor, delete)
419       assert res == :ok
420
421       assert called(
422                Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
423                  inbox: "https://domain.com/users/nick1/inbox",
424                  actor_id: actor.id,
425                  id: delete.data["id"]
426                })
427              )
428
429       assert called(
430                Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
431                  inbox: "https://domain2.com/users/nick1/inbox",
432                  actor_id: actor.id,
433                  id: delete.data["id"]
434                })
435              )
436     end
437   end
438 end