Skip to content
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

Create new meeting instead of using PMI #147

Merged
merged 23 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b69eaf6
Create new meeting instead of using PMI
larkox Jul 17, 2020
78b11e7
Fix test and remove unused debug line
larkox Jul 20, 2020
621279b
Merge branch 'master' into CreateMeeting
larkox Nov 16, 2020
b867c90
Fix test
larkox Nov 16, 2020
7540819
Add some missing comments, fix the user email issue and add constants…
larkox Nov 16, 2020
ac3d5c9
Uncomment recurrence
larkox Nov 16, 2020
0984644
Make constants names more verbose
larkox Nov 16, 2020
066ed9b
Merge branch 'master' into CreateMeeting
larkox Dec 15, 2020
04b3d83
Add missing api URL to create meeting link
larkox Jan 12, 2021
82af528
Merge branch 'CreateMeeting' of https://github.com/larkox/mattermost-…
larkox Jan 12, 2021
5262966
Merge branch 'master' into CreateMeeting
larkox Jan 12, 2021
d267f71
Merge branch 'master' into CreateMeeting
larkox Jan 14, 2021
5b1d61e
Correct docs and cleanup screenshots
larkox Jan 14, 2021
7b4d8a4
Add error handling for meeting not created
larkox Jan 14, 2021
fb44c14
Merge branch 'master' into CreateMeeting
larkox Jan 15, 2021
23cf867
Merge branch 'master' into CreateMeeting
hanzei Jan 26, 2021
168e28d
Merge branch 'master' into CreateMeeting
larkox Feb 3, 2021
8903373
Merge branch 'master' into CreateMeeting
mattermod Feb 7, 2021
a86a77d
Merge branch 'master' into CreateMeeting
hanzei Oct 26, 2021
1dcf8de
Merge branch 'master' into CreateMeeting
hanzei Oct 28, 2021
66b7dd5
Merge branch 'master' into CreateMeeting
larkox Dec 1, 2021
04466a7
Merge branch 'master' into CreateMeeting
larkox Dec 16, 2021
660ddf8
Use new meeting ID on first connect and minor fixes
larkox Dec 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed docs/.gitbook/assets/2019-12-16_23-44-50.png
Binary file not shown.
Binary file removed docs/.gitbook/assets/2019-12-16_23-52-27.png
Binary file not shown.
Binary file removed docs/.gitbook/assets/2020-11-03_09-08-36.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (1) (2) (2).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (1) (2) (3).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (1) (2) (4).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (1) (2) (5).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (1) (2).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (2).png
Binary file not shown.
Binary file removed docs/.gitbook/assets/image (3).png
Binary file not shown.
Binary file added docs/.gitbook/assets/scopes.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 not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Connect with your team members easily

The Mattermost/Zoom integration allows team members to initiate a Zoom meeting with a single click. All participants in a channel can easily join the Zoom meeting and the shared link is updated when the meeting is over.

![](.gitbook/assets/42196048-af54d2b8-7e30-11e8-80a0-5e160ae06f03%20%282%29%20%283%29.png)
![](.gitbook/assets/example.png)

# Important

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/mattermost-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description: Configuration steps for the Mattermost server
* Enable settings for [overriding usernames](https://docs.mattermost.com/administration/config-settings.html#enable-integrations-to-override-usernames) and [overriding profile picture icons](https://docs.mattermost.com/administration/config-settings.html#enable-integrations-to-override-profile-picture-icons).
* Go to **System Console > Plugins > Zoom** to configure the Zoom Plugin.

![](../.gitbook/assets/image%20%281%29%20%282%29.png)
![](../.gitbook/assets/system_console.png)

### Plugin configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ When a Zoom meeting ends, the original link shared in the channel can be changed
* `SITEURL` should be your Mattermost server URL.
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md).

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

* Click **Add events** and select the **End Meeting** event.

![Event types screen](../../.gitbook/assets/screenshot-from-2020-06-05-20-43-04%20%282%29%20%281%29.png)
![Event types screen](../../.gitbook/assets/event_types.png)

* Click **Done** and then save your app.

10 changes: 5 additions & 5 deletions docs/installation/zoom-configuration/zoom-setup-oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ You can set the **OAuth ClientID** and **OAuth Secret**, generated by Zoom, and
6. Choose whether you **Would like to publish this app on Zoom Marketplace**. In most cases you'll want this to be disabled, but the plugin supports apps that are published in the Zoom Marketplace.
7. Select **Create**.

![](../../.gitbook/assets/2020-11-03_09-08-49.png)
![](../../.gitbook/assets/create_account_level.png)

## Configure your new OAuth app to work with Mattermost

