[MM-19473] Fix data race on user login (#12870)
* Avoid writes to App.Session outside the app layer * Fix merge * Remove unneeded else condition
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6f6eb13a47
Коммит
422f377c96
@@ -1399,7 +1399,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
c.LogAuditWithUserId(user.Id, "authenticated")
|
c.LogAuditWithUserId(user.Id, "authenticated")
|
||||||
|
|
||||||
session, err := c.App.DoLogin(w, r, user, deviceId)
|
err = c.App.DoLogin(w, r, user, deviceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
@@ -1408,7 +1408,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.LogAuditWithUserId(user.Id, "success")
|
c.LogAuditWithUserId(user.Id, "success")
|
||||||
|
|
||||||
if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
c.App.AttachSessionCookies(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
userTermsOfService, err := c.App.GetUserTermsOfService(user.Id)
|
userTermsOfService, err := c.App.GetUserTermsOfService(user.Id)
|
||||||
|
|||||||
19
app/login.go
19
app/login.go
@@ -110,7 +110,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError)
|
|||||||
return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
|
return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) {
|
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) *model.AppError {
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
var rejectionReason string
|
var rejectionReason string
|
||||||
pluginContext := a.PluginContext()
|
pluginContext := a.PluginContext()
|
||||||
@@ -120,7 +120,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
}, plugin.UserWillLogInId)
|
}, plugin.UserWillLogInId)
|
||||||
|
|
||||||
if rejectionReason != "" {
|
if rejectionReason != "" {
|
||||||
return nil, model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
|
return model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
// A special case where we logout of all other sessions with the same Id
|
// A special case where we logout of all other sessions with the same Id
|
||||||
if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
||||||
err.StatusCode = http.StatusInternalServerError
|
err.StatusCode = http.StatusInternalServerError
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
|
session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
|
||||||
@@ -158,10 +158,11 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
var err *model.AppError
|
var err *model.AppError
|
||||||
if session, err = a.CreateSession(session); err != nil {
|
if session, err = a.CreateSession(session); err != nil {
|
||||||
err.StatusCode = http.StatusInternalServerError
|
err.StatusCode = http.StatusInternalServerError
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
|
|
||||||
a.Session = *session
|
a.Session = *session
|
||||||
|
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
@@ -174,10 +175,10 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, session *model.Session) {
|
func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request) {
|
||||||
secure := false
|
secure := false
|
||||||
if GetProtocol(r) == "https" {
|
if GetProtocol(r) == "https" {
|
||||||
secure = true
|
secure = true
|
||||||
@@ -190,7 +191,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
||||||
sessionCookie := &http.Cookie{
|
sessionCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: session.Token,
|
Value: a.Session.Token,
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
@@ -201,7 +202,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
|
|
||||||
userCookie := &http.Cookie{
|
userCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_USER,
|
Name: model.SESSION_COOKIE_USER,
|
||||||
Value: session.UserId,
|
Value: a.Session.UserId,
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
@@ -211,7 +212,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
|
|
||||||
csrfCookie := &http.Cookie{
|
csrfCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_CSRF,
|
Name: model.SESSION_COOKIE_CSRF,
|
||||||
Value: session.GetCSRF(),
|
Value: a.Session.GetCSRF(),
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
|
|||||||
@@ -694,7 +694,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
||||||
}
|
}
|
||||||
@@ -733,10 +733,10 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
session, err := th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||||
assert.Equal(t, session.UserId, th.BasicUser.Id)
|
assert.Equal(t, th.App.Session.UserId, th.BasicUser.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserHasLoggedIn(t *testing.T) {
|
func TestUserHasLoggedIn(t *testing.T) {
|
||||||
@@ -774,7 +774,7 @@ func TestUserHasLoggedIn(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -52,6 +52,8 @@ require (
|
|||||||
github.com/miekg/dns v1.1.19 // indirect
|
github.com/miekg/dns v1.1.19 // indirect
|
||||||
github.com/minio/minio-go/v6 v6.0.38
|
github.com/minio/minio-go/v6 v6.0.38
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/muesli/smartcrop v0.3.0 // indirect
|
github.com/muesli/smartcrop v0.3.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.1 // indirect
|
github.com/olekukonko/tablewriter v0.0.1 // indirect
|
||||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
||||||
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||||
} else {
|
} else {
|
||||||
session, err := c.App.DoLogin(w, r, user, "")
|
err = c.App.DoLogin(w, r, user, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err.Translate(c.App.T)
|
err.Translate(c.App.T)
|
||||||
c.Err = err
|
c.Err = err
|
||||||
@@ -293,9 +293,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
c.App.AttachSessionCookies(w, r)
|
||||||
|
|
||||||
c.App.Session = *session
|
|
||||||
|
|
||||||
if _, ok := props["redirect_to"]; ok {
|
if _, ok := props["redirect_to"]; ok {
|
||||||
redirectUrl = props["redirect_to"]
|
redirectUrl = props["redirect_to"]
|
||||||
|
|||||||
91
web/saml.go
91
web/saml.go
@@ -87,7 +87,8 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.LogAudit("attempt")
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
action := relayProps["action"]
|
action := relayProps["action"]
|
||||||
if user, err := samlInterface.DoLogin(encodedXML, relayProps); err != nil {
|
user, err := samlInterface.DoLogin(encodedXML, relayProps)
|
||||||
|
if err != nil {
|
||||||
c.LogAudit("fail")
|
c.LogAudit("fail")
|
||||||
|
|
||||||
if action == model.OAUTH_ACTION_MOBILE {
|
if action == model.OAUTH_ACTION_MOBILE {
|
||||||
@@ -98,64 +99,62 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.Err.StatusCode = http.StatusFound
|
c.Err.StatusCode = http.StatusFound
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
if err := c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
|
||||||
c.Err = err
|
|
||||||
c.Err.StatusCode = http.StatusFound
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
if err = c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
||||||
case model.OAUTH_ACTION_SIGNUP:
|
c.Err = err
|
||||||
teamId := relayProps["team_id"]
|
c.Err.StatusCode = http.StatusFound
|
||||||
if len(teamId) > 0 {
|
return
|
||||||
c.App.Srv.Go(func() {
|
}
|
||||||
if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil {
|
|
||||||
mlog.Error(err.Error())
|
switch action {
|
||||||
} else {
|
case model.OAUTH_ACTION_SIGNUP:
|
||||||
c.App.AddDirectChannels(teamId, user)
|
teamId := relayProps["team_id"]
|
||||||
}
|
if len(teamId) > 0 {
|
||||||
})
|
|
||||||
}
|
|
||||||
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.Srv.Go(func() {
|
c.App.Srv.Go(func() {
|
||||||
if err := c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil {
|
if err = c.App.AddUserToTeamByTeamId(teamId, user); err != nil {
|
||||||
mlog.Error(err.Error())
|
mlog.Error(err.Error())
|
||||||
|
} else {
|
||||||
|
c.App.AddDirectChannels(teamId, user)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||||
c.LogAuditWithUserId(user.Id, "obtained user")
|
if err = c.App.RevokeAllSessions(user.Id); err != nil {
|
||||||
|
|
||||||
session, err := c.App.DoLogin(w, r, user, "")
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.LogAuditWithUserId(user.Id, "Revoked all sessions for user")
|
||||||
|
c.App.Srv.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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
c.LogAuditWithUserId(user.Id, "success")
|
c.LogAuditWithUserId(user.Id, "obtained user")
|
||||||
|
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
err = c.App.DoLogin(w, r, user, "")
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.App.Session = *session
|
c.LogAuditWithUserId(user.Id, "success")
|
||||||
|
|
||||||
if val, ok := relayProps["redirect_to"]; ok {
|
c.App.AttachSessionCookies(w, r)
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
if val, ok := relayProps["redirect_to"]; ok {
|
||||||
case model.OAUTH_ACTION_MOBILE:
|
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
||||||
ReturnStatusOK(w)
|
return
|
||||||
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
}
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?extra=signin_change", http.StatusFound)
|
|
||||||
default:
|
switch action {
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader(), http.StatusFound)
|
case model.OAUTH_ACTION_MOBILE:
|
||||||
}
|
ReturnStatusOK(w)
|
||||||
|
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||||
|
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?extra=signin_change", http.StatusFound)
|
||||||
|
default:
|
||||||
|
http.Redirect(w, r, c.GetSiteURLHeader(), http.StatusFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user