github.com/crspeller/mattermost-server@v0.0.0-20190328001957-a200beb3d111/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/crspeller/mattermost-server/app" 12 "github.com/crspeller/mattermost-server/mlog" 13 "github.com/crspeller/mattermost-server/model" 14 "github.com/crspeller/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 csrfCheckPassed := false 106 107 // CSRF Check 108 if tokenLocation == app.TokenLocationCookie && h.RequireSession && !h.TrustRequester && r.Method != "GET" { 109 csrfHeader := r.Header.Get(model.HEADER_CSRF_TOKEN) 110 if csrfHeader == session.GetCSRF() { 111 csrfCheckPassed = true 112 } else if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML { 113 // ToDo(DSchalla) 2019/01/04: Remove after deprecation period and only allow CSRF Header (MM-13657) 114 csrfErrorMessage := "CSRF Header check failed for request - Please upgrade your web application or custom app to set a CSRF Header" 115 if *c.App.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { 116 c.Log.Warn(csrfErrorMessage) 117 } else { 118 c.Log.Debug(csrfErrorMessage) 119 csrfCheckPassed = true 120 } 121 } 122 123 if !csrfCheckPassed { 124 token = "" 125 session = nil 126 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) 127 } 128 } else { 129 csrfCheckPassed = true 130 } 131 132 if csrfCheckPassed { 133 if err != nil { 134 c.Log.Info("Invalid session", mlog.Err(err)) 135 if err.StatusCode == http.StatusInternalServerError { 136 c.Err = err 137 } else if h.RequireSession { 138 c.RemoveSessionCookie(w, r) 139 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) 140 } 141 } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { 142 c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) 143 } else { 144 c.App.Session = *session 145 } 146 147 // Rate limit by UserID 148 if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.App.Session.UserId, w) { 149 return 150 } 151 } 152 } 153 154 c.Log = c.App.Log.With( 155 mlog.String("path", c.App.Path), 156 mlog.String("request_id", c.App.RequestId), 157 mlog.String("ip_addr", c.App.IpAddress), 158 mlog.String("user_id", c.App.Session.UserId), 159 mlog.String("method", r.Method), 160 ) 161 162 if c.Err == nil && h.RequireSession { 163 c.SessionRequired() 164 } 165 166 if c.Err == nil && h.RequireMfa { 167 c.MfaRequired() 168 } 169 170 if c.Err == nil { 171 h.HandleFunc(c, w, r) 172 } 173 174 // Handle errors that have occurred 175 if c.Err != nil { 176 c.Err.Translate(c.App.T) 177 c.Err.RequestId = c.App.RequestId 178 179 if c.Err.Id == "api.context.session_expired.app_error" { 180 c.LogInfo(c.Err) 181 } else { 182 c.LogError(c.Err) 183 } 184 185 c.Err.Where = r.URL.Path 186 187 // Block out detailed error when not in developer mode 188 if !*c.App.Config().ServiceSettings.EnableDeveloper { 189 c.Err.DetailedError = "" 190 } 191 192 // Sanitize all 5xx error messages in hardened mode 193 if *c.App.Config().ServiceSettings.ExperimentalEnableHardenedMode && c.Err.StatusCode >= 500 { 194 c.Err.Id = "" 195 c.Err.Message = "Internal Server Error" 196 c.Err.DetailedError = "" 197 c.Err.StatusCode = 500 198 c.Err.Where = "" 199 c.Err.IsOAuth = false 200 } 201 202 if IsApiCall(c.App, r) || IsWebhookCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 { 203 w.WriteHeader(c.Err.StatusCode) 204 w.Write([]byte(c.Err.ToJson())) 205 } else { 206 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 207 } 208 209 if c.App.Metrics != nil { 210 c.App.Metrics.IncrementHttpError() 211 } 212 } 213 214 if c.App.Metrics != nil { 215 c.App.Metrics.IncrementHttpRequest() 216 217 if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { 218 elapsed := float64(time.Since(now)) / float64(time.Second) 219 c.App.Metrics.ObserveHttpRequestDuration(elapsed) 220 } 221 } 222 }