diff --git a/README.md b/README.md index 515267df..f910c851 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ You will need a paid Zoom account to use the plugin. 1. Go to **System Console > Plugins > Zoom** to configure the Zoom Plugin. -![image](./assets/settings.png) +![image](https://github.com/mattermost/docs/raw/master/source/images/zoom_system_console.png) 2. If you're using a self-hosted private cloud or on-premise Zoom server, enter the **Zoom URL** and **Zoom API URL** for the Zoom server, for example `https://yourzoom.com` and `https://api.yourzoom.com/v2` respectively. Leave blank if you're using Zoom's vendor-hosted SaaS service. @@ -37,28 +37,9 @@ You will need a paid Zoom account to use the plugin. To generate an **API Key** and **API Secret** requires a [Pro, Business, Education, or API Zoom plan](https://zoom.us/pricing). -4. Set the **OAuth ClientID** and **OAuth Secret**, generated by Zoom and used to create meetings and pull user data: +4. 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 https://marketplace.zoom.us/ and log in. - - In the top left click on **Develop** and then **Build App**. - - Select **OAuth** in **Choose your app type** section. - - Enter a name for your app and disable **Intend to publish this app on Zoom Marketplace**. - - Choose **Account-level app** as the app type. - - Click **Create**. - - Enter the **Company Name** and **Developer Contact Information** for your app. - - Go to the **App Credentials** tab on the left. Here you'll find your **Client ID** and **Client Secret**. - - Enter a Valid **Redirect URL for OAuth** (`https:///plugins/zoom/oauth2/complete`) and add the same url under **Whitelist URL**. - * `SiteUrl` should be your mattermost server url - - Add following scopes "user:read", "meeting:write", "webinar:write", "recording:write" - - Paste the **Client ID** and **Client Secret** into the fields in the System Console, and hit **Save**. - - Generate an **Encryption Key** to save the encryped tokens. - -![create OAuth app scrren](./assets/oauth_creds.png) - - -5. 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). - -6. Activate the plugin at **System Console > Plugins > Management** by clicking **Activate** for Zoom. +5. Activate the plugin at **System Console > Plugins > Management** by clicking **Activate** for Zoom. ![image](https://github.com/mattermost/docs/blob/master/source/images/zoom_system-console_management.png) diff --git a/assets/oauth_creds.png b/assets/oauth_creds.png deleted file mode 100644 index f2e6190c..00000000 Binary files a/assets/oauth_creds.png and /dev/null differ diff --git a/assets/settings.png b/assets/settings.png deleted file mode 100644 index 05277b61..00000000 Binary files a/assets/settings.png and /dev/null differ diff --git a/go.mod b/go.mod index d9ea908a..4b9ab047 100644 --- a/go.mod +++ b/go.mod @@ -8,5 +8,4 @@ require ( github.com/mattermost/mattermost-server/v5 v5.18.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.4.0 - golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 ) diff --git a/go.sum b/go.sum index 289c93cc..c759a668 100644 --- a/go.sum +++ b/go.sum @@ -386,7 +386,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= diff --git a/plugin.json b/plugin.json index 90337eda..564de4a3 100644 --- a/plugin.json +++ b/plugin.json @@ -32,39 +32,6 @@ "help_text": "The API URL for a self-hosted private cloud or on-premise Zoom server. For example, https://api.yourzoom.com/v2. Leave blank if you're using Zoom's vendor-hosted SaaS service.", "placeholder": "https://api.zoom.us/v2" }, - { - "key": "EnableOAuth", - "display_name": "Enable OAuth", - "type": "bool", - "help_text": "When true, OAuth will be used as authentication means with Zoom. \n Please enable only either one of OAuth based or Password base authentication.", - "default": false - }, - { - "key": "OAuthClientID", - "display_name": "Zoom OAuth Client ID", - "type": "text", - "help_text": "The Client ID for the OAuth app registered with Zoom. Leave blank if not using OAuth" - }, - { - "key": "OAuthClientSecret", - "display_name": "Zoom OAuth Client Secret", - "type": "text", - "help_text": "The Client Secret for the OAuth app registered with Zoom. Leave blank if not using OAuth" - }, - { - "key": "EncryptionKey", - "display_name": "At Rest Token Encryption Key", - "type": "generated", - "help_text": "The AES encryption key used to encrypt stored access tokens.", - "regenerate_help_text": "Regenerates the encryption key for Zoom OAuth Token. Regenerating the key invalidates your existing Zoom OAuth." - }, - { - "key": "EnableLegacyAuth", - "display_name": "Enable Password based authentication", - "type": "bool", - "help_text": "When true, user's email and password will be used to authenticate with Zoom. \n Please enable only either one of OAuth based or Password base authentication.", - "default": true - }, { "key": "APIKey", "display_name": "API Key", diff --git a/server/command.go b/server/command.go index f16c562a..ad627c7e 100644 --- a/server/command.go +++ b/server/command.go @@ -33,6 +33,7 @@ func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string) { } func (p *Plugin) executeCommand(c *plugin.Context, args *model.CommandArgs) (string, error) { + split := strings.Fields(args.Command) command := split[0] action := "" @@ -68,12 +69,12 @@ func (p *Plugin) executeCommand(c *plugin.Context, args *model.CommandArgs) (str return "", nil } - zoomUser, authErr := p.authenticateAndFetchZoomUser(userID, user.Email, args.ChannelId) - if authErr != nil { - return authErr.Message, authErr.Err + // create a personal zoom meeting + ru, clientErr := p.zoomClient.GetUser(user.Email) + if clientErr != nil { + return "We could not verify your Mattermost account in Zoom. Please ensure that your Mattermost email address matches your Zoom login email address.", nil } - - meetingID := zoomUser.Pmi + meetingID := ru.Pmi _, appErr = p.postMeeting(user.Username, meetingID, args.ChannelId, "") if appErr != nil { diff --git a/server/configuration.go b/server/configuration.go index a60e9e73..1d9aa726 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -21,17 +21,11 @@ import ( // If you add non-reference types to your configuration struct, be sure to rewrite Clone as a deep // copy appropriate for your types. type configuration struct { - ZoomURL string - ZoomAPIURL string - EnableLegacyAuth bool - APIKey string - APISecret string - EnableOAuth bool - OAuthClientID string - OAuthClientSecret string - OAuthRedirectUrl string - EncryptionKey string - WebhookSecret string + ZoomURL string + ZoomAPIURL string + APIKey string + APISecret string + WebhookSecret string } // Clone shallow copies the configuration. Your implementation may require a deep copy if @@ -43,33 +37,12 @@ func (c *configuration) Clone() *configuration { // IsValid checks if all needed fields are set. func (c *configuration) IsValid() error { - - if _, err := isValidAuthConfig(c); err != nil { - return err + if len(c.APIKey) == 0 { + return errors.New("APIKey is not configured") } - switch { - case c.EnableLegacyAuth: - switch { - case len(c.APIKey) == 0: - return errors.New("APIKey is not configured") - - case len(c.APISecret) == 0: - return errors.New("APISecret is not configured") - } - case c.EnableOAuth: - switch { - case len(c.OAuthClientSecret) == 0: - return errors.New("OAuthClientSecret is not configured") - - case len(c.OAuthClientID) == 0: - return errors.New("OAuthClientID is not configured") - - case len(c.EncryptionKey) == 0: - return errors.New("Please generate EncryptionKey from Zoom plugin settings") - } - default: - return errors.New("Please select either OAuth or Password based authentication") + if len(c.APISecret) == 0 { + return errors.New("APISecret is not configured") } if len(c.WebhookSecret) == 0 { @@ -128,29 +101,7 @@ func (p *Plugin) OnConfigurationChange() error { if err := p.API.LoadPluginConfiguration(configuration); err != nil { return errors.Wrap(err, "failed to load plugin configuration") } - if _, err := isValidAuthConfig(configuration); err != nil { - - if apiErr := p.API.DisablePlugin(manifest.Id); apiErr != nil { - return errors.Wrap(apiErr, "failed to disable plugin on invalid configuration change") - } - - return errors.Wrap(err, "failed to validate authentication configuration") - } - p.setConfiguration(configuration) return nil } - -// function to validate authentication config -func isValidAuthConfig(configuration *configuration) (bool, error) { - switch { - case configuration.EnableLegacyAuth && configuration.EnableOAuth: - return false, errors.New( - "Only one authentication scheme (OAuth or Password) is allowed to be enabled at the same time.") - case !configuration.EnableLegacyAuth && !configuration.EnableOAuth: - return false, errors.New("Please enable authentication") - default: - return true, nil - } -} diff --git a/server/http.go b/server/http.go index 618e2ebc..25d0b8e7 100644 --- a/server/http.go +++ b/server/http.go @@ -4,11 +4,9 @@ package main import ( - "context" "crypto/subtle" "encoding/json" "fmt" - "log" "net/http" "strings" "time" @@ -17,7 +15,6 @@ import ( "github.com/mattermost/mattermost-plugin-zoom/server/zoom" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" - "golang.org/x/oauth2" ) func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { @@ -32,151 +29,11 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req p.handleWebhook(w, r) case "/api/v1/meetings": p.handleStartMeeting(w, r) - case "/oauth2/connect": - p.connectUserToZoom(w, r) - case "/oauth2/complete": - p.completeUserOAuthToZoom(w, r) default: http.NotFound(w, r) } } -func (p *Plugin) connectUserToZoom(w http.ResponseWriter, r *http.Request) { - userID := r.Header.Get("Mattermost-User-ID") - if userID == "" { - http.Error(w, "Not authorized", http.StatusUnauthorized) - return - } - - channelID := r.URL.Query().Get("channelID") - - if channelID == "" { - http.Error(w, "Not authorized", http.StatusUnauthorized) - return - } - - conf, err := p.getOAuthConfig() - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - key := fmt.Sprintf("%v_%v", model.NewId()[0:15], userID) - state := fmt.Sprintf("%v_%v", key, channelID) - - appErr := p.API.KVSet(key, []byte(state)) - if appErr != nil { - http.Error(w, appErr.Error(), http.StatusInternalServerError) - } - - url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline) - http.Redirect(w, r, url, http.StatusFound) -} - -func (p *Plugin) completeUserOAuthToZoom(w http.ResponseWriter, r *http.Request) { - authedUserID := r.Header.Get("Mattermost-User-ID") - if authedUserID == "" { - http.Error(w, "Not authorized, missing Mattermost user id", http.StatusUnauthorized) - return - } - - ctx := context.Background() - conf, err := p.getOAuthConfig() - - if err != nil { - http.Error(w, "error in oauth config", http.StatusInternalServerError) - } - - code := r.URL.Query().Get("code") - if len(code) == 0 { - http.Error(w, "missing authorization code", http.StatusBadRequest) - return - } - - state := r.URL.Query().Get("state") - stateComponents := strings.Split(state, "_") - - if len(stateComponents) != zoomStateLength { - log.Printf("stateComponents: %v, state: %v", stateComponents, state) - http.Error(w, "invalid state", http.StatusBadRequest) - - } - key := fmt.Sprintf("%v_%v", stateComponents[0], stateComponents[1]) - - var storedState []byte - var appErr *model.AppError - storedState, appErr = p.API.KVGet(key) - if appErr != nil { - fmt.Println(appErr) - http.Error(w, "missing stored state", http.StatusBadRequest) - return - } - - if string(storedState) != state { - http.Error(w, "invalid state", http.StatusBadRequest) - return - } - - userID := stateComponents[1] - channelID := stateComponents[2] - - p.API.KVDelete(state) - - if userID != authedUserID { - http.Error(w, "Not authorized, incorrect user", http.StatusUnauthorized) - return - } - - tok, err := conf.Exchange(ctx, code) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - zoomUser, err := p.getZoomUserWithToken(tok) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - zoomUserInfo := &ZoomUserInfo{ - ZoomEmail: zoomUser.Email, - UserID: userID, - OAuthToken: tok, - } - - if err := p.storeZoomUserInfo(zoomUserInfo); err != nil { - http.Error(w, "Unable to connect user to Zoom", http.StatusInternalServerError) - return - } - - user, _ := p.API.GetUser(userID) - - _, appErr = p.postMeeting(user.Username, zoomUser.Pmi, channelID, "") - if appErr != nil { - http.Error(w, appErr.Error(), appErr.StatusCode) - return - } - - html := ` - - - - - - -

Completed connecting to Zoom. Please close this window.

- - -` - - w.Header().Set("Content-Type", "text/html") - w.Write([]byte(html)) -} - func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { config := p.getConfiguration() @@ -315,13 +172,12 @@ func (p *Plugin) handleStartMeeting(w http.ResponseWriter, r *http.Request) { } } - zoomUser, authErr := p.authenticateAndFetchZoomUser(userID, user.Email, req.ChannelID) - if authErr != nil { - http.Error(w, authErr.Error(), http.StatusInternalServerError) + ru, clientErr := p.zoomClient.GetUser(user.Email) + if clientErr != nil { + http.Error(w, clientErr.Error(), clientErr.StatusCode) return } - - meetingID := zoomUser.Pmi + meetingID := ru.Pmi createdPost, appErr := p.postMeeting(user.Username, meetingID, req.ChannelID, req.Topic) if appErr != nil { diff --git a/server/plugin.go b/server/plugin.go index b68bbb32..c63b3772 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -4,13 +4,7 @@ package main import ( - "bytes" - "context" - "encoding/json" - "fmt" "io/ioutil" - "log" - "net/http" "path/filepath" "sync" @@ -18,7 +12,6 @@ import ( "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" "github.com/pkg/errors" - "golang.org/x/oauth2" ) const ( @@ -27,14 +20,6 @@ const ( botUserName = "zoom" botDisplayName = "Zoom" botDescription = "Created by the Zoom plugin." - - zoomDefaultUrl = "https://zoom.us" - zoomDefaultAPIUrl = "https://api.zoom.com/v2" - zoomTokenKey = "zoomtoken_" - - zoomStateLength = 3 - zoomOAuthmessage = "[Click here to link your Zoom account.](%s/plugins/zoom/oauth2/connect?channelID=%s)" - zoomEmailMismatch = "We could not verify your Mattermost account in Zoom. Please ensure that your Mattermost email address %s matches your Zoom login email address." ) type Plugin struct { @@ -60,10 +45,6 @@ func (p *Plugin) OnActivate() error { return err } - if _, err := p.getSiteUrl(); err != nil { - return err - } - botUserID, err := p.Helpers.EnsureBot(&model.Bot{ Username: botUserName, DisplayName: botDisplayName, @@ -96,192 +77,3 @@ func (p *Plugin) OnActivate() error { return nil } - -func (p *Plugin) getSiteUrl() (string, error) { - var siteUrl string - if siteUrlRef := p.API.GetConfig().ServiceSettings.SiteURL; siteUrlRef != nil || *siteUrlRef == "" { - siteUrl = *siteUrlRef - } else { - return "", errors.New("error fetching siteUrl") - } - return siteUrl, nil -} - -func (p *Plugin) getOAuthConfig() (*oauth2.Config, error) { - config := p.getConfiguration() - - clientID := config.OAuthClientID - clientSecret := config.OAuthClientSecret - zoomUrl := config.ZoomURL - zoomAPIUrl := config.ZoomAPIURL - - if zoomUrl == "" { - zoomUrl = zoomDefaultUrl - } - if zoomAPIUrl == "" { - zoomAPIUrl = zoomDefaultAPIUrl - } - - authUrl := fmt.Sprintf("%v/oauth/authorize", zoomUrl) - tokenUrl := fmt.Sprintf("%v/oauth/token", zoomUrl) - - siteUrl, err := p.getSiteUrl() - if err != nil { - return nil, err - } - - redirectUrl := fmt.Sprintf("%s/plugins/zoom/oauth2/complete", siteUrl) - - return &oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - Endpoint: oauth2.Endpoint{ - AuthURL: authUrl, - TokenURL: tokenUrl, - }, - RedirectURL: redirectUrl, - Scopes: []string{ - "user:read", - "meeting:write", - "webinar:write", - "recording:write"}, - }, nil -} - -type ZoomUserInfo struct { - ZoomEmail string - - // Zoom OAuth Token, ttl 15 years - OAuthToken *oauth2.Token - - // Mattermorst userID - UserID string -} - -type AuthError struct { - Message string `json:"message"` - Err error `json:"err"` -} - -func (ae *AuthError) Error() string { - errorString, _ := json.Marshal(ae) - return string(errorString) -} - -func (p *Plugin) storeZoomUserInfo(info *ZoomUserInfo) error { - config := p.getConfiguration() - - encryptedToken, err := encrypt([]byte(config.EncryptionKey), info.OAuthToken.AccessToken) - if err != nil { - return err - } - - info.OAuthToken.AccessToken = encryptedToken - - jsonInfo, err := json.Marshal(info) - if err != nil { - return err - } - - if err := p.API.KVSet(zoomTokenKey+info.UserID, jsonInfo); err != nil { - return err - } - - return nil -} - -func (p *Plugin) getZoomUserInfo(userID string) (*ZoomUserInfo, error) { - config := p.getConfiguration() - - var userInfo ZoomUserInfo - - if infoBytes, err := p.API.KVGet(zoomTokenKey + userID); err != nil || infoBytes == nil { - return nil, errors.New("Must connect user account to Zoom first.") - } else if err := json.Unmarshal(infoBytes, &userInfo); err != nil { - return nil, errors.New("Unable to parse token.") - } - - unencryptedToken, err := decrypt([]byte(config.EncryptionKey), userInfo.OAuthToken.AccessToken) - if err != nil { - log.Println(err.Error()) - return nil, errors.New("Unable to decrypt access token.") - } - - userInfo.OAuthToken.AccessToken = unencryptedToken - - return &userInfo, nil -} - -func (p *Plugin) authenticateAndFetchZoomUser(userID, userEmail, channelID string) (*zoom.User, *AuthError) { - var zoomUser *zoom.User - var clientErr *zoom.ClientError - var err error - config := p.getConfiguration() - - // use OAuth - if config.EnableOAuth { - zoomUserInfo, apiErr := p.getZoomUserInfo(userID) - oauthMsg := fmt.Sprintf( - zoomOAuthmessage, - *p.API.GetConfig().ServiceSettings.SiteURL, channelID) - - if apiErr != nil || zoomUserInfo == nil { - return nil, &AuthError{Message: oauthMsg, Err: apiErr} - } - zoomUser, err = p.getZoomUserWithToken(zoomUserInfo.OAuthToken) - if err != nil || zoomUser == nil { - return nil, &AuthError{Message: oauthMsg, Err: apiErr} - } - } else if config.EnableLegacyAuth { - // use personal credentials - zoomUser, clientErr = p.zoomClient.GetUser(userEmail) - if clientErr != nil { - includeEmailInErr := fmt.Sprintf(zoomEmailMismatch, userEmail) - return nil, &AuthError{Message: includeEmailInErr, Err: clientErr} - } - } - return zoomUser, nil -} - -func (p *Plugin) getZoomUserWithToken(token *oauth2.Token) (*zoom.User, error) { - - config := p.getConfiguration() - ctx := context.Background() - - conf, err := p.getOAuthConfig() - if err != nil { - return nil, err - } - - client := conf.Client(ctx, token) - - apiUrl := config.ZoomAPIURL - if apiUrl == "" { - apiUrl = zoomDefaultAPIUrl - } - - url := fmt.Sprintf("%v/users/me", config.ZoomAPIURL) - res, err := client.Get(url) - if err != nil || res == nil { - return nil, errors.New("error fetching zoom user") - } - - defer closeBody(res) - if res.StatusCode != http.StatusOK { - return nil, errors.New("error fetching zoom user") - } - - buf := new(bytes.Buffer) - - if _, err = buf.ReadFrom(res.Body); err != nil { - return nil, errors.New("error reading response body for zoom user") - } - - var zoomUser zoom.User - - if err := json.Unmarshal(buf.Bytes(), &zoomUser); err != nil { - return nil, errors.New("error unmarshalling zoom user") - } - - return &zoomUser, nil -} diff --git a/server/plugin_test.go b/server/plugin_test.go index 20eefa48..0580aae6 100644 --- a/server/plugin_test.go +++ b/server/plugin_test.go @@ -107,20 +107,12 @@ func TestPlugin(t *testing.T) { api.On("SetProfileImage", botUserID, mock.Anything).Return(nil) api.On("RegisterCommand", mock.AnythingOfType("*model.Command")).Return(nil) - siteUrl := "localhost" - api.On("GetConfig").Return(&model.Config{ - ServiceSettings: model.ServiceSettings{ - SiteURL: &siteUrl, - }, - }) - p := Plugin{} p.setConfiguration(&configuration{ - ZoomAPIURL: ts.URL, - APIKey: "theapikey", - APISecret: "theapisecret", - WebhookSecret: "thewebhooksecret", - EnableLegacyAuth: true, + ZoomAPIURL: ts.URL, + APIKey: "theapikey", + APISecret: "theapisecret", + WebhookSecret: "thewebhooksecret", }) p.SetAPI(api) diff --git a/server/utils.go b/server/utils.go deleted file mode 100644 index 26ec1556..00000000 --- a/server/utils.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "errors" - "io" - "io/ioutil" - "net/http" -) - -func pad(src []byte) []byte { - padding := aes.BlockSize - len(src)%aes.BlockSize - padtext := bytes.Repeat([]byte{byte(padding)}, padding) - return append(src, padtext...) -} - -func unpad(src []byte) ([]byte, error) { - length := len(src) - unpadding := int(src[length-1]) - - if unpadding > length { - return nil, errors.New("unpad error. This could happen when incorrect encryption key is used") - } - - return src[:(length - unpadding)], nil -} - -func encrypt(key []byte, text string) (string, error) { - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - - msg := pad([]byte(text)) - ciphertext := make([]byte, aes.BlockSize+len(msg)) - iv := ciphertext[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return "", err - } - - cfb := cipher.NewCFBEncrypter(block, iv) - cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg)) - finalMsg := base64.URLEncoding.EncodeToString(ciphertext) - return finalMsg, nil -} - -func decrypt(key []byte, text string) (string, error) { - block, err := aes.NewCipher(key) - if err != nil { - return "", err - } - - decodedMsg, err := base64.URLEncoding.DecodeString(text) - if err != nil { - return "", err - } - - if (len(decodedMsg) % aes.BlockSize) != 0 { - return "", errors.New("blocksize must be multiple of decoded message length") - } - - iv := decodedMsg[:aes.BlockSize] - msg := decodedMsg[aes.BlockSize:] - - cfb := cipher.NewCFBDecrypter(block, iv) - cfb.XORKeyStream(msg, msg) - - unpadMsg, err := unpad(msg) - if err != nil { - return "", err - } - - return string(unpadMsg), nil -} - -func closeBody(r *http.Response) { - if r != nil && r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } -} diff --git a/webapp/src/actions/index.js b/webapp/src/actions/index.js index fda1f9c4..1011fc10 100644 --- a/webapp/src/actions/index.js +++ b/webapp/src/actions/index.js @@ -14,13 +14,13 @@ export function startMeeting(channelId, force = false) { window.open(meetingURL); } } catch (error) { - let m; + let m = 'We could not verify your Mattermost account in Zoom. Please ensure that your Mattermost email address matches your Zoom email address.'; if (error.message && error.message[0] === '{') { const e = JSON.parse(error.message); // Error is from Zoom API if (e && e.message) { - m = '\nZoom error: ' + e.message; + m += '\nZoom error: ' + e.message; } }