-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement webhook validation #279
Conversation
if !strings.Contains(r.Header.Get("Content-Type"), "application/json") { | ||
res := fmt.Sprintf("Expected Content-Type 'application/json' for webhook request, received '%s'.", r.Header.Get("Content-Type")) | ||
p.API.LogWarn(res) | ||
http.Error(w, res, http.StatusBadRequest) |
Check warning
Code scanning / CodeQL
Reflected cross-site scripting
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## master #279 +/- ##
===========================================
+ Coverage 0.00% 23.51% +23.51%
===========================================
Files 8 9 +1
Lines 1143 1212 +69
===========================================
+ Hits 0 285 +285
+ Misses 1143 874 -269
- Partials 0 53 +53
... and 4 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
This seems functional I can see the hook working when It's deployed. However the validation functionality seems to now be removed from the Zoom so I can't test the API change. Also it's a bit odd that we're keeping the webhook secret in the query string. We would need to be very clear with the documentation about this but it is working 👍 |
cc @mickmister ☝️ |
@DHaussermann I kept the webhook secret in the query string because it is something we have control over and is battle tested in our system. We can treat it as the primary secret check, while adhering to Zoom's new requirements when they take effect. This also was the simplest route for backwards compatibility. |
@DHaussermann Can you please test this for backwards compatibility? Thank you! |
cc: @DHaussermann , when you have the bandwidth (or have Malik looking into it). |
/update-branch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some work here :)
I left some comments, the only ones I consider blocking are the double-deserialization ones.
On the other hand, I had some problems doing manual QA here but it's probably my fault. Also manually tested 🎉 .
"key": "ZoomWebhookSecret", | ||
"display_name": "Zoom Webhook Secret:", | ||
"type": "text", | ||
"help_text": "`Secret Token` taken from Zoom's webhook configuration page", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a followup jira task/PR to update the documentation with this step?
EncryptionKey string | ||
WebhookSecret string | ||
ZoomWebhookSecret string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZoomWebhookSecret string | |
// ZoomWebhookSecret is the secret taken from Zoom's webhook configuration page | |
ZoomWebhookSecret string |
2/5 I know that this is a bit repetitive but it helps a lot to disambiguate when seeing at WebhookSecret vs ZoomWebhookSecret as conf keys.
I'd add the docstring at least in both secret fields.
server/webhook.go
Outdated
|
||
func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { | ||
if !p.verifyWebhookSecret(r) { | ||
p.API.LogWarn("Could not verify webhook secret") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0/5 if it's easy I'd return a different log/string-error for secrets that are empty or exactly "WEBHOOKSECRET" (people copying exactly the doc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually enforce the value to be defined on plugin startup, so no need to have that condition handled here
p.API.LogError("Error unmarshaling webhook", "err", err.Error()) | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts on merging the read block and unmarshal block?
if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil {
// log
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is written the way that it is because we would essentially be "discarding" the HTTP body that would need to be re-read if json.NewDecoder
was used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
server/http.go
Outdated
func (p *Plugin) deauthorizeUser(w http.ResponseWriter, r *http.Request) { | ||
if !p.verifyWebhookSecret(r) { | ||
if !p.verifyMattermostWebhookSecret(r) { | ||
http.Error(w, "Not authorized", http.StatusUnauthorized) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After renaming this called method and durfaced this change, it seems odd to me that we are using a webhook secret for a deauthorizeUser
operation. I'm not sure if this route gets called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a feature of the plugin that uses the webhooks secret:
mattermost-plugin-zoom/docs/installation/zoom-configuration/zoom-setup-oauth.md
Lines 44 to 53 in 229517f
## Deauthorization | |
This plugin allows users to be deauthorized directly from Zoom, in order to comply with Zoom’s commitment to security and the protection of user data. If you **would like to publish on Zoom Marketplace**, you can set up a deauthorization URL. | |
1. Select **Information**. | |
2. Near the end of the page, is a section called **Deauthorization Notification**. | |
3. Enter a valid **Endpoint URL** \(`https://SITEURL/plugins/zoom/deauthorization?secret=WEBHOOKSECRET`\). | |
* `SITEURL` should be your Mattermost server URL. | |
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md). | |
We aren't checking the verification of Zoom's signature in this case. I don't think this is worth holding up this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree on doing that in a separate effort, pls create a Jira ticket for that and add a comment with the jira link close to the related code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created ticket here #291
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Could you please add a comment to deauthorizeUser
with the link to the ticket?
@mickmister I do not see a change in behavior when running this build. I double checked the URL and secret. I'm a bit confused....
|
@DHaussermann I tested this out again and it's working for me. Can we meet to discuss this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and passed
- Webhooks are working
- Webhook validation wizard is working
- Dev provided doc changes for new secret value
- Tested on Zoom upgrade and Fresh install
- Tested on new Zoom app and editing features of an existing Zoom app
LGTM!
* move webhook handlers to its own file * implement webhook validation * lint * return useful error message * document usage of zoom's webhook secret * PR feedback * rename methods for readability * add todo comment --------- Co-authored-by: Mattermost Build <[email protected]>
* bump version to 1.6.2 * update github actions * .nvmrc * golangci * Implement webhook validation (#279) * move webhook handlers to its own file * implement webhook validation * lint * return useful error message * document usage of zoom's webhook secret * PR feedback * rename methods for readability * add todo comment --------- Co-authored-by: Mattermost Build <[email protected]> * fix merge issues * go mod tidy * [MM-51751] Fix regex for tags trigger in CI/CD (#294) --------- Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Ben Schumacher <[email protected]>
Summary
Viewing the second commit in isolation will make reviewing this PR much more straightforward, since I moved around some code in the first commit.
Description copied from #278:
Zoom has changed how their webhook setup works, and requires some logic on our end to validate the webhook is setup properly. This involves:
The webhook must be validated before Zoom's interface will allow the admin to save the webhook configuration in Zoom's app setup UI. This does not affect existing setups of the plugin, but it will affect any new setup, and any instance of an admin wanting to update the webhook secret or change the events associated with the webhook configuration.
Setting up the webhook for validation requires us to store a new
Secret Token
from Zoom's webhook configuration UI. If this value is not set, we simply skip the "signature verification" step when processing webhook events. We technically don't need to do this step, because Zoom has no way of verifying this is occurring on subsequent webhook events, and because we are already using our own secret for the webhook. Either way, it's best to comply with what Zoom is asking for, and it adds another layer of security provided by Zoom.Ticket Link
Fixes #278