Skip to content

Commit

Permalink
Release 1.6.2 (#292)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people committed Apr 3, 2023
1 parent 9d5a119 commit f1b4e6c
Show file tree
Hide file tree
Showing 22 changed files with 475 additions and 150 deletions.
13 changes: 13 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: "CodeQL config"

query-filters:
- exclude:
problem.severity:
- warning
- recommendation
- exclude:
id: go/log-injection

paths-ignore:
- '**/*_test.go'
- '**/*.test.*'
18 changes: 18 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: cd
on:
workflow_run:
workflows: ["ci"]
branches-ignore: ["*"]
types:
- completed
push:
tags:
- "v*"

permissions:
contents: read

jobs:
plugin-cd:
uses: mattermost/actions-workflows/.github/workflows/plugin-cd.yml@main
secrets: inherit
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: ci
on:
schedule:
- cron: "0 0 * * *"
push:
branches:
- master
tags:
- "v*"
pull_request:

permissions:
contents: read

jobs:
plugin-ci:
uses: mattermost/actions-workflows/.github/workflows/plugin-ci.yml@main
secrets: inherit
25 changes: 14 additions & 11 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ on:
branches: [ master ]
schedule:
- cron: '30 0 * * 0'

permissions:
contents: read

jobs:
analyze:
permissions:
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
Expand All @@ -25,18 +26,20 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
debug: false
config-file: ./.github/codeql/codeql-config.yml

# Autobuild attempts to build any compiled languages
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2

# Perform Analysis
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ linters-settings:
govet:
check-shadowing: true
enable-all: true
disable:
- fieldalignment
misspell:
locale: US

Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14.21.1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/.gitbook/assets/webhook_url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/.gitbook/assets/zoom_webhook_secret.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 24 additions & 10 deletions docs/installation/zoom-configuration/webhook-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@

When a Zoom meeting ends, the original link shared in the channel can be changed to indicate the meeting has ended and how long it lasted. To enable this functionality, we need to create a webhook subscription in Zoom that tells the Mattermost server every time a meeting ends. The Mattermost server then updates the original Zoom message.

1. Click on **Feature**.
2. Enable **Event Subscriptions**.
3. Click **Add New Event Subscription** and give it a name \(e.g. "Meeting Ended"\).
4. Enter a valid **Event notification endpoint URL** \(`https://SITEURL/plugins/zoom/webhook?secret=WEBHOOKSECRET`\).
* `SITEURL` should be your Mattermost server URL.
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md).
Select **Feature** in the left sidebar to begin setting up the webhook subscription.

![Feature screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-51-56%20%284%29.png)
### Configuring webhook authentication

* Click **Add events** and select the **End Meeting** event.
1. Copy the "Secret Token" value from Zoom's form.
2. Paste this value into the Zoom plugin's settings in the Mattermost system console for the `Zoom Webhook Secret` field.

![Event types screen](../../.gitbook/assets/screenshot-from-2020-06-05-20-43-04%20%282%29%20%281%29.png)
![Mattermost Webhook Secret](../../.gitbook/assets/mattermost_webhook_secret.png)
![Zoom Webhook Secret](../../.gitbook/assets/zoom_webhook_secret.png)

* Click **Done** and then save your app.
3. In the Mattermost system console, generate and copy the `Webhook Secret`. We'll use this value in the next section.

Zoom's webhook secret authentication system has been made required for all webhooks created or modified after the new system was rolled out. In order to maintain backwards compatibility with existing installations of the Zoom plugin, we still support (and also require) the original webhook secret system used by the plugin. So any new webhooks created will need to be configured with both secrets as mentioned in the steps above.

### Configuring webhook events

1. Enable **Event Subscriptions**.
2. Select **Add New Event Subscription** and give it a name \(e.g. "Meeting Ended"\).
3. Construct the following URL, where `SITEURL` is your Mattermost server URL, and `WEBHOOKSECRET` is the value in the system console labeled `Webhook Secret`.
4. Enter in the **Event notification endpoint URL** field the constructed URL:

`https://SITEURL/plugins/zoom/webhook?secret=WEBHOOKSECRET`

![Webhook URL](../../.gitbook/assets/webhook_url.png)

5. Select **Add events** and select the **End Meeting** event.
6. Select **Done** and to save your app.

![Event types screen](../../.gitbook/assets/event_types.png)
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/sys v0.6.0 // indirect
)
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1004,8 +1004,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
17 changes: 13 additions & 4 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"id": "zoom",
"name": "Zoom",
"description": "Zoom audio and video conferencing plugin for Mattermost 5.2+.",
"description": "Zoom audio and video conferencing plugin",
"homepage_url": "https://github.com/mattermost/mattermost-plugin-zoom",
"support_url": "https://github.com/mattermost/mattermost-plugin-zoom/issues",
"release_notes_url": "https://github.com/mattermost/mattermost-plugin-zoom/releases/tag/v1.6.1",
"release_notes_url": "https://github.com/mattermost/mattermost-plugin-zoom/releases/tag/v1.6.2",
"icon_path": "assets/profile.svg",
"version": "1.6.1",
"version": "1.6.2",
"min_server_version": "5.12.0",
"server": {
"executables": {
Expand Down Expand Up @@ -52,7 +52,7 @@
"key": "AccountLevelApp",
"display_name": "OAuth by Account Level App (Beta):",
"type": "bool",
"help_text": "When true, only an account administrator has to log in. The rest of the users will use their email to log in.",
"help_text": "When true, only an account administrator has to log in. The rest of the users will automatically use their Mattermost email to authenticate when starting meetings.",
"placeholder": "",
"default": false
},
Expand Down Expand Up @@ -107,6 +107,15 @@
"regenerate_help_text": "Regenerates the secret for the webhook URL endpoint. Regenerating the secret invalidates your existing Zoom plugin.",
"placeholder": "",
"default": null
},
{
"key": "ZoomWebhookSecret",
"display_name": "Zoom Webhook Secret:",
"type": "text",
"help_text": "`Secret Token` taken from Zoom's webhook configuration page",
"regenerate_help_text": "",
"placeholder": "",
"default": null
}
]
}
Expand Down
8 changes: 6 additions & 2 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ type configuration struct {
AccountLevelApp bool
OAuthClientID string
OAuthClientSecret string
OAuthRedirectURL string
EncryptionKey string
WebhookSecret string

// WebhookSecret is generated in the Mattermost system console
WebhookSecret string

// ZoomWebhookSecret is the `Secret Token` taken from Zoom's webhook configuration page
ZoomWebhookSecret string
}

