Don't try to handle non-media objects as media

Trying to display non-media as media crashed the renderer,
but when posting a status with a valid, non-media object id
the post was still created, but then crashed e.g. timeline rendering.
It also crashed C2S inbox reads, so this could not be used to leak
private posts.
This commit is contained in:
Oneric 2024-04-25 18:16:21 +02:00
parent fbd961c747
commit 9a91299f96
6 changed files with 51 additions and 5 deletions

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Fixed ## Fixed
- Issue allowing non-owners to use media objects in posts - Issue allowing non-owners to use media objects in posts
- Issue allowing use of non-media objects as attachments and crashing timeline rendering
## 2024.04 ## 2024.04

View file

@ -64,4 +64,7 @@ defmodule Pleroma.Constants do
"Service" "Service"
] ]
) )
# Internally used as top-level types for media attachments and user images
const(attachment_types, do: ["Document", "Image"])
end end

View file

@ -40,8 +40,13 @@ defp attachments_from_ids(user, [media_id | ids], acc) do
end end
end end
defp get_attachment(media_id) do def get_attachment(media_id) do
Repo.get(Object, media_id) with %Object{} = object <- Repo.get(Object, media_id),
true <- object.data["type"] in Pleroma.Constants.attachment_types() do
object
else
_ -> nil
end
end end
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())} @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@ -55,12 +56,15 @@ def create2(_conn, _data), do: {:error, :bad_request}
@doc "PUT /api/v1/media/:id" @doc "PUT /api/v1/media/:id"
def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do def update(%{assigns: %{user: user}, body_params: %{description: description}} = conn, %{id: id}) do
with %Object{} = object <- Object.get_by_id(id), with {_, %Object{} = object} <- {:get, Utils.get_attachment(id)},
:ok <- Object.authorize_access(object, user), :ok <- Object.authorize_access(object, user),
{:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
attachment_data = Map.put(data, "id", object.id) attachment_data = Map.put(data, "id", object.id)
render(conn, "attachment.json", %{attachment: attachment_data}) render(conn, "attachment.json", %{attachment: attachment_data})
else
{:get, _} -> {:error, :not_found}
e -> e
end end
end end
@ -68,11 +72,14 @@ def update(conn, data), do: show(conn, data)
@doc "GET /api/v1/media/:id" @doc "GET /api/v1/media/:id"
def show(%{assigns: %{user: user}} = conn, %{id: id}) do def show(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Object{data: data, id: object_id} = object <- Object.get_by_id(id), with {_, %Object{data: data, id: object_id} = object} <- {:get, Utils.get_attachment(id)},
:ok <- Object.authorize_access(object, user) do :ok <- Object.authorize_access(object, user) do
attachment_data = Map.put(data, "id", object_id) attachment_data = Map.put(data, "id", object_id)
render(conn, "attachment.json", %{attachment: attachment_data}) render(conn, "attachment.json", %{attachment: attachment_data})
else
{:get, _} -> {:error, :not_found}
e -> e
end end
end end

View file

@ -593,10 +593,16 @@ test "returns recipients when object not found" do
describe "attachments_from_ids/1" do describe "attachments_from_ids/1" do
test "returns attachments without descs" do test "returns attachments without descs" do
user = insert(:user) user = insert(:user)
object = insert(:note, user: user) object = insert(:attachment, user: user)
assert Utils.attachments_from_ids(user, %{media_ids: ["#{object.id}"]}) == [object.data] assert Utils.attachments_from_ids(user, %{media_ids: ["#{object.id}"]}) == [object.data]
end end
test "returns [] when passed non-media object ids" do
user = insert(:user)
object = insert(:note, user: user)
assert Utils.attachments_from_ids(user, %{media_ids: ["#{object.id}"]}) == []
end
test "returns [] when not pass media_ids" do test "returns [] when not pass media_ids" do
user = insert(:user) user = insert(:user)
assert Utils.attachments_from_ids(user, %{}) == [] assert Utils.attachments_from_ids(user, %{}) == []

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
use Pleroma.Web.ConnCase, async: false use Pleroma.Web.ConnCase, async: false
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Pleroma.Factory
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@ -174,6 +175,18 @@ test "/api/v1/media/:id good request", %{conn: conn, object: object} do
assert media["description"] == "test-media" assert media["description"] == "test-media"
assert refresh_record(object).data["name"] == "test-media" assert refresh_record(object).data["name"] == "test-media"
end end
test "won't update non-media", %{conn: conn, user: user} do
object = insert(:note, user: user)
response =
conn
|> put_req_header("content-type", "multipart/form-data")
|> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
|> json_response(404)
assert response == %{"error" => "Record not found"}
end
end end
describe "Get media by id (/api/v1/media/:id)" do describe "Get media by id (/api/v1/media/:id)" do
@ -207,6 +220,17 @@ test "it returns media object when requested by owner", %{conn: conn, object: ob
assert media["id"] assert media["id"]
end end
test "it returns 404 when requesting non-media object", %{conn: conn, user: user} do
object = insert(:note, user: user)
response =
conn
|> get("/api/v1/media/#{object.id}")
|> json_response(404)
assert response == %{"error" => "Record not found"}
end
test "it returns 403 if media object requested by non-owner", %{object: object, user: user} do test "it returns 403 if media object requested by non-owner", %{object: object, user: user} do
%{conn: conn, user: other_user} = oauth_access(["read:media"]) %{conn: conn, user: other_user} = oauth_access(["read:media"])