
It was used to migrate OStatus connections to ActivityPub if possible, but support for OStatus was long since dropped, all new actors always AP and if anything wasn't migrated before, their instance is already marked as unreachable anyway. The associated logic was also buggy in several ways and deleted users got set to ap_enabled=false also causing some issues. This patch is a pretty direct port of the original Pleroma MR; follow-up commits will further fix and clean up remaining issues. Changes made (other than trivial merge conflict resolutions): - converted CHANGELOG format - adapted migration id for Akkoma’s timeline - removed ap_enabled from additional tests Ported-from: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3880
521 lines
16 KiB
Elixir
521 lines
16 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
|
use Pleroma.Web.ConnCase, async: false
|
|
@moduletag :mocked
|
|
|
|
import ExUnit.CaptureLog
|
|
import Pleroma.Factory
|
|
import Tesla.Mock
|
|
import Mock
|
|
|
|
alias Pleroma.Activity
|
|
alias Pleroma.Instances
|
|
alias Pleroma.Object
|
|
alias Pleroma.Web.ActivityPub.Publisher
|
|
alias Pleroma.Web.CommonAPI
|
|
|
|
@as_public "https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
setup do
|
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
:ok
|
|
end
|
|
|
|
setup_all do
|
|
clear_config([:instance, :federating], true)
|
|
clear_config([:instance, :quarantined_instances], [])
|
|
clear_config([:mrf_simple, :reject], [])
|
|
end
|
|
|
|
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",
|
|
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
|
|
}
|
|
]
|
|
|
|
assert expected_links == Publisher.gather_webfinger_links(user)
|
|
end
|
|
end
|
|
|
|
describe "determine_inbox/2" do
|
|
test "it returns sharedInbox for messages involving as:Public in to" do
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
|
|
|
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
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
|
|
|
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
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
|
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
|
|
user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
|
|
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 =
|
|
insert(:user, %{
|
|
shared_inbox: "http://example.com/inbox",
|
|
inbox: "http://example.com/personal-inbox"
|
|
})
|
|
|
|
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 =
|
|
insert(:user, %{
|
|
shared_inbox: "http://example.com/inbox",
|
|
inbox: "http://example.com/personal-inbox"
|
|
})
|
|
|
|
activity = %Activity{
|
|
data: %{"to" => [user.ap_id], "cc" => []}
|
|
}
|
|
|
|
assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
|
|
end
|
|
end
|
|
|
|
describe "publish_one/1" do
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
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
|
|
|
|
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
|
|
Instances,
|
|
[:passthrough],
|
|
[] do
|
|
actor =
|
|
insert(:user)
|
|
|> with_signing_key()
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
|
|
|
assert capture_log(fn ->
|
|
assert {:error, _} =
|
|
Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
|
end) =~ "connrefused"
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
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)
|
|
|> with_signing_key()
|
|
|
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
|
|
|
assert capture_log(fn ->
|
|
assert {:error, _} =
|
|
Publisher.publish_one(%{
|
|
inbox: inbox,
|
|
json: "{}",
|
|
actor: actor,
|
|
id: 1,
|
|
unreachable_since: NaiveDateTime.utc_now()
|
|
})
|
|
end) =~ "connrefused"
|
|
|
|
refute called(Instances.set_unreachable(inbox))
|
|
end
|
|
end
|
|
|
|
describe "publish/2" do
|
|
test_with_mock "doesn't publish any activity to quarantined or rejected instances.",
|
|
Pleroma.Web.Federator.Publisher,
|
|
[:passthrough],
|
|
[] do
|
|
Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
|
|
|
|
Config.put([:mrf_simple, :reject], [{"rejected.com", "some reason"}])
|
|
|
|
follower =
|
|
insert(:user, %{
|
|
local: false,
|
|
inbox: "https://domain.com/users/nick1/inbox"
|
|
})
|
|
|
|
another_follower =
|
|
insert(:user, %{
|
|
local: false,
|
|
inbox: "https://rejected.com/users/nick2/inbox"
|
|
})
|
|
|
|
actor =
|
|
insert(:user, follower_address: follower.ap_id)
|
|
|> with_signing_key()
|
|
|
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
|
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
|
|
|
|
actor = refresh_record(actor)
|
|
|
|
note_activity =
|
|
insert(:followers_only_note_activity,
|
|
user: actor,
|
|
recipients: [follower.ap_id]
|
|
)
|
|
|
|
public_note_activity =
|
|
insert(:note_activity,
|
|
user: actor,
|
|
recipients: [follower.ap_id, @as_public]
|
|
)
|
|
|
|
res = Publisher.publish(actor, note_activity)
|
|
|
|
assert res == :ok
|
|
|
|
:ok = Publisher.publish(actor, public_note_activity)
|
|
|
|
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"]
|
|
})
|
|
)
|
|
|
|
assert not called(
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
inbox: "https://domain.com/users/nick1/inbox",
|
|
actor_id: actor.id,
|
|
id: public_note_activity.data["id"]
|
|
})
|
|
)
|
|
|
|
assert not called(
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
inbox: "https://rejected.com/users/nick2/inbox",
|
|
actor_id: actor.id,
|
|
id: note_activity.data["id"]
|
|
})
|
|
)
|
|
|
|
assert not called(
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
inbox: "https://rejected.com/users/nick2/inbox",
|
|
actor_id: actor.id,
|
|
id: public_note_activity.data["id"]
|
|
})
|
|
)
|
|
end
|
|
|
|
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
|
|
Pleroma.Web.Federator.Publisher,
|
|
[:passthrough],
|
|
[] do
|
|
Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
|
|
|
|
follower =
|
|
insert(:user, %{
|
|
local: false,
|
|
inbox: "https://domain.com/users/nick1/inbox"
|
|
})
|
|
|
|
actor =
|
|
insert(:user, follower_address: follower.ap_id)
|
|
|> with_signing_key()
|
|
|
|
{: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
|
|
|
|
test_with_mock "publishes an activity with BCC to all relevant peers.",
|
|
Pleroma.Web.Federator.Publisher,
|
|
[:passthrough],
|
|
[] do
|
|
Config.put([:instance, :quarantined_instances], [])
|
|
|
|
follower =
|
|
insert(:user, %{
|
|
local: false,
|
|
inbox: "https://domain.com/users/nick1/inbox"
|
|
})
|
|
|
|
actor = insert(:user, follower_address: follower.ap_id)
|
|
user = insert(:user)
|
|
|
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
|
|
|
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",
|
|
actor_id: actor.id,
|
|
id: note_activity.data["id"]
|
|
})
|
|
)
|
|
end
|
|
|
|
test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
|
|
Pleroma.Web.Federator.Publisher,
|
|
[:passthrough],
|
|
[] do
|
|
clear_config([:instance, :quarantined_instances], [])
|
|
|
|
fetcher =
|
|
insert(:user,
|
|
local: false,
|
|
inbox: "https://domain.com/users/nick1/inbox"
|
|
)
|
|
|
|
another_fetcher =
|
|
insert(:user,
|
|
local: false,
|
|
inbox: "https://domain2.com/users/nick1/inbox"
|
|
)
|
|
|
|
actor = insert(:user)
|
|
|
|
note_activity = insert(:note_activity, user: actor)
|
|
object = Object.normalize(note_activity, fetch: false)
|
|
|
|
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",
|
|
actor_id: actor.id,
|
|
id: delete.data["id"]
|
|
})
|
|
)
|
|
|
|
assert called(
|
|
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
|
|
inbox: "https://domain2.com/users/nick1/inbox",
|
|
actor_id: actor.id,
|
|
id: delete.data["id"]
|
|
})
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "should_federate/1" do
|
|
test "should not obliterate itself if the inbox URL is bad" do
|
|
url = "/inbox"
|
|
refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url)
|
|
|
|
url = nil
|
|
refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url)
|
|
end
|
|
end
|
|
end
|