Harden webhook API input

This commit is contained in:
Lain Soykaf 2026-05-26 15:03:22 +04:00
commit ab634a4aa3
No known key found for this signature in database
3 changed files with 64 additions and 8 deletions

View file

@ -40,14 +40,14 @@ defmodule Pleroma.Web.AdminAPI.WebhookController do
end end
def create(%{body_params: params} = conn, _) do def create(%{body_params: params} = conn, _) do
with {:ok, webhook} <- Webhook.create(params) do with {:ok, webhook} <- Webhook.create(webhook_params(params)) do
render(conn, "show.json", webhook: webhook) render(conn, "show.json", webhook: webhook)
end end
end end
def update(%{body_params: params} = conn, %{id: id}) do def update(%{body_params: params} = conn, %{id: id}) do
with %Webhook{internal: false} = webhook <- Webhook.get(id), with %Webhook{internal: false} = webhook <- Webhook.get(id),
{:ok, webhook} <- Webhook.update(webhook, params) do {:ok, webhook} <- Webhook.update(webhook, webhook_params(params)) do
render(conn, "show.json", webhook: webhook) render(conn, "show.json", webhook: webhook)
else else
%Webhook{internal: true} -> {:error, :forbidden} %Webhook{internal: true} -> {:error, :forbidden}
@ -94,4 +94,8 @@ defmodule Pleroma.Web.AdminAPI.WebhookController do
nil -> {:error, :not_found} nil -> {:error, :not_found}
end end
end end
defp webhook_params(params) do
Map.drop(params, [:internal, "internal"])
end
end end

View file

@ -58,9 +58,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.WebhookOperation do
required: [:url, :events], required: [:url, :events],
properties: %{ properties: %{
url: %Schema{type: :string, format: :uri}, url: %Schema{type: :string, format: :uri},
events: event_type(), events: event_type(min_items: 1),
enabled: %Schema{type: :boolean} enabled: %Schema{type: :boolean}
} },
additionalProperties: false
} }
), ),
responses: %{ responses: %{
@ -84,9 +85,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.WebhookOperation do
type: :object, type: :object,
properties: %{ properties: %{
url: %Schema{type: :string, format: :uri}, url: %Schema{type: :string, format: :uri},
events: event_type(), events: event_type(min_items: 1),
enabled: %Schema{type: :boolean} enabled: %Schema{type: :boolean}
} },
additionalProperties: false
} }
), ),
responses: %{ responses: %{
@ -180,8 +182,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.WebhookOperation do
} }
end end
defp event_type do defp event_type(opts \\ []) do
%Schema{ schema = %Schema{
type: :array, type: :array,
items: %Schema{ items: %Schema{
title: "Event", title: "Event",
@ -190,6 +192,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.WebhookOperation do
enum: ["account.created", "report.created"] enum: ["account.created", "report.created"]
} }
} }
case Keyword.fetch(opts, :min_items) do
{:ok, min_items} -> %{schema | minItems: min_items}
:error -> schema
end
end end
defp id_param do defp id_param do

View file

@ -61,6 +61,27 @@ defmodule Pleroma.Web.AdminAPI.WebhookControllerTest do
assert %{url: "http://example.com/webhook", events: [:"account.created"]} = Webhook.get(id) assert %{url: "http://example.com/webhook", events: [:"account.created"]} = Webhook.get(id)
end end
test "rejects empty events", %{conn: conn} do
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/webhooks", %{
url: "http://example.com/webhook",
events: []
})
|> json_response(:bad_request)
end
test "rejects internal flag", %{conn: conn} do
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/webhooks", %{
url: "http://example.com/webhook",
events: ["account.created"],
internal: true
})
|> json_response(:bad_request)
end
end end
describe "PATCH /api/pleroma/admin/webhooks" do describe "PATCH /api/pleroma/admin/webhooks" do
@ -78,6 +99,30 @@ defmodule Pleroma.Web.AdminAPI.WebhookControllerTest do
assert %{events: [:"report.created", :"account.created"]} = Webhook.get(id) assert %{events: [:"report.created", :"account.created"]} = Webhook.get(id)
end end
test "rejects empty events", %{conn: conn} do
{:ok, %{id: id}} =
Webhook.create(%{url: "https://example.com/webhook1", events: [:"report.created"]})
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/pleroma/admin/webhooks/#{id}", %{events: []})
|> json_response(:bad_request)
assert %{events: [:"report.created"]} = Webhook.get(id)
end
test "rejects internal flag", %{conn: conn} do
{:ok, %{id: id}} =
Webhook.create(%{url: "https://example.com/webhook1", events: [:"report.created"]})
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/pleroma/admin/webhooks/#{id}", %{internal: true})
|> json_response(:bad_request)
assert %{internal: false} = Webhook.get(id)
end
test "can't edit an internal webhook", %{conn: conn} do test "can't edit an internal webhook", %{conn: conn} do
{:ok, %{id: id}} = {:ok, %{id: id}} =
Webhook.create(%{url: "https://example.com/webhook1", events: [], internal: true}) Webhook.create(%{url: "https://example.com/webhook1", events: [], internal: true})