mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
Fixed PatchChannelModerationsForChannel where AppError instances were created without setting StatusCode, causing zero-value StatusCode in error handling. Also added defensive hardening to handleContextError to detect and correct any future occurrences by logging diagnostic information and defaulting to 500.
1182 lines
36 KiB
Go
1182 lines
36 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package web
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
)
|
|
|
|
func handlerForHTTPErrors(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
|
|
}
|
|
|
|
func TestHandlerServeHTTPErrors(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(handlerForHTTPErrors)
|
|
|
|
var flagtests = []struct {
|
|
name string
|
|
url string
|
|
mobile bool
|
|
redirect bool
|
|
}{
|
|
{"redirect on desktop non-api endpoint", "/login/sso/saml", false, true},
|
|
{"not redirect on desktop api endpoint", "/api/v4/test", false, false},
|
|
{"not redirect on mobile non-api endpoint", "/login/sso/saml", true, false},
|
|
{"not redirect on mobile api endpoint", "/api/v4/test", true, false},
|
|
}
|
|
|
|
for _, tt := range flagtests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
request := httptest.NewRequest("GET", tt.url, nil)
|
|
if tt.mobile {
|
|
request.Header.Add("X-Mobile-App", "mattermost")
|
|
}
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if tt.redirect {
|
|
assert.Equal(t, response.Code, http.StatusFound)
|
|
} else {
|
|
assert.NotContains(t, response.Body.String(), "/error?message=")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func handlerForServeDefaultSecurityHeaders(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
|
|
func TestHandlerServeDefaultSecurityHeaders(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(handlerForServeDefaultSecurityHeaders)
|
|
|
|
paths := []string{
|
|
"/api/v4/test", // API
|
|
"/static/manifest.json", // this should always exist. Static files have their own handler
|
|
// Note that the plugin handler isn't tested, also plugins may support arbitrary functionality
|
|
}
|
|
|
|
for _, path := range paths {
|
|
request := httptest.NewRequest("GET", path, nil)
|
|
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
// header.Get returns a "" also if the header doesn't exist so we check that there is at least
|
|
// one Permissions-Policy header and their value is "". We check with .Values() as it canonicalizes
|
|
// the key.
|
|
permissionsPolicyHeader := response.Header().Get("Permissions-Policy")
|
|
permissionsPolicyHeaderValues := response.Header().Values("Permissions-Policy")
|
|
|
|
contentTypeOptionsHeader := response.Header().Get("X-Content-Type-Options")
|
|
referrerPolicyHeader := response.Header().Get("Referrer-Policy")
|
|
|
|
assert.NotEqualf(t, 0, len(permissionsPolicyHeaderValues), "Permissions-Policy header doesn't exist")
|
|
assert.Equal(t, "", permissionsPolicyHeader, "Permissions-Policy is not empty")
|
|
assert.Equal(t, "nosniff", contentTypeOptionsHeader)
|
|
assert.Equal(t, "no-referrer", referrerPolicyHeader)
|
|
}
|
|
}
|
|
|
|
func handlerForHTTPSecureTransport(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
|
|
func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
*config.ServiceSettings.TLSStrictTransport = true
|
|
*config.ServiceSettings.TLSStrictTransportMaxAge = 6000
|
|
})
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(handlerForHTTPSecureTransport)
|
|
|
|
request := httptest.NewRequest("GET", "/api/v4/test", nil)
|
|
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
header := response.Header().Get("Strict-Transport-Security")
|
|
|
|
if header == "" {
|
|
t.Errorf("Strict-Transport-Security expected but not existent")
|
|
}
|
|
|
|
if header != "max-age=6000" {
|
|
t.Errorf("Expected max-age=6000, got %s", header)
|
|
}
|
|
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
*config.ServiceSettings.TLSStrictTransport = false
|
|
})
|
|
|
|
request = httptest.NewRequest("GET", "/api/v4/test", nil)
|
|
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
header = response.Header().Get("Strict-Transport-Security")
|
|
|
|
if header != "" {
|
|
t.Errorf("Strict-Transport-Security header is not expected, but returned")
|
|
}
|
|
}
|
|
|
|
func handlerForCSRFToken(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
|
|
func TestHandlerServeCSRFToken(t *testing.T) {
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
CreateAt: model.GetMillis(),
|
|
Roles: model.SystemUserRoleId,
|
|
IsOAuth: false,
|
|
}
|
|
session.GenerateCSRF()
|
|
th.App.SetSessionExpireInHours(session, 24)
|
|
session, err := th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSRFToken,
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
}
|
|
|
|
cookie := &http.Cookie{
|
|
Name: model.SessionCookieUser,
|
|
Value: th.BasicUser.Username,
|
|
}
|
|
cookie2 := &http.Cookie{
|
|
Name: model.SessionCookieToken,
|
|
Value: session.Token,
|
|
}
|
|
cookie3 := &http.Cookie{
|
|
Name: model.SessionCookieCsrf,
|
|
Value: session.GetCSRF(),
|
|
}
|
|
|
|
// CSRF Token Used - Success Expected
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.AddCookie(cookie2)
|
|
request.AddCookie(cookie3)
|
|
request.Header.Add(model.HeaderCsrfToken, session.GetCSRF())
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != 200 {
|
|
t.Errorf("Expected status 200, got %d", response.Code)
|
|
}
|
|
|
|
// No CSRF Token Used - Failure Expected
|
|
|
|
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.AddCookie(cookie2)
|
|
request.AddCookie(cookie3)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != 401 {
|
|
t.Errorf("Expected status 401, got %d", response.Code)
|
|
}
|
|
|
|
// Fallback Behavior Used - Success expected
|
|
// ToDo (DSchalla) 2019/01/04: Remove once legacy CSRF Handling is removed
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = false
|
|
})
|
|
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.AddCookie(cookie2)
|
|
request.AddCookie(cookie3)
|
|
request.Header.Add(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != 200 {
|
|
t.Errorf("Expected status 200, got %d", response.Code)
|
|
}
|
|
|
|
// Fallback Behavior Used with Strict Enforcement - Failure Expected
|
|
// ToDo (DSchalla) 2019/01/04: Remove once legacy CSRF Handling is removed
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = true
|
|
})
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != 401 {
|
|
t.Errorf("Expected status 200, got %d", response.Code)
|
|
}
|
|
|
|
// Handler with RequireSession set to false
|
|
|
|
handlerNoSession := Handler{
|
|
Srv: th.Server,
|
|
HandleFunc: handlerForCSRFToken,
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
}
|
|
|
|
// CSRF Token Used - Success Expected
|
|
|
|
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.AddCookie(cookie2)
|
|
request.AddCookie(cookie3)
|
|
request.Header.Add(model.HeaderCsrfToken, session.GetCSRF())
|
|
response = httptest.NewRecorder()
|
|
handlerNoSession.ServeHTTP(response, request)
|
|
|
|
if response.Code != 200 {
|
|
t.Errorf("Expected status 200, got %d", response.Code)
|
|
}
|
|
|
|
// No CSRF Token Used - Failure Expected
|
|
|
|
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.AddCookie(cookie2)
|
|
request.AddCookie(cookie3)
|
|
response = httptest.NewRecorder()
|
|
handlerNoSession.ServeHTTP(response, request)
|
|
|
|
if response.Code != 401 {
|
|
t.Errorf("Expected status 401, got %d", response.Code)
|
|
}
|
|
}
|
|
|
|
func handlerForCSPHeader(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
}
|
|
|
|
func TestHandlerServeCSPHeader(t *testing.T) {
|
|
t.Run("non-static", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSPHeader,
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, 200, response.Code)
|
|
assert.Empty(t, response.Header()["Content-Security-Policy"])
|
|
})
|
|
|
|
t.Run("static, without subpath", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSPHeader,
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: true,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, 200, response.Code)
|
|
assert.Equal(t, []string{"frame-ancestors 'self' " + *th.App.Config().ServiceSettings.FrameAncestors + "; script-src 'self'"}, response.Header()["Content-Security-Policy"])
|
|
})
|
|
|
|
t.Run("static, with subpath and frame ancestors", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath"
|
|
*cfg.ServiceSettings.FrameAncestors = "teams.microsoft.com *.cloud.microsoft"
|
|
})
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSPHeader,
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: true,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, 200, response.Code)
|
|
assert.Equal(t, []string{"frame-ancestors 'self' " + *th.App.Config().ServiceSettings.FrameAncestors + "; script-src 'self'"}, response.Header()["Content-Security-Policy"])
|
|
|
|
// TODO: It's hard to unit test this now that the CSP directive is effectively
|
|
// decided in Setup(). Circle back to this in master once the memory store is
|
|
// merged, allowing us to mock the desired initial config to take effect in Setup().
|
|
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='")
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath2"
|
|
})
|
|
|
|
request = httptest.NewRequest("POST", "/", nil)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, 200, response.Code)
|
|
assert.Equal(t, []string{"frame-ancestors 'self' " + *th.App.Config().ServiceSettings.FrameAncestors + "; script-src 'self'"}, response.Header()["Content-Security-Policy"])
|
|
// TODO: See above.
|
|
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='", "csp header incorrectly changed after subpath changed")
|
|
})
|
|
|
|
t.Run("dev mode", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
oldBuildNumber := model.BuildNumber
|
|
model.BuildNumber = "dev"
|
|
defer func() {
|
|
model.BuildNumber = oldBuildNumber
|
|
}()
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSPHeader,
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: true,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, 200, response.Code)
|
|
assert.Equal(t, []string{"frame-ancestors 'self' " + *th.App.Config().ServiceSettings.FrameAncestors + "; script-src 'self' 'unsafe-eval' 'unsafe-inline'"}, response.Header()["Content-Security-Policy"])
|
|
})
|
|
}
|
|
|
|
func TestGenerateDevCSP(t *testing.T) {
|
|
t.Run("dev mode", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
oldBuildNumber := model.BuildNumber
|
|
model.BuildNumber = "dev"
|
|
defer func() {
|
|
model.BuildNumber = oldBuildNumber
|
|
}()
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
}
|
|
|
|
devCSP := generateDevCSP(*c)
|
|
|
|
assert.Equal(t, " 'unsafe-eval' 'unsafe-inline'", devCSP)
|
|
})
|
|
|
|
t.Run("allowed dev flags", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
oldBuildNumber := model.BuildNumber
|
|
model.BuildNumber = "0"
|
|
defer func() {
|
|
model.BuildNumber = oldBuildNumber
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.DeveloperFlags = "unsafe-inline=true,unsafe-eval=true"
|
|
})
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
}
|
|
|
|
devCSP := generateDevCSP(*c)
|
|
|
|
assert.Equal(t, " 'unsafe-inline' 'unsafe-eval'", devCSP)
|
|
})
|
|
|
|
t.Run("partial dev flags", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
oldBuildNumber := model.BuildNumber
|
|
model.BuildNumber = "0"
|
|
defer func() {
|
|
model.BuildNumber = oldBuildNumber
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.DeveloperFlags = "unsafe-inline=false,unsafe-eval=true"
|
|
})
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
}
|
|
|
|
devCSP := generateDevCSP(*c)
|
|
|
|
assert.Equal(t, " 'unsafe-eval'", devCSP)
|
|
})
|
|
|
|
t.Run("unknown dev flags", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
oldBuildNumber := model.BuildNumber
|
|
model.BuildNumber = "0"
|
|
defer func() {
|
|
model.BuildNumber = oldBuildNumber
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.DeveloperFlags = "unknown=true,unsafe-inline=false,unsafe-eval=true"
|
|
})
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
}
|
|
|
|
devCSP := generateDevCSP(*c)
|
|
|
|
assert.Equal(t, " 'unsafe-eval'", devCSP)
|
|
})
|
|
|
|
t.Run("empty dev flags", func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.DeveloperFlags = ""
|
|
})
|
|
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
buf := &mlog.Buffer{}
|
|
require.NoError(t, mlog.AddWriterTarget(logger, buf, false, mlog.LvlWarn))
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: logger,
|
|
}
|
|
|
|
generateDevCSP(*c)
|
|
|
|
assert.Equal(t, "", buf.String())
|
|
})
|
|
}
|
|
|
|
func TestHandlerServeInvalidToken(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
SiteURL string
|
|
ExpectedSetCookieHeaderRegexp string
|
|
}{
|
|
{"no subpath", "http://localhost:8065", "^MMAUTHTOKEN=; Path=/"},
|
|
{"subpath", "http://localhost:8065/subpath", "^MMAUTHTOKEN=; Path=/subpath"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Description, func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = tc.SiteURL
|
|
})
|
|
|
|
web := New(th.Server)
|
|
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSRFToken,
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
}
|
|
|
|
cookie := &http.Cookie{
|
|
Name: model.SessionCookieToken,
|
|
Value: "invalid",
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
require.Equal(t, http.StatusUnauthorized, response.Code)
|
|
|
|
cookies := response.Header().Get("Set-Cookie")
|
|
assert.Regexp(t, tc.ExpectedSetCookieHeaderRegexp, cookies)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandlerServeCSRFFailureClearsAuthCookie(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
SiteURL string
|
|
ExpectedSetCookieHeaderRegexp string
|
|
ExperimentalStrictCSRFEnforcement bool
|
|
}{
|
|
{"no subpath", "http://localhost:8065", "^MMAUTHTOKEN=; Path=/", false},
|
|
{"subpath", "http://localhost:8065/subpath", "^MMAUTHTOKEN=; Path=/subpath", false},
|
|
{"no subpath", "http://localhost:8065", "^MMAUTHTOKEN=; Path=/", true},
|
|
{"subpath", "http://localhost:8065/subpath", "^MMAUTHTOKEN=; Path=/subpath", true},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Description, func(t *testing.T) {
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = tc.SiteURL
|
|
*cfg.ServiceSettings.ExperimentalStrictCSRFEnforcement = tc.ExperimentalStrictCSRFEnforcement
|
|
})
|
|
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
CreateAt: model.GetMillis(),
|
|
Roles: model.SystemUserRoleId,
|
|
IsOAuth: false,
|
|
}
|
|
session.GenerateCSRF()
|
|
th.App.SetSessionExpireInHours(session, 24)
|
|
var err *model.AppError
|
|
session, err = th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
web := New(th.Server)
|
|
handler := Handler{
|
|
Srv: web.srv,
|
|
HandleFunc: handlerForCSRFToken,
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
}
|
|
|
|
cookie := &http.Cookie{
|
|
Name: model.SessionCookieToken,
|
|
Value: session.Token,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
request.AddCookie(cookie)
|
|
request.Header.Add(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if tc.ExperimentalStrictCSRFEnforcement {
|
|
require.Equal(t, http.StatusUnauthorized, response.Code)
|
|
cookies := response.Header().Get("Set-Cookie")
|
|
assert.Regexp(t, tc.ExpectedSetCookieHeaderRegexp, cookies)
|
|
} else {
|
|
require.Equal(t, http.StatusOK, response.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckCSRFToken(t *testing.T) {
|
|
t.Run("should allow a POST request with a valid CSRF token header", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
r.Header.Set(model.HeaderCsrfToken, token)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, 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 := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
Logger: th.App.Log(),
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
r.Header.Set(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, 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 := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
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,
|
|
Logger: th.App.Log(),
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
r.Header.Set(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
|
|
assert.True(t, checked)
|
|
assert.False(t, passed)
|
|
assert.Nil(t, c.Err)
|
|
})
|
|
|
|
t.Run("should not allow a POST request without either header", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
|
|
assert.True(t, checked)
|
|
assert.False(t, passed)
|
|
assert.Nil(t, c.Err)
|
|
})
|
|
|
|
t.Run("should not check GET requests", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodGet, "", nil)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
|
|
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 := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationHeader
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
|
|
assert.False(t, checked)
|
|
assert.False(t, passed)
|
|
assert.Nil(t, c.Err)
|
|
})
|
|
|
|
t.Run("should not check a request passing a nil session", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
r.Header.Set(model.HeaderCsrfToken, token)
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, nil)
|
|
|
|
assert.False(t, checked)
|
|
assert.False(t, passed)
|
|
assert.Nil(t, c.Err)
|
|
})
|
|
|
|
t.Run("should check requests for handlers that don't require a session but have one", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
h := &Handler{
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
}
|
|
|
|
token := "token"
|
|
tokenLocation := app.TokenLocationCookie
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
}
|
|
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
|
r.Header.Set(model.HeaderCsrfToken, token)
|
|
session := &model.Session{
|
|
Props: map[string]string{
|
|
"csrf": token,
|
|
},
|
|
}
|
|
|
|
checked, passed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
|
|
assert.True(t, checked)
|
|
assert.True(t, passed)
|
|
assert.Nil(t, c.Err)
|
|
})
|
|
}
|
|
|
|
func TestGetOriginClient(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
userAgent string
|
|
mobilev2 bool
|
|
expectedClient OriginClient
|
|
}{
|
|
{
|
|
name: "No user agent - unknown client",
|
|
userAgent: "",
|
|
expectedClient: OriginClientUnknown,
|
|
},
|
|
{
|
|
name: "Mozilla user agent",
|
|
userAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0",
|
|
expectedClient: OriginClientWeb,
|
|
},
|
|
{
|
|
name: "Chrome user agent",
|
|
userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
|
expectedClient: OriginClientWeb,
|
|
},
|
|
{
|
|
name: "Mobile post v2",
|
|
userAgent: "someother-agent/3.2.4",
|
|
mobilev2: true,
|
|
expectedClient: OriginClientMobile,
|
|
},
|
|
{
|
|
name: "Mobile Android",
|
|
userAgent: "rnbeta/2.0.0.441 someother-agent/3.2.4",
|
|
expectedClient: OriginClientMobile,
|
|
},
|
|
{
|
|
name: "Mobile iOS",
|
|
userAgent: "Mattermost/2.0.0.441 someother-agent/3.2.4",
|
|
expectedClient: OriginClientMobile,
|
|
},
|
|
{
|
|
name: "Desktop user agent",
|
|
userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.177 Electron/23.1.2 Safari/537.36 Mattermost/5.3.1",
|
|
expectedClient: OriginClientDesktop,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
req, err := http.NewRequest(http.MethodGet, "example.com", nil)
|
|
require.NoError(t, err)
|
|
|
|
// Set User-Agent header, if any
|
|
if tc.userAgent != "" {
|
|
req.Header.Set("User-Agent", tc.userAgent)
|
|
}
|
|
|
|
// Set mobilev2 query if needed
|
|
if tc.mobilev2 {
|
|
q := req.URL.Query()
|
|
q.Add("mobilev2", "true")
|
|
req.URL.RawQuery = q.Encode()
|
|
}
|
|
|
|
// Compute origin client
|
|
actualClient := GetOriginClient(req)
|
|
|
|
require.Equal(t, tc.expectedClient, actualClient)
|
|
}
|
|
}
|
|
|
|
func noOpHandler(_ *Context, _ http.ResponseWriter, _ *http.Request) {
|
|
// no op
|
|
}
|
|
|
|
func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
|
t.Run("Should not cause 414 error if url is smaller than configured limit", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(noOpHandler)
|
|
|
|
// using the default URL length
|
|
request := httptest.NewRequest("GET", "/api/v4/test?with=not&many=query_params", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, http.StatusOK, response.Code)
|
|
})
|
|
|
|
t.Run("Should cause 414 error if url is longer than configured limit", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
config.ServiceSettings.MaximumURLLength = model.NewPointer(10)
|
|
})
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(noOpHandler)
|
|
|
|
request := httptest.NewRequest("GET", "/api/v4/test?a_url_longer_than_10_characters_including_query_params", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
|
})
|
|
|
|
t.Run("414 error should include query params in computing URL length", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
config.ServiceSettings.MaximumURLLength = model.NewPointer(20)
|
|
})
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(noOpHandler)
|
|
|
|
// this URL is within the 20 characters limit excluding query params.
|
|
// but this should still fail as URL length includes query params
|
|
request := httptest.NewRequest("GET", "/api/v4/test?a_url_longer_than_10_characters_including_query_params", nil)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
|
})
|
|
}
|
|
|
|
func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) {
|
|
jsonReaderHandler := func(context *Context, writer http.ResponseWriter, request *http.Request) {
|
|
// read request body into a string
|
|
var body *string
|
|
err := json.NewDecoder(request.Body).Decode(&body)
|
|
if err != nil {
|
|
fmt.Printf("Error occurred reading request body, error: %s", err.Error())
|
|
context.Err = model.NewAppError("TestHandlerServeHTTPRequestPayloadLimit", "", nil, "", http.StatusBadRequest).Wrap(err)
|
|
} else {
|
|
fmt.Printf("Received body- '%s'", *body)
|
|
writer.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
t.Run("should allow payload smaller than set limit", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(jsonReaderHandler)
|
|
|
|
body := strings.NewReader("\"a very small request body\"")
|
|
request := httptest.NewRequest("POST", "/api/v4/test", body)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
assert.Equal(t, http.StatusOK, response.Code)
|
|
})
|
|
|
|
t.Run("Should error out when request body is larger than set limit", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(config *model.Config) {
|
|
config.ServiceSettings.MaximumPayloadSizeBytes = model.NewPointer(int64(1))
|
|
})
|
|
|
|
web := New(th.Server)
|
|
handler := web.NewHandler(jsonReaderHandler)
|
|
|
|
// this is a 600 character long string.
|
|
// Even though we have set the max payload size to be 1, we still need at least 513 bytes (1 byte configured + bytes.MinRead = 1 + 512 = 513) bytes.
|
|
// This is because the buffer will always be at least bytes.MinRead bytes large, so the effective payload limit is bytes.MinRead + the configured value.
|
|
body := strings.NewReader("\"weunfrwghyajuaqqkrecexurpmrmgpimjieymiwfhfrrrgiqpqrznkjtubwcuybyixyxwtwddpytukritccyugyeuvdtzjkkyiwhquzqkrqkhgyyfqnquzchjqkrkzfrxthduzizqtdxzqirxhzihbivmkdwpbeddepdanzuuzqxbdqfvgkwumervhghywexitbjdnvxcniuamwmqdecbbqbgnjjqwkdcucvnpwynuruztpdtmmvbpkevurpjdwdhpayaindzmnkmyybudfkjdkqwuiviriudtqytybuwfkkwpepwhpekfewnxgpkfctdqjmemngvntnizvfznaiqpbumgtcxidvawtgcdyqijbxzrgezvjmcwikiabbpqabrwfgncrmvqththepffatnhchhnmrhkuqvgrzfugzhuwicaemhcacrazmgzmrgrkuhwucfydhwxfhzfukzjhvdxkuhzjrxwippxadvwzigndxwdxvganxggjjxwdqtgqgnpqqygndviadvttwfntcreitijaqrpfygdehbcftyfcjvrfwvjmbtdptutjgtbyhbyddfecyyujgrxyujzmryymj\"")
|
|
request := httptest.NewRequest("POST", "/api/v4/test", body)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
|
|
assert.Equal(t, http.StatusRequestEntityTooLarge, response.Code)
|
|
})
|
|
}
|
|
|
|
func TestHandleContextErrorZeroStatusCode(t *testing.T) {
|
|
t.Run("should set StatusCode to 500 when AppError has zero StatusCode", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
appErr := &model.AppError{
|
|
Message: "Cannot add a permission that is restricted by the team or system permission scheme",
|
|
StatusCode: 0,
|
|
Id: "test.error",
|
|
Where: "TestFunction",
|
|
DetailedError: "test details",
|
|
}
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
Err: appErr,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
h := Handler{
|
|
Srv: th.Server,
|
|
}
|
|
|
|
h.handleContextError(c, response, request)
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, c.Err.StatusCode)
|
|
assert.Equal(t, http.StatusInternalServerError, response.Code)
|
|
})
|
|
|
|
t.Run("should not modify StatusCode when AppError has valid StatusCode", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
|
|
appErr := model.NewAppError("TestFunction", "test.error", nil, "test details", http.StatusBadRequest)
|
|
|
|
c := &Context{
|
|
App: th.App,
|
|
AppContext: th.Context,
|
|
Logger: th.App.Log(),
|
|
Err: appErr,
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
h := Handler{
|
|
Srv: th.Server,
|
|
}
|
|
|
|
h.handleContextError(c, response, request)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, c.Err.StatusCode)
|
|
assert.Equal(t, http.StatusBadRequest, response.Code)
|
|
})
|
|
}
|