diff options
author | Christopher Speller <crspeller@gmail.com> | 2017-07-18 15:45:23 -0700 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2017-07-18 15:45:23 -0700 |
commit | 97f34e483b0fa8b2a8cfe75b72168cfa38cc9d80 (patch) | |
tree | ec2d68077dd2b12de3173871622f3ec2a2b61d35 /app | |
parent | 21a3219b9b1df033635631afa751742bd4c56ea0 (diff) | |
parent | a350f4dc0754e1aeabb64bd712ce05f7c59cfa60 (diff) | |
download | chat-97f34e483b0fa8b2a8cfe75b72168cfa38cc9d80.tar.gz chat-97f34e483b0fa8b2a8cfe75b72168cfa38cc9d80.tar.bz2 chat-97f34e483b0fa8b2a8cfe75b72168cfa38cc9d80.zip |
Merge branch 'release-4.0'
Diffstat (limited to 'app')
-rw-r--r-- | app/oauth.go | 113 | ||||
-rw-r--r-- | app/oauth_test.go | 52 | ||||
-rw-r--r-- | app/post.go | 16 | ||||
-rw-r--r-- | app/saml.go | 6 |
4 files changed, 162 insertions, 25 deletions
diff --git a/app/oauth.go b/app/oauth.go index a2edae8be..e2d389569 100644 --- a/app/oauth.go +++ b/app/oauth.go @@ -12,6 +12,7 @@ import ( "net/http" "net/url" "strings" + "time" l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/einterfaces" @@ -20,6 +21,11 @@ import ( "github.com/mattermost/platform/utils" ) +const ( + OAUTH_COOKIE_MAX_AGE_SECONDS = 30 * 60 // 30 minutes + COOKIE_OAUTH = "MMOAUTH" +) + 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) @@ -56,6 +62,8 @@ func DeleteOAuthApp(appId string) *model.AppError { return err } + InvalidateAllCaches() + return nil } @@ -289,7 +297,7 @@ func newSessionUpdateToken(appName string, accessData *model.AccessData, user *m return accessRsp, nil } -func GetOAuthLoginEndpoint(service, teamId, action, redirectTo, loginHint string) (string, *model.AppError) { +func GetOAuthLoginEndpoint(w http.ResponseWriter, r *http.Request, service, teamId, action, redirectTo, loginHint string) (string, *model.AppError) { stateProps := map[string]string{} stateProps["action"] = action if len(teamId) != 0 { @@ -300,21 +308,21 @@ func GetOAuthLoginEndpoint(service, teamId, action, redirectTo, loginHint string stateProps["redirect_to"] = redirectTo } - if authUrl, err := GetAuthorizationCode(service, stateProps, loginHint); err != nil { + if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, loginHint); err != nil { return "", err } else { return authUrl, nil } } -func GetOAuthSignupEndpoint(service, teamId string) (string, *model.AppError) { +func GetOAuthSignupEndpoint(w http.ResponseWriter, r *http.Request, 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 { + if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil { return "", err } else { return authUrl, nil @@ -519,17 +527,69 @@ func CompleteSwitchWithOAuth(service string, userData io.ReadCloser, email strin return user, nil } -func GetAuthorizationCode(service string, props map[string]string, loginHint string) (string, *model.AppError) { +func CreateOAuthStateToken(extra string) (*model.Token, *model.AppError) { + token := model.NewToken(model.TOKEN_TYPE_OAUTH, extra) + + if result := <-Srv.Store.Token().Save(token); result.Err != nil { + return nil, result.Err + } + + return token, nil +} + +func GetOAuthStateToken(token string) (*model.Token, *model.AppError) { + if result := <-Srv.Store.Token().GetByToken(token); result.Err != nil { + return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, result.Err.Error(), http.StatusBadRequest) + } else { + token := result.Data.(*model.Token) + if token.Type != model.TOKEN_TYPE_OAUTH { + return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, "", http.StatusBadRequest) + } + + return token, nil + } +} + +func generateOAuthStateTokenExtra(email, action, cookie string) string { + return email + ":" + action + ":" + cookie +} + +func GetAuthorizationCode(w http.ResponseWriter, r *http.Request, 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) } + secure := false + if GetProtocol(r) == "https" { + secure = true + } + + cookieValue := model.NewId() + expiresAt := time.Unix(model.GetMillis()/1000+int64(OAUTH_COOKIE_MAX_AGE_SECONDS), 0) + oauthCookie := &http.Cookie{ + Name: COOKIE_OAUTH, + Value: cookieValue, + Path: "/", + MaxAge: OAUTH_COOKIE_MAX_AGE_SECONDS, + Expires: expiresAt, + HttpOnly: true, + Secure: secure, + } + + http.SetCookie(w, oauthCookie) + clientId := sso.Id endpoint := sso.AuthEndpoint scope := sso.Scope - props["hash"] = utils.HashSha256(clientId) + tokenExtra := generateOAuthStateTokenExtra(props["email"], props["action"], cookieValue) + stateToken, err := CreateOAuthStateToken(tokenExtra) + if err != nil { + return "", err + } + + props["token"] = stateToken.Token state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete" @@ -547,7 +607,7 @@ func GetAuthorizationCode(service string, props map[string]string, loginHint str return authUrl, nil } -func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) { +func AuthorizeOAuthUser(w http.ResponseWriter, r *http.Request, 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) @@ -562,10 +622,41 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser stateProps := model.MapFromJson(strings.NewReader(stateStr)) - if stateProps["hash"] != utils.HashSha256(sso.Id) { - return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "") + expectedToken, err := GetOAuthStateToken(stateProps["token"]) + if err != nil { + return nil, "", nil, err + } + + stateEmail := stateProps["email"] + stateAction := stateProps["action"] + if stateAction == model.OAUTH_ACTION_EMAIL_TO_SSO && stateEmail == "" { + return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) } + cookieValue := "" + if cookie, err := r.Cookie(COOKIE_OAUTH); err != nil { + return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) + } else { + cookieValue = cookie.Value + } + + expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookieValue) + if expectedTokenExtra != expectedToken.Extra { + return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) + } + + DeleteToken(expectedToken) + + cookie := &http.Cookie{ + Name: COOKIE_OAUTH, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + } + + http.SetCookie(w, cookie) + teamId := stateProps["team_id"] p := url.Values{} @@ -617,7 +708,7 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser } -func SwitchEmailToOAuth(email, password, code, service string) (string, *model.AppError) { +func SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) { var user *model.User var err *model.AppError if user, err = GetUserByEmail(email); err != nil { @@ -635,7 +726,7 @@ func SwitchEmailToOAuth(email, password, code, service string) (string, *model.A 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 { + if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil { return "", err } else { return authUrl, nil diff --git a/app/oauth_test.go b/app/oauth_test.go index 9e8fdfc7d..185f5d73f 100644 --- a/app/oauth_test.go +++ b/app/oauth_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" ) func TestOAuthRevokeAccessToken(t *testing.T) { @@ -42,3 +43,54 @@ func TestOAuthRevokeAccessToken(t *testing.T) { t.Fatal(err) } } + +func TestOAuthDeleteApp(t *testing.T) { + Setup() + + oldSetting := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider + defer func() { + utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = oldSetting + }() + utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true + + a1 := &model.OAuthApp{} + a1.CreatorId = model.NewId() + a1.Name = "TestApp" + model.NewId() + a1.CallbackUrls = []string{"https://nowhere.com"} + a1.Homepage = "https://nowhere.com" + + var err *model.AppError + a1, err = CreateOAuthApp(a1) + if err != nil { + t.Fatal(err) + } + + session := &model.Session{} + session.CreateAt = model.GetMillis() + session.UserId = model.NewId() + session.Token = model.NewId() + session.Roles = model.ROLE_SYSTEM_USER.Id + session.IsOAuth = true + session.SetExpireInDays(1) + + session, _ = CreateSession(session) + + accessData := &model.AccessData{} + accessData.Token = session.Token + accessData.UserId = session.UserId + accessData.RedirectUri = "http://example.com" + accessData.ClientId = a1.Id + accessData.ExpiresAt = session.ExpiresAt + + if result := <-Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil { + t.Fatal(result.Err) + } + + if err := DeleteOAuthApp(a1.Id); err != nil { + t.Fatal(err) + } + + if _, err := GetSession(session.Token); err == nil { + t.Fatal("should not get session from cache or db") + } +} diff --git a/app/post.go b/app/post.go index d9e08c864..7ad072943 100644 --- a/app/post.go +++ b/app/post.go @@ -227,19 +227,19 @@ func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post { } func UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { - if utils.IsLicensed { - if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER { - err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden) - return nil, err - } - } - var oldPost *model.Post if result := <-Srv.Store.Post().Get(post.Id); result.Err != nil { return nil, result.Err } else { oldPost = result.Data.(*model.PostList).Posts[post.Id] + if utils.IsLicensed { + if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message { + err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden) + return nil, err + } + } + if oldPost == nil { err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) return nil, err @@ -256,7 +256,7 @@ func UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError } if utils.IsLicensed { - if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) { + if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) return nil, err } diff --git a/app/saml.go b/app/saml.go index 730e29efc..e5d1e8b3e 100644 --- a/app/saml.go +++ b/app/saml.go @@ -68,7 +68,6 @@ func AddSamlPublicCertificate(fileData *multipart.FileHeader) *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } @@ -88,7 +87,6 @@ func AddSamlPrivateCertificate(fileData *multipart.FileHeader) *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } @@ -108,7 +106,6 @@ func AddSamlIdpCertificate(fileData *multipart.FileHeader) *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } @@ -144,7 +141,6 @@ func RemoveSamlPublicCertificate() *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } @@ -165,7 +161,6 @@ func RemoveSamlPrivateCertificate() *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } @@ -186,7 +181,6 @@ func RemoveSamlIdpCertificate() *model.AppError { } utils.SaveConfig(utils.CfgFileName, cfg) - utils.LoadConfig(utils.CfgFileName) return nil } |