Expand All @@ -29,13 +29,13 @@ If you **would like to publish on Zoom Marketplace**, you'll find two sets of va
2. Enter a valid **Redirect URL for OAuth** \(`https://SITEURL/plugins/zoom/oauth2/complete`\) and add the same URL under **Whitelist URL**.
* `SITEURL` should be your Mattermost server URL.

![App Credentials screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-34-13%20%282%29.png)
![App Credentials screen](../../.gitbook/assets/credentials.png)

## Add user scopes to the app

Select **Scopes** and add the following scopes: **meeting:read**, **user:read**.
Select **Scopes** and add the following scopes: **meeting:write**, **user:read**, **.

![Scopes screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-37-47%20%282%29.png)
![Scopes screen](../../.gitbook/assets/scopes.png)

## Do not perform the install step

Expand All @@ -51,7 +51,7 @@ This plugin allows users to be deauthorized directly from Zoom, in order to comp
* `SITEURL` should be your Mattermost server URL.
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md).

![Deauthorization Notification section](../../.gitbook/assets/screenshot-from-2020-06-05-20-04-33%20%282%29.png)
![Deauthorization Notification section](../../.gitbook/assets/deauthorization.png)

## Finish setting up Mattermost server

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ You can set the **OAuth ClientID** and **OAuth Secret**, generated by Zoom, and
6. Choose whether you **Would like to publish this app on Zoom Marketplace**. In most cases you'll want this to be disabled, but the plugin supports apps that are published in the Zoom Marketplace.
7. Select **Create**.

![Create an OAuth app screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-31-06%20%282%29.png)
![Create an OAuth app screen](../../.gitbook/assets/create_user_managed.png)

## Configure your new OAuth app to work with Mattermost

Expand All @@ -27,13 +27,13 @@ If you **would like to publish on Zoom Marketplace**, you'll find two sets of va
1. Go to the **App Credentials** tab on the left. Here you'll find your **Client ID** and **Client Secret**. These will be needed during [Mattermost Setup](../mattermost-setup.md).
2. Enter a valid **Redirect URL for OAuth** \(`https://SITEURL/plugins/zoom/oauth2/complete`\) and add the same URL under **Whitelist URL**. Note that `SITEURL` should be your Mattermost server URL.

![App Credentials screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-34-13%20%282%29.png)
![App Credentials screen](../../.gitbook/assets/credentials.png)

## Add user scopes to the app

Select **Scopes** and add the following scopes: **meeting:read**, **user:read**.
Select **Scopes** and add the following scopes: **meeting:write**, **user:read**.

![Scopes screen](../../.gitbook/assets/screenshot-from-2020-06-05-19-37-47%20%282%29.png)
![Scopes screen](../../.gitbook/assets/scopes.png)

## Do not perform the install step

Expand All @@ -49,7 +49,7 @@ This plugin allows users to be deauthorized directly from Zoom, in order to comp
* `SITEURL` should be your Mattermost server URL.
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md).

![Deauthorization Notification section](../../.gitbook/assets/screenshot-from-2020-06-05-20-04-33%20%282%29.png)
![Deauthorization Notification section](../../.gitbook/assets/deauthorization.png)

## Finish setting up Mattermost server

Expand Down
2 changes: 1 addition & 1 deletion docs/usage/start-meetings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Once enabled, clicking the video icon in a Mattermost channel invites team members to join a Zoom call, hosted using the credentials of the user who initiated the call.

![](../.gitbook/assets/42196048-af54d2b8-7e30-11e8-80a0-5e160ae06f03%20%282%29%20%283%29.png)
![](../.gitbook/assets/example.png)

## Slash Command

Expand Down
14 changes: 13 additions & 1 deletion server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,19 @@ func (p *Plugin) runStartCommand(args *model.CommandArgs, user *model.User, topi
return authErr.Message, authErr.Err
}

if err := p.postMeeting(user, zoomUser.Pmi, args.ChannelId, topic); err != nil {
client, _, err := p.getActiveClient(user)
if err != nil {
p.API.LogWarn("Error creating the client", "err", err)
return "Error creating the client.", nil
}

meeting, err := client.CreateMeeting(zoomUser, topic)
if err != nil {
p.API.LogWarn("Error creating the meeting", "err", err)
return "Error creating the meeting.", nil
}

if err := p.postMeeting(user, meeting.ID, args.ChannelId, topic); err != nil {
return "Failed to post message. Please try again.", nil
}

Expand Down
14 changes: 13 additions & 1 deletion server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,19 @@ func (p *Plugin) handleStartMeeting(w http.ResponseWriter, r *http.Request) {
return
}

meetingID := zoomUser.Pmi
client, _, err := p.getActiveClient(user)
if err != nil {
p.API.LogWarn("Error getting the client", "err", err)
return
}

meeting, err := client.CreateMeeting(zoomUser, defaultMeetingTopic)
if err != nil {
p.API.LogWarn("Error creating the meeting", "err", err)
return
}

meetingID := meeting.ID
if err = p.postMeeting(user, meetingID, req.ChannelID, req.Topic); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down
10 changes: 2 additions & 8 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,6 @@ type Plugin struct {
tracker telemetry.Tracker
}

// Client defines a common interface for the API and OAuth Zoom clients
type Client interface {
GetMeeting(meetingID int) (*zoom.Meeting, error)
GetUser(user *model.User) (*zoom.User, *zoom.AuthError)
}

// OnActivate checks if the configurations is valid and ensures the bot account exists
func (p *Plugin) OnActivate() error {
p.client = pluginapi.NewClient(p.API, p.Driver)
Expand Down Expand Up @@ -139,8 +133,8 @@ func (p *Plugin) registerSiteURL() error {
return nil
}

// getActiveClient returns an OAuth Zoom client if available, otherwise it returns the API client.
func (p *Plugin) getActiveClient(user *model.User) (Client, string, error) {
// getActiveClient returns an OAuth Zoom client if available, otherwise an error and a user facing error message.
func (p *Plugin) getActiveClient(user *model.User) (zoom.Client, string, error) {
config := p.getConfiguration()

// JWT
Expand Down
24 changes: 18 additions & 6 deletions server/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,24 @@ func TestPlugin(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/users/theuseremail" {
user := &zoom.User{
ID: "thezoomuserid",
Pmi: 123,
ID: "thezoomuserid",
Email: "theuseremail",
Pmi: 123,
}

str, _ := json.Marshal(user)

if _, err := w.Write(str); err != nil {
require.NoError(t, err)
}
}
if r.URL.Path == "/users/theuseremail/meetings" {
meeting := &zoom.Meeting{
ID: 234,
}

str, _ := json.Marshal(meeting)

if _, err := w.Write(str); err != nil {
require.NoError(t, err)
}
Expand All @@ -45,8 +57,8 @@ func TestPlugin(t *testing.T) {

noAuthMeetingRequest := httptest.NewRequest("POST", "/api/v1/meetings", strings.NewReader("{\"channel_id\": \"thechannelid\"}"))

personalMeetingRequest := httptest.NewRequest("POST", "/api/v1/meetings", strings.NewReader("{\"channel_id\": \"thechannelid\", \"personal\": true}"))
personalMeetingRequest.Header.Add("Mattermost-User-Id", "theuserid")
meetingRequest := httptest.NewRequest("POST", "/api/v1/meetings", strings.NewReader("{\"channel_id\": \"thechannelid\"}"))
meetingRequest.Header.Add("Mattermost-User-Id", "theuserid")

endedPayload := `{"event": "meeting.ended", "payload": {"object": {"id": "234"}}}`
validStoppedWebhookRequest := httptest.NewRequest("POST", "/webhook?secret=thewebhooksecret", strings.NewReader(endedPayload))
Expand All @@ -68,8 +80,8 @@ func TestPlugin(t *testing.T) {
ExpectedStatusCode: http.StatusUnauthorized,
HasPermissionToChannel: true,
},
"ValidPersonalMeetingRequest": {
Request: personalMeetingRequest,
"ValidMeetingRequest": {
Request: meetingRequest,
ExpectedStatusCode: http.StatusOK,
HasPermissionToChannel: true,
},
Expand Down
2 changes: 2 additions & 0 deletions server/zoom/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ func (err *AuthError) Error() string {

var errNotFound = errors.New("not found")

// Client interface for Zoom
type Client interface {
GetMeeting(meetingID int) (*Meeting, error)
GetUser(user *model.User) (*User, *AuthError)
CreateMeeting(user *User, topic string) (*Meeting, error)
}

type PluginAPI interface {
Expand Down
11 changes: 11 additions & 0 deletions server/zoom/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ func (c *JWTClient) GetUser(user *model.User) (*User, *AuthError) {
return &zoomUser, nil
}

// CreateMeeting creates a new meeting for the user and returns the created meeting.
func (c *JWTClient) CreateMeeting(user *User, topic string) (*Meeting, error) {
var ret Meeting
meetingRequest := CreateMeetingRequest{
Topic: topic,
Type: MeetingTypeInstant,
}
err := c.request(http.MethodPost, fmt.Sprintf("/users/%s/meetings", user.Email), meetingRequest, &ret)
return &ret, err
}

func (c *JWTClient) generateJWT() (string, error) {
claims := jwt.MapClaims{}
claims["iss"] = c.apiKey
Expand Down
104 changes: 88 additions & 16 deletions server/zoom/meeting.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,39 @@

package zoom

// MeetingType as defined at https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
type MeetingType int

const (
// MeetingTypeInstant meeting
MeetingTypeInstant MeetingType = 1
// MeetingTypeScheduled meeting
MeetingTypeScheduled MeetingType = 2
// MeetingTypeRecurringWithNoFixedTime meeting
MeetingTypeRecurringWithNoFixedTime MeetingType = 3
// MeetingTypeRecurringWithFixedTime meeting
MeetingTypeRecurringWithFixedTime MeetingType = 8
)

// Meeting is defined at https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meeting
type Meeting struct {
UUID string `json:"uuid"`
ID int `json:"id"`
HostID string `json:"host_id"`
Topic string `json:"topic"`
Type int `json:"type"`
Status string `json:"status"`
StartTime string `json:"start_time"`
Duration int `json:"duration"`
Timezone string `json:"timezone"`
CreatedAt string `json:"created_at"`
Agenda string `json:"agenda"`
JoinURL string `json:"join_url"`
Password string `json:"password"`
H323Password string `json:"h323_password"`
EncryptedPassword string `json:"encrypted_password"`
PMI int `json:"pmi"`
UUID string `json:"uuid"`
ID int `json:"id"`
HostID string `json:"host_id"`
Topic string `json:"topic"`
Type MeetingType `json:"type"`
Status string `json:"status"`
StartTime string `json:"start_time"`
Duration int `json:"duration"`
Timezone string `json:"timezone"`
CreatedAt string `json:"created_at"`
Agenda string `json:"agenda"`
JoinURL string `json:"join_url"`
StartURL string `json:"start_url"`
Password string `json:"password"`
H323Password string `json:"h323_password"`
EncryptedPassword string `json:"encrypted_password"`
PMI int `json:"pmi"`
TrackingFields []struct {
Field string `json:"field"`
Value string `json:"value"`
Expand Down Expand Up @@ -64,3 +79,60 @@ type Meeting struct {
AuthenticationName string `json:"authentication_name"`
} `json:"settings"`
}

// CreateMeetingRequest as defined at https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
type CreateMeetingRequest struct {
Topic string `json:"topic"`
Type MeetingType `json:"type"`
StartTime string `json:"start_time,omitempty"`
Duration int `json:"duration,omitempty"`
ScheduleFor string `json:"schedule_for,omitempty"`
Timezone string `json:"timezone,omitempty"`
Password string `json:"password"`
Agenda string `json:"agenda"`
TrackingFields []struct {
Field string `json:"field"`
Value string `json:"value"`
} `json:"tracking_fields"`
Recurrence *struct {
Type int `json:"type"`
RepeatInterval int `json:"repeat_interval"`
WeeklyDays string `json:"weekly_days,omitempty"`
MonthlyDay int `json:"monthly_day,omitempty"`
MonthlyWeekDay int `json:"monthly_week_day,omitempty"`
EndTimes int `json:"end_times,omitempty"`
EndDateTime int `json:"end_date_time,omitempty"`
} `json:"recurrence,omitempty"`
Settings struct {
HostVideo bool `json:"host_video"`
ParticipantVideo bool `json:"participant_video"`
CNMeeting bool `json:"cn_meeting"`
INMeeting bool `json:"in_meeting"`
JoinBeforeHost bool `json:"join_before_host"`
MuteUponEntry bool `json:"mute_upon_entry"`
Watermark bool `json:"watermark"`
UsePMI bool `json:"use_pmi"`
ApprovalType int `json:"approval_type"`
RegistrationType int `json:"registration_type"`
Audio string `json:"audio"`
AutoRecording string `json:"auto_recording"`
AlternativeHosts string `json:"alternative_hosts"`
WaitingRoom bool `json:"waiting_room"`
GlobalDialInCountries []string `json:"global_dial_in_countries"`
GlobalDialInNumbers []struct {
Country string `json:"country"`
CountryName string `json:"country_name"`
City string `json:"city"`
Number string `json:"number"`
Type string `json:"type"`
} `json:"global_dial_in_numbers"`
ContactName string `json:"contact_name"`
ContactEmail string `json:"contact_email"`
RegistrantsConfirmationEmail bool `json:"registrants_confirmation_email"`
RegistrantsEmailNotification bool `json:"registrants_email_notification"`
MeetingAuthentication bool `json:"meeting_authentication"`
AuthenticationOption string `json:"authentication_option"`
AuthenticationDomains string `json:"authentication_domains"`
AuthenticationName string `json:"authentication_name"`
} `json:"settings,omitempty"`
}
Loading