2019-07-21 20:18:45 -06:00
|
|
|
# Pleroma: A lightweight social networking server
|
2022-02-25 23:11:42 -07:00
|
|
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
2019-07-21 20:18:45 -06:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
2019-09-13 02:36:49 -06:00
|
|
|
use Pleroma.Web.ConnCase
|
2019-07-21 20:18:45 -06:00
|
|
|
|
2019-09-13 09:46:41 -06:00
|
|
|
import ExUnit.CaptureLog
|
2019-07-21 20:18:45 -06:00
|
|
|
import Pleroma.Factory
|
2019-07-23 13:15:48 -06:00
|
|
|
import Tesla.Mock
|
|
|
|
import Mock
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
alias Pleroma.Activity
|
2019-07-23 13:15:48 -06:00
|
|
|
alias Pleroma.Instances
|
2019-09-13 03:06:31 -06:00
|
|
|
alias Pleroma.Object
|
2019-07-21 20:18:45 -06:00
|
|
|
alias Pleroma.Web.ActivityPub.Publisher
|
2019-09-13 02:36:49 -06:00
|
|
|
alias Pleroma.Web.CommonAPI
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
@as_public "https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
|
2019-07-23 13:15:48 -06:00
|
|
|
setup do
|
|
|
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
|
2020-03-20 09:33:00 -06:00
|
|
|
setup_all do: clear_config([:instance, :federating], true)
|
2020-02-22 09:48:41 -07:00
|
|
|
|
2019-12-20 06:34:14 -07:00
|
|
|
describe "gather_webfinger_links/1" do
|
|
|
|
test "it returns links" do
|
|
|
|
user = insert(:user)
|
|
|
|
|
|
|
|
expected_links = [
|
|
|
|
%{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"},
|
|
|
|
%{
|
|
|
|
"href" => user.ap_id,
|
|
|
|
"rel" => "self",
|
|
|
|
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
|
|
|
},
|
|
|
|
%{
|
|
|
|
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
2021-05-31 14:09:11 -06:00
|
|
|
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
|
2019-12-20 06:34:14 -07:00
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
assert expected_links == Publisher.gather_webfinger_links(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-21 20:18:45 -06:00
|
|
|
describe "determine_inbox/2" do
|
|
|
|
test "it returns sharedInbox for messages involving as:Public in to" do
|
2020-03-31 23:47:07 -06:00
|
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"to" => [@as_public], "cc" => [user.follower_address]}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it returns sharedInbox for messages involving as:Public in cc" do
|
2020-03-31 23:47:07 -06:00
|
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"cc" => [@as_public], "to" => [user.follower_address]}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it returns sharedInbox for messages involving multiple recipients in to" do
|
2020-03-31 23:47:07 -06:00
|
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
2019-07-21 20:18:45 -06:00
|
|
|
user_two = insert(:user)
|
|
|
|
user_three = insert(:user)
|
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it returns sharedInbox for messages involving multiple recipients in cc" do
|
2020-03-31 23:47:07 -06:00
|
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
2019-07-21 20:18:45 -06:00
|
|
|
user_two = insert(:user)
|
|
|
|
user_three = insert(:user)
|
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it returns sharedInbox for messages involving multiple recipients in total" do
|
|
|
|
user =
|
2020-03-31 23:47:07 -06:00
|
|
|
insert(:user, %{
|
|
|
|
shared_inbox: "http://example.com/inbox",
|
|
|
|
inbox: "http://example.com/personal-inbox"
|
|
|
|
})
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
user_two = insert(:user)
|
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it returns inbox for messages involving single recipients in total" do
|
|
|
|
user =
|
2020-03-31 23:47:07 -06:00
|
|
|
insert(:user, %{
|
|
|
|
shared_inbox: "http://example.com/inbox",
|
|
|
|
inbox: "http://example.com/personal-inbox"
|
|
|
|
})
|
2019-07-21 20:18:45 -06:00
|
|
|
|
|
|
|
activity = %Activity{
|
|
|
|
data: %{"to" => [user.ap_id], "cc" => []}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
|
|
|
|
end
|
|
|
|
end
|
2019-07-23 13:15:48 -06:00
|
|
|
|
|
|
|
describe "publish_one/1" do
|
2020-07-14 12:44:08 -06:00
|
|
|
test "publish to url with with different ports" do
|
|
|
|
inbox80 = "http://42.site/users/nick1/inbox"
|
|
|
|
inbox42 = "http://42.site:42/users/nick1/inbox"
|
|
|
|
|
|
|
|
mock(fn
|
|
|
|
%{method: :post, url: "http://42.site:42/users/nick1/inbox"} ->
|
|
|
|
{:ok, %Tesla.Env{status: 200, body: "port 42"}}
|
|
|
|
|
|
|
|
%{method: :post, url: "http://42.site/users/nick1/inbox"} ->
|
|
|
|
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
|
|
|
|
end)
|
|
|
|
|
|
|
|
actor = insert(:user)
|
|
|
|
|
|
|
|
assert {:ok, %{body: "port 42"}} =
|
|
|
|
Publisher.publish_one(%{
|
|
|
|
inbox: inbox42,
|
|
|
|
json: "{}",
|
|
|
|
actor: actor,
|
|
|
|
id: 1,
|
|
|
|
unreachable_since: true
|
|
|
|
})
|
|
|
|
|
|
|
|
assert {:ok, %{body: "port 80"}} =
|
|
|
|
Publisher.publish_one(%{
|
|
|
|
inbox: inbox80,
|
|
|
|
json: "{}",
|
|
|
|
actor: actor,
|
|
|
|
id: 1,
|
|
|
|
unreachable_since: true
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2019-07-23 13:15:48 -06:00
|
|
|
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://200.site/users/nick1/inbox"
|
|
|
|
|
|
|
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
|
|
assert called(Instances.set_reachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://200.site/users/nick1/inbox"
|
|
|
|
|
|
|
|
assert {:ok, _} =
|
|
|
|
Publisher.publish_one(%{
|
|
|
|
inbox: inbox,
|
|
|
|
json: "{}",
|
|
|
|
actor: actor,
|
|
|
|
id: 1,
|
|
|
|
unreachable_since: NaiveDateTime.utc_now()
|
|
|
|
})
|
|
|
|
|
|
|
|
assert called(Instances.set_reachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://200.site/users/nick1/inbox"
|
|
|
|
|
|
|
|
assert {:ok, _} =
|
|
|
|
Publisher.publish_one(%{
|
|
|
|
inbox: inbox,
|
|
|
|
json: "{}",
|
|
|
|
actor: actor,
|
|
|
|
id: 1,
|
|
|
|
unreachable_since: nil
|
|
|
|
})
|
|
|
|
|
|
|
|
refute called(Instances.set_reachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://404.site/users/nick1/inbox"
|
|
|
|
|
|
|
|
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
|
|
|
|
|
|
assert called(Instances.set_unreachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
|
|
|
|
2019-09-13 09:46:41 -06:00
|
|
|
assert capture_log(fn ->
|
|
|
|
assert {:error, _} =
|
|
|
|
Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
|
|
end) =~ "connrefused"
|
2019-07-23 13:15:48 -06:00
|
|
|
|
|
|
|
assert called(Instances.set_unreachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://200.site/users/nick1/inbox"
|
|
|
|
|
|
|
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
|
|
|
|
|
|
refute called(Instances.set_unreachable(inbox))
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
|
|
|
|
Instances,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
actor = insert(:user)
|
|
|
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
|
|
|
|
2019-09-13 09:46:41 -06:00
|
|
|
assert capture_log(fn ->
|
|
|
|
assert {:error, _} =
|
|
|
|
Publisher.publish_one(%{
|
|
|
|
inbox: inbox,
|
|
|
|
json: "{}",
|
|
|
|
actor: actor,
|
|
|
|
id: 1,
|
|
|
|
unreachable_since: NaiveDateTime.utc_now()
|
|
|
|
})
|
|
|
|
end) =~ "connrefused"
|
2019-07-23 13:15:48 -06:00
|
|
|
|
|
|
|
refute called(Instances.set_unreachable(inbox))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "publish/2" do
|
2020-09-25 15:36:19 -06:00
|
|
|
test_with_mock "doesn't publish a non-public activity to quarantined instances.",
|
|
|
|
Pleroma.Web.Federator.Publisher,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
2020-10-02 06:51:39 -06:00
|
|
|
Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
|
2020-09-25 15:36:19 -06:00
|
|
|
|
|
|
|
follower =
|
|
|
|
insert(:user, %{
|
|
|
|
local: false,
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
|
|
|
ap_enabled: true
|
|
|
|
})
|
|
|
|
|
|
|
|
actor = insert(:user, follower_address: follower.ap_id)
|
|
|
|
|
|
|
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
|
|
|
actor = refresh_record(actor)
|
|
|
|
|
|
|
|
note_activity =
|
|
|
|
insert(:followers_only_note_activity,
|
|
|
|
user: actor,
|
|
|
|
recipients: [follower.ap_id]
|
|
|
|
)
|
|
|
|
|
|
|
|
res = Publisher.publish(actor, note_activity)
|
|
|
|
|
|
|
|
assert res == :ok
|
|
|
|
|
|
|
|
assert not called(
|
|
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
|
|
|
actor_id: actor.id,
|
|
|
|
id: note_activity.data["id"]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
|
|
|
|
Pleroma.Web.Federator.Publisher,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
2020-10-02 06:51:39 -06:00
|
|
|
Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
|
2020-09-25 15:36:19 -06:00
|
|
|
|
|
|
|
follower =
|
|
|
|
insert(:user, %{
|
|
|
|
local: false,
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
|
|
|
ap_enabled: true
|
|
|
|
})
|
|
|
|
|
|
|
|
actor = insert(:user, follower_address: follower.ap_id)
|
|
|
|
|
|
|
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
|
|
|
actor = refresh_record(actor)
|
|
|
|
|
|
|
|
note_activity =
|
|
|
|
insert(:followers_only_note_activity,
|
|
|
|
user: actor,
|
|
|
|
recipients: [follower.ap_id]
|
|
|
|
)
|
|
|
|
|
|
|
|
res = Publisher.publish(actor, note_activity)
|
|
|
|
|
|
|
|
assert res == :ok
|
|
|
|
|
|
|
|
assert called(
|
|
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
|
|
|
actor_id: actor.id,
|
|
|
|
id: note_activity.data["id"]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2019-07-23 13:15:48 -06:00
|
|
|
test_with_mock "publishes an activity with BCC to all relevant peers.",
|
|
|
|
Pleroma.Web.Federator.Publisher,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
follower =
|
2020-03-31 23:47:07 -06:00
|
|
|
insert(:user, %{
|
2019-07-23 13:15:48 -06:00
|
|
|
local: false,
|
2020-03-31 23:47:07 -06:00
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
2019-10-16 12:59:21 -06:00
|
|
|
ap_enabled: true
|
2020-03-31 23:47:07 -06:00
|
|
|
})
|
2019-07-23 13:15:48 -06:00
|
|
|
|
|
|
|
actor = insert(:user, follower_address: follower.ap_id)
|
|
|
|
user = insert(:user)
|
|
|
|
|
2020-12-01 13:17:52 -07:00
|
|
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
2019-07-23 13:15:48 -06:00
|
|
|
|
|
|
|
note_activity =
|
|
|
|
insert(:note_activity,
|
|
|
|
recipients: [follower.ap_id],
|
|
|
|
data_attrs: %{"bcc" => [user.ap_id]}
|
|
|
|
)
|
|
|
|
|
|
|
|
res = Publisher.publish(actor, note_activity)
|
|
|
|
assert res == :ok
|
|
|
|
|
|
|
|
assert called(
|
|
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
2019-08-01 08:28:00 -06:00
|
|
|
actor_id: actor.id,
|
2019-07-23 13:15:48 -06:00
|
|
|
id: note_activity.data["id"]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
end
|
2019-09-13 02:36:49 -06:00
|
|
|
|
|
|
|
test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
|
|
|
|
Pleroma.Web.Federator.Publisher,
|
|
|
|
[:passthrough],
|
|
|
|
[] do
|
|
|
|
fetcher =
|
|
|
|
insert(:user,
|
|
|
|
local: false,
|
2020-03-31 23:47:07 -06:00
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
2019-10-16 12:59:21 -06:00
|
|
|
ap_enabled: true
|
2019-09-13 02:36:49 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
another_fetcher =
|
|
|
|
insert(:user,
|
|
|
|
local: false,
|
2020-03-31 23:47:07 -06:00
|
|
|
inbox: "https://domain2.com/users/nick1/inbox",
|
2019-10-16 12:59:21 -06:00
|
|
|
ap_enabled: true
|
2019-09-13 02:36:49 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
actor = insert(:user)
|
|
|
|
|
|
|
|
note_activity = insert(:note_activity, user: actor)
|
2021-01-04 05:38:31 -07:00
|
|
|
object = Object.normalize(note_activity, fetch: false)
|
2019-09-13 02:36:49 -06:00
|
|
|
|
|
|
|
activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
|
|
|
|
object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
|
|
|
|
|
|
|
|
build_conn()
|
|
|
|
|> put_req_header("accept", "application/activity+json")
|
|
|
|
|> assign(:user, fetcher)
|
|
|
|
|> get(object_path)
|
|
|
|
|> json_response(200)
|
|
|
|
|
|
|
|
build_conn()
|
|
|
|
|> put_req_header("accept", "application/activity+json")
|
|
|
|
|> assign(:user, another_fetcher)
|
|
|
|
|> get(activity_path)
|
|
|
|
|> json_response(200)
|
|
|
|
|
|
|
|
{:ok, delete} = CommonAPI.delete(note_activity.id, actor)
|
|
|
|
|
|
|
|
res = Publisher.publish(actor, delete)
|
|
|
|
assert res == :ok
|
|
|
|
|
|
|
|
assert called(
|
|
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
2019-09-16 04:23:06 -06:00
|
|
|
actor_id: actor.id,
|
2019-09-13 02:36:49 -06:00
|
|
|
id: delete.data["id"]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
assert called(
|
|
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
|
|
inbox: "https://domain2.com/users/nick1/inbox",
|
2019-09-16 04:23:06 -06:00
|
|
|
actor_id: actor.id,
|
2019-09-13 02:36:49 -06:00
|
|
|
id: delete.data["id"]
|
|
|
|
})
|
|
|
|
)
|
|
|
|
end
|
2019-07-23 13:15:48 -06:00
|
|
|
end
|
2019-07-21 20:18:45 -06:00
|
|
|
end
|