mirror of
https://github.com/lucaspalomodevelop/indico-plugins.git
synced 2026-03-12 23:27:22 +00:00
VC/Zoom: Add webhook validation
This commit is contained in:
parent
d948366ccc
commit
b994a0be5c
@ -11,6 +11,10 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
### 3.2.5
|
||||
|
||||
- Handle webhook validation request and use zoom webhook secret token for request verification
|
||||
|
||||
### 3.2.4
|
||||
|
||||
- Adapt to Indico 3.2.6 changes
|
||||
@ -109,7 +113,9 @@
|
||||
|
||||
**URL:** `https://yourserver/api/plugin/zoom/webhook`
|
||||
|
||||
(write down the "Verification Token", as you will need it in the plugin configuration below)
|
||||
Copy the "Secret Token", as you will need it in the plugin configuration below. Note that in order
|
||||
to actually create the webhook you need to validate the URL, which requires the token to saved in
|
||||
the Indico plugin configuration.
|
||||
|
||||
Select the following "Event types":
|
||||
* `Meeting has been updated`
|
||||
@ -124,7 +130,7 @@ These are the most relevant configuration options:
|
||||
|
||||
* **Notification email addresses** - Additional e-mails which will receive notifications
|
||||
* **E-mail domains** - List of e-mail domains which can be used for the Zoom API (e.g. `cern.ch`)
|
||||
* **Webhook token** (optional) - the token which Zoom requests will authenticate with (get it from Zoom Marketplace)
|
||||
* **Webhook Secret Token** (optional) - the token which Zoom requests will authenticate with (get it from Zoom Marketplace)
|
||||
|
||||
|
||||
### Zoom Server-to-Server OAuth
|
||||
|
||||
@ -5,13 +5,16 @@
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from flask import flash, jsonify, request, session
|
||||
from flask_pluginengine import current_plugin
|
||||
from marshmallow import EXCLUDE
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from webargs import fields
|
||||
from webargs.flaskparser import use_kwargs
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from werkzeug.exceptions import Forbidden, ServiceUnavailable
|
||||
|
||||
from indico.core.db import db
|
||||
from indico.core.errors import UserValueError
|
||||
@ -48,17 +51,44 @@ class RHRoomAlternativeHost(RHVCSystemEventBase):
|
||||
class RHWebhook(RH):
|
||||
CSRF_ENABLED = False
|
||||
|
||||
def _is_validation_event(self, event):
|
||||
return event == 'endpoint.url_validation'
|
||||
|
||||
def _get_hmac(self, data):
|
||||
webhook_token = current_plugin.settings.get('webhook_token')
|
||||
if not webhook_token:
|
||||
current_plugin.logger.warning('Tried to validate Zoom webhook, but no secret token has been configured')
|
||||
raise ServiceUnavailable('No Zoom Webhook Secret Token configured')
|
||||
return hmac.new(webhook_token.encode(), data, hashlib.sha256).hexdigest()
|
||||
|
||||
def _check_access(self):
|
||||
token = request.headers.get('Authorization')
|
||||
expected_token = current_plugin.settings.get('webhook_token')
|
||||
if not expected_token or not token or token != expected_token:
|
||||
raise Forbidden
|
||||
timestamp = request.headers.get('x-zm-request-timestamp')
|
||||
zoom_signature_header = request.headers.get('x-zm-signature')
|
||||
signature = self._get_hmac(b'v0:%s:%s' % (timestamp.encode(), request.data))
|
||||
expected_header = f'v0={signature}'
|
||||
if zoom_signature_header != expected_header:
|
||||
current_plugin.logger.warning('Received request with invalid signature: Expected %s, got %s, payload %s',
|
||||
expected_header, zoom_signature_header, request.data.decode())
|
||||
raise Forbidden('Zoom signature verification failed')
|
||||
|
||||
@use_kwargs({
|
||||
'event': fields.String(required=True),
|
||||
'payload': fields.Dict(required=True)
|
||||
}, unknown=EXCLUDE)
|
||||
def _process(self, event, payload):
|
||||
if self._is_validation_event(event):
|
||||
return self._handle_validation(payload)
|
||||
return self._handle_zoom_event(event, payload)
|
||||
|
||||
def _handle_validation(self, payload):
|
||||
plain_token = payload['plainToken']
|
||||
signed_token = self._get_hmac(plain_token.encode())
|
||||
return jsonify({
|
||||
'plainToken': plain_token,
|
||||
'encryptedToken': signed_token
|
||||
})
|
||||
|
||||
def _handle_zoom_event(self, event, payload):
|
||||
meeting_id = payload['object']['id']
|
||||
vc_room = VCRoom.query.filter(VCRoom.data.contains({'zoom_id': meeting_id})).first()
|
||||
|
||||
|
||||
@ -58,8 +58,9 @@ class PluginSettingsForm(VCPluginSettingsFormBase):
|
||||
client_id = StringField(_('Client ID'), [])
|
||||
client_secret = IndicoPasswordField(_('Client Secret'), [], toggle=True)
|
||||
|
||||
webhook_token = IndicoPasswordField(_('Webhook Token'), toggle=True,
|
||||
description=_("Specify Zoom's webhook token if you want live updates"))
|
||||
webhook_token = IndicoPasswordField(_('Webhook Secret Token'), toggle=True,
|
||||
description=_("Specify the \"Secret Token\" of your Zoom Webhook if you want "
|
||||
"live updates in case of modified/deleted Zoom meetings."))
|
||||
|
||||
user_lookup_mode = IndicoEnumSelectField(_('User lookup mode'), [DataRequired()], enum=UserLookupMode,
|
||||
description=_('Specify how Indico should look up the zoom user that '
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = indico-plugin-vc-zoom
|
||||
version = 3.2.4
|
||||
version = 3.2.5
|
||||
description = Zoom video-conferencing plugin for Indico
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown; charset=UTF-8; variant=GFM
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user