github.com/cjdelisle/matterfoss@v5.11.1+incompatible/web/handlers.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 "fmt" 8 "net/http" 9 "time" 10 11 "github.com/mattermost/mattermost-server/app" 12 "github.com/mattermost/mattermost-server/mlog" 13 "github.com/mattermost/mattermost-server/model" 14 "github.com/mattermost/mattermost-server/utils" 15 ) 16 17 func (w *Web) NewHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 18 return &Handler{ 19 GetGlobalAppOptions: w.GetGlobalAppOptions, 20 HandleFunc: h, 21 RequireSession: false, 22 TrustRequester: false, 23 RequireMfa: false, 24 IsStatic: false, 25 } 26 } 27 28 func (w *Web) NewStaticHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { 29 // Determine the CSP SHA directive needed for subpath support, if any. This value is fixed 30 // on server start and intentionally requires a restart to take effect. 31 subpath, _ := utils.GetSubpathFromConfig(w.ConfigService.Config()) 32 33 return &Handler{ 34 GetGlobalAppOptions: w.GetGlobalAppOptions, 35 HandleFunc: h, 36 RequireSession: false, 37 TrustRequester: false, 38 RequireMfa: false, 39 IsStatic: true, 40 41 cspShaDirective: utils.GetSubpathScriptHash(subpath), 42 } 43 } 44 45 type Handler struct { 46 GetGlobalAppOptions app.AppOptionCreator 47 HandleFunc func(*Context, http.ResponseWriter, *http.Request) 48 RequireSession bool 49 TrustRequester bool 50 RequireMfa bool 51 IsStatic bool 52 53 cspShaDirective string 54 } 55 56 func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 57 now := time.Now() 58 mlog.Debug(fmt.Sprintf("%v - %v", r.Method, r.URL.Path)) 59 60 c := &Context{} 61 c.App = app.New( 62 h.GetGlobalAppOptions()..., 63 ) 64 c.App.T, _ = utils.GetTranslationsAndLocale(w, r) 65 c.App.RequestId = model.NewId() 66 c.App.IpAddress = utils.GetIpAddress(r) 67 c.App.UserAgent = r.UserAgent() 68 c.App.AcceptLanguage = r.Header.Get("Accept-Language") 69 c.Params = ParamsFromRequest(r) 70 c.App.Path = r.URL.Path 71 c.Log = c.App.Log 72 73 subpath, _ := utils.GetSubpathFromConfig(c.App.Config()) 74 siteURLHeader := app.GetProtocol(r) + "://" + r.Host + subpath 75 c.SetSiteURLHeader(siteURLHeader) 76 77 w.Header().Set(model.HEADER_REQUEST_ID, c.App.RequestId) 78 w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil)) 79 80 if *c.App.Config().ServiceSettings.TLSStrictTransport { 81 w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", *c.App.Config().ServiceSettings.TLSStrictTransportMaxAge)) 82 } 83 84 if h.IsStatic { 85 // Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking 86 w.Header().Set("X-Frame-Options", "SAMEORIGIN") 87 // Set content security policy. This is also specified in the root.html of the webapp in a meta tag. 88 w.Header().Set("Content-Security-Policy", fmt.Sprintf( 89 "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/%s", 90 h.cspShaDirective, 91 )) 92 } else { 93 // All api response bodies will be JSON formatted by default 94 w.Header().Set("Content-Type", "application/json") 95 96 if r.Method == "GET" { 97 w.Header().Set("Expires", "0") 98 } 99 } 100 101 token, tokenLocation := app.ParseAuthTokenFromRequest(r) 102 103 if len(token) != 0 { 104 session, err := c.App.GetSession(token) 105 if err != nil { 106 c.Log.Info("Invalid session", mlog.Err(err)) 107 if err.StatusCode == http.StatusInternalServerError { 108 c.Err = err 109 } else if h.RequireSession { 110 c.RemoveSessionCookie(w, r) 111 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) 112 } 113 } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { 114 c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) 115 } else { 116 c.App.Session = *session 117 } 118 119 // Rate limit by UserID 120 if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.App.Session.UserId, w) { 121 return 122 } 123 124 h.checkCSRFToken(c, r, token, tokenLocation, session) 125 } 126 127 c.Log = c.App.Log.With( 128 mlog.String("path", c.App.Path), 129 mlog.String("request_id", c.App.RequestId), 130 mlog.String("ip_addr", c.App.IpAddress), 131 mlog.String("user_id", c.App.Session.UserId), 132 mlog.String("method", r.Method), 133 ) 134 135 if c.Err == nil && h.RequireSession { 136 c.SessionRequired() 137 } 138 139 if c.Err == nil && h.RequireMfa { 140 c.MfaRequired() 141 } 142 143 if c.Err == nil { 144 h.HandleFunc(c, w, r) 145 } 146 147 // Handle errors that have occurred 148 if c.Err != nil { 149 c.Err.Translate(c.App.T) 150 c.Err.RequestId = c.App.RequestId 151 152 if c.Err.Id == "api.context.session_expired.app_error" { 153 c.LogInfo(c.Err) 154 } else { 155 c.LogError(c.Err) 156 } 157 158 c.Err.Where = r.URL.Path 159 160 // Block out detailed error when not in developer mode 161 if !*c.App.Config().ServiceSettings.EnableDeveloper { 162 c.Err.DetailedError = "" 163 } 164 165 // Sanitize all 5xx error messages in hardened mode 166 if *c.App.Config().ServiceSettings.ExperimentalEnableHardenedMode && c.Err.StatusCode >= 500 { 167 c.Err.Id = "" 168 c.Err.Message = "Internal Server Error" 169 c.Err.DetailedError = "" 170 c.Err.StatusCode = 500 171 c.Err.Where = "" 172 c.Err.IsOAuth = false 173 } 174 175 if IsApiCall(c.App, r) || IsWebhookCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 { 176 w.WriteHeader(c.Err.StatusCode) 177 w.Write([]byte(c.Err.ToJson())) 178 } else { 179 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 180 } 181 182 if c.App.Metrics != nil { 183 c.App.Metrics.IncrementHttpError() 184 } 185 } 186 187 if c.App.Metrics != nil { 188 c.App.Metrics.IncrementHttpRequest() 189 190 if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { 191 elapsed := float64(time.Since(now)) / float64(time.Second) 192 c.App.Metrics.ObserveHttpRequestDuration(elapsed) 193 } 194 } 195 } 196 197 // checkCSRFToken performs a CSRF check on the provided request with the given CSRF token. Returns whether or not 198 // a CSRF check occurred and whether or not it succeeded. 199 func (h *Handler) checkCSRFToken(c *Context, r *http.Request, token string, tokenLocation app.TokenLocation, session *model.Session) (checked bool, passed bool) { 200 csrfCheckNeeded := c.Err == nil && tokenLocation == app.TokenLocationCookie && h.RequireSession && !h.TrustRequester && r.Method != "GET" 201 csrfCheckPassed := false 202 203 if csrfCheckNeeded { 204 csrfHeader := r.Header.Get(model.HEADER_CSRF_TOKEN) 205 206 if csrfHeader == session.GetCSRF() { 207 csrfCheckPassed = true 208 } else if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML { 209 // ToDo(DSchalla) 2019/01/04: Remove after deprecation period and only allow CSRF Header (MM-13657) 210 csrfErrorMessage := "CSRF Header check failed for request - Please upgrade your web application or custom app to set a CSRF Header" 211 if *c.App.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { 212 c.Log.Warn(csrfErrorMessage) 213 } else { 214 c.Log.Debug(csrfErrorMessage) 215 csrfCheckPassed = true 216 } 217 } 218 219 if !csrfCheckPassed { 220 c.App.Session = model.Session{} 221 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) 222 } 223 } 224 225 return csrfCheckNeeded, csrfCheckPassed 226 }