Harden ParadeDB search result safety
Re-apply public Create visibility checks when resolving backend hits, improve UUID/FlakeId id parsing, and add tests for poisoned hits and UUID-string ids. Also tighten the docker integration test with localhost-only binding and config restoration.
This commit is contained in:
parent
dee7ac0415
commit
74da3e01f7
3 changed files with 111 additions and 6 deletions
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Search.ParadeDB do
|
||||||
@behaviour Pleroma.Search.SearchBackend
|
@behaviour Pleroma.Search.SearchBackend
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
|
@ -153,6 +154,7 @@ defmodule Pleroma.Search.ParadeDB do
|
||||||
from(a in Activity, where: a.id in ^dumped_ids)
|
from(a in Activity, where: a.id in ^dumped_ids)
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|
|> maybe_restrict_public(user)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> maybe_restrict_author(author)
|
|> maybe_restrict_author(author)
|
||||||
|> maybe_restrict_blocked(user)
|
|> maybe_restrict_blocked(user)
|
||||||
|
|
@ -168,6 +170,25 @@ defmodule Pleroma.Search.ParadeDB do
|
||||||
|> maybe_fetch(user, query)
|
|> maybe_fetch(user, query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_restrict_public(query, %Pleroma.User{}) do
|
||||||
|
intended_recipients = [
|
||||||
|
Pleroma.Constants.as_public(),
|
||||||
|
Pleroma.Web.ActivityPub.Utils.as_local_public()
|
||||||
|
]
|
||||||
|
|
||||||
|
from(a in query,
|
||||||
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
|
where: fragment("? && ?", ^intended_recipients, a.recipients)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_restrict_public(query, _user) do
|
||||||
|
from(a in query,
|
||||||
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
|
where: ^Pleroma.Constants.as_public() in a.recipients
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp search_ids(query, limit, offset, author) do
|
defp search_ids(query, limit, offset, author) do
|
||||||
base_sql = "SELECT id FROM #{table()} WHERE content &&& $1"
|
base_sql = "SELECT id FROM #{table()} WHERE content &&& $1"
|
||||||
params = [query]
|
params = [query]
|
||||||
|
|
@ -186,7 +207,7 @@ defmodule Pleroma.Search.ParadeDB do
|
||||||
|
|
||||||
sql =
|
sql =
|
||||||
base_sql <>
|
base_sql <>
|
||||||
" ORDER BY published_at DESC LIMIT $#{limit_arg} OFFSET $#{offset_arg}"
|
" ORDER BY published_at DESC, id DESC LIMIT $#{limit_arg} OFFSET $#{offset_arg}"
|
||||||
|
|
||||||
params = params ++ [limit, offset]
|
params = params ++ [limit, offset]
|
||||||
|
|
||||||
|
|
@ -223,11 +244,23 @@ defmodule Pleroma.Search.ParadeDB do
|
||||||
defp dump_activity_id(<<_::binary-size(16)>> = uuid), do: uuid
|
defp dump_activity_id(<<_::binary-size(16)>> = uuid), do: uuid
|
||||||
|
|
||||||
defp dump_activity_id(id) when is_binary(id) do
|
defp dump_activity_id(id) when is_binary(id) do
|
||||||
case FlakeId.Ecto.CompatType.dump(id) do
|
case Ecto.UUID.dump(id) do
|
||||||
{:ok, <<_::binary-size(16)>> = uuid} -> uuid
|
{:ok, <<_::binary-size(16)>> = uuid} ->
|
||||||
_ -> nil
|
uuid
|
||||||
|
|
||||||
|
:error ->
|
||||||
|
dump_flake_activity_id(id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp dump_activity_id(_), do: nil
|
defp dump_activity_id(_), do: nil
|
||||||
|
|
||||||
|
defp dump_flake_activity_id(id) do
|
||||||
|
case FlakeId.Ecto.CompatType.dump(id) do
|
||||||
|
{:ok, <<_::binary-size(16)>> = uuid} -> uuid
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.Search.ParadeDBIntegrationTest do
|
||||||
alias Pleroma.Search.ParadeDB
|
alias Pleroma.Search.ParadeDB
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
@image "paradedb/paradedb:latest"
|
@image System.get_env("PARADEDB_TEST_IMAGE", "paradedb/paradedb:latest")
|
||||||
@db_user "postgres"
|
@db_user "postgres"
|
||||||
@db_password "postgres"
|
@db_password "postgres"
|
||||||
@db_name "paradedb"
|
@db_name "paradedb"
|
||||||
|
|
@ -46,7 +46,7 @@ defmodule Pleroma.Search.ParadeDBIntegrationTest do
|
||||||
"-e",
|
"-e",
|
||||||
"POSTGRES_DB=#{@db_name}",
|
"POSTGRES_DB=#{@db_name}",
|
||||||
"-p",
|
"-p",
|
||||||
"5432",
|
"127.0.0.1::5432",
|
||||||
"-d",
|
"-d",
|
||||||
@image
|
@image
|
||||||
])
|
])
|
||||||
|
|
@ -75,9 +75,18 @@ defmodule Pleroma.Search.ParadeDBIntegrationTest do
|
||||||
|
|
||||||
{:ok, _pid} = start_supervised({Pleroma.Search.ParadeDB.Repo, pool_size: 1})
|
{:ok, _pid} = start_supervised({Pleroma.Search.ParadeDB.Repo, pool_size: 1})
|
||||||
|
|
||||||
|
previous_table = Pleroma.Config.get([Pleroma.Search.ParadeDB, :table], nil)
|
||||||
table = "pleroma_search_documents_it_#{System.unique_integer([:positive])}"
|
table = "pleroma_search_documents_it_#{System.unique_integer([:positive])}"
|
||||||
Pleroma.Config.put([Pleroma.Search.ParadeDB, :table], table)
|
Pleroma.Config.put([Pleroma.Search.ParadeDB, :table], table)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
if is_nil(previous_table) do
|
||||||
|
Pleroma.Config.delete([Pleroma.Search.ParadeDB, :table])
|
||||||
|
else
|
||||||
|
Pleroma.Config.put([Pleroma.Search.ParadeDB, :table], previous_table)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
container: container,
|
container: container,
|
||||||
|
|
|
||||||
|
|
@ -172,5 +172,68 @@ defmodule Pleroma.Search.ParadeDBTest do
|
||||||
assert [%{id: ^activity2_id}, %{id: ^activity1_id}] =
|
assert [%{id: ^activity2_id}, %{id: ^activity1_id}] =
|
||||||
ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
|
ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "search excludes non-public activities from backend hits" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, public_activity} =
|
||||||
|
CommonAPI.post(user, %{status: "public swamp", visibility: "public"})
|
||||||
|
|
||||||
|
{:ok, private_activity} =
|
||||||
|
CommonAPI.post(user, %{status: "private swamp", visibility: "private"})
|
||||||
|
|
||||||
|
public_activity_id = public_activity.id
|
||||||
|
|
||||||
|
{:ok, dumped_public_id} = FlakeId.Ecto.CompatType.dump(public_activity.id)
|
||||||
|
{:ok, dumped_private_id} = FlakeId.Ecto.CompatType.dump(private_activity.id)
|
||||||
|
|
||||||
|
ClientMock
|
||||||
|
|> expect(:query, fn sql, params ->
|
||||||
|
assert sql =~ "SELECT id FROM pleroma_search_documents"
|
||||||
|
assert ["swamp", _limit, _offset] = params
|
||||||
|
|
||||||
|
{:ok, %{rows: [[dumped_private_id], [dumped_public_id]]}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config
|
||||||
|
|> expect(:get, 2, fn
|
||||||
|
[Pleroma.Search.ParadeDB, :client_impl], nil ->
|
||||||
|
ClientMock
|
||||||
|
|
||||||
|
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
|
||||||
|
"pleroma_search_documents"
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert [%{id: ^public_activity_id}] = ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "search supports backend ids returned as uuid strings" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "uuid swamp", visibility: "public"})
|
||||||
|
|
||||||
|
activity_id = activity.id
|
||||||
|
{:ok, dumped_id} = FlakeId.Ecto.CompatType.dump(activity_id)
|
||||||
|
{:ok, uuid_string} = Ecto.UUID.load(dumped_id)
|
||||||
|
|
||||||
|
ClientMock
|
||||||
|
|> expect(:query, fn sql, params ->
|
||||||
|
assert sql =~ "SELECT id FROM pleroma_search_documents"
|
||||||
|
assert ["swamp", _limit, _offset] = params
|
||||||
|
|
||||||
|
{:ok, %{rows: [[uuid_string]]}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config
|
||||||
|
|> expect(:get, 2, fn
|
||||||
|
[Pleroma.Search.ParadeDB, :client_impl], nil ->
|
||||||
|
ClientMock
|
||||||
|
|
||||||
|
[Pleroma.Search.ParadeDB, :table], "pleroma_search_documents" ->
|
||||||
|
"pleroma_search_documents"
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert [%{id: ^activity_id}] = ParadeDB.search(nil, "swamp", limit: 40, offset: 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue