github.com/space0122/mattermost-server@v5.11.1+incompatible/web/handlers_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package web
     5  
     6  import (
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  
    11  	"github.com/mattermost/mattermost-server/app"
    12  	"github.com/mattermost/mattermost-server/model"
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  func handlerForHTTPErrors(c *Context, w http.ResponseWriter, r *http.Request) {
    17  	c.Err = model.NewAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "", http.StatusFound)
    18  }
    19  
    20  func TestHandlerServeHTTPErrors(t *testing.T) {
    21  	th := Setup().InitBasic()
    22  	defer th.TearDown()
    23  
    24  	web := New(th.Server, th.Server.AppOptions, th.Server.Router)
    25  	handler := web.NewHandler(handlerForHTTPErrors)
    26  
    27  	var flagtests = []struct {
    28  		name     string
    29  		url      string
    30  		mobile   bool
    31  		redirect bool
    32  	}{
    33  		{"redirect on desktop non-api endpoint", "/login/sso/saml", false, true},
    34  		{"not redirect on desktop api endpoint", "/api/v4/test", false, false},
    35  		{"not redirect on mobile non-api endpoint", "/login/sso/saml", true, false},
    36  		{"not redirect on mobile api endpoint", "/api/v4/test", true, false},
    37  	}
    38  
    39  	for _, tt := range flagtests {
    40  		t.Run(tt.name, func(t *testing.T) {
    41  			request := httptest.NewRequest("GET", tt.url, nil)
    42  			if tt.mobile {
    43  				request.Header.Add("X-Mobile-App", "mattermost")
    44  			}
    45  			response := httptest.NewRecorder()
    46  			handler.ServeHTTP(response, request)
    47  
    48  			if tt.redirect {
    49  				assert.Equal(t, response.Code, http.StatusFound)
    50  			} else {
    51  				assert.NotContains(t, response.Body.String(), "/error?message=")
    52  			}
    53  		})
    54  	}
    55  }
    56  
    57  func handlerForHTTPSecureTransport(c *Context, w http.ResponseWriter, r *http.Request) {
    58  }
    59  
    60  func TestHandlerServeHTTPSecureTransport(t *testing.T) {
    61  	th := Setup().InitBasic()
    62  	defer th.TearDown()
    63  
    64  	th.App.UpdateConfig(func(config *model.Config) {
    65  		*config.ServiceSettings.TLSStrictTransport = true
    66  		*config.ServiceSettings.TLSStrictTransportMaxAge = 6000
    67  	})
    68  
    69  	web := New(th.Server, th.Server.AppOptions, th.Server.Router)
    70  	handler := web.NewHandler(handlerForHTTPSecureTransport)
    71  
    72  	request := httptest.NewRequest("GET", "/api/v4/test", nil)
    73  
    74  	response := httptest.NewRecorder()
    75  	handler.ServeHTTP(response, request)
    76  	header := response.Header().Get("Strict-Transport-Security")
    77  
    78  	if header == "" {
    79  		t.Errorf("Strict-Transport-Security expected but not existent")
    80  	}
    81  
    82  	if header != "max-age=6000" {
    83  		t.Errorf("Expected max-age=6000, got %s", header)
    84  	}
    85  
    86  	th.App.UpdateConfig(func(config *model.Config) {
    87  		*config.ServiceSettings.TLSStrictTransport = false
    88  	})
    89  
    90  	request = httptest.NewRequest("GET", "/api/v4/test", nil)
    91  
    92  	response = httptest.NewRecorder()
    93  	handler.ServeHTTP(response, request)
    94  	header = response.Header().Get("Strict-Transport-Security")
    95  
    96  	if header != "" {
    97  		t.Errorf("Strict-Transport-Security header is not expected, but returned")
    98  	}
    99  }
   100  
   101  func handlerForCSRFToken(c *Context, w http.ResponseWriter, r *http.Request) {
   102  }
   103  
   104  func TestHandlerServeCSRFToken(t *testing.T) {
   105  	th := Setup().InitBasic()
   106  	defer th.TearDown()
   107  
   108  	session := &model.Session{
   109  		UserId:   th.BasicUser.Id,
   110  		CreateAt: model.GetMillis(),
   111  		Roles:    model.SYSTEM_USER_ROLE_ID,
   112  		IsOAuth:  false,
   113  	}
   114  	session.GenerateCSRF()
   115  	session.SetExpireInDays(1)
   116  	session, err := th.App.CreateSession(session)
   117  	if err != nil {
   118  		t.Errorf("Expected nil, got %s", err)
   119  	}
   120  
   121  	web := New(th.Server, th.Server.AppOptions, th.Server.Router)
   122  
   123  	handler := Handler{
   124  		GetGlobalAppOptions: web.GetGlobalAppOptions,
   125  		HandleFunc:          handlerForCSRFToken,
   126  		RequireSession:      true,
   127  		TrustRequester:      false,
   128  		RequireMfa:          false,
   129  		IsStatic:            false,
   130  	}
   131  
   132  	cookie := &http.Cookie{
   133  		Name:  model.SESSION_COOKIE_USER,
   134  		Value: th.BasicUser.Username,
   135  	}
   136  	cookie2 := &http.Cookie{
   137  		Name:  model.SESSION_COOKIE_TOKEN,
   138  		Value: session.Token,
   139  	}
   140  	cookie3 := &http.Cookie{
   141  		Name:  model.SESSION_COOKIE_CSRF,
   142  		Value: session.GetCSRF(),
   143  	}
   144  
   145  	// CSRF Token Used - Success Expected
   146  
   147  	request := httptest.NewRequest("POST", "/api/v4/test", nil)
   148  	request.AddCookie(cookie)
   149  	request.AddCookie(cookie2)
   150  	request.AddCookie(cookie3)
   151  	request.Header.Add(model.HEADER_CSRF_TOKEN, session.GetCSRF())
   152  	response := httptest.NewRecorder()
   153  	handler.ServeHTTP(response, request)
   154  
   155  	if response.Code != 200 {
   156  		t.Errorf("Expected status 200, got %d", response.Code)
   157  	}
   158  
   159  	// No CSRF Token Used - Failure Expected
   160  
   161  	request = httptest.NewRequest("POST", "/api/v4/test", nil)
   162  	request.AddCookie(cookie)
   163  	request.AddCookie(cookie2)
   164  	request.AddCookie(cookie3)
   165  	response = httptest.NewRecorder()
   166  	handler.ServeHTTP(response, request)
   167  
   168  	if response.Code != 401 {
   169  		t.Errorf("Expected status 401, got %d", response.Code)
   170  	}
   171  
   172  	// Fallback Behavior Used - Success expected
   173  	// ToDo (DSchalla) 2019/01/04: Remove once legacy CSRF Handling is removed
   174  	th.App.UpdateConfig(func(config *model.Config) {
   175  		*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = false
   176  	})
   177  	request = httptest.NewRequest("POST", "/api/v4/test", nil)
   178  	request.AddCookie(cookie)
   179  	request.AddCookie(cookie2)
   180  	request.AddCookie(cookie3)
   181  	request.Header.Add(model.HEADER_REQUESTED_WITH, model.HEADER_REQUESTED_WITH_XML)
   182  	response = httptest.NewRecorder()
   183  	handler.ServeHTTP(response, request)
   184  
   185  	if response.Code != 200 {
   186  		t.Errorf("Expected status 200, got %d", response.Code)
   187  	}
   188  
   189  	// Fallback Behavior Used with Strict Enforcement - Failure Expected
   190  	// ToDo (DSchalla) 2019/01/04: Remove once legacy CSRF Handling is removed
   191  	th.App.UpdateConfig(func(config *model.Config) {
   192  		*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = true
   193  	})
   194  	response = httptest.NewRecorder()
   195  	handler.ServeHTTP(response, request)
   196  
   197  	if response.Code != 401 {
   198  		t.Errorf("Expected status 200, got %d", response.Code)
   199  	}
   200  }
   201  
   202  func handlerForCSPHeader(c *Context, w http.ResponseWriter, r *http.Request) {
   203  }
   204  
   205  func TestHandlerServeCSPHeader(t *testing.T) {
   206  	t.Run("non-static", func(t *testing.T) {
   207  		th := Setup().InitBasic()
   208  		defer th.TearDown()
   209  
   210  		web := New(th.Server, th.Server.AppOptions, th.Server.Router)
   211  
   212  		handler := Handler{
   213  			GetGlobalAppOptions: web.GetGlobalAppOptions,
   214  			HandleFunc:          handlerForCSPHeader,
   215  			RequireSession:      false,
   216  			TrustRequester:      false,
   217  			RequireMfa:          false,
   218  			IsStatic:            false,
   219  		}
   220  
   221  		request := httptest.NewRequest("POST", "/api/v4/test", nil)
   222  		response := httptest.NewRecorder()
   223  		handler.ServeHTTP(response, request)
   224  		assert.Equal(t, 200, response.Code)
   225  		assert.Empty(t, response.Header()["Content-Security-Policy"])
   226  	})
   227  
   228  	t.Run("static, without subpath", func(t *testing.T) {
   229  		th := Setup().InitBasic()
   230  		defer th.TearDown()
   231  
   232  		web := New(th.Server, th.Server.AppOptions, th.Server.Router)
   233  
   234  		handler := Handler{
   235  			GetGlobalAppOptions: web.GetGlobalAppOptions,
   236  			HandleFunc:          handlerForCSPHeader,
   237  			RequireSession:      false,
   238  			TrustRequester:      false,
   239  			RequireMfa:          false,
   240  			IsStatic:            true,
   241  		}
   242  
   243  		request := httptest.NewRequest("POST", "/", nil)
   244  		response := httptest.NewRecorder()
   245  		handler.ServeHTTP(response, request)
   246  		assert.Equal(t, 200, response.Code)
   247  		assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
   248  	})
   249  
   250  	t.Run("static, with subpath", func(t *testing.T) {
   251  		th := Setup().InitBasic()
   252  		defer th.TearDown()
   253  
   254  		th.App.UpdateConfig(func(cfg *model.Config) {
   255  			*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath"
   256  		})
   257  
   258  		web := New(th.Server, th.Server.AppOptions, th.Server.Router)
   259  
   260  		handler := Handler{
   261  			GetGlobalAppOptions: web.GetGlobalAppOptions,
   262  			HandleFunc:          handlerForCSPHeader,
   263  			RequireSession:      false,
   264  			TrustRequester:      false,
   265  			RequireMfa:          false,
   266  			IsStatic:            true,
   267  		}
   268  
   269  		request := httptest.NewRequest("POST", "/", nil)
   270  		response := httptest.NewRecorder()
   271  		handler.ServeHTTP(response, request)
   272  		assert.Equal(t, 200, response.Code)
   273  		assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
   274  
   275  		// TODO: It's hard to unit test this now that the CSP directive is effectively
   276  		// decided in Setup(). Circle back to this in master once the memory store is
   277  		// merged, allowing us to mock the desired initial config to take effect in Setup().
   278  		// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='")
   279  
   280  		th.App.UpdateConfig(func(cfg *model.Config) {
   281  			*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath2"
   282  		})
   283  
   284  		request = httptest.NewRequest("POST", "/", nil)
   285  		response = httptest.NewRecorder()
   286  		handler.ServeHTTP(response, request)
   287  		assert.Equal(t, 200, response.Code)
   288  		assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/"})
   289  		// TODO: See above.
   290  		// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='", "csp header incorrectly changed after subpath changed")
   291  	})
   292  }
   293  func TestCheckCSRFToken(t *testing.T) {
   294  	t.Run("should allow a POST request with a valid CSRF token header", func(t *testing.T) {
   295  		th := Setup()
   296  		defer th.TearDown()
   297  
   298  		h := &Handler{
   299  			RequireSession: true,
   300  			TrustRequester: false,
   301  		}
   302  
   303  		token := "token"
   304  		tokenLocation := app.TokenLocationCookie
   305  
   306  		c := &Context{
   307  			App: th.App,
   308  		}
   309  		r, _ := http.NewRequest(http.MethodPost, "", nil)
   310  		r.Header.Set(model.HEADER_CSRF_TOKEN, token)
   311  		session := &model.Session{
   312  			Props: map[string]string{
   313  				"csrf": token,
   314  			},
   315  		}
   316  
   317  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
   318  
   319  		assert.True(t, checked)
   320  		assert.True(t, passed)
   321  		assert.Nil(t, c.Err)
   322  	})
   323  
   324  	t.Run("should allow a POST request with an X-Requested-With header", func(t *testing.T) {
   325  		th := Setup()
   326  		defer th.TearDown()
   327  
   328  		h := &Handler{
   329  			RequireSession: true,
   330  			TrustRequester: false,
   331  		}
   332  
   333  		token := "token"
   334  		tokenLocation := app.TokenLocationCookie
   335  
   336  		c := &Context{
   337  			App: th.App,
   338  			Log: th.App.Log,
   339  		}
   340  		r, _ := http.NewRequest(http.MethodPost, "", nil)
   341  		r.Header.Set(model.HEADER_REQUESTED_WITH, model.HEADER_REQUESTED_WITH_XML)
   342  		session := &model.Session{
   343  			Props: map[string]string{
   344  				"csrf": token,
   345  			},
   346  		}
   347  
   348  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
   349  
   350  		assert.True(t, checked)
   351  		assert.True(t, passed)
   352  		assert.Nil(t, c.Err)
   353  	})
   354  
   355  	t.Run("should not allow a POST request with an X-Requested-With header with strict CSRF enforcement enabled", func(t *testing.T) {
   356  		th := Setup()
   357  		defer th.TearDown()
   358  
   359  		th.App.UpdateConfig(func(cfg *model.Config) {
   360  			*cfg.ServiceSettings.ExperimentalStrictCSRFEnforcement = true
   361  		})
   362  
   363  		h := &Handler{
   364  			RequireSession: true,
   365  			TrustRequester: false,
   366  		}
   367  
   368  		token := "token"
   369  		tokenLocation := app.TokenLocationCookie
   370  
   371  		c := &Context{
   372  			App: th.App,
   373  			Log: th.App.Log,
   374  		}
   375  		r, _ := http.NewRequest(http.MethodPost, "", nil)
   376  		r.Header.Set(model.HEADER_REQUESTED_WITH, model.HEADER_REQUESTED_WITH_XML)
   377  		session := &model.Session{
   378  			Props: map[string]string{
   379  				"csrf": token,
   380  			},
   381  		}
   382  
   383  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
   384  
   385  		assert.True(t, checked)
   386  		assert.False(t, passed)
   387  		assert.NotNil(t, c.Err)
   388  	})
   389  
   390  	t.Run("should not allow a POST request without either header", func(t *testing.T) {
   391  		th := Setup()
   392  		defer th.TearDown()
   393  
   394  		h := &Handler{
   395  			RequireSession: true,
   396  			TrustRequester: false,
   397  		}
   398  
   399  		token := "token"
   400  		tokenLocation := app.TokenLocationCookie
   401  
   402  		c := &Context{
   403  			App: th.App,
   404  		}
   405  		r, _ := http.NewRequest(http.MethodPost, "", nil)
   406  		session := &model.Session{
   407  			Props: map[string]string{
   408  				"csrf": token,
   409  			},
   410  		}
   411  
   412  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, session)
   413  
   414  		assert.True(t, checked)
   415  		assert.False(t, passed)
   416  		assert.NotNil(t, c.Err)
   417  	})
   418  
   419  	t.Run("should not check GET requests", func(t *testing.T) {
   420  		th := Setup()
   421  		defer th.TearDown()
   422  
   423  		h := &Handler{
   424  			RequireSession: true,
   425  			TrustRequester: false,
   426  		}
   427  
   428  		token := "token"
   429  		tokenLocation := app.TokenLocationCookie
   430  
   431  		c := &Context{
   432  			App: th.App,
   433  		}
   434  		r, _ := http.NewRequest(http.MethodGet, "", nil)
   435  
   436  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, nil)
   437  
   438  		assert.False(t, checked)
   439  		assert.False(t, passed)
   440  		assert.Nil(t, c.Err)
   441  	})
   442  
   443  	t.Run("should not check a request passing the auth token in a header", func(t *testing.T) {
   444  		th := Setup()
   445  		defer th.TearDown()
   446  
   447  		h := &Handler{
   448  			RequireSession: true,
   449  			TrustRequester: false,
   450  		}
   451  
   452  		token := "token"
   453  		tokenLocation := app.TokenLocationHeader
   454  
   455  		c := &Context{
   456  			App: th.App,
   457  		}
   458  		r, _ := http.NewRequest(http.MethodPost, "", nil)
   459  
   460  		checked, passed := h.checkCSRFToken(c, r, token, tokenLocation, nil)
   461  
   462  		assert.False(t, checked)
   463  		assert.False(t, passed)
   464  		assert.Nil(t, c.Err)
   465  	})
   466  }