Mastodon-compatible webhooks
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
parent
1efe48672d
commit
9d7bdab7a2
25 changed files with 1059 additions and 6 deletions
1
changelog.d/webhooks.add
Normal file
1
changelog.d/webhooks.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for Mastodon-compatible webhooks
|
||||||
|
|
@ -926,7 +926,8 @@ config :pleroma, Pleroma.User.Backup,
|
||||||
timeout: :timer.minutes(30)
|
timeout: :timer.minutes(30)
|
||||||
|
|
||||||
config :pleroma, ConcurrentLimiter, [
|
config :pleroma, ConcurrentLimiter, [
|
||||||
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
{Pleroma.Search, [max_running: 30, max_waiting: 50]},
|
||||||
|
{Pleroma.Webhook.Notify, [max_running: 5, max_waiting: 200]}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
|
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
|
||||||
|
|
|
||||||
|
|
@ -3479,6 +3479,26 @@ config :pleroma, :config_description, [
|
||||||
suggestion: [5]
|
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]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1806,3 +1806,92 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns
|
||||||
```json
|
```json
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/admin/webhooks`
|
||||||
|
|
||||||
|
### List webhooks
|
||||||
|
|
||||||
|
- Method: `GET`
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"id": "2",
|
||||||
|
"events": ["account.created"],
|
||||||
|
"url": "https://webhook.example/",
|
||||||
|
"secret": "eb85d4ccd8510e78f912743949dc354e8146987d",
|
||||||
|
"updated_at": "2022-10-29T17:44:16.000Z",
|
||||||
|
"created_at": "2022-10-29T17:44:13.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Get an individual webhook
|
||||||
|
|
||||||
|
- Method: `GET`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks`
|
||||||
|
|
||||||
|
### Create a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `url`: **string** Webhook URL
|
||||||
|
- *optional* `events`: **[string]** Types of events to trigger on (`account.created`, `report.created`)
|
||||||
|
- *optional* `enabled`: **boolean** Whether webhook is enabled
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `PATCH /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Update a webhook
|
||||||
|
|
||||||
|
- Method: `PATCH`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- *optional* `url`: **string** Webhook URL
|
||||||
|
- *optional* `events`: **[string]** Types of events to trigger on (`account.created`, `report.created`)
|
||||||
|
- *optional* `enabled`: **boolean** Whether webhook is enabled
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `DELETE /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Delete a webhook
|
||||||
|
|
||||||
|
- Method: `DELETE`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/enable`
|
||||||
|
|
||||||
|
### Activate a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/disable`
|
||||||
|
|
||||||
|
### Deactivate a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/rotate_secret`
|
||||||
|
|
||||||
|
### Rotate webhook signing secret
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,8 @@ defmodule Pleroma.Application do
|
||||||
config = Config.get(ConcurrentLimiter, [])
|
config = Config.get(ConcurrentLimiter, [])
|
||||||
|
|
||||||
[
|
[
|
||||||
Pleroma.Search
|
Pleroma.Search,
|
||||||
|
Pleroma.Webhook.Notify
|
||||||
]
|
]
|
||||||
|> Enum.each(fn module ->
|
|> Enum.each(fn module ->
|
||||||
mod_config = Keyword.get(config, module, [])
|
mod_config = Keyword.get(config, module, [])
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.OAuth
|
alias Pleroma.Web.OAuth
|
||||||
alias Pleroma.Web.RelMe
|
alias Pleroma.Web.RelMe
|
||||||
|
alias Pleroma.Webhook.Notify
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
alias Pleroma.Workers.DeleteWorker
|
alias Pleroma.Workers.DeleteWorker
|
||||||
alias Pleroma.Workers.UserRefreshWorker
|
alias Pleroma.Workers.UserRefreshWorker
|
||||||
|
|
@ -980,6 +981,7 @@ defmodule Pleroma.User do
|
||||||
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
|
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
|
||||||
def register(%Ecto.Changeset{} = changeset) do
|
def register(%Ecto.Changeset{} = changeset) do
|
||||||
with {:ok, user} <- Repo.insert(changeset) do
|
with {:ok, user} <- Repo.insert(changeset) do
|
||||||
|
Notify.trigger_webhooks(user, :"account.created")
|
||||||
post_register_action(user)
|
post_register_action(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Pleroma.Web.ActivityPub.Utils
|
import Pleroma.Web.ActivityPub.Utils
|
||||||
import Pleroma.Web.ActivityPub.Visibility
|
import Pleroma.Web.ActivityPub.Visibility
|
||||||
|
import Pleroma.Webhook.Notify, only: [trigger_webhooks: 2]
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
@ -415,7 +416,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
with flag_data <- make_flag_data(params, additional),
|
with flag_data <- make_flag_data(params, additional),
|
||||||
{:ok, activity} <- insert(flag_data, local),
|
{:ok, activity} <- insert(flag_data, local),
|
||||||
_ <- notify_and_stream(activity),
|
_ <- notify_and_stream(activity),
|
||||||
:ok <- maybe_federate(activity) do
|
_ <- trigger_webhooks(activity, :"report.created"),
|
||||||
|
:ok <-
|
||||||
|
maybe_federate(activity) do
|
||||||
User.all_users_with_privilege(:reports_manage_reports)
|
User.all_users_with_privilege(:reports_manage_reports)
|
||||||
|> Enum.filter(fn user -> user.ap_id != actor end)
|
|> Enum.filter(fn user -> user.ap_id != actor end)
|
||||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||||
|
|
|
||||||
88
lib/pleroma/web/admin_api/controllers/webhook_controller.ex
Normal file
88
lib/pleroma/web/admin_api/controllers/webhook_controller.ex
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
# 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),
|
||||||
|
webhook <- Webhook.update(webhook, params) 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
|
||||||
|
|
@ -105,7 +105,8 @@ defmodule Pleroma.Web.ApiSpec do
|
||||||
"Report management",
|
"Report management",
|
||||||
"Status administration",
|
"Status administration",
|
||||||
"User administration",
|
"User administration",
|
||||||
"Announcement management"
|
"Announcement management",
|
||||||
|
"Webhooks"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
||||||
|
|
|
||||||
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
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
alias OpenApiSpex.Operation
|
|
||||||
alias OpenApiSpex.Operation
|
alias OpenApiSpex.Operation
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
|
|
|
||||||
65
lib/pleroma/web/mastodon_api/admin/views/account_view.ex
Normal file
65
lib/pleroma/web/mastodon_api/admin/views/account_view.ex
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI
|
||||||
|
|
||||||
|
def render("index.json", %{users: users}) do
|
||||||
|
render_many(users, __MODULE__, "show.json", as: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{user: user}) do
|
||||||
|
account =
|
||||||
|
MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true})
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: user.id,
|
||||||
|
username: username_from_nickname(user.nickname),
|
||||||
|
domain: domain_from_nickname(user.nickname),
|
||||||
|
created_at: Utils.to_masto_date(user.inserted_at),
|
||||||
|
email: user.email,
|
||||||
|
ip: nil,
|
||||||
|
role: role(user),
|
||||||
|
confirmed: user.is_confirmed,
|
||||||
|
sensitized: nil,
|
||||||
|
suspened: nil,
|
||||||
|
silenced: nil,
|
||||||
|
disabled: !user.is_active,
|
||||||
|
approved: user.is_approved,
|
||||||
|
locale: nil,
|
||||||
|
invite_request: user.registration_reason,
|
||||||
|
ips: nil,
|
||||||
|
account: account
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp username_from_nickname(string) when is_binary(string) do
|
||||||
|
hd(String.split(string, "@"))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp username_from_nickname(_), do: nil
|
||||||
|
|
||||||
|
defp domain_from_nickname(string) when is_binary(string) do
|
||||||
|
String.split(string, "@")
|
||||||
|
|> Enum.at(1, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp domain_from_nickname(_), do: nil
|
||||||
|
|
||||||
|
defp role(%User{is_admin: true}) do
|
||||||
|
"admin"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp role(%User{is_moderator: true}) do
|
||||||
|
"moderator"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp role(_user) do
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
54
lib/pleroma/web/mastodon_api/admin/views/report_view.ex
Normal file
54
lib/pleroma/web/mastodon_api/admin/views/report_view.ex
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.Admin.ReportView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.HTML
|
||||||
|
alias Pleroma.Web.AdminAPI.Report
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI.Admin.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("index.json", %{reports: reports}) do
|
||||||
|
reports
|
||||||
|
|> Enum.map(&Report.extract_report_info/1)
|
||||||
|
|> Enum.map(&render(__MODULE__, "show.json", &1))
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{
|
||||||
|
report: report,
|
||||||
|
user: account,
|
||||||
|
account: target_account,
|
||||||
|
statuses: statuses
|
||||||
|
}) do
|
||||||
|
created_at = Utils.to_masto_date(report.data["published"])
|
||||||
|
|
||||||
|
content =
|
||||||
|
unless is_nil(report.data["content"]) do
|
||||||
|
HTML.filter_tags(report.data["content"])
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: report.id,
|
||||||
|
action_taken: report.data["state"] != "open",
|
||||||
|
category: "other",
|
||||||
|
comment: content,
|
||||||
|
created_at: created_at,
|
||||||
|
updated_at: created_at,
|
||||||
|
account: AccountView.render("show.json", %{user: account}),
|
||||||
|
target_account: AccountView.render("show.json", %{user: target_account}),
|
||||||
|
assigned_account: nil,
|
||||||
|
action_taken_by_account: nil,
|
||||||
|
statuses:
|
||||||
|
StatusView.render("index.json", %{
|
||||||
|
activities: statuses,
|
||||||
|
as: :activity
|
||||||
|
}),
|
||||||
|
rules: []
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -320,6 +320,15 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/announcements/:id", AnnouncementController, :show)
|
get("/announcements/:id", AnnouncementController, :show)
|
||||||
patch("/announcements/:id", AnnouncementController, :change)
|
patch("/announcements/:id", AnnouncementController, :change)
|
||||||
delete("/announcements/:id", AnnouncementController, :delete)
|
delete("/announcements/:id", AnnouncementController, :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
|
end
|
||||||
|
|
||||||
# AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
|
# AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
|
||||||
|
|
|
||||||
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)
|
||||||
|
field(:events, {:array, Ecto.Enum}, values: @event_types, default: [])
|
||||||
|
field(:secret, :string, default: "")
|
||||||
|
field(:enabled, :boolean, 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
|
||||||
83
lib/pleroma/webhook/notify.ex
Normal file
83
lib/pleroma/webhook/notify.ex
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# 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
|
||||||
|
alias Pleroma.Workers.WebhookWorker
|
||||||
|
|
||||||
|
def trigger_webhooks(%Activity{} = activity, :"report.created" = type) do
|
||||||
|
webhooks = Webhook.get_by_type(type)
|
||||||
|
|
||||||
|
webhooks
|
||||||
|
|> Enum.map(fn webhook ->
|
||||||
|
WebhookWorker.new(%{
|
||||||
|
"op" => "notify",
|
||||||
|
"type" => type,
|
||||||
|
"webhook_id" => webhook.id,
|
||||||
|
"activity_id" => activity.id
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|> Oban.insert_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def trigger_webhooks(%User{} = user, :"account.created" = type) do
|
||||||
|
webhooks = Webhook.get_by_type(type)
|
||||||
|
|
||||||
|
webhooks
|
||||||
|
|> Enum.map(fn webhook ->
|
||||||
|
WebhookWorker.new(%{
|
||||||
|
"op" => "notify",
|
||||||
|
"type" => type,
|
||||||
|
"webhook_id" => webhook.id,
|
||||||
|
"user_id" => user.id
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|> Oban.insert_all()
|
||||||
|
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
|
||||||
41
lib/pleroma/workers/webhook_worker.ex
Normal file
41
lib/pleroma/workers/webhook_worker.ex
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.WebhookWorker do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
|
||||||
|
use Oban.Worker, queue: :background
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def perform(%Job{
|
||||||
|
args: %{
|
||||||
|
"op" => "notify",
|
||||||
|
"type" => "report.created",
|
||||||
|
"webhook_id" => webhook_id,
|
||||||
|
"activity_id" => report_id
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
webhook = Pleroma.Webhook.get(webhook_id)
|
||||||
|
report = Activity.get_by_id(report_id)
|
||||||
|
|
||||||
|
Pleroma.Webhook.Notify.report_created(webhook, report)
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(%Job{
|
||||||
|
args: %{
|
||||||
|
"op" => "notify",
|
||||||
|
"type" => "account.created",
|
||||||
|
"webhook_id" => webhook_id,
|
||||||
|
"user_id" => user_id
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
webhook = Pleroma.Webhook.get(webhook_id)
|
||||||
|
user = Pleroma.User.get_by_id(user_id)
|
||||||
|
|
||||||
|
Pleroma.Webhook.Notify.account_created(webhook, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def timeout(_job), do: :timer.seconds(10)
|
||||||
|
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
|
||||||
|
|
@ -416,7 +416,8 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||||
["user_invite_tokens"],
|
["user_invite_tokens"],
|
||||||
["user_notes"],
|
["user_notes"],
|
||||||
["user_relationships"],
|
["user_relationships"],
|
||||||
["users"]
|
["users"],
|
||||||
|
["webhooks"]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,14 @@ defmodule Pleroma.UserTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Webhook.Notify
|
||||||
|
|
||||||
use Pleroma.DataCase, async: false
|
use Pleroma.DataCase, async: false
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
|
import Mock
|
||||||
import Swoosh.TestAssertions
|
import Swoosh.TestAssertions
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
|
|
@ -719,6 +721,14 @@ defmodule Pleroma.UserTest do
|
||||||
|
|
||||||
assert user.is_confirmed
|
assert user.is_confirmed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test_with_mock "triggers webhooks", Notify, trigger_webhooks: fn _, _ -> nil end do
|
||||||
|
cng = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
|
||||||
|
{:ok, registered_user} = User.register(cng)
|
||||||
|
|
||||||
|
assert_called(Notify.trigger_webhooks(registered_user, :"account.created"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user registration, with :account_activation_required" do
|
describe "user registration, with :account_activation_required" do
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Webhook.Notify
|
||||||
|
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
import Mock
|
import Mock
|
||||||
|
|
@ -1715,6 +1716,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
assert Repo.aggregate(Object, :count, :id) == 1
|
assert Repo.aggregate(Object, :count, :id) == 1
|
||||||
assert Repo.aggregate(Notification, :count, :id) == 0
|
assert Repo.aggregate(Notification, :count, :id) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test_with_mock "triggers webhooks",
|
||||||
|
%{
|
||||||
|
reporter: reporter,
|
||||||
|
context: context,
|
||||||
|
target_account: target_account,
|
||||||
|
reported_activity: reported_activity,
|
||||||
|
content: content
|
||||||
|
},
|
||||||
|
Notify,
|
||||||
|
[:passthrough],
|
||||||
|
trigger_webhooks: fn _, _ -> nil end do
|
||||||
|
{:ok, activity} =
|
||||||
|
ActivityPub.flag(%{
|
||||||
|
actor: reporter,
|
||||||
|
context: context,
|
||||||
|
account: target_account,
|
||||||
|
statuses: [reported_activity],
|
||||||
|
content: content
|
||||||
|
})
|
||||||
|
|
||||||
|
assert_called(Notify.trigger_webhooks(activity, :"report.created"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fetch_activities/2 returns activities addressed to a list " do
|
test "fetch_activities/2 returns activities addressed to a list " do
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
# 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
|
||||||
|
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
|
||||||
75
test/pleroma/webhook/notify_test.exs
Normal file
75
test/pleroma/webhook/notify_test.exs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Webhook.NotifyTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Webhook
|
||||||
|
alias Pleroma.Webhook.Notify
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "triggers a webhook for a report" do
|
||||||
|
%{id: activity_id} = activity = insert(:report_activity)
|
||||||
|
|
||||||
|
url = "https://example.com/webhook"
|
||||||
|
|
||||||
|
Webhook.create(%{url: url, events: [:"report.created"]})
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn %{url: ^url, body: body} = _ ->
|
||||||
|
report =
|
||||||
|
body
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.get("object")
|
||||||
|
|
||||||
|
assert %{"id" => ^activity_id} = report
|
||||||
|
|
||||||
|
%Tesla.Env{status: 200, body: ""}
|
||||||
|
end)
|
||||||
|
|
||||||
|
[job] = Notify.trigger_webhooks(activity, :"report.created")
|
||||||
|
|
||||||
|
Pleroma.Workers.WebhookWorker.perform(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "triggers a webhook for an account" do
|
||||||
|
%{id: account_id} = user = insert(:user)
|
||||||
|
|
||||||
|
url = "https://example.com/webhook"
|
||||||
|
|
||||||
|
Webhook.create(%{url: url, events: [:"account.created"]})
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn %{url: ^url, body: body} = _ ->
|
||||||
|
report =
|
||||||
|
body
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.get("object")
|
||||||
|
|
||||||
|
assert %{"id" => ^account_id} = report
|
||||||
|
|
||||||
|
%Tesla.Env{status: 200, body: ""}
|
||||||
|
end)
|
||||||
|
|
||||||
|
[job] = Notify.trigger_webhooks(user, :"account.created")
|
||||||
|
|
||||||
|
Pleroma.Workers.WebhookWorker.perform(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "notifies have a valid signature" do
|
||||||
|
activity = insert(:report_activity)
|
||||||
|
|
||||||
|
%{secret: secret} =
|
||||||
|
webhook = Webhook.create(%{url: "https://example.com/webhook", events: [:"report.created"]})
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn %{url: "https://example.com/webhook", body: body, headers: headers} = _ ->
|
||||||
|
{"X-Hub-Signature", "sha256=" <> signature} =
|
||||||
|
Enum.find(headers, fn {key, _} -> key == "X-Hub-Signature" end)
|
||||||
|
|
||||||
|
assert signature == :crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16()
|
||||||
|
%Tesla.Env{status: 200, body: ""}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Notify.report_created(webhook, activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
56
test/pleroma/webhook_test.exs
Normal file
56
test/pleroma/webhook_test.exs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# 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.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…
Add table
Add a link
Reference in a new issue