diff options
Diffstat (limited to 'app')
71 files changed, 1170 insertions, 96 deletions
diff --git a/app/admin.go b/app/admin.go index b86eb5993..103c4617b 100644 --- a/app/admin.go +++ b/app/admin.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -136,7 +136,6 @@ func SaveConfig(cfg *model.Config) *model.AppError { return model.NewLocAppError("saveConfig", "ent.cluster.save_config.error", nil, "") } - //oldCfg := utils.Cfg utils.DisableConfigWatch() utils.SaveConfig(utils.CfgFileName, cfg) utils.LoadConfig(utils.CfgFileName) @@ -150,6 +149,7 @@ func SaveConfig(cfg *model.Config) *model.AppError { } } + // oldCfg := utils.Cfg // Future feature is to sync the configuration files // if einterfaces.GetClusterInterface() != nil { // err := einterfaces.GetClusterInterface().ConfigChanged(cfg, oldCfg, true) diff --git a/app/analytics.go b/app/analytics.go index f1146327f..78e1fe7b4 100644 --- a/app/analytics.go +++ b/app/analytics.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/app.go b/app/app.go index 8568c7bba..2758add0e 100644 --- a/app/app.go +++ b/app/app.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/apptestlib.go b/app/apptestlib.go index 52530f92f..0c7086c64 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -75,6 +75,14 @@ func (me *TestHelper) InitBasic() *TestHelper { return me } +func (me *TestHelper) MakeUsername() string { + return "un_" + model.NewId() +} + +func (me *TestHelper) MakeEmail() string { + return "success_" + model.NewId() + "@simulator.amazonses.com" +} + func (me *TestHelper) CreateTeam() *model.Team { id := model.NewId() team := &model.Team{ diff --git a/app/audit.go b/app/audit.go index 6b7439d43..fdd152719 100644 --- a/app/audit.go +++ b/app/audit.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/authentication.go b/app/authentication.go index 369458527..5e1b4461f 100644 --- a/app/authentication.go +++ b/app/authentication.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -43,7 +43,7 @@ func checkUserPassword(user *model.User, password string) *model.AppError { return result.Err } - return model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id) + return model.NewAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized) } else { if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil { return result.Err @@ -57,8 +57,7 @@ func checkLdapUserPasswordAndAllCriteria(ldapId *string, password string, mfaTok ldapInterface := einterfaces.GetLdapInterface() if ldapInterface == nil || ldapId == nil { - err := model.NewLocAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "") - err.StatusCode = http.StatusNotImplemented + err := model.NewAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "", http.StatusNotImplemented) return nil, err } @@ -109,13 +108,13 @@ func CheckUserMfa(user *model.User, token string) *model.AppError { mfaInterface := einterfaces.GetMfaInterface() if mfaInterface == nil { - return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "") + return model.NewAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "", http.StatusNotImplemented) } if ok, err := mfaInterface.ValidateToken(user.MfaSecret, token); err != nil { return err } else if !ok { - return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "") + return model.NewAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "", http.StatusUnauthorized) } return nil @@ -123,7 +122,7 @@ func CheckUserMfa(user *model.User, token string) *model.AppError { func checkUserLoginAttempts(user *model.User) *model.AppError { if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts { - return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id, http.StatusForbidden) + return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized) } return nil @@ -131,14 +130,14 @@ func checkUserLoginAttempts(user *model.User) *model.AppError { func checkEmailVerified(user *model.User) *model.AppError { if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification { - return model.NewLocAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id) + return model.NewAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized) } return nil } func checkUserNotDisabled(user *model.User) *model.AppError { if user.DeleteAt > 0 { - return model.NewLocAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id) + return model.NewAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized) } return nil } @@ -148,8 +147,7 @@ func authenticateUser(user *model.User, password, mfaToken string) (*model.User, if user.AuthService == model.USER_AUTH_SERVICE_LDAP { if !ldapAvailable { - err := model.NewLocAppError("login", "api.user.login_ldap.not_available.app_error", nil, "") - err.StatusCode = http.StatusNotImplemented + err := model.NewAppError("login", "api.user.login_ldap.not_available.app_error", nil, "", http.StatusNotImplemented) return user, err } else if ldapUser, err := checkLdapUserPasswordAndAllCriteria(user.AuthData, password, mfaToken); err != nil { err.StatusCode = http.StatusUnauthorized @@ -163,8 +161,7 @@ func authenticateUser(user *model.User, password, mfaToken string) (*model.User, if authService == model.USER_AUTH_SERVICE_SAML { authService = strings.ToUpper(authService) } - err := model.NewLocAppError("login", "api.user.login.use_auth_service.app_error", map[string]interface{}{"AuthService": authService}, "") - err.StatusCode = http.StatusBadRequest + err := model.NewAppError("login", "api.user.login.use_auth_service.app_error", map[string]interface{}{"AuthService": authService}, "", http.StatusBadRequest) return user, err } else { if err := CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil { diff --git a/app/authorization.go b/app/authorization.go index 4d36c63e8..9fc2edfb9 100644 --- a/app/authorization.go +++ b/app/authorization.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/authorization_test.go b/app/authorization_test.go index 049567483..4d2fdd5a0 100644 --- a/app/authorization_test.go +++ b/app/authorization_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_channels.go b/app/auto_channels.go index 3945a5a4f..2f91cf14b 100644 --- a/app/auto_channels.go +++ b/app/auto_channels.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_constants.go b/app/auto_constants.go index c8c903e32..2a9438396 100644 --- a/app/auto_constants.go +++ b/app/auto_constants.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_environment.go b/app/auto_environment.go index b0a4f54b8..8827ddf87 100644 --- a/app/auto_environment.go +++ b/app/auto_environment.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_posts.go b/app/auto_posts.go index b32407539..07d260846 100644 --- a/app/auto_posts.go +++ b/app/auto_posts.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_teams.go b/app/auto_teams.go index 6e66f4446..029d33aa1 100644 --- a/app/auto_teams.go +++ b/app/auto_teams.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/auto_users.go b/app/auto_users.go index 7a99cc90b..71e20817d 100644 --- a/app/auto_users.go +++ b/app/auto_users.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/brand.go b/app/brand.go index 9b3df3145..bb11bd581 100644 --- a/app/brand.go +++ b/app/brand.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/channel.go b/app/channel.go index 1c04905c2..17fa02ad3 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -81,7 +81,7 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string, us err = cmResult.Err } - if requestor == nil { + if requestor == nil { if err := postJoinChannelMessage(user, offTopic); err != nil { l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err) } diff --git a/app/command.go b/app/command.go index 188729ad5..2af934710 100644 --- a/app/command.go +++ b/app/command.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -312,6 +312,7 @@ func GetCommand(commandId string) (*model.Command, *model.AppError) { } if result := <-Srv.Store.Command().Get(commandId); result.Err != nil { + result.Err.StatusCode = http.StatusNotFound return nil, result.Err } else { return result.Data.(*model.Command), nil diff --git a/app/command_away.go b/app/command_away.go index 55553fa3f..f4150dfeb 100644 --- a/app/command_away.go +++ b/app/command_away.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_echo.go b/app/command_echo.go index 40d70e54a..ef70cb609 100644 --- a/app/command_echo.go +++ b/app/command_echo.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_expand_collapse.go b/app/command_expand_collapse.go index a4a152c60..314a54b2a 100644 --- a/app/command_expand_collapse.go +++ b/app/command_expand_collapse.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_invite_people.go b/app/command_invite_people.go index 0496dadca..6b0ee96b4 100644 --- a/app/command_invite_people.go +++ b/app/command_invite_people.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_join.go b/app/command_join.go index 5b19dd7a0..9ad3682a1 100644 --- a/app/command_join.go +++ b/app/command_join.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_loadtest.go b/app/command_loadtest.go index d3c7474ae..ad64573fc 100644 --- a/app/command_loadtest.go +++ b/app/command_loadtest.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_logout.go b/app/command_logout.go index 1a353056e..cc59d2686 100644 --- a/app/command_logout.go +++ b/app/command_logout.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_me.go b/app/command_me.go index bb29ec1e0..be09ac5dd 100644 --- a/app/command_me.go +++ b/app/command_me.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_msg.go b/app/command_msg.go index fd4ace61a..977e403d5 100644 --- a/app/command_msg.go +++ b/app/command_msg.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_offline.go b/app/command_offline.go index 6e2c125f8..5316ccc5f 100644 --- a/app/command_offline.go +++ b/app/command_offline.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_online.go b/app/command_online.go index bd6fbab60..b0becaf14 100644 --- a/app/command_online.go +++ b/app/command_online.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_shortcuts.go b/app/command_shortcuts.go index 93e5f0f51..7df5dbaed 100644 --- a/app/command_shortcuts.go +++ b/app/command_shortcuts.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/command_shrug.go b/app/command_shrug.go index 12d1039ec..88dbcef8c 100644 --- a/app/command_shrug.go +++ b/app/command_shrug.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/compliance.go b/app/compliance.go index 966b9b523..cb1eece70 100644 --- a/app/compliance.go +++ b/app/compliance.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/diagnostics.go b/app/diagnostics.go index 2c8211f42..295164c97 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/diagnostics_test.go b/app/diagnostics_test.go index 21b077bd8..80c12fd2d 100644 --- a/app/diagnostics_test.go +++ b/app/diagnostics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/email.go b/app/email.go index cb2fa213a..235d949be 100644 --- a/app/email.go +++ b/app/email.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -18,7 +18,7 @@ func SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL st subject := T("api.templates.username_change_subject", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], - "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) + "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) bodyPage := utils.NewHTMLTemplate("email_change_body", locale) bodyPage.Props["SiteURL"] = siteURL @@ -40,8 +40,7 @@ func SendEmailChangeVerifyEmail(userId, newUserEmail, locale, siteURL string) *m subject := T("api.templates.email_change_verify_subject", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], - "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) - + "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) bodyPage := utils.NewHTMLTemplate("email_change_verify_body", locale) bodyPage.Props["SiteURL"] = siteURL @@ -63,7 +62,7 @@ func SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL string) *model.App subject := T("api.templates.email_change_subject", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], - "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) + "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) bodyPage := utils.NewHTMLTemplate("email_change_body", locale) bodyPage.Props["SiteURL"] = siteURL @@ -128,7 +127,7 @@ func SendWelcomeEmail(userId string, email string, verified bool, locale, siteUR subject := T("api.templates.welcome_subject", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], - "ServerURL": rawUrl.Host}) + "ServerURL": rawUrl.Host}) bodyPage := utils.NewHTMLTemplate("welcome_body", locale) bodyPage.Props["SiteURL"] = siteURL @@ -161,7 +160,7 @@ func SendPasswordChangeEmail(email, method, locale, siteURL string) *model.AppEr subject := T("api.templates.password_change_subject", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], - "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) + "TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) bodyPage := utils.NewHTMLTemplate("password_change_body", locale) bodyPage.Props["SiteURL"] = siteURL @@ -234,8 +233,8 @@ func SendInviteEmails(team *model.Team, senderName string, invites []string, sit subject := utils.T("api.templates.invite_subject", map[string]interface{}{"SenderName": senderName, - "TeamDisplayName": team.DisplayName, - "SiteName": utils.ClientCfg["SiteName"]}) + "TeamDisplayName": team.DisplayName, + "SiteName": utils.ClientCfg["SiteName"]}) bodyPage := utils.NewHTMLTemplate("invite_body", model.DEFAULT_LOCALE) bodyPage.Props["SiteURL"] = siteURL diff --git a/app/email_batching.go b/app/email_batching.go index ac91aae08..6d7a376ef 100644 --- a/app/email_batching.go +++ b/app/email_batching.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/email_batching_test.go b/app/email_batching_test.go index 23722facd..74fbea5c3 100644 --- a/app/email_batching_test.go +++ b/app/email_batching_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/email_test.go b/app/email_test.go index 6d1a6f14a..3f57c54f9 100644 --- a/app/email_test.go +++ b/app/email_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/emoji.go b/app/emoji.go new file mode 100644 index 000000000..b0c8418aa --- /dev/null +++ b/app/emoji.go @@ -0,0 +1,206 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "bytes" + "image" + "image/color/palette" + "image/draw" + "image/gif" + _ "image/jpeg" + "image/png" + "io" + "mime/multipart" + "net/http" + + l4g "github.com/alecthomas/log4go" + + "github.com/disintegration/imaging" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +const ( + MaxEmojiFileSize = 1000 * 1024 // 1 MB + MaxEmojiWidth = 128 + MaxEmojiHeight = 128 +) + +func CreateEmoji(sessionUserId string, emoji *model.Emoji, multiPartImageData *multipart.Form) (*model.Emoji, *model.AppError) { + // wipe the emoji id so that existing emojis can't get overwritten + emoji.Id = "" + + // do our best to validate the emoji before committing anything to the DB so that we don't have to clean up + // orphaned files left over when validation fails later on + emoji.PreSave() + if err := emoji.IsValid(); err != nil { + return nil, err + } + + if emoji.CreatorId != sessionUserId { + return nil, model.NewAppError("createEmoji", "api.emoji.create.other_user.app_error", nil, "", http.StatusForbidden) + } + + if result := <-Srv.Store.Emoji().GetByName(emoji.Name); result.Err == nil && result.Data != nil { + return nil, model.NewAppError("createEmoji", "api.emoji.create.duplicate.app_error", nil, "", http.StatusBadRequest) + } + + if imageData := multiPartImageData.File["image"]; len(imageData) == 0 { + err := model.NewLocAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": "createEmoji"}, "") + err.StatusCode = http.StatusBadRequest + return nil, err + } else if err := UploadEmojiImage(emoji.Id, imageData[0]); err != nil { + return nil, err + } + + if result := <-Srv.Store.Emoji().Save(emoji); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Emoji), nil + } +} + +func GetEmojiList() ([]*model.Emoji, *model.AppError) { + if result := <-Srv.Store.Emoji().GetAll(); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Emoji), nil + } +} + +func UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError { + file, err := imageData.Open() + if err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.open.app_error", nil, "", http.StatusBadRequest) + } + defer file.Close() + + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + + // make sure the file is an image and is within the required dimensions + if config, _, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())); err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.image.app_error", nil, "", http.StatusBadRequest) + } else if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight { + data := buf.Bytes() + newbuf := bytes.NewBuffer(nil) + if info, err := model.GetInfoForBytes(imageData.Filename, data); err != nil { + return err + } else if info.MimeType == "image/gif" { + if gif_data, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_decode_error", nil, "", http.StatusBadRequest) + } else { + resized_gif := resizeEmojiGif(gif_data) + if err := gif.EncodeAll(newbuf, resized_gif); err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "", http.StatusBadRequest) + } + if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { + return err + } + } + } else { + if img, _, err := image.Decode(bytes.NewReader(data)); err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.decode_error", nil, "", http.StatusBadRequest) + } else { + resized_image := resizeEmoji(img, config.Width, config.Height) + if err := png.Encode(newbuf, resized_image); err != nil { + return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "", http.StatusBadRequest) + } + if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil { + return err + } + } + } + } else { + if err := WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil { + return err + } + } + + return nil +} + +func DeleteEmoji(emoji *model.Emoji) *model.AppError { + if err := (<-Srv.Store.Emoji().Delete(emoji.Id, model.GetMillis())).Err; err != nil { + return err + } + + go deleteEmojiImage(emoji.Id) + go deleteReactionsForEmoji(emoji.Name) + return nil +} + +func GetEmoji(emojiId string) (*model.Emoji, *model.AppError) { + if !*utils.Cfg.ServiceSettings.EnableCustomEmoji { + return nil, model.NewAppError("deleteEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + if len(utils.Cfg.FileSettings.DriverName) == 0 { + return nil, model.NewAppError("deleteImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.Emoji().Get(emojiId, false); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Emoji), nil + } +} + +func resizeEmojiGif(gifImg *gif.GIF) *gif.GIF { + // Create a new RGBA image to hold the incremental frames. + firstFrame := gifImg.Image[0].Bounds() + b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy()) + img := image.NewRGBA(b) + + resizedImage := image.Image(nil) + // Resize each frame. + for index, frame := range gifImg.Image { + bounds := frame.Bounds() + draw.Draw(img, bounds, frame, bounds.Min, draw.Over) + resizedImage = resizeEmoji(img, firstFrame.Dx(), firstFrame.Dy()) + gifImg.Image[index] = imageToPaletted(resizedImage) + } + // Set new gif width and height + gifImg.Config.Width = resizedImage.Bounds().Dx() + gifImg.Config.Height = resizedImage.Bounds().Dy() + return gifImg +} + +func getEmojiImagePath(id string) string { + return "emoji/" + id + "/image" +} + +func resizeEmoji(img image.Image, width int, height int) image.Image { + emojiWidth := float64(width) + emojiHeight := float64(height) + + var emoji image.Image + if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth { + emoji = img + } else { + emoji = imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos) + } + return emoji +} + +func imageToPaletted(img image.Image) *image.Paletted { + b := img.Bounds() + pm := image.NewPaletted(b, palette.Plan9) + draw.FloydSteinberg.Draw(pm, b, img, image.ZP) + return pm +} + +func deleteEmojiImage(id string) { + if err := MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil { + l4g.Error("Failed to rename image when deleting emoji %v", id) + } +} + +func deleteReactionsForEmoji(emojiName string) { + if result := <-Srv.Store.Reaction().DeleteAllWithEmojiName(emojiName); result.Err != nil { + l4g.Warn(utils.T("api.emoji.delete.delete_reactions.app_error"), emojiName) + l4g.Warn(result.Err) + } +} diff --git a/app/file.go b/app/file.go index 8c0960fe8..c5e2982d4 100644 --- a/app/file.go +++ b/app/file.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/file_test.go b/app/file_test.go index 9df03315e..683b574b8 100644 --- a/app/file_test.go +++ b/app/file_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/import.go b/app/import.go index 6bf4e8a89..f92c9b1cc 100644 --- a/app/import.go +++ b/app/import.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/import_test.go b/app/import_test.go index e3ed80169..847864977 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/ldap.go b/app/ldap.go index fe68dfa81..1b823dc47 100644 --- a/app/ldap.go +++ b/app/ldap.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -18,7 +18,7 @@ func SyncLdap() { if ldapI := einterfaces.GetLdapInterface(); ldapI != nil { ldapI.SyncNow() } else { - l4g.Error("%v", model.NewLocAppError("ldapSyncNow", "ent.ldap.disabled.app_error", nil, "").Error()) + l4g.Error("%v", model.NewLocAppError("SyncLdap", "ent.ldap.disabled.app_error", nil, "").Error()) } } }() @@ -31,10 +31,84 @@ func TestLdap() *model.AppError { return err } } else { - err := model.NewLocAppError("ldapTest", "ent.ldap.disabled.app_error", nil, "") + err := model.NewLocAppError("TestLdap", "ent.ldap.disabled.app_error", nil, "") err.StatusCode = http.StatusNotImplemented return err } return nil } + +func SwitchEmailToLdap(email, password, code, ldapId, ldapPassword string) (string, *model.AppError) { + user, err := GetUserByEmail(email) + if err != nil { + return "", err + } + + if err := CheckPasswordAndAllCriteria(user, password, code); err != nil { + return "", err + } + + if err := RevokeAllSessions(user.Id); err != nil { + return "", err + } + + ldapInterface := einterfaces.GetLdapInterface() + if ldapInterface == nil { + return "", model.NewAppError("SwitchEmailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusNotImplemented) + } + + if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil { + return "", err + } + + go func() { + if err := SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, utils.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() + + return "/login?extra=signin_change", nil +} + +func SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (string, *model.AppError) { + user, err := GetUserByEmail(email) + if err != nil { + return "", err + } + + if user.AuthService != model.USER_AUTH_SERVICE_LDAP { + return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_ldap_account.app_error", nil, "", http.StatusBadRequest) + } + + ldapInterface := einterfaces.GetLdapInterface() + if ldapInterface == nil || user.AuthData == nil { + return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusNotImplemented) + } + + if err := ldapInterface.CheckPassword(*user.AuthData, ldapPassword); err != nil { + return "", err + } + + if err := CheckUserMfa(user, code); err != nil { + return "", err + } + + if err := UpdatePassword(user, newPassword); err != nil { + return "", err + } + + if err := RevokeAllSessions(user.Id); err != nil { + return "", err + } + + T := utils.GetUserTranslations(user.Locale) + + go func() { + if err := SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() + + return "/login?extra=signin_change", nil +} diff --git a/app/license.go b/app/license.go index c41c17fd8..7a00d7fb4 100644 --- a/app/license.go +++ b/app/license.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/license_test.go b/app/license_test.go index d7d851589..a7761b204 100644 --- a/app/license_test.go +++ b/app/license_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/login.go b/app/login.go index e9bcf1f03..4c7ab8474 100644 --- a/app/login.go +++ b/app/login.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/notification.go b/app/notification.go index ef1b70aad..e983f5e8c 100644 --- a/app/notification.go +++ b/app/notification.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/notification_test.go b/app/notification_test.go index 3768a95c7..794bb4b37 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/oauth.go b/app/oauth.go index 3e8b0b8d2..260e4ac00 100644 --- a/app/oauth.go +++ b/app/oauth.go @@ -1,14 +1,386 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app import ( + "bytes" + "crypto/tls" + b64 "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" ) -func RevokeAccessToken(token string) *model.AppError { +func CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + secret := model.NewId() + app.ClientSecret = secret + + if result := <-Srv.Store.OAuth().SaveApp(app); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.OAuthApp), nil + } +} + +func GetOAuthApp(appId string) (*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("GetOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.OAuth().GetApp(appId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.OAuthApp), nil + } +} + +func DeleteOAuthApp(appId string) *model.AppError { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return model.NewAppError("DeleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if err := (<-Srv.Store.OAuth().DeleteApp(appId)).Err; err != nil { + return err + } + + return nil +} + +func GetOAuthApps(page, perPage int) ([]*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("GetOAuthApps", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.OAuth().GetApps(page*perPage, perPage); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.OAuthApp), nil + } +} + +func GetOAuthAppsByCreator(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("GetOAuthAppsByUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.OAuth().GetAppByUser(userId, page*perPage, perPage); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.OAuthApp), nil + } +} + +func AllowOAuthAppAccessToUser(userId, responseType, clientId, redirectUri, scope, state string) (string, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if len(scope) == 0 { + scope = model.DEFAULT_SCOPE + } + + var oauthApp *model.OAuthApp + if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil { + return "", result.Err + } else { + oauthApp = result.Data.(*model.OAuthApp) + } + + if !oauthApp.IsValidRedirectURL(redirectUri) { + return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) + } + + if responseType != model.AUTHCODE_RESPONSE_TYPE { + return redirectUri + "?error=unsupported_response_type&state=" + state, nil + } + + authData := &model.AuthData{UserId: userId, ClientId: clientId, CreateAt: model.GetMillis(), RedirectUri: redirectUri, State: state, Scope: scope} + authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, userId)) + + // this saves the OAuth2 app as authorized + authorizedApp := model.Preference{ + UserId: userId, + Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, + Name: clientId, + Value: scope, + } + + if result := <-Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil { + return redirectUri + "?error=server_error&state=" + state, nil + } + + if result := <-Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil { + return redirectUri + "?error=server_error&state=" + state, nil + } + + return redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State), nil +} + +func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken string) (*model.AccessResponse, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + var oauthApp *model.OAuthApp + if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusNotFound) + } else { + oauthApp = result.Data.(*model.OAuthApp) + } + + if oauthApp.ClientSecret != secret { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusForbidden) + } + + var user *model.User + var accessData *model.AccessData + var accessRsp *model.AccessResponse + if grantType == model.ACCESS_TOKEN_GRANT_TYPE { + + var authData *model.AuthData + if result := <-Srv.Store.OAuth().GetAuthData(code); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusInternalServerError) + } else { + authData = result.Data.(*model.AuthData) + } + + if authData.IsExpired() { + <-Srv.Store.OAuth().RemoveAuthData(authData.Code) + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusForbidden) + } + + if authData.RedirectUri != redirectUri { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest) + } + + if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusBadRequest) + } + + if result := <-Srv.Store.User().Get(authData.UserId); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound) + } else { + user = result.Data.(*model.User) + } + if result := <-Srv.Store.OAuth().GetPreviousAccessData(user.Id, clientId); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal.app_error", nil, "", http.StatusInternalServerError) + } else if result.Data != nil { + accessData := result.Data.(*model.AccessData) + if accessData.IsExpired() { + if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { + return nil, err + } else { + accessRsp = access + } + } else { + //return the same token and no need to create a new session + accessRsp = &model.AccessResponse{ + AccessToken: accessData.Token, + TokenType: model.ACCESS_TOKEN_TYPE, + ExpiresIn: int32((accessData.ExpiresAt - model.GetMillis()) / 1000), + } + } + } else { + // create a new session and return new access token + var session *model.Session + if result, err := newSession(oauthApp.Name, user); err != nil { + return nil, err + } else { + session = result + } + + accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt, Scope: authData.Scope} + + if result := <-Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil { + l4g.Error(result.Err) + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) + } + + accessRsp = &model.AccessResponse{ + AccessToken: session.Token, + TokenType: model.ACCESS_TOKEN_TYPE, + RefreshToken: accessData.RefreshToken, + ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), + } + } + + <-Srv.Store.OAuth().RemoveAuthData(authData.Code) + } else { + // when grantType is refresh_token + if result := <-Srv.Store.OAuth().GetAccessDataByRefreshToken(refreshToken); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "", http.StatusNotFound) + } else { + accessData = result.Data.(*model.AccessData) + } + + if result := <-Srv.Store.User().Get(accessData.UserId); result.Err != nil { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound) + } else { + user = result.Data.(*model.User) + } + + if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { + return nil, err + } else { + accessRsp = access + } + } + + return accessRsp, nil +} + +func newSession(appName string, user *model.User) (*model.Session, *model.AppError) { + // set new token an session + session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true} + session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays) + session.AddProp(model.SESSION_PROP_PLATFORM, appName) + session.AddProp(model.SESSION_PROP_OS, "OAuth2") + session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2") + + if result := <-Srv.Store.Session().Save(session); result.Err != nil { + return nil, model.NewAppError("newSession", "api.oauth.get_access_token.internal_session.app_error", nil, "", http.StatusInternalServerError) + } else { + session = result.Data.(*model.Session) + AddSessionToCache(session) + } + + return session, nil +} + +func newSessionUpdateToken(appName string, accessData *model.AccessData, user *model.User) (*model.AccessResponse, *model.AppError) { + var session *model.Session + <-Srv.Store.Session().Remove(accessData.Token) //remove the previous session + + if result, err := newSession(appName, user); err != nil { + return nil, err + } else { + session = result + } + + accessData.Token = session.Token + accessData.ExpiresAt = session.ExpiresAt + if result := <-Srv.Store.OAuth().UpdateAccessData(accessData); result.Err != nil { + l4g.Error(result.Err) + return nil, model.NewAppError("newSessionUpdateToken", "web.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) + } + accessRsp := &model.AccessResponse{ + AccessToken: session.Token, + TokenType: model.ACCESS_TOKEN_TYPE, + ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), + } + + return accessRsp, nil +} + +func GetOAuthLoginEndpoint(service, teamId, redirectTo, loginHint string) (string, *model.AppError) { + stateProps := map[string]string{} + stateProps["action"] = model.OAUTH_ACTION_LOGIN + if len(teamId) != 0 { + stateProps["team_id"] = teamId + } + + if len(redirectTo) != 0 { + stateProps["redirect_to"] = redirectTo + } + + if authUrl, err := GetAuthorizationCode(service, stateProps, loginHint); err != nil { + return "", err + } else { + return authUrl, nil + } +} + +func GetOAuthSignupEndpoint(service, teamId string) (string, *model.AppError) { + stateProps := map[string]string{} + stateProps["action"] = model.OAUTH_ACTION_SIGNUP + if len(teamId) != 0 { + stateProps["team_id"] = teamId + } + + if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil { + return "", err + } else { + return authUrl, nil + } +} + +func GetAuthorizedAppsForUser(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("GetAuthorizedAppsForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + if result := <-Srv.Store.OAuth().GetAuthorizedApps(userId, page*perPage, perPage); result.Err != nil { + return nil, result.Err + } else { + apps := result.Data.([]*model.OAuthApp) + for k, a := range apps { + a.Sanitize() + apps[k] = a + } + + return apps, nil + } +} + +func DeauthorizeOAuthAppForUser(userId, appId string) *model.AppError { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return model.NewAppError("DeauthorizeOAuthAppForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + // revoke app sessions + if result := <-Srv.Store.OAuth().GetAccessDataByUserForApp(userId, appId); result.Err != nil { + return result.Err + } else { + accessData := result.Data.([]*model.AccessData) + + for _, a := range accessData { + if err := RevokeAccessToken(a.Token); err != nil { + return err + } + + if rad := <-Srv.Store.OAuth().RemoveAccessData(a.Token); rad.Err != nil { + return rad.Err + } + } + } + + // Deauthorize the app + if err := (<-Srv.Store.Preference().Delete(userId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, appId)).Err; err != nil { + return err + } + + return nil +} + +func RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) { + if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { + return nil, model.NewAppError("RegenerateOAuthAppSecret", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) + } + + app.ClientSecret = model.NewId() + if update := <-Srv.Store.OAuth().UpdateApp(app); update.Err != nil { + return nil, update.Err + } + + return app, nil +} + +func RevokeAccessToken(token string) *model.AppError { session, _ := GetSession(token) schan := Srv.Store.Session().Remove(token) @@ -32,3 +404,276 @@ func RevokeAccessToken(token string) *model.AppError { return nil } + +func CompleteOAuth(service string, body io.ReadCloser, teamId string, props map[string]string) (*model.User, *model.AppError) { + defer func() { + ioutil.ReadAll(body) + body.Close() + }() + + action := props["action"] + + switch action { + case model.OAUTH_ACTION_SIGNUP: + return CreateOAuthUser(service, body, teamId) + case model.OAUTH_ACTION_LOGIN: + return LoginByOAuth(service, body, teamId) + case model.OAUTH_ACTION_EMAIL_TO_SSO: + return CompleteSwitchWithOAuth(service, body, props["email"]) + case model.OAUTH_ACTION_SSO_TO_EMAIL: + return LoginByOAuth(service, body, teamId) + default: + return LoginByOAuth(service, body, teamId) + } +} + +func LoginByOAuth(service string, userData io.Reader, teamId string) (*model.User, *model.AppError) { + buf := bytes.Buffer{} + buf.ReadFrom(userData) + + authData := "" + provider := einterfaces.GetOauthProvider(service) + if provider == nil { + return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error", + map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented) + } else { + authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes())) + } + + if len(authData) == 0 { + return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", + map[string]interface{}{"Service": service}, "", http.StatusBadRequest) + } + + user, err := GetUserByAuth(&authData, service) + if err != nil { + if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { + return CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), teamId) + } + return nil, err + } + + if err = UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil { + return nil, err + } + + if len(teamId) > 0 { + err = AddUserToTeamByTeamId(teamId, user) + } + + if err != nil { + return nil, err + } + + return user, nil +} + +func CompleteSwitchWithOAuth(service string, userData io.ReadCloser, email string) (*model.User, *model.AppError) { + authData := "" + ssoEmail := "" + provider := einterfaces.GetOauthProvider(service) + if provider == nil { + return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", + map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented) + } else { + ssoUser := provider.GetUserFromJson(userData) + ssoEmail = ssoUser.Email + + if ssoUser.AuthData != nil { + authData = *ssoUser.AuthData + } + } + + if len(authData) == 0 { + return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error", + map[string]interface{}{"Service": service}, "", http.StatusBadRequest) + } + + if len(email) == 0 { + return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "", http.StatusBadRequest) + } + + var user *model.User + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { + return nil, result.Err + } else { + user = result.Data.(*model.User) + } + + if err := RevokeAllSessions(user.Id); err != nil { + return nil, err + } + + if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil { + return nil, result.Err + } + + go func() { + if err := SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, utils.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() + + return user, nil +} + +func GetAuthorizationCode(service string, props map[string]string, loginHint string) (string, *model.AppError) { + sso := utils.Cfg.GetSSOService(service) + if sso != nil && !sso.Enable { + return "", model.NewAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented) + } + + clientId := sso.Id + endpoint := sso.AuthEndpoint + scope := sso.Scope + + props["hash"] = model.HashPassword(clientId) + state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) + + redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete" + + authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state) + + if len(scope) > 0 { + authUrl += "&scope=" + utils.UrlEncode(scope) + } + + if len(loginHint) > 0 { + authUrl += "&login_hint=" + utils.UrlEncode(loginHint) + } + + return authUrl, nil +} + +func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) { + sso := utils.Cfg.GetSSOService(service) + if sso == nil || !sso.Enable { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service) + } + + stateStr := "" + if b, err := b64.StdEncoding.DecodeString(state); err != nil { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error()) + } else { + stateStr = string(b) + } + + stateProps := model.MapFromJson(strings.NewReader(stateStr)) + + if !model.ComparePassword(stateProps["hash"], sso.Id) { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "") + } + + teamId := stateProps["team_id"] + + p := url.Values{} + p.Set("client_id", sso.Id) + p.Set("client_secret", sso.Secret) + p.Set("code", code) + p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + p.Set("redirect_uri", redirectUri) + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + client := &http.Client{Transport: tr} + req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode())) + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + + var ar *model.AccessResponse + var respBody []byte + if resp, err := client.Do(req); err != nil { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error()) + } else { + ar = model.AccessResponseFromJson(resp.Body) + defer func() { + ioutil.ReadAll(resp.Body) + resp.Body.Close() + }() + if ar == nil { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, "") + } + } + + if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+string(respBody)) + } + + if len(ar.AccessToken) == 0 { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "") + } + + p = url.Values{} + p.Set("access_token", ar.AccessToken) + req, _ = http.NewRequest("GET", sso.UserApiEndpoint, strings.NewReader("")) + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", "Bearer "+ar.AccessToken) + + if resp, err := client.Do(req); err != nil { + return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", + map[string]interface{}{"Service": service}, err.Error()) + } else { + return resp.Body, teamId, stateProps, nil + } + +} + +func SwitchEmailToOAuth(email, password, code, service string) (string, *model.AppError) { + var user *model.User + var err *model.AppError + if user, err = GetUserByEmail(email); err != nil { + return "", err + } + + if err := CheckPasswordAndAllCriteria(user, password, code); err != nil { + return "", err + } + + stateProps := map[string]string{} + stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO + stateProps["email"] = email + + if service == model.USER_AUTH_SERVICE_SAML { + return utils.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email, nil + } else { + if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil { + return "", err + } else { + return authUrl, nil + } + } +} + +func SwitchOAuthToEmail(email, password, requesterId string) (string, *model.AppError) { + var user *model.User + var err *model.AppError + if user, err = GetUserByEmail(email); err != nil { + return "", err + } + + if user.Id != requesterId { + return "", model.NewAppError("SwitchOAuthToEmail", "api.user.oauth_to_email.context.app_error", nil, "", http.StatusForbidden) + } + + if err := UpdatePassword(user, password); err != nil { + return "", err + } + + T := utils.GetUserTranslations(user.Locale) + + go func() { + if err := SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() + + if err := RevokeAllSessions(requesterId); err != nil { + return "", err + } + + return "/login?extra=signin_change", nil +} diff --git a/app/oauth_test.go b/app/oauth_test.go index 3ca3a2d4a..9e8fdfc7d 100644 --- a/app/oauth_test.go +++ b/app/oauth_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/post.go b/app/post.go index 7f38a9bd2..bf61bafb2 100644 --- a/app/post.go +++ b/app/post.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -402,6 +402,14 @@ func GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*mode } } +func GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { + if result := <-Srv.Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.PostList), nil + } +} + func GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { if result := <-Srv.Store.Post().Get(postId); result.Err != nil { return nil, result.Err @@ -559,13 +567,13 @@ func GetOpenGraphMetadata(url string) *opengraph.OpenGraph { res, err := httpClient.Get(url) if err != nil { - l4g.Error(err.Error()) + l4g.Error("GetOpenGraphMetadata request failed for url=%v with err=%v", url, err.Error()) return og } defer CloseBody(res) if err := og.ProcessHTML(res.Body); err != nil { - l4g.Error(err.Error()) + l4g.Error("GetOpenGraphMetadata processing failed for url=%v with err=%v", url, err.Error()) } return og diff --git a/app/preference.go b/app/preference.go index ff251fb16..793f1802b 100644 --- a/app/preference.go +++ b/app/preference.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/reaction.go b/app/reaction.go new file mode 100644 index 000000000..cc31018ec --- /dev/null +++ b/app/reaction.go @@ -0,0 +1,16 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" +) + +func GetReactionsForPost(postId string) ([]*model.Reaction, *model.AppError) { + if result := <-Srv.Store.Reaction().GetForPost(postId, true); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Reaction), nil + } +} diff --git a/app/saml.go b/app/saml.go index e2bf4ccb2..8a6e6f16c 100644 --- a/app/saml.go +++ b/app/saml.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/security_update_check.go b/app/security_update_check.go index 2b000f2b2..12014bdf3 100644 --- a/app/security_update_check.go +++ b/app/security_update_check.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/server.go b/app/server.go index 972c91ea3..a757e184e 100644 --- a/app/server.go +++ b/app/server.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/session.go b/app/session.go index 9e4fda4bd..0df643743 100644 --- a/app/session.go +++ b/app/session.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/session_test.go b/app/session_test.go index aea31cf86..b3cd9fd57 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/slackimport.go b/app/slackimport.go index 3e226203b..71f16c874 100644 --- a/app/slackimport.go +++ b/app/slackimport.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/slackimport_test.go b/app/slackimport_test.go index 6db6adf2d..87de597ca 100644 --- a/app/slackimport_test.go +++ b/app/slackimport_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/status.go b/app/status.go index f6565b7d2..a3b921700 100644 --- a/app/status.go +++ b/app/status.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/team.go b/app/team.go index 12c970665..d4e6d6308 100644 --- a/app/team.go +++ b/app/team.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -6,6 +6,7 @@ package app import ( "fmt" "net/http" + "net/url" "strconv" "strings" @@ -747,3 +748,33 @@ func GetTeamStats(teamId string) (*model.TeamStats, *model.AppError) { return stats, nil } + +func GetTeamIdFromQuery(query url.Values) (string, *model.AppError) { + hash := query.Get("h") + inviteId := query.Get("id") + + if len(hash) > 0 { + data := query.Get("d") + props := model.MapFromJson(strings.NewReader(data)) + + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { + return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest) + } + + t, err := strconv.ParseInt(props["time"], 10, 64) + if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours + return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "", http.StatusBadRequest) + } + + return props["id"], nil + } else if len(inviteId) > 0 { + if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { + // soft fail, so we still create user but don't auto-join team + l4g.Error("%v", result.Err) + } else { + return result.Data.(*model.Team).Id, nil + } + } + + return "", nil +} diff --git a/app/team_test.go b/app/team_test.go index 86a383a39..f2356d562 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/user.go b/app/user.go index 99e9d46a3..e339dfd5b 100644 --- a/app/user.go +++ b/app/user.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app @@ -186,7 +186,9 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) { } } - user.Locale = *utils.Cfg.LocalizationSettings.DefaultClientLocale + if _, ok := utils.GetSupportedLocales()[user.Locale]; !ok { + user.Locale = *utils.Cfg.LocalizationSettings.DefaultClientLocale + } if ruser, err := createUser(user); err != nil { return nil, err diff --git a/app/user_test.go b/app/user_test.go index ff9e4d500..4cd26e729 100644 --- a/app/user_test.go +++ b/app/user_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/web_conn.go b/app/web_conn.go index 8d604ff3e..000704791 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/web_hub.go b/app/web_hub.go index f65683f70..da2a41ec4 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/webhook.go b/app/webhook.go index 0ec06365d..e095c1b80 100644 --- a/app/webhook.go +++ b/app/webhook.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/webrtc.go b/app/webrtc.go new file mode 100644 index 000000000..6692fff60 --- /dev/null +++ b/app/webrtc.go @@ -0,0 +1,87 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/tls" + "encoding/base64" + "net/http" + "strconv" + "strings" + "time" + + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +func GetWebrtcInfoForSession(sessionId string) (*model.WebrtcInfoResponse, *model.AppError) { + token, err := GetWebrtcToken(sessionId) + if err != nil { + return nil, err + } + + result := &model.WebrtcInfoResponse{ + Token: token, + GatewayUrl: *utils.Cfg.WebrtcSettings.GatewayWebsocketUrl, + } + + if len(*utils.Cfg.WebrtcSettings.StunURI) > 0 { + result.StunUri = *utils.Cfg.WebrtcSettings.StunURI + } + + if len(*utils.Cfg.WebrtcSettings.TurnURI) > 0 { + timestamp := strconv.FormatInt(utils.EndOfDay(time.Now().AddDate(0, 0, 1)).Unix(), 10) + username := timestamp + ":" + *utils.Cfg.WebrtcSettings.TurnUsername + + result.TurnUri = *utils.Cfg.WebrtcSettings.TurnURI + result.TurnPassword = GenerateTurnPassword(username, *utils.Cfg.WebrtcSettings.TurnSharedKey) + result.TurnUsername = username + } + + return result, nil +} + +func GetWebrtcToken(sessionId string) (string, *model.AppError) { + if !*utils.Cfg.WebrtcSettings.Enable { + return "", model.NewAppError("WebRTC.getWebrtcToken", "api.webrtc.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + token := base64.StdEncoding.EncodeToString([]byte(sessionId)) + + data := make(map[string]string) + data["janus"] = "add_token" + data["token"] = token + data["transaction"] = model.NewId() + data["admin_secret"] = *utils.Cfg.WebrtcSettings.GatewayAdminSecret + + rq, _ := http.NewRequest("POST", *utils.Cfg.WebrtcSettings.GatewayAdminUrl, strings.NewReader(model.MapToJson(data))) + rq.Header.Set("Content-Type", "application/json") + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + httpClient := &http.Client{Transport: tr} + if rp, err := httpClient.Do(rq); err != nil { + return "", model.NewAppError("WebRTC.Token", "model.client.connecting.app_error", nil, err.Error(), http.StatusInternalServerError) + } else if rp.StatusCode >= 300 { + defer CloseBody(rp) + return "", model.AppErrorFromJson(rp.Body) + } else { + janusResponse := model.GatewayResponseFromJson(rp.Body) + if janusResponse.Status != "success" { + return "", model.NewAppError("getWebrtcToken", "api.webrtc.register_token.app_error", nil, "", http.StatusInternalServerError) + } + } + + return token, nil +} + +func GenerateTurnPassword(username string, secret string) string { + key := []byte(secret) + h := hmac.New(sha1.New, key) + h.Write([]byte(username)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/app/websocket_router.go b/app/websocket_router.go index 4569134b0..84806b5cf 100644 --- a/app/websocket_router.go +++ b/app/websocket_router.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app diff --git a/app/webtrc.go b/app/webtrc.go index b526c96a6..a2ead21ab 100644 --- a/app/webtrc.go +++ b/app/webtrc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package app |