Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
marcin mikołajczak
pleroma
Commits
155287ea
Commit
155287ea
authored
Jun 24, 2022
by
marcin mikołajczak
Browse files
Mastodon-compatible webhooks
Signed-off-by:
marcin mikołajczak
<
git@mkljczk.pl
>
parent
75f912c6
Pipeline
#40301
failed with stages
in 41 seconds
Changes
14
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
config/config.exs
View file @
155287ea
...
...
@@ -855,7 +855,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
.
Web
.
ActivityPub
.
MRF
.
MediaProxyWarmingPolicy
,
[
max_running:
5
,
max_waiting:
5
]},
{
Pleroma
.
Webhook
.
Notify
,
[
max_running:
5
,
max_waiting:
:infinity
]}
]
# Import environment specific config. This must remain at the bottom
...
...
config/description.exs
View file @
155287ea
...
...
@@ -3437,6 +3437,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
]
}
]
}
]
}
...
...
lib/pleroma/application.ex
View file @
155287ea
...
...
@@ -308,7 +308,11 @@ defmodule Pleroma.Application 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
,
[])
...
...
lib/pleroma/user.ex
View file @
155287ea
...
...
@@ -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
...
...
@@ -849,6 +851,7 @@ defmodule Pleroma.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
...
...
lib/pleroma/web/activity_pub/activity_pub.ex
View file @
155287ea
...
...
@@ -24,12 +24,14 @@ 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
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
...
...
@@ -390,6 +392,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_superusers
()
...
...
lib/pleroma/web/admin_api/controllers/webhook_controller.ex
0 → 100644
View file @
155287ea
# 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
lib/pleroma/web/admin_api/views/webhook_view.ex
0 → 100644
View file @
155287ea
# 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
lib/pleroma/web/api_spec/operations/admin/webhook_operation.ex
0 → 100644
View file @
155287ea
# 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
lib/pleroma/web/api_spec/operations/notification_operation.ex
View file @
155287ea
...
...
@@ -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
...
...
lib/pleroma/web/router.ex
View file @
155287ea
...
...
@@ -229,6 +229,15 @@ defmodule Pleroma.Web.Router do
post
(
"/frontends/install"
,
FrontendController
,
:install
)
post
(
"/backups"
,
AdminAPIController
,
:create_backup
)
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)
...
...
lib/pleroma/webhook.ex
0 → 100644
View file @
155287ea
# 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
lib/pleroma/webhook/notify.ex
0 → 100644
View file @
155287ea
# 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"
,