Explorar el Código

Enforce poll limits and add error handling for MastodonAPI's post endpoint

tags/v1.1.4
rinpatch hace 5 años
padre
commit
3f96b3e4b8
Se han modificado 3 ficheros con 173 adiciones y 48 borrados
  1. +51
    -18
      lib/pleroma/web/common_api/utils.ex
  2. +31
    -14
      lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
  3. +91
    -16
      test/web/mastodon_api/mastodon_api_controller_test.exs

+ 51
- 18
lib/pleroma/web/common_api/utils.ex Ver fichero

@@ -104,28 +104,61 @@ defmodule Pleroma.Web.CommonAPI.Utils do


def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data) def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
when is_list(options) and is_integer(expires_in) do when is_list(options) and is_integer(expires_in) do
{poll, emoji} =
Enum.map_reduce(options, %{}, fn option, emoji ->
{%{
"name" => option,
"type" => "Note",
"replies" => %{"type" => "Collection", "totalItems" => 0}
}, Map.merge(emoji, Formatter.get_emoji_map(option))}
end)
%{max_expiration: max_expiration, min_expiration: min_expiration} =
limits = Pleroma.Config.get([:instance, :poll_limits])


end_time =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(expires_in)
|> NaiveDateTime.to_iso8601()
# XXX: There is probably a cleaner way of doing this
try do
if Enum.count(options) > limits.max_options do
raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options"
end


poll =
if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
%{"type" => "Question", "anyOf" => poll, "closed" => end_time}
else
%{"type" => "Question", "oneOf" => poll, "closed" => end_time}
{poll, emoji} =
Enum.map_reduce(options, %{}, fn option, emoji ->
if String.length(option) > limits.max_option_chars do
raise ArgumentError,
message:
"Poll options cannot be longer than #{limits.max_option_chars} characters each"
end

{%{
"name" => option,
"type" => "Note",
"replies" => %{"type" => "Collection", "totalItems" => 0}
}, Map.merge(emoji, Formatter.get_emoji_map(option))}
end)

case expires_in do
expires_in when expires_in > max_expiration ->
raise ArgumentError, message: "Expiration date is too far in the future"

expires_in when expires_in < min_expiration ->
raise ArgumentError, message: "Expiration date is too soon"

_ ->
:noop
end end


{poll, emoji}
end_time =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(expires_in)
|> NaiveDateTime.to_iso8601()

poll =
if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
%{"type" => "Question", "anyOf" => poll, "closed" => end_time}
else
%{"type" => "Question", "oneOf" => poll, "closed" => end_time}
end

{poll, emoji}
rescue
e in ArgumentError -> e.message
end
end

def make_poll_data(%{"poll" => _}) do
"Invalid poll"
end end


def make_poll_data(_data) do def make_poll_data(_data) do


+ 31
- 14
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex Ver fichero

@@ -473,12 +473,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params params
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])


idempotency_key =
case get_req_header(conn, "idempotency-key") do
[key] -> key
_ -> Ecto.UUID.generate()
end

scheduled_at = params["scheduled_at"] scheduled_at = params["scheduled_at"]


if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
@@ -491,17 +485,40 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else else
params = Map.drop(params, ["scheduled_at"]) params = Map.drop(params, ["scheduled_at"])


{:ok, activity} =
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
CommonAPI.post(user, params)
end)

conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
case get_cached_status_or_post(conn, params) do
{:ignore, message} ->
conn
|> put_status(401)
|> json(%{error: message})

{:error, message} ->
conn
|> put_status(401)
|> json(%{error: message})

{_, activity} ->
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
end
end end
end end


defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do
idempotency_key =
case get_req_header(conn, "idempotency-key") do
[key] -> key
_ -> Ecto.UUID.generate()
end

Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
case CommonAPI.post(user, params) do
{:ok, activity} -> activity
{:error, message} -> {:ignore, message}
end
end)
end

def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{}) json(conn, %{})


+ 91
- 16
test/web/mastodon_api/mastodon_api_controller_test.exs Ver fichero

@@ -146,26 +146,101 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
refute id == third_id refute id == third_id
end end


test "posting a poll", %{conn: conn} do
user = insert(:user)
time = NaiveDateTime.utc_now()
describe "posting polls" do
test "posting a poll", %{conn: conn} do
user = insert(:user)
time = NaiveDateTime.utc_now()


conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "Who is the best girl?",
"poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "Who is the #bestgrill?",
"poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
})

response = json_response(conn, 200)


response = json_response(conn, 200)
assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
title in ["Rei", "Asuka", "Misato"]
end)


assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
title in ["Rei", "Asuka", "Misato"]
end)
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
refute response["poll"]["expred"]
end


assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
refute response["poll"]["expred"]
test "option limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])

conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "desu~",
"poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
})

%{"error" => error} = json_response(conn, 401)
assert error == "Poll can't contain more than #{limit} options"
end

test "option character limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])

conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "...",
"poll" => %{
"options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
"expires_in" => 1
}
})

%{"error" => error} = json_response(conn, 401)
assert error == "Poll options cannot be longer than #{limit} characters each"
end

test "minimal date limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])

conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "imagine arbitrary limits",
"poll" => %{
"options" => ["this post was made by pleroma gang"],
"expires_in" => limit - 1
}
})

%{"error" => error} = json_response(conn, 401)
assert error == "Expiration date is too soon"
end

test "maximum date limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])

conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "imagine arbitrary limits",
"poll" => %{
"options" => ["this post was made by pleroma gang"],
"expires_in" => limit + 1
}
})

%{"error" => error} = json_response(conn, 401)
assert error == "Expiration date is too far in the future"
end
end end


test "posting a sensitive status", %{conn: conn} do test "posting a sensitive status", %{conn: conn} do


Cargando…
Cancelar
Guardar