Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85827996
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
index e72f45c21..d8fb54cad 100644
--- a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
@@ -1,37 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.FallbackController do
use Pleroma.Web, :controller
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> json(%{error: dgettext("errors", "Not found")})
end
+ def call(conn, {:error, :forbidden}) do
+ conn
+ |> put_status(:forbidden)
+ |> json(%{error: dgettext("errors", "Forbidden")})
+ end
+
def call(conn, {:error, reason}) do
conn
|> put_status(:bad_request)
|> json(%{error: reason})
end
def call(conn, {:errors, errors}) do
conn
|> put_status(:bad_request)
|> json(%{errors: errors})
end
def call(conn, {:param_cast, _}) do
conn
|> put_status(:bad_request)
|> json(dgettext("errors", "Invalid parameters"))
end
def call(conn, _) do
conn
|> put_status(:internal_server_error)
|> json(%{error: dgettext("errors", "Something went wrong")})
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/webhook_controller.ex b/lib/pleroma/web/admin_api/controllers/webhook_controller.ex
index 8a6b0de7a..dd9712a31 100644
--- a/lib/pleroma/web/admin_api/controllers/webhook_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/webhook_controller.ex
@@ -1,88 +1,95 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.WebhookController do
use Pleroma.Web, :controller
alias Pleroma.Repo
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Webhook
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write"]}
when action in [:update, :create, :delete, :enable, :disable, :rotate_secret]
)
plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.WebhookOperation
def index(conn, _) do
webhooks =
Webhook
|> Repo.all()
render(conn, "index.json", webhooks: webhooks)
end
def show(conn, %{id: id}) do
with %Webhook{} = webhook <- Webhook.get(id) do
render(conn, "show.json", webhook: webhook)
else
nil -> {:error, :not_found}
end
end
def create(%{body_params: params} = conn, _) do
with webhook <- Webhook.create(params) do
render(conn, "show.json", webhook: webhook)
end
end
def update(%{body_params: params} = conn, %{id: id}) do
- with %Webhook{} = webhook <- Webhook.get(id),
+ with %Webhook{internal: false} = webhook <- Webhook.get(id),
webhook <- Webhook.update(webhook, params) do
render(conn, "show.json", webhook: webhook)
+ else
+ %Webhook{internal: true} -> {:error, :forbidden}
end
end
def delete(conn, %{id: id}) do
- with %Webhook{} = webhook <- Webhook.get(id),
+ with %Webhook{internal: false} = webhook <- Webhook.get(id),
{:ok, webhook} <- Webhook.delete(webhook) do
render(conn, "show.json", webhook: webhook)
+ else
+ %Webhook{internal: true} -> {:error, :forbidden}
end
end
def enable(conn, %{id: id}) do
- with %Webhook{} = webhook <- Webhook.get(id),
+ with %Webhook{internal: false} = webhook <- Webhook.get(id),
{:ok, webhook} <- Webhook.set_enabled(webhook, true) do
render(conn, "show.json", webhook: webhook)
else
+ %Webhook{internal: true} -> {:error, :forbidden}
nil -> {:error, :not_found}
end
end
def disable(conn, %{id: id}) do
- with %Webhook{} = webhook <- Webhook.get(id),
+ with %Webhook{internal: false} = webhook <- Webhook.get(id),
{:ok, webhook} <- Webhook.set_enabled(webhook, false) do
render(conn, "show.json", webhook: webhook)
else
+ %Webhook{internal: true} -> {:error, :forbidden}
nil -> {:error, :not_found}
end
end
def rotate_secret(conn, %{id: id}) do
- with %Webhook{} = webhook <- Webhook.get(id),
+ with %Webhook{internal: false} = webhook <- Webhook.get(id),
{:ok, webhook} <- Webhook.rotate_secret(webhook) do
render(conn, "show.json", webhook: webhook)
else
+ %Webhook{internal: true} -> {:error, :forbidden}
nil -> {:error, :not_found}
end
end
end
diff --git a/lib/pleroma/web/admin_api/views/webhook_view.ex b/lib/pleroma/web/admin_api/views/webhook_view.ex
index 725183029..552aba4f1 100644
--- a/lib/pleroma/web/admin_api/views/webhook_view.ex
+++ b/lib/pleroma/web/admin_api/views/webhook_view.ex
@@ -1,33 +1,34 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.WebhookView do
use Pleroma.Web, :view
alias Pleroma.Web.CommonAPI.Utils
def render("index.json", %{webhooks: webhooks}) do
render_many(webhooks, __MODULE__, "show.json")
end
def render("show.json", %{webhook: webhook}) do
%{
id: webhook.id |> to_string(),
url: webhook.url,
events: webhook.events,
secret: webhook.secret,
enabled: webhook.enabled,
+ internal: webhook.internal,
created_at: Utils.to_masto_date(webhook.inserted_at),
updated_at: Utils.to_masto_date(webhook.updated_at)
}
end
def render("event.json", %{type: type, object: object}) do
%{
type: type,
created_at: Utils.to_masto_date(NaiveDateTime.utc_now()),
object: object
}
end
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex b/lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
index c83caa555..fd5f3a561 100644
--- a/lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
@@ -1,193 +1,201 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.WebhookOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Webhooks"],
summary: "Retrieve a list of webhooks",
operationId: "AdminAPI.WebhookController.index",
security: [%{"oAuth" => ["admin:show"]}],
responses: %{
200 =>
Operation.response("Array of webhooks", "application/json", %Schema{
type: :array,
items: webhook()
})
}
}
end
def show_operation do
%Operation{
tags: ["Webhooks"],
summary: "Retrieve a webhook",
operationId: "AdminAPI.WebhookController.show",
security: [%{"oAuth" => ["admin:show"]}],
parameters: [id_param()],
responses: %{
200 => Operation.response("Webhook", "application/json", webhook())
}
}
end
def create_operation do
%Operation{
tags: ["Webhooks"],
summary: "Create a webhook",
operationId: "AdminAPI.WebhookController.create",
security: [%{"oAuth" => ["admin:write"]}],
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for creating a webhook",
type: :object,
properties: %{
url: %Schema{type: :string, format: :uri, required: true},
events: event_type(true),
enabled: %Schema{type: :boolean}
}
}
),
responses: %{
200 => Operation.response("Webhook", "application/json", webhook())
}
}
end
def update_operation do
%Operation{
tags: ["Webhooks"],
summary: "Update a webhook",
operationId: "AdminAPI.WebhookController.update",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [id_param()],
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for updating a webhook",
type: :object,
properties: %{
url: %Schema{type: :string, format: :uri},
events: event_type(),
enabled: %Schema{type: :boolean}
}
}
),
responses: %{
- 200 => Operation.response("Webhook", "application/json", webhook())
+ 200 => Operation.response("Webhook", "application/json", webhook()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Webhooks"],
summary: "Delete a webhook",
operationId: "AdminAPI.WebhookController.delete",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [id_param()],
responses: %{
- 200 => Operation.response("Webhook", "application/json", webhook())
+ 200 => Operation.response("Webhook", "application/json", webhook()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def enable_operation do
%Operation{
tags: ["Webhooks"],
summary: "Enable a webhook",
operationId: "AdminAPI.WebhookController.enable",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [id_param()],
responses: %{
- 200 => Operation.response("Webhook", "application/json", webhook())
+ 200 => Operation.response("Webhook", "application/json", webhook()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def disable_operation do
%Operation{
tags: ["Webhooks"],
summary: "Disable a webhook",
operationId: "AdminAPI.WebhookController.disable",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [id_param()],
responses: %{
- 200 => Operation.response("Webhook", "application/json", webhook())
+ 200 => Operation.response("Webhook", "application/json", webhook()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def rotate_secret_operation do
%Operation{
tags: ["Webhooks"],
summary: "Rotate webhook signing secret",
operationId: "AdminAPI.WebhookController.rotate_secret",
security: [%{"oAuth" => ["admin:write"]}],
parameters: [id_param()],
responses: %{
- 200 => Operation.response("Webhook", "application/json", webhook())
+ 200 => Operation.response("Webhook", "application/json", webhook()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
defp webhook do
%Schema{
title: "Webhook",
description: "Schema for a webhook",
type: :object,
properties: %{
id: %Schema{type: :string},
url: %Schema{type: :string, format: :uri},
events: event_type(),
secret: %Schema{type: :string},
enabled: %Schema{type: :boolean},
+ internal: %Schema{type: :boolean},
created_at: %Schema{type: :string, format: :"date-time"},
updated_at: %Schema{type: :string, format: :"date-time"}
},
example: %{
"id" => "1",
"url" => "https://example.com/webhook",
"events" => ["report.created"],
"secret" => "d3d8cf4bc11fd9c41fd34dcc38d282e451c8bd34",
"enabled" => true,
+ "internal" => false,
"created_at" => "2022-06-24T16:19:38.523Z",
"updated_at" => "2022-06-24T16:19:38.523Z"
}
}
end
defp event_type(required \\ nil) do
%Schema{
type: :array,
items: %Schema{
title: "Event",
description: "Event type",
type: :string,
enum: ["account.created", "report.created"],
required: required
}
}
end
defp id_param do
Operation.parameter(:id, :path, :string, "Webhook ID",
example: "123",
required: true
)
end
end
diff --git a/lib/pleroma/webhook.ex b/lib/pleroma/webhook.ex
index 6cf47fd68..f1e1b7678 100644
--- a/lib/pleroma/webhook.ex
+++ b/lib/pleroma/webhook.ex
@@ -1,100 +1,101 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Webhook do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Repo
@event_types [:"account.created", :"report.created"]
schema "webhooks" do
field(:url, ObjectValidators.Uri)
field(:events, {:array, Ecto.Enum}, values: @event_types, default: [])
field(:secret, :string, default: "")
field(:enabled, :boolean, default: true)
+ field(:internal, :boolean, default: false)
timestamps()
end
def get(id), do: Repo.get(__MODULE__, id)
def get_by_type(type) do
__MODULE__
|> where([w], ^type in w.events)
|> Repo.all()
end
def changeset(%__MODULE__{} = webhook, params) do
webhook
- |> cast(params, [:url, :events, :enabled])
+ |> cast(params, [:url, :events, :enabled, :internal])
|> validate_required([:url, :events])
|> unique_constraint(:url)
|> strip_events()
|> put_secret()
end
def update_changeset(%__MODULE__{} = webhook, params \\ %{}) do
webhook
- |> cast(params, [:url, :events, :enabled])
+ |> cast(params, [:url, :events, :enabled, :internal])
|> unique_constraint(:url)
|> strip_events()
end
def create(params) do
{:ok, webhook} =
%__MODULE__{}
|> changeset(params)
|> Repo.insert()
webhook
end
def update(%__MODULE__{} = webhook, params) do
{:ok, webhook} =
webhook
|> update_changeset(params)
|> Repo.update()
webhook
end
def delete(webhook), do: webhook |> Repo.delete()
def rotate_secret(%__MODULE__{} = webhook) do
webhook
|> cast(%{}, [])
|> put_secret()
|> Repo.update()
end
def set_enabled(%__MODULE__{} = webhook, enabled) do
webhook
|> cast(%{enabled: enabled}, [:enabled])
|> Repo.update()
end
defp strip_events(params) do
if Map.has_key?(params, :events) do
params
|> Map.put(:events, Enum.filter(params[:events], &Enum.member?(@event_types, &1)))
else
params
end
end
defp put_secret(changeset) do
changeset
|> put_change(:secret, generate_secret())
end
defp generate_secret do
Base.encode16(:crypto.strong_rand_bytes(20))
|> String.downcase()
end
end
diff --git a/priv/repo/migrations/20221029171353_add_internal_to_webhooks.exs b/priv/repo/migrations/20221029171353_add_internal_to_webhooks.exs
new file mode 100644
index 000000000..8c8ce7fc7
--- /dev/null
+++ b/priv/repo/migrations/20221029171353_add_internal_to_webhooks.exs
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.AddInternalToWebhooks do
+ use Ecto.Migration
+
+ def change do
+ alter table(:webhooks) do
+ add(:internal, :boolean, default: false, null: false)
+ end
+ end
+end
diff --git a/test/pleroma/web/admin_api/controllers/webhook_controller_test.exs b/test/pleroma/web/admin_api/controllers/webhook_controller_test.exs
index 6a1586ff1..0cd00e753 100644
--- a/test/pleroma/web/admin_api/controllers/webhook_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/webhook_controller_test.exs
@@ -1,84 +1,98 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.WebhookControllerTest do
use Pleroma.Web.ConnCase, async: true
import Pleroma.Factory
alias Pleroma.Webhook
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
describe "GET /api/pleroma/admin/webhook" do
test "lists existing webhooks", %{conn: conn} do
Webhook.create(%{url: "https://example.com/webhook1", events: [:"report.created"]})
Webhook.create(%{url: "https://example.com/webhook2", events: [:"account.created"]})
response =
conn
|> get("/api/pleroma/admin/webhooks")
|> json_response_and_validate_schema(:ok)
assert length(response) == 2
end
end
describe "POST /api/pleroma/admin/webhooks" do
test "creates a webhook", %{conn: conn} do
%{"id" => id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/webhooks", %{
url: "http://example.com/webhook",
events: ["account.created"]
})
|> json_response_and_validate_schema(:ok)
assert %{url: "http://example.com/webhook", events: [:"account.created"]} = Webhook.get(id)
end
end
describe "PATCH /api/pleroma/admin/webhooks" do
test "edits a webhook", %{conn: conn} do
%{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: ["report.created", "account.created"]
})
|> json_response_and_validate_schema(:ok)
assert %{events: [:"report.created", :"account.created"]} = Webhook.get(id)
end
+
+ test "can't edit an internal webhook", %{conn: conn} do
+ %{id: id} =
+ Webhook.create(%{url: "https://example.com/webhook1", events: [], internal: true})
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/pleroma/admin/webhooks/#{id}", %{
+ events: ["report.created", "account.created"]
+ })
+ |> json_response_and_validate_schema(:forbidden)
+
+ assert %{events: []} = Webhook.get(id)
+ end
end
describe "DELETE /api/pleroma/admin/webhooks" do
test "deletes a webhook", %{conn: conn} do
%{id: id} =
Webhook.create(%{url: "https://example.com/webhook1", events: [:"report.created"]})
conn
|> put_req_header("content-type", "application/json")
|> delete("/api/pleroma/admin/webhooks/#{id}")
|> json_response_and_validate_schema(:ok)
assert [] =
Webhook
|> Pleroma.Repo.all()
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 31, 1:28 AM (20 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1800858
Default Alt Text
(19 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline