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