Merge pull request 'Don’t pretend internal actors have follow(er|ing) collections' (#856) from Oneric/akkoma:fetch-actor-follow-collections into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/856
This commit is contained in:
commit
aac5493dd5
9 changed files with 132 additions and 34 deletions
|
@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Fixed
|
||||
- Internal actors no longer pretend to have unresolvable follow(er|ing) collections
|
||||
|
||||
### Changed
|
||||
- Internal and relay actors are now again represented with type "Application"
|
||||
|
||||
## 2025.03
|
||||
|
||||
## Added
|
||||
|
|
|
@ -2022,12 +2022,13 @@ def get_or_fetch_by_ap_id(ap_id, options \\ []) do
|
|||
Creates an internal service actor by URI if missing.
|
||||
Optionally takes nickname for addressing.
|
||||
"""
|
||||
@spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
|
||||
def get_or_create_service_actor_by_ap_id(uri, nickname) do
|
||||
@spec get_or_create_service_actor_by_ap_id(String.t(), String.t(), Keyword.t()) ::
|
||||
User.t() | nil
|
||||
def get_or_create_service_actor_by_ap_id(uri, nickname, create_opts \\ []) do
|
||||
{_, user} =
|
||||
case get_cached_by_ap_id(uri) do
|
||||
nil ->
|
||||
with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
|
||||
with {:error, %{errors: errors}} <- create_service_actor(uri, nickname, create_opts) do
|
||||
Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
|
||||
{:error, nil}
|
||||
end
|
||||
|
@ -2049,15 +2050,27 @@ defp set_invisible(user) do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@spec create_service_actor(String.t(), String.t()) ::
|
||||
@spec create_service_actor(String.t(), String.t(), Keyword.t()) ::
|
||||
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp create_service_actor(uri, nickname) do
|
||||
defp create_service_actor(uri, nickname, opts) do
|
||||
%User{
|
||||
invisible: true,
|
||||
local: true,
|
||||
ap_id: uri,
|
||||
nickname: nickname,
|
||||
follower_address: uri <> "/followers"
|
||||
actor_type: "Application",
|
||||
follower_address:
|
||||
if Keyword.get(opts, :followable, false) do
|
||||
uri <> "/followers"
|
||||
else
|
||||
nil
|
||||
end,
|
||||
following_address:
|
||||
if Keyword.get(opts, :following, false) do
|
||||
uri <> "/following"
|
||||
else
|
||||
nil
|
||||
end
|
||||
}
|
||||
|> change
|
||||
|> put_private_key()
|
||||
|
|
|
@ -16,7 +16,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
def ap_id, do: "#{Pleroma.Web.Endpoint.url()}/#{@nickname}"
|
||||
|
||||
@spec get_actor() :: User.t() | nil
|
||||
def get_actor, do: User.get_or_create_service_actor_by_ap_id(ap_id(), @nickname)
|
||||
def get_actor,
|
||||
do:
|
||||
User.get_or_create_service_actor_by_ap_id(ap_id(), @nickname,
|
||||
followable: true,
|
||||
following: true
|
||||
)
|
||||
|
||||
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def follow(target_instance) do
|
||||
|
|
|
@ -16,9 +16,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
|
||||
%{"sharedInbox" => url(~p"/inbox")}
|
||||
end
|
||||
defp maybe_put(map, _, nil), do: map
|
||||
defp maybe_put(map, k, v), do: Map.put(map, k, v)
|
||||
|
||||
def render("endpoints.json", %{user: %User{local: true} = _user}) do
|
||||
%{
|
||||
|
@ -39,13 +38,11 @@ def render("service.json", %{user: user}) do
|
|||
%{
|
||||
"id" => user.ap_id,
|
||||
"type" => "Application",
|
||||
"following" => "#{user.ap_id}/following",
|
||||
"followers" => "#{user.ap_id}/followers",
|
||||
"inbox" => "#{user.ap_id}/inbox",
|
||||
"outbox" => "#{user.ap_id}/outbox",
|
||||
"name" => "Pleroma",
|
||||
"name" => "Akkoma",
|
||||
"summary" =>
|
||||
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
|
||||
"An internal service actor for this Akkoma instance. No user-serviceable parts inside.",
|
||||
"url" => user.ap_id,
|
||||
"manuallyApprovesFollowers" => false,
|
||||
"publicKey" => %{
|
||||
|
@ -56,16 +53,15 @@ def render("service.json", %{user: user}) do
|
|||
"endpoints" => endpoints,
|
||||
"invisible" => User.invisible?(user)
|
||||
}
|
||||
|> maybe_put("following", user.following_address)
|
||||
|> maybe_put("followers", user.follower_address)
|
||||
|> maybe_put("preferredUsername", user.nickname)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
# the instance itself is not a Person, but instead an Application
|
||||
def render("user.json", %{user: %User{nickname: nil} = user}),
|
||||
def render("user.json", %{user: %User{actor_type: "Application"} = user}),
|
||||
do: render("service.json", %{user: user})
|
||||
|
||||
def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
|
||||
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
||||
|
||||
def render("user.json", %{user: user}) do
|
||||
public_key =
|
||||
case User.SigningKey.public_key_pem(user) do
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
defmodule Pleroma.Repo.Migrations.InstanceActorsTweaks do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def up() do
|
||||
# since Akkoma isn’t up and running at this point, Web.endpoint()
|
||||
# isn’t available and we can't use the functions from Relay and InternalFetchActor,
|
||||
# thus the AP ID suffix are hardcoded here and used together with a check for locality
|
||||
# (e.g. custom ports make it hard to hardcode the full base url)
|
||||
relay_ap_id = "%/relay"
|
||||
fetch_ap_id = "%/internal/fetch"
|
||||
|
||||
# Convert to Application type
|
||||
Pleroma.User
|
||||
|> where([u], u.local and (like(u.ap_id, ^fetch_ap_id) or like(u.ap_id, ^relay_ap_id)))
|
||||
|> Pleroma.Repo.update_all(set: [actor_type: "Application"])
|
||||
|
||||
# Drop bogus follow* addresses
|
||||
Pleroma.User
|
||||
|> where([u], u.local and like(u.ap_id, ^fetch_ap_id))
|
||||
|> Pleroma.Repo.update_all(set: [follower_address: nil, following_address: nil])
|
||||
|
||||
# Add required follow* addresses
|
||||
Pleroma.User
|
||||
|> where([u], u.local and like(u.ap_id, ^relay_ap_id))
|
||||
|> update([u],
|
||||
set: [
|
||||
follower_address: fragment("CONCAT(?, '/followers')", u.ap_id),
|
||||
following_address: fragment("CONCAT(?, '/following')", u.ap_id)
|
||||
]
|
||||
)
|
||||
|> Pleroma.Repo.update_all([])
|
||||
end
|
||||
|
||||
def down do
|
||||
# We don't know if the type was Person or Application before and
|
||||
# without this or the lost patch it didn't matter, so just do nothing
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -58,7 +58,11 @@ test "returns relay user" do
|
|||
local: true,
|
||||
ap_id: ^uri,
|
||||
follower_address: ^followers_uri
|
||||
} = User.get_or_create_service_actor_by_ap_id(uri, "relay")
|
||||
} =
|
||||
User.get_or_create_service_actor_by_ap_id(uri, "relay",
|
||||
followable: true,
|
||||
following: true
|
||||
)
|
||||
|
||||
assert capture_log(fn ->
|
||||
refute User.get_or_create_service_actor_by_ap_id("/relay", "relay")
|
||||
|
@ -67,7 +71,6 @@ test "returns relay user" do
|
|||
|
||||
test "returns invisible actor" do
|
||||
uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test"
|
||||
followers_uri = "#{uri}/followers"
|
||||
user = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
|
||||
|
||||
assert %User{
|
||||
|
@ -75,7 +78,7 @@ test "returns invisible actor" do
|
|||
invisible: true,
|
||||
local: true,
|
||||
ap_id: ^uri,
|
||||
follower_address: ^followers_uri
|
||||
follower_address: nil
|
||||
} = user
|
||||
|
||||
user2 = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
|
||||
|
|
32
test/pleroma/web/activity_pub/internal_fetch_actor_tests.exs
Normal file
32
test/pleroma/web/activity_pub/internal_fetch_actor_tests.exs
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Akkoma: Magically expressive social media
|
||||
# Copyright © 2025 Akkoma Authors <https://akkoma.dev/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.InternalFetchActorTests do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
|
||||
test "creates a fetch actor if needed" do
|
||||
user = InternalFetchActor.get_actor()
|
||||
assert user
|
||||
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/internal/fetch"
|
||||
end
|
||||
|
||||
test "fetch actor is an application" do
|
||||
user = InternalFetchActor.get_actor()
|
||||
assert user.actor_type == "Application"
|
||||
end
|
||||
|
||||
test "fetch actor doesn't expose follow* collections" do
|
||||
user = InternalFetchActor.get_actor()
|
||||
refute user.follower_address
|
||||
refute user.following_address
|
||||
end
|
||||
|
||||
test "fetch actor is invisible" do
|
||||
user = InternalFetchActor.get_actor()
|
||||
assert User.invisible?(user)
|
||||
end
|
||||
end
|
|
@ -20,6 +20,18 @@ test "gets an actor for the relay" do
|
|||
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
end
|
||||
|
||||
test "relay actor is an application" do
|
||||
# See <https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application>
|
||||
user = Relay.get_actor()
|
||||
assert user.actor_type == "Application"
|
||||
end
|
||||
|
||||
test "relay actor has follow* collections" do
|
||||
user = Relay.get_actor()
|
||||
assert user.follower_address
|
||||
assert user.following_address
|
||||
end
|
||||
|
||||
test "relay actor is invisible" do
|
||||
user = Relay.get_actor()
|
||||
assert User.invisible?(user)
|
||||
|
|
|
@ -126,18 +126,6 @@ test "remote users have an empty endpoints structure" do
|
|||
assert result["id"] == user.ap_id
|
||||
assert result["endpoints"] == %{}
|
||||
end
|
||||
|
||||
test "instance users do not expose oAuth endpoints" do
|
||||
user =
|
||||
insert(:user, nickname: nil, local: true)
|
||||
|> with_signing_key()
|
||||
|
||||
result = UserView.render("user.json", %{user: user})
|
||||
|
||||
refute result["endpoints"]["oauthAuthorizationEndpoint"]
|
||||
refute result["endpoints"]["oauthRegistrationEndpoint"]
|
||||
refute result["endpoints"]["oauthTokenEndpoint"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "followers" do
|
||||
|
|
Loading…
Reference in a new issue