diff options
-rw-r--r-- | api/user.go | 131 | ||||
-rw-r--r-- | api4/api.go | 6 | ||||
-rw-r--r-- | api4/apitestlib.go | 2 | ||||
-rw-r--r-- | web/saml.go | 149 | ||||
-rw-r--r-- | web/web.go | 7 |
5 files changed, 159 insertions, 136 deletions
diff --git a/api/user.go b/api/user.go index 5931eac1e..7592d1119 100644 --- a/api/user.go +++ b/api/user.go @@ -4,11 +4,9 @@ package api import ( - b64 "encoding/base64" "fmt" "net/http" "strconv" - "strings" "time" "github.com/gorilla/mux" @@ -62,9 +60,6 @@ func (api *API) InitUser() { api.BaseRoutes.NeedUser.Handle("/audits", api.ApiUserRequired(getAudits)).Methods("GET") api.BaseRoutes.NeedUser.Handle("/image", api.ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET") api.BaseRoutes.NeedUser.Handle("/update_roles", api.ApiUserRequired(updateRoles)).Methods("POST") - - api.BaseRoutes.Root.Handle("/login/sso/saml", api.AppHandlerIndependent(loginWithSaml)).Methods("GET") - api.BaseRoutes.Root.Handle("/login/sso/saml", api.AppHandlerIndependent(completeSaml)).Methods("POST") } func createUser(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1080,132 +1075,6 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(rdata))) } -func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) { - samlInterface := c.App.Saml - - if samlInterface == nil { - c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound) - return - } - - teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) - if err != nil { - c.Err = err - return - } - action := r.URL.Query().Get("action") - redirectTo := r.URL.Query().Get("redirect_to") - relayProps := map[string]string{} - relayState := "" - - if len(action) != 0 { - relayProps["team_id"] = teamId - relayProps["action"] = action - if action == model.OAUTH_ACTION_EMAIL_TO_SSO { - relayProps["email"] = r.URL.Query().Get("email") - } - } - - if len(redirectTo) != 0 { - relayProps["redirect_to"] = redirectTo - } - - if len(relayProps) > 0 { - relayState = b64.StdEncoding.EncodeToString([]byte(model.MapToJson(relayProps))) - } - - if data, err := samlInterface.BuildRequest(relayState); err != nil { - c.Err = err - return - } else { - w.Header().Set("Content-Type", "application/x-www-form-urlencoded") - http.Redirect(w, r, data.URL, http.StatusFound) - } -} - -func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { - samlInterface := c.App.Saml - - if samlInterface == nil { - c.Err = model.NewAppError("completeSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound) - return - } - - //Validate that the user is with SAML and all that - encodedXML := r.FormValue("SAMLResponse") - relayState := r.FormValue("RelayState") - - relayProps := make(map[string]string) - if len(relayState) > 0 { - stateStr := "" - if b, err := b64.StdEncoding.DecodeString(relayState); err != nil { - c.Err = model.NewAppError("completeSaml", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error(), http.StatusFound) - return - } else { - stateStr = string(b) - } - relayProps = model.MapFromJson(strings.NewReader(stateStr)) - } - - action := relayProps["action"] - if user, err := samlInterface.DoLogin(encodedXML, relayProps); err != nil { - if action == model.OAUTH_ACTION_MOBILE { - err.Translate(c.T) - w.Write([]byte(err.ToJson())) - } else { - c.Err = err - c.Err.StatusCode = http.StatusFound - } - return - } else { - if err := c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil { - c.Err = err - c.Err.StatusCode = http.StatusFound - return - } - - switch action { - case model.OAUTH_ACTION_SIGNUP: - teamId := relayProps["team_id"] - if len(teamId) > 0 { - c.App.Go(func() { - if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil { - mlog.Error(err.Error()) - } else { - c.App.AddDirectChannels(teamId, user) - } - }) - } - case model.OAUTH_ACTION_EMAIL_TO_SSO: - if err := c.App.RevokeAllSessions(user.Id); err != nil { - c.Err = err - return - } - c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") - c.App.Go(func() { - if err := c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil { - mlog.Error(err.Error()) - } - }) - } - doLogin(c, w, r, user, "") - if c.Err != nil { - return - } - - if val, ok := relayProps["redirect_to"]; ok { - http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound) - return - } - - if action == model.OAUTH_ACTION_MOBILE { - ReturnStatusOK(w) - } else { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusFound) - } - } -} - func sanitizeProfile(c *Context, user *model.User) *model.User { options := c.App.Config().GetSanitizeOptions() diff --git a/api4/api.go b/api4/api.go index acce923c0..9172391dd 100644 --- a/api4/api.go +++ b/api4/api.go @@ -243,8 +243,4 @@ func (api *API) Handle404(w http.ResponseWriter, r *http.Request) { web.Handle404(api.App, w, r) } -func ReturnStatusOK(w http.ResponseWriter) { - m := make(map[string]string) - m[model.STATUS] = model.STATUS_OK - w.Write([]byte(model.MapToJson(m))) -} +var ReturnStatusOK = web.ReturnStatusOK diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 48765687a..0ce334154 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -26,6 +26,7 @@ import ( "github.com/mattermost/mattermost-server/store/sqlstore" "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" + "github.com/mattermost/mattermost-server/web" "github.com/mattermost/mattermost-server/wsapi" s3 "github.com/minio/minio-go" @@ -120,6 +121,7 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) Init(th.App, th.App.Srv.Router, true) + web.NewWeb(th.App, th.App.Srv.Router) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() th.App.DoAdvancedPermissionsMigration() diff --git a/web/saml.go b/web/saml.go new file mode 100644 index 000000000..f3e5a12e8 --- /dev/null +++ b/web/saml.go @@ -0,0 +1,149 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package web + +import ( + b64 "encoding/base64" + "net/http" + "strings" + + "github.com/mattermost/mattermost-server/app" + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" +) + +func (w *Web) InitSaml() { + w.MainRouter.Handle("/login/sso/saml", w.NewHandler(loginWithSaml)).Methods("GET") + w.MainRouter.Handle("/login/sso/saml", w.NewHandler(completeSaml)).Methods("POST") +} + +func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) { + samlInterface := c.App.Saml + + if samlInterface == nil { + c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound) + return + } + + teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) + if err != nil { + c.Err = err + return + } + action := r.URL.Query().Get("action") + redirectTo := r.URL.Query().Get("redirect_to") + relayProps := map[string]string{} + relayState := "" + + if len(action) != 0 { + relayProps["team_id"] = teamId + relayProps["action"] = action + if action == model.OAUTH_ACTION_EMAIL_TO_SSO { + relayProps["email"] = r.URL.Query().Get("email") + } + } + + if len(redirectTo) != 0 { + relayProps["redirect_to"] = redirectTo + } + + if len(relayProps) > 0 { + relayState = b64.StdEncoding.EncodeToString([]byte(model.MapToJson(relayProps))) + } + + if data, err := samlInterface.BuildRequest(relayState); err != nil { + c.Err = err + return + } else { + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + http.Redirect(w, r, data.URL, http.StatusFound) + } +} + +func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { + samlInterface := c.App.Saml + + if samlInterface == nil { + c.Err = model.NewAppError("completeSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound) + return + } + + //Validate that the user is with SAML and all that + encodedXML := r.FormValue("SAMLResponse") + relayState := r.FormValue("RelayState") + + relayProps := make(map[string]string) + if len(relayState) > 0 { + stateStr := "" + if b, err := b64.StdEncoding.DecodeString(relayState); err != nil { + c.Err = model.NewAppError("completeSaml", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error(), http.StatusFound) + return + } else { + stateStr = string(b) + } + relayProps = model.MapFromJson(strings.NewReader(stateStr)) + } + + action := relayProps["action"] + if user, err := samlInterface.DoLogin(encodedXML, relayProps); err != nil { + if action == model.OAUTH_ACTION_MOBILE { + err.Translate(c.T) + w.Write([]byte(err.ToJson())) + } else { + c.Err = err + c.Err.StatusCode = http.StatusFound + } + return + } else { + if err := c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil { + c.Err = err + c.Err.StatusCode = http.StatusFound + return + } + + switch action { + case model.OAUTH_ACTION_SIGNUP: + teamId := relayProps["team_id"] + if len(teamId) > 0 { + c.App.Go(func() { + if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil { + mlog.Error(err.Error()) + } else { + c.App.AddDirectChannels(teamId, user) + } + }) + } + case model.OAUTH_ACTION_EMAIL_TO_SSO: + if err := c.App.RevokeAllSessions(user.Id); err != nil { + c.Err = err + return + } + c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") + c.App.Go(func() { + if err := c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil { + mlog.Error(err.Error()) + } + }) + } + + session, err := c.App.DoLogin(w, r, user, "") + if err != nil { + c.Err = err + return + } + + c.Session = *session + + if val, ok := relayProps["redirect_to"]; ok { + http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound) + return + } + + if action == model.OAUTH_ACTION_MOBILE { + ReturnStatusOK(w) + } else { + http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusFound) + } + } +} diff --git a/web/web.go b/web/web.go index 94363cfde..53276953e 100644 --- a/web/web.go +++ b/web/web.go @@ -32,6 +32,7 @@ func NewWeb(a *app.App, root *mux.Router) *Web { web.InitStatic() web.InitWebhooks() + web.InitSaml() return web } @@ -71,3 +72,9 @@ func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { func IsApiCall(r *http.Request) bool { return strings.Index(r.URL.Path, "/api/") == 0 } + +func ReturnStatusOK(w http.ResponseWriter) { + m := make(map[string]string) + m[model.STATUS] = model.STATUS_OK + w.Write([]byte(model.MapToJson(m))) +} |