// Clone shallow copies the configuration. Your implementation may require a deep copy if
Expand Down
113 changes: 1 addition & 112 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ package main
import (
"bytes"
"context"
"crypto/subtle"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -194,109 +191,6 @@ func (p *Plugin) completeUserOAuthToZoom(w http.ResponseWriter, r *http.Request)
}
}

func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
if !p.verifyWebhookSecret(r) {
p.API.LogWarn("Could not verify webhook secreet")
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}

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)
return
}

b, err := ioutil.ReadAll(r.Body)
if err != nil {
p.API.LogWarn("Cannot read body from Webhook")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

var webhook zoom.Webhook
if err = json.Unmarshal(b, &webhook); err != nil {
p.API.LogError("Error unmarshaling webhook", "err", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if webhook.Event != zoom.EventTypeMeetingEnded {
w.WriteHeader(http.StatusNotImplemented)
return
}

var meetingWebhook zoom.MeetingWebhook
if err = json.Unmarshal(b, &meetingWebhook); err != nil {
p.API.LogError("Error unmarshaling meeting webhook", "err", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
p.handleMeetingEnded(w, r, &meetingWebhook)
}

func (p *Plugin) handleMeetingEnded(w http.ResponseWriter, r *http.Request, webhook *zoom.MeetingWebhook) {
meetingPostID := webhook.Payload.Object.ID
postID, appErr := p.fetchMeetingPostID(meetingPostID)
if appErr != nil {
http.Error(w, appErr.Error(), appErr.StatusCode)
return
}

post, appErr := p.API.GetPost(postID)
if appErr != nil {
p.API.LogWarn("Could not get meeting post by id", "err", appErr)
http.Error(w, appErr.Error(), appErr.StatusCode)
return
}

start := time.Unix(0, post.CreateAt*int64(time.Millisecond))
length := int(math.Ceil(float64((model.GetMillis()-post.CreateAt)/1000) / 60))
startText := start.Format("Mon Jan 2 15:04:05 -0700 MST 2006")
topic, ok := post.Props["meeting_topic"].(string)
if !ok {
topic = defaultMeetingTopic
}

meetingID, ok := post.Props["meeting_id"].(float64)
if !ok {
meetingID = 0
}

slackAttachment := model.SlackAttachment{
Fallback: fmt.Sprintf("Meeting %s has ended: started at %s, length: %d minute(s).", post.Props["meeting_id"], startText, length),
Title: topic,
Text: fmt.Sprintf(
"Personal Meeting ID (PMI) : %d\n\n##### Meeting Summary\n\nDate: %s\n\nMeeting Length: %d minute(s)",
int(meetingID),
startText,
length,
),
}

post.Message = "The meeting has ended."
post.Props["meeting_status"] = zoom.WebhookStatusEnded
post.Props["attachments"] = []*model.SlackAttachment{&slackAttachment}

_, appErr = p.API.UpdatePost(post)
if appErr != nil {
p.API.LogWarn("Could not update the post", "err", appErr)
http.Error(w, appErr.Error(), appErr.StatusCode)
return
}

if appErr = p.deleteMeetingPostID(meetingPostID); appErr != nil {
p.API.LogWarn("failed to delete db entry", "error", appErr.Error())
return
}

_, err := w.Write([]byte(post.ToJson()))
if err != nil {
p.API.LogWarn("failed to write response", "error", err.Error())
}
}

func (p *Plugin) postMeeting(creator *model.User, meetingID int, channelID string, topic string) error {
meetingURL := p.getMeetingURL(creator, meetingID)

Expand Down Expand Up @@ -521,7 +415,7 @@ func getString(key string, props model.StringInterface) string {
}

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
}
Expand Down Expand Up @@ -555,11 +449,6 @@ func (p *Plugin) deauthorizeUser(w http.ResponseWriter, r *http.Request) {
}
}

func (p *Plugin) verifyWebhookSecret(r *http.Request) bool {
config := p.getConfiguration()
return subtle.ConstantTimeCompare([]byte(r.URL.Query().Get("secret")), []byte(config.WebhookSecret)) == 1
}

func (p *Plugin) completeCompliance(payload zoom.DeauthorizationPayload) error {
data := zoom.ComplianceRequest{
ClientID: payload.ClientID,
Expand Down
2 changes: 1 addition & 1 deletion server/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ var manifest = struct {
Version string
}{
ID: "zoom",
Version: "1.6.1",
Version: "1.6.2",
}
Loading

0 comments on commit f1b4e6c

Please sign in to comment.