Mastodon-compatible webhooks
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
c897625efa
commit
4a57a65695
14 changed files with 608 additions and 3 deletions
|
@ -871,7 +871,8 @@
|
|||
|
||||
config :pleroma, ConcurrentLimiter, [
|
||||
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
|
||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
|
||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]},
|
||||
{Pleroma.Webhook.Notify, [max_running: 5, max_waiting: :infinity]}
|
||||
]
|
||||
|
||||
import_config "soapbox.exs"
|
||||
|
|
|
@ -3460,6 +3460,26 @@
|
|||
suggestion: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: Pleroma.Webhook.Notify,
|
||||
type: :keyword,
|
||||
description: "Concurrent limits configuration for webhooks.",
|
||||
suggestions: [max_running: 5, max_waiting: 5],
|
||||
children: [
|
||||
%{
|
||||
key: :max_running,
|
||||
type: :integer,
|
||||
description: "Max running concurrently jobs.",
|
||||
suggestion: [5]
|
||||
},
|
||||
%{
|
||||
key: :max_waiting,
|
||||
type: :integer,
|
||||
description: "Max waiting jobs.",
|
||||
suggestion: [5]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -308,7 +308,11 @@ defp http_children(_, _), do: []
|
|||
def limiters_setup do
|
||||
config = Config.get(ConcurrentLimiter, [])
|
||||
|
||||
[Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|
||||
[
|
||||
Pleroma.Web.RichMedia.Helpers,
|
||||
Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy,
|
||||
Pleroma.Webhook.Notify
|
||||
]
|
||||
|> Enum.each(fn module ->
|
||||
mod_config = Keyword.get(config, module, [])
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.User do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
import Ecto, only: [assoc: 2]
|
||||
import Pleroma.Webhook.Notify, only: [trigger_webhooks: 2]
|
||||
|
||||
alias Ecto.Multi
|
||||
alias Pleroma.Activity
|
||||
|
@ -36,6 +37,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.OAuth
|
||||
alias Pleroma.Web.RelMe
|
||||
alias Pleroma.Webhook
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
|
||||
require Logger
|
||||
|
@ -860,6 +862,7 @@ defp autofollowing_users(user) do
|
|||
def register(%Ecto.Changeset{} = changeset) do
|
||||
with {:ok, user} <- Repo.insert(changeset) do
|
||||
post_register_action(user)
|
||||
trigger_webhooks(user, :"account.created")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Webhook
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
alias Pleroma.Workers.PollWorker
|
||||
|
||||
|
@ -31,6 +32,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
import Ecto.Query
|
||||
import Pleroma.Web.ActivityPub.Utils
|
||||
import Pleroma.Web.ActivityPub.Visibility
|
||||
import Pleroma.Webhook.Notify, only: [trigger_webhooks: 2]
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
@ -391,6 +393,7 @@ defp do_flag(
|
|||
{:ok, activity} <- insert(flag_data, local),
|
||||
{:ok, stripped_activity} <- strip_report_status_data(activity),
|
||||
_ <- notify_and_stream(activity),
|
||||
_ <- trigger_webhooks(activity, :"report.created"),
|
||||
:ok <-
|
||||
maybe_federate(stripped_activity) do
|
||||
User.all_superusers()
|
||||
|
|
91
lib/pleroma/web/admin_api/controllers/webhook_controller.ex
Normal file
91
lib/pleroma/web/admin_api/controllers/webhook_controller.ex
Normal file
|
@ -0,0 +1,91 @@
|
|||
# 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, :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 {:ok, webhook} <- Webhook.create(params) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
# else
|
||||
# nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{body_params: params} = conn, %{id: id}) do
|
||||
with %Webhook{} = webhook <- Webhook.get(id),
|
||||
changeset <- Webhook.update(webhook, params),
|
||||
{:ok, webhook} <- Repo.update(changeset) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{id: id}) do
|
||||
with %Webhook{} = webhook <- Webhook.get(id),
|
||||
{:ok, webhook} <- Webhook.delete(webhook) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
end
|
||||
end
|
||||
|
||||
def enable(conn, %{id: id}) do
|
||||
with %Webhook{} = webhook <- Webhook.get(id),
|
||||
{:ok, webhook} <- Webhook.set_enabled(webhook, true) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def disable(conn, %{id: id}) do
|
||||
with %Webhook{} = webhook <- Webhook.get(id),
|
||||
{:ok, webhook} <- Webhook.set_enabled(webhook, false) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def rotate_secret(conn, %{id: id}) do
|
||||
with %Webhook{} = webhook <- Webhook.get(id),
|
||||
{:ok, webhook} <- Webhook.rotate_secret(webhook) do
|
||||
render(conn, "show.json", webhook: webhook)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
end
|
33
lib/pleroma/web/admin_api/views/webhook_view.ex
Normal file
33
lib/pleroma/web/admin_api/views/webhook_view.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
# 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,
|
||||
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
|
193
lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
Normal file
193
lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
Normal file
|
@ -0,0 +1,193 @@
|
|||
# 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
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
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},
|
||||
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,
|
||||
"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
|
|
@ -3,7 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
|
|
|
@ -238,6 +238,15 @@ defmodule Pleroma.Web.Router do
|
|||
post("/rules", RuleController, :create)
|
||||
patch("/rules/:id", RuleController, :update)
|
||||
delete("/rules/:id", RuleController, :delete)
|
||||
|
||||
get("/webhooks", WebhookController, :index)
|
||||
get("/webhooks/:id", WebhookController, :show)
|
||||
post("/webhooks", WebhookController, :create)
|
||||
patch("/webhooks/:id", WebhookController, :update)
|
||||
delete("/webhooks/:id", WebhookController, :delete)
|
||||
post("/webhooks/:id/enable", WebhookController, :enable)
|
||||
post("/webhooks/:id/disable", WebhookController, :disable)
|
||||
post("/webhooks/:id/rotate_secret", WebhookController, :rotate_secret)
|
||||
end
|
||||
|
||||
# AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
|
||||
|
|
100
lib/pleroma/webhook.ex
Normal file
100
lib/pleroma/webhook.ex
Normal file
|
@ -0,0 +1,100 @@
|
|||
# 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, null: false)
|
||||
field(:events, {:array, Ecto.Enum}, values: @event_types, null: false, default: [])
|
||||
field(:secret, :string, null: false, default: "")
|
||||
field(:enabled, :boolean, null: false, default: true)
|
||||
|
||||
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])
|
||||
|> 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])
|
||||
|> 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
|
72
lib/pleroma/webhook/notify.ex
Normal file
72
lib/pleroma/webhook/notify.ex
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Webhook.Notify do
|
||||
alias Phoenix.View
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
alias Pleroma.Webhook
|
||||
|
||||
def trigger_webhooks(%Activity{} = activity, :"report.created" = type) do
|
||||
webhooks = Webhook.get_by_type(type)
|
||||
|
||||
Enum.each(webhooks, fn webhook ->
|
||||
ConcurrentLimiter.limit(Webhook.Notify, fn ->
|
||||
Task.start(fn -> report_created(webhook, activity) end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def trigger_webhooks(%User{} = user, :"account.created" = type) do
|
||||
webhooks = Webhook.get_by_type(type)
|
||||
|
||||
Enum.each(webhooks, fn webhook ->
|
||||
ConcurrentLimiter.limit(Webhook.Notify, fn ->
|
||||
Task.start(fn -> account_created(webhook, user) end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def report_created(%Webhook{} = webhook, %Activity{} = report) do
|
||||
object =
|
||||
View.render(
|
||||
Pleroma.Web.MastodonAPI.Admin.ReportView,
|
||||
"show.json",
|
||||
Report.extract_report_info(report)
|
||||
)
|
||||
|
||||
deliver(webhook, object, :"report.created")
|
||||
end
|
||||
|
||||
def account_created(%Webhook{} = webhook, %User{} = user) do
|
||||
object =
|
||||
View.render(
|
||||
Pleroma.Web.MastodonAPI.Admin.AccountView,
|
||||
"show.json",
|
||||
user: user
|
||||
)
|
||||
|
||||
deliver(webhook, object, :"account.created")
|
||||
end
|
||||
|
||||
defp deliver(%Webhook{url: url, secret: secret}, object, type) do
|
||||
body =
|
||||
View.render_to_string(Pleroma.Web.AdminAPI.WebhookView, "event.json",
|
||||
type: type,
|
||||
object: object
|
||||
)
|
||||
|
||||
headers = [
|
||||
{"Content-Type", "application/json"},
|
||||
{"X-Hub-Signature", "sha256=#{signature(body, secret)}"}
|
||||
]
|
||||
|
||||
Pleroma.HTTP.post(url, body, headers)
|
||||
end
|
||||
|
||||
defp signature(body, secret) do
|
||||
:crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16()
|
||||
end
|
||||
end
|
20
priv/repo/migrations/20220624104914_create_webhooks.exs
Normal file
20
priv/repo/migrations/20220624104914_create_webhooks.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
# 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.CreateWebhooks do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists table(:webhooks) do
|
||||
add(:url, :string, null: false)
|
||||
add(:events, {:array, :string}, null: false, default: [])
|
||||
add(:secret, :string, null: false, default: "")
|
||||
add(:enabled, :boolean, null: false, default: true)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:webhooks, [:url]))
|
||||
end
|
||||
end
|
57
test/pleroma/webhook_test.ex
Normal file
57
test/pleroma/webhook_test.ex
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.WebhookTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Webhook
|
||||
|
||||
test "creating a webhook" do
|
||||
%{id: id} = Webhook.create(%{url: "https://example.com/webhook", events: [:"report.created"]})
|
||||
|
||||
assert %{url: "https://example.com/webhook"} = Webhook.get(id)
|
||||
end
|
||||
|
||||
test "editing a webhook" do
|
||||
%{id: id} =
|
||||
webhook = Webhook.create(%{url: "https://example.com/webhook", events: [:"report.created"]})
|
||||
|
||||
Webhook.update(webhook, %{events: [:"account.created"]})
|
||||
|
||||
assert %{events: [:"account.created"]} = Webhook.get(id)
|
||||
end
|
||||
|
||||
test "filter webhooks by type" do
|
||||
%{id: id1} =
|
||||
Webhook.create(%{url: "https://example.com/webhook1", events: [:"report.created"]})
|
||||
|
||||
%{id: id2} =
|
||||
Webhook.create(%{
|
||||
url: "https://example.com/webhook2",
|
||||
events: [:"account.created", :"report.created"]
|
||||
})
|
||||
|
||||
Webhook.create(%{url: "https://example.com/webhook3", events: [:"account.created"]})
|
||||
|
||||
assert [%{id: ^id1}, %{id: ^id2}] = Webhook.get_by_type(:"report.created")
|
||||
end
|
||||
|
||||
test "change webhook state" do
|
||||
%{id: id, enabled: true} =
|
||||
webhook = Webhook.create(%{url: "https://example.com/webhook", events: [:"report.created"]})
|
||||
|
||||
Webhook.set_enabled(webhook, false)
|
||||
assert %{enabled: false} = Webhook.get(id)
|
||||
end
|
||||
|
||||
test "rotate webhook secrets" do
|
||||
%{id: id, secret: secret} =
|
||||
webhook = Webhook.create(%{url: "https://example.com/webhook", events: [:"report.created"]})
|
||||
|
||||
Webhook.rotate_secret(webhook)
|
||||
%{secret: new_secret} = Webhook.get(id)
|
||||
assert secret != new_secret
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue