Compare commits

...

9 commits

Author SHA1 Message Date
marcin mikołajczak 934a2b8852 Merge branch 'webhooks_' into 'develop'
Mastodon-compatible webhooks

See merge request pleroma/pleroma!3683
2024-04-25 20:41:04 +00:00
lain 50af909c01 Merge branch 'pleroma-card-image-description' into 'develop'
Include image description in status media cards

See merge request pleroma/pleroma!4101
2024-04-19 07:39:05 +00:00
marcin mikołajczak 6f6bede900 Include image description in status media cards
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-19 10:20:31 +04:00
lain 87b8ac3ce6 Merge branch 'receiverworker-error-handling' into 'develop'
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}

See merge request pleroma/pleroma!4100
2024-04-19 06:04:44 +00:00
Haelwenn (lanodan) Monnier a299ddb10e
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}
Otherwise an error like `{:signature, {:error, {:error, :not_found}}}` ends up considered a success.
2024-04-17 07:43:47 +02:00
marcin mikołajczak 3ed4ae17bb Merge remote-tracking branch 'origin/develop' into webhooks_
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-12-22 13:58:25 +01:00
marcin mikołajczak 10b9e4ca74 Add Webhooks tag to ApiSpec
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-08-19 15:31:26 +02:00
marcin mikołajczak e79b26a3b4 Add documentation for webhooks API
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-08-15 00:13:57 +02:00
marcin mikołajczak fc78e532b8 Mastodon-compatible webhooks
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-08-14 23:53:29 +02:00
28 changed files with 852 additions and 6 deletions

View file

@ -0,0 +1 @@
Include image description in status media cards

View file

@ -0,0 +1 @@
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}

1
changelog.d/webhooks.add Normal file
View file

@ -0,0 +1 @@
Add support for Mastodon-compatible webhooks

View file

@ -889,7 +889,8 @@ config :pleroma, Pleroma.User.Backup,
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.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

View file

@ -3484,6 +3484,26 @@ config :pleroma, :config_description, [
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]
}
]
}
]
},

View file

@ -1751,3 +1751,92 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns
```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

View file

@ -287,7 +287,8 @@ defmodule Pleroma.Application do
[
Pleroma.Web.RichMedia.Helpers,
Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy,
Pleroma.Search
Pleroma.Search,
Pleroma.Webhook.Notify
]
|> Enum.each(fn module ->
mod_config = Keyword.get(config, module, [])

View file

@ -37,6 +37,7 @@ defmodule Pleroma.User do
alias Pleroma.Web.Endpoint
alias Pleroma.Web.OAuth
alias Pleroma.Web.RelMe
alias Pleroma.Webhook.Notify
alias Pleroma.Workers.BackgroundWorker
require Logger
@ -931,6 +932,7 @@ defmodule Pleroma.User do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset) do
Notify.trigger_webhooks(user, :"account.created")
post_register_action(user)
end
end

View file

@ -30,6 +30,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
@ -415,6 +416,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{: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_users_with_privilege(:reports_manage_reports)

View 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

View 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

View file

@ -104,7 +104,8 @@ defmodule Pleroma.Web.ApiSpec do
"Report management",
"Status administration",
"User administration",
"Announcement management"
"Announcement management",
"Webhooks"
]
},
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},

View 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

View file

@ -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

View file

@ -58,6 +58,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
format: :uri,
description: "Preview thumbnail"
},
image_description: %Schema{
type: :string,
description: "Alternate text that describes what is in the thumbnail"
},
title: %Schema{type: :string, description: "Title of linked resource"},
description: %Schema{type: :string, description: "Description of preview"}
}

View file

@ -589,6 +589,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url,
image_description: rich_media["image:alt"] || "",
title: rich_media["title"] || "",
description: rich_media["description"] || "",
pleroma: %{

View file

@ -303,6 +303,15 @@ defmodule Pleroma.Web.Router do
get("/announcements/:id", AnnouncementController, :show)
patch("/announcements/:id", AnnouncementController, :change)
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
# AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)

100
lib/pleroma/webhook.ex Normal file
View 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

View 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
defp 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
defp 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

View file

@ -52,7 +52,8 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:error, {:reject, reason}} -> {:cancel, reason}
{:signature, false} -> {:cancel, :invalid_signature}
{:error, {:error, reason = "Object has been deleted"}} -> {:cancel, reason}
e -> e
{:error, _} = e -> e
e -> {:error, e}
end
end
end

View 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

View file

@ -11,12 +11,14 @@ defmodule Pleroma.UserTest do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Webhook.Notify
use Pleroma.DataCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
import ExUnit.CaptureLog
import Mock
import Swoosh.TestAssertions
setup do
@ -719,6 +721,14 @@ defmodule Pleroma.UserTest do
assert user.is_confirmed
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
describe "user registration, with :account_activation_required" do

View file

@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
alias Pleroma.Webhook.Notify
import ExUnit.CaptureLog
import Mock
@ -1655,6 +1656,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert Repo.aggregate(Object, :count, :id) == 1
assert Repo.aggregate(Notification, :count, :id) == 0
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
test "fetch_activities/2 returns activities addressed to a list " do

View file

@ -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

View file

@ -1717,6 +1717,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"image_description" => "",
"provider_name" => "example.com",
"provider_url" => "https://example.com",
"title" => "The Rock",
@ -1770,6 +1771,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
"title" => "Pleroma",
"description" => "",
"image" => nil,
"image_description" => "",
"provider_name" => "example.com",
"provider_url" => "https://example.com",
"url" => "https://example.com/ogp-missing-data",

View file

@ -773,6 +773,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
page_url = "http://example.com"
card = %{
"image:alt" => "Example image description",
url: page_url,
site_name: "Example site name",
title: "Example website",
@ -780,7 +781,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
description: "Example description"
}
%{provider_name: "example.com"} =
%{provider_name: "example.com", image_description: "Example image description"} =
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
end

View file

@ -0,0 +1,29 @@
# 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 "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

View 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