Compare commits

...

12 commits

Author SHA1 Message Date
b0a109a146 Merge branch 'develop' of https://akkoma.dev/AkkomaGang/akkoma into akko.wtf 2025-05-06 12:28:23 -04:00
550d2b30dd Merge remote-tracking branch 'oneric/shared-inbox' into akko.wtf 2025-05-06 11:56:49 -04:00
Oneric
6b97f085d8 Refresh mix.lock
Fixes omission in 936be4edbb
2025-05-06 17:24:58 +02:00
818ddeca84 Merge pull request 'add instruction to make asdf work when logging into it with sudo' (#894) from shadowjonathan/akkoma:add-sudo-asdf into develop
Reviewed-on: AkkomaGang/akkoma#894
2025-05-05 20:45:50 +00:00
0ad6bfe0eb Merge branch 'develop' of https://akkoma.dev/AkkomaGang/akkoma into akko.wtf 2025-05-03 17:43:49 -04:00
f0653efe13 Merge pull request 'Fix Pleroma’s unlisted posts' (#885) from Oneric/akkoma:pleroma_unlisted into develop
Reviewed-on: AkkomaGang/akkoma#885
2025-05-02 22:26:59 +00:00
cb51d69108 Merge pull request 'deps: upgrade flake_id to fix crash' (#906) from Oneric/akkoma:fix_flake_crash into develop
Reviewed-on: AkkomaGang/akkoma#906
Reviewed-by: floatingghost <hannah@coffee-and-dreams.uk>
2025-05-02 22:26:41 +00:00
Oneric
936be4edbb deps: upgrade flake_id to fix crash
Currently FlakeId.flake_id crashes if receiving non-UTF-8 binaries,
but we use it e.g. in the /:nick_or_id path used in akkoma-fe user
profiles.
With the upgrade such invalid binaries simply fail the id check.

Reported-in: https://meta.akkoma.dev/t/frontend-unicodeconversionerror/847
2025-05-02 21:46:04 +02:00
Jonathan de Jong
bb50e9050b rearrange shims 2025-04-26 16:17:31 +02:00
Jonathan de Jong
64e7f25252 add instruction to make asdf work when logging into it with sudo 2025-03-31 13:27:25 +02:00
Oneric
9495a61f00 federation: always prefer the shared inbox
In theory a pedantic reading of the spec indeed suggests
DMs must only be delivered to personal inboxes. However,
in practice the normative force of real-world implementations
disagrees. Mastodon, Iceshrimp.NET and GtS (the latter notably has a
config option to never use sharedInboxes) all unconditionally prefer
sharedInbox for everything without ill effect. This saves on duplicate
deliveries on the sending and processing on the receiving end.
(Typically the receiving side ends up rejecting
 all but the first copy as duplicates)

Furthermore current determine_inbox logic also actually needs up
forcing personal inboxes for follower-only posts, unless they
additionally explicitly address at least one specific actor.
This is even much wasteful and directly contradicts
the explicit intent of the spec.

There’s one part where the use of sharedInbox falls apart,
namely spec-compliant bcc and bto addressing. AP spec requires
bcc/bto fields to be stripped before delivery and then implicitly
reconstructed by the receiver based on the addressed personal inbox.
In practice however, this addressing mode is almost unused. Neither of
the three implementations brought up above supports it and while *oma
does use bcc for list addressing, it does not use it in a spec-compliant
way and even copies same-host recipients into cc before delivery.
Messages with bcc addressing are handled in another function clause,
always force personal inboxes for every recipient and not affected by
this commit.
In theory it would be beneficial to use sharedInbox there too for all
but bcc recipients. But in practice list addressing has been broken for
quite some time already and is not actually exposed in any frontend,
as discussed in AkkomaGang/akkoma#812.
Therefore any changes here have virtually no effect anyway
and all code concerning it may just be outright removed.
2025-03-15 01:12:12 +01:00
Oneric
75fda65065 publisher: don't mangle between string and atom
Oban jobs only can have string args and there’s no reason to insist on atoms here.

Plus this used unchecked string_to_atom
2025-03-14 20:31:07 +01:00
6 changed files with 82 additions and 187 deletions

View file

@ -50,6 +50,11 @@ First, install some dependencies needed to build Elixir and Erlang:
sudo apt install curl unzip build-essential autoconf m4 libncurses5-dev libssh-dev unixodbc-dev xsltproc libxml2-utils libncurses-dev
```
Second, make sure that erlang and elixir (via asdf) is reachable from `sudo -Hu akkoma`:
```shell
echo 'Defaults>akkoma secure_path="/var/lib/akkoma/.asdf/shims:/sbin:/bin:/usr/sbin:/usr/bin"' > /etc/sudoers.d/custom_path
```
Then login to the `akkoma` user.
Install asdf by following steps 1 to 3 on [their website](https://asdf-vm.com/guide/getting-started.html), then restart the shell to load asdf:

View file

@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
* `actor`: the actor which is signing the message
* `id`: the ActivityStreams URI of the message
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
def publish_one(%{"inbox" => inbox, "json" => json, "actor" => %User{} = actor, "id" => id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
uri = %{path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -74,24 +74,24 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", digest}
]
) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
if not Map.has_key?(params, "unreachable_since") || params["unreachable_since"] do
Instances.set_reachable(inbox)
end
result
else
{_post_result, response} ->
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
unless params["unreachable_since"], do: Instances.set_unreachable(inbox)
{:error, response}
end
end
def publish_one(%{actor_id: actor_id} = params) do
def publish_one(%{"actor_id" => actor_id} = params) do
actor = User.get_cached_by_id(actor_id)
params
|> Map.delete(:actor_id)
|> Map.put(:actor, actor)
|> Map.delete("actor_id")
|> Map.put("actor", actor)
|> publish_one()
end
@ -170,42 +170,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Enum.map(& &1.ap_id)
end
defp maybe_use_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
@doc """
Determine a user inbox to use based on heuristics. These heuristics
are based on an approximation of the ``sharedInbox`` rules in the
[ActivityPub specification][ap-sharedinbox].
Please do not edit this function (or its children) without reading
the spec, as editing the code is likely to introduce some breakage
without some familiarity.
[ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
"""
def determine_inbox(
%Activity{data: activity_data},
%User{inbox: inbox} = user
) do
to = activity_data["to"] || []
cc = activity_data["cc"] || []
type = activity_data["type"]
cond do
type == "Delete" ->
maybe_use_sharedinbox(user)
Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
maybe_use_sharedinbox(user)
length(to) + length(cc) > 1 ->
maybe_use_sharedinbox(user)
true ->
inbox
end
end
defp try_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
defp try_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
@doc """
Publishes an activity with BCC to all relevant peers.
@ -237,11 +203,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Jason.encode!()
Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
inbox: inbox,
json: json,
actor_id: actor.id,
id: activity.data["id"],
unreachable_since: unreachable_since
"inbox" => inbox,
"json" => json,
"actor_id" => actor.id,
"id" => activity.data["id"],
"unreachable_since" => unreachable_since
})
end)
end)
@ -261,7 +227,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
recipients(actor, activity)
|> Enum.map(fn %User{} = user ->
determine_inbox(activity, user)
try_sharedinbox(user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox) end)
@ -270,11 +236,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
Pleroma.Web.Federator.Publisher.enqueue_one(
__MODULE__,
%{
inbox: inbox,
json: json,
actor_id: actor.id,
id: activity.data["id"],
unreachable_since: unreachable_since
"inbox" => inbox,
"json" => json,
"actor_id" => actor.id,
"id" => activity.data["id"],
"unreachable_since" => unreachable_since
}
)
end)

View file

@ -30,7 +30,6 @@ defmodule Pleroma.Workers.PublisherWorker do
end
def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
Federator.perform(:publish_one, String.to_atom(module_name), params)
Federator.perform(:publish_one, String.to_existing_atom(module_name), params)
end
end

View file

@ -174,7 +174,9 @@ defmodule Pleroma.Mixfile do
{:pot, "~> 1.0"},
{:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"},
{:flake_id, "~> 0.1.0"},
{:flake_id,
git: "https://akkoma.dev/AkkomaGang/flake_id.git",
ref: "5a68513f7e7353706e788781eff6e56bf00bb41b"},
{:concurrent_limiter,
git: "https://akkoma.dev/AkkomaGang/concurrent-limiter.git",
ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"},

View file

@ -50,7 +50,7 @@
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
"flake_id": {:git, "https://akkoma.dev/AkkomaGang/flake_id.git", "5a68513f7e7353706e788781eff6e56bf00bb41b", [ref: "5a68513f7e7353706e788781eff6e56bf00bb41b"]},
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},

View file

@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
import Tesla.Mock
import Mock
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Publisher
@ -51,82 +50,6 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
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"
@ -146,20 +69,20 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert {:ok, %{body: "port 42"}} =
Publisher.publish_one(%{
inbox: inbox42,
json: "{}",
actor: actor,
id: 1,
unreachable_since: true
"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
"inbox" => inbox80,
"json" => "{}",
"actor" => actor,
"id" => 1,
"unreachable_since" => true
})
end
@ -173,7 +96,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert {:ok, _} = Publisher.publish_one(%{"inbox" => inbox, "json" => "{}", "actor" => actor, "id" => 1})
assert called(Instances.set_reachable(inbox))
end
@ -189,11 +112,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
"inbox" => inbox,
"json" => "{}",
"actor" => actor,
"id" => 1,
"unreachable_since" => NaiveDateTime.utc_now()
})
assert called(Instances.set_reachable(inbox))
@ -211,11 +134,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: nil
"inbox" => inbox,
"json" => "{}",
"actor" => actor,
"id" => 1,
"unreachable_since" => nil
})
refute called(Instances.set_reachable(inbox))
@ -231,7 +154,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
inbox = "http://404.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert {:error, _} = Publisher.publish_one(%{"inbox" => inbox, "json" => "{}", "actor" => actor, "id" => 1})
assert called(Instances.set_unreachable(inbox))
end
@ -248,7 +171,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert capture_log(fn ->
assert {:error, _} =
Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
Publisher.publish_one(%{"inbox" => inbox, "json" => "{}", "actor" => actor, "id" => 1})
end) =~ "connrefused"
assert called(Instances.set_unreachable(inbox))
@ -264,7 +187,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert {:ok, _} = Publisher.publish_one(%{"inbox" => inbox, "json" => "{}", "actor" => actor, "id" => 1})
refute called(Instances.set_unreachable(inbox))
end
@ -282,11 +205,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert capture_log(fn ->
assert {:error, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
"inbox" => inbox,
"json" => "{}",
"actor" => actor,
"id" => 1,
"unreachable_since" => NaiveDateTime.utc_now()
})
end) =~ "connrefused"
@ -344,33 +267,33 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
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"]
"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"]
"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"]
"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"]
"inbox" => "https://rejected.com/users/nick2/inbox",
"actor_id" => actor.id,
"id" => public_note_activity.data["id"]
})
)
end
@ -406,9 +329,9 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
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"]
"inbox" => "https://domain.com/users/nick1/inbox",
"actor_id" => actor.id,
"id" => note_activity.data["id"]
})
)
end
@ -441,9 +364,9 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
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"]
"inbox" => "https://domain.com/users/nick1/inbox",
"actor_id" => actor.id,
"id" => note_activity.data["id"]
})
)
end
@ -493,17 +416,17 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert called(
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
inbox: "https://domain.com/users/nick1/inbox",
actor_id: actor.id,
id: delete.data["id"]
"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"]
"inbox" => "https://domain2.com/users/nick1/inbox",
"actor_id" => actor.id,
"id" => delete.data["id"]
})
)
end