Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Sat, Oct 31, 1:28 AM (19 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1800858
Default Alt Text
(19 KB)

Event Timeline