mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-15889 Add unit tests for CSRF checks (#11058)
* MM-15889 Add unit tests for CSRF checks * Moved CSRF token test to login tests * Remove empty test * Remove debug messages
This commit is contained in:
@@ -121,30 +121,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
csrfCheckPassed := false
|
||||
|
||||
// CSRF Check
|
||||
if c.Err == nil && tokenLocation == app.TokenLocationCookie && h.RequireSession && !h.TrustRequester && r.Method != "GET" {
|
||||
csrfHeader := r.Header.Get(model.HEADER_CSRF_TOKEN)
|
||||
if csrfHeader == session.GetCSRF() {
|
||||
csrfCheckPassed = true
|
||||
} else if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
||||
// ToDo(DSchalla) 2019/01/04: Remove after deprecation period and only allow CSRF Header (MM-13657)
|
||||
csrfErrorMessage := "CSRF Header check failed for request - Please upgrade your web application or custom app to set a CSRF Header"
|
||||
if *c.App.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement {
|
||||
c.Log.Warn(csrfErrorMessage)
|
||||
} else {
|
||||
c.Log.Debug(csrfErrorMessage)
|
||||
csrfCheckPassed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !csrfCheckPassed {
|
||||
token = ""
|
||||
c.App.Session = model.Session{}
|
||||
c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
h.checkCSRFToken(c, r, token, tokenLocation, session)
|
||||
}
|
||||
|
||||
c.Log = c.App.Log.With(
|
||||
@@ -216,3 +193,34 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCSRFToken performs a CSRF check on the provided request with the given CSRF token. Returns whether or not
|
||||
// a CSRF check occurred and whether or not it succeeded.
|
||||
func (h *Handler) checkCSRFToken(c *Context, r *http.Request, token string, tokenLocation app.TokenLocation, session *model.Session) (checked bool, passed bool) {
|
||||
csrfCheckNeeded := c.Err == nil && tokenLocation == app.TokenLocationCookie && h.RequireSession && !h.TrustRequester && r.Method != "GET"
|
||||
csrfCheckPassed := false
|
||||
|
||||
if csrfCheckNeeded {
|
||||
csrfHeader := r.Header.Get(model.HEADER_CSRF_TOKEN)
|
||||
|
||||
if csrfHeader == session.GetCSRF() {
|
||||
csrfCheckPassed = true
|
||||
} else if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
||||
// ToDo(DSchalla) 2019/01/04: Remove after deprecation period and only allow CSRF Header (MM-13657)
|
||||
csrfErrorMessage := "CSRF Header check failed for request - Please upgrade your web application or custom app to set a CSRF Header"
|
||||
if *c.App.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement {
|
||||
c.Log.Warn(csrfErrorMessage)
|
||||
} else {
|
||||
c.Log.Debug(csrfErrorMessage)
|
||||
csrfCheckPassed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !csrfCheckPassed {
|
||||
c.App.Session = model.Session{}
|
||||
c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
return csrfCheckNeeded, csrfCheckPassed
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -337,3 +338,178 @@ func TestHandlerServeInvalidToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCSRFToken(t *testing.T) {
|
||||
t.Run("should allow a POST request with a valid CSRF token header", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationCookie
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Header.Set(model.HEADER_CSRF_TOKEN, token)
|
||||
session := &model.Session{
|
||||
Props: map[string]string{
|
||||
"csrf": token,
|
||||
},
|
||||
}
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
|
||||
|
||||
assert.True(t, checked)
|
||||
assert.True(t, passed)
|
||||
assert.Nil(t, c.Err)
|
||||
})
|
||||
|
||||
t.Run("should allow a POST request with an X-Requested-With header", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationCookie
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
Log: th.App.Log,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Header.Set(model.HEADER_REQUESTED_WITH, model.HEADER_REQUESTED_WITH_XML)
|
||||
session := &model.Session{
|
||||
Props: map[string]string{
|
||||
"csrf": token,
|
||||
},
|
||||
}
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
|
||||
|
||||
assert.True(t, checked)
|
||||
assert.True(t, passed)
|
||||
assert.Nil(t, c.Err)
|
||||
})
|
||||
|
||||
t.Run("should not allow a POST request with an X-Requested-With header with strict CSRF enforcement enabled", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ExperimentalStrictCSRFEnforcement = true
|
||||
})
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationCookie
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
Log: th.App.Log,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r.Header.Set(model.HEADER_REQUESTED_WITH, model.HEADER_REQUESTED_WITH_XML)
|
||||
session := &model.Session{
|
||||
Props: map[string]string{
|
||||
"csrf": token,
|
||||
},
|
||||
}
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
|
||||
|
||||
assert.True(t, checked)
|
||||
assert.False(t, passed)
|
||||
assert.NotNil(t, c.Err)
|
||||
})
|
||||
|
||||
t.Run("should not allow a POST request without either header", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationCookie
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
session := &model.Session{
|
||||
Props: map[string]string{
|
||||
"csrf": token,
|
||||
},
|
||||
}
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
|
||||
|
||||
assert.True(t, checked)
|
||||
assert.False(t, passed)
|
||||
assert.NotNil(t, c.Err)
|
||||
})
|
||||
|
||||
t.Run("should not check GET requests", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationCookie
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, nil)
|
||||
|
||||
assert.False(t, checked)
|
||||
assert.False(t, passed)
|
||||
assert.Nil(t, c.Err)
|
||||
})
|
||||
|
||||
t.Run("should not check a request passing the auth token in a header", func(t *testing.T) {
|
||||
th := Setup()
|
||||
defer th.TearDown()
|
||||
|
||||
h := &Handler{
|
||||
RequireSession: true,
|
||||
TrustRequester: false,
|
||||
}
|
||||
|
||||
token := "token"
|
||||
tokenLocation := app.TokenLocationHeader
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
}
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
|
||||
checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, nil)
|
||||
|
||||
assert.False(t, checked)
|
||||
assert.False(t, passed)
|
||||
assert.Nil(t, c.Err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,6 +132,8 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
c.App.AttachSessionCookies(w, r, session)
|
||||
|
||||
c.App.Session = *session
|
||||
|
||||
if val, ok := relayProps["redirect_to"]; ok {
|
||||
|
||||
Reference in New Issue
Block a user