github.com/levb/mattermost-server@v5.3.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 App: w.App, 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 return &Handler{ 30 App: w.App, 31 HandleFunc: h, 32 RequireSession: false, 33 TrustRequester: false, 34 RequireMfa: false, 35 IsStatic: true, 36 } 37 } 38 39 type Handler struct { 40 App *app.App 41 HandleFunc func(*Context, http.ResponseWriter, *http.Request) 42 RequireSession bool 43 TrustRequester bool 44 RequireMfa bool 45 IsStatic bool 46 } 47 48 func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 49 now := time.Now() 50 mlog.Debug(fmt.Sprintf("%v - %v", r.Method, r.URL.Path)) 51 52 c := &Context{} 53 c.App = h.App 54 c.T, _ = utils.GetTranslationsAndLocale(w, r) 55 c.RequestId = model.NewId() 56 c.IpAddress = utils.GetIpAddress(r) 57 c.Params = ParamsFromRequest(r) 58 c.Path = r.URL.Path 59 c.Log = c.App.Log 60 61 token, tokenLocation := app.ParseAuthTokenFromRequest(r) 62 63 // CSRF Check 64 if tokenLocation == app.TokenLocationCookie && h.RequireSession && !h.TrustRequester { 65 if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML { 66 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) 67 token = "" 68 } 69 } 70 71 subpath, _ := utils.GetSubpathFromConfig(c.App.Config()) 72 siteURLHeader := app.GetProtocol(r) + "://" + r.Host + subpath 73 c.SetSiteURLHeader(siteURLHeader) 74 75 w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) 76 w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil)) 77 78 if h.IsStatic { 79 // Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking 80 w.Header().Set("X-Frame-Options", "SAMEORIGIN") 81 w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'") 82 } else { 83 // All api response bodies will be JSON formatted by default 84 w.Header().Set("Content-Type", "application/json") 85 86 if r.Method == "GET" { 87 w.Header().Set("Expires", "0") 88 } 89 } 90 91 if len(token) != 0 { 92 session, err := c.App.GetSession(token) 93 94 if err != nil { 95 c.Log.Info("Invalid session", mlog.Err(err)) 96 if err.StatusCode == http.StatusInternalServerError { 97 c.Err = err 98 } else if h.RequireSession { 99 c.RemoveSessionCookie(w, r) 100 c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) 101 } 102 } else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString { 103 c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) 104 } else { 105 c.Session = *session 106 } 107 108 // Rate limit by UserID 109 if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.Session.UserId, w) { 110 return 111 } 112 } 113 114 c.Log = c.App.Log.With( 115 mlog.String("path", c.Path), 116 mlog.String("request_id", c.RequestId), 117 mlog.String("ip_addr", c.IpAddress), 118 mlog.String("user_id", c.Session.UserId), 119 mlog.String("method", r.Method), 120 ) 121 122 if c.Err == nil && h.RequireSession { 123 c.SessionRequired() 124 } 125 126 if c.Err == nil && h.RequireMfa { 127 c.MfaRequired() 128 } 129 130 if c.Err == nil { 131 h.HandleFunc(c, w, r) 132 } 133 134 // Handle errors that have occurred 135 if c.Err != nil { 136 c.Err.Translate(c.T) 137 c.Err.RequestId = c.RequestId 138 139 if c.Err.Id == "api.context.session_expired.app_error" { 140 c.LogInfo(c.Err) 141 } else { 142 c.LogError(c.Err) 143 } 144 145 c.Err.Where = r.URL.Path 146 147 // Block out detailed error when not in developer mode 148 if !*c.App.Config().ServiceSettings.EnableDeveloper { 149 c.Err.DetailedError = "" 150 } 151 152 // Sanitize all 5xx error messages in hardened mode 153 if *c.App.Config().ServiceSettings.ExperimentalEnableHardenedMode && c.Err.StatusCode >= 500 { 154 c.Err.Id = "" 155 c.Err.Message = "Internal Server Error" 156 c.Err.DetailedError = "" 157 c.Err.StatusCode = 500 158 c.Err.Where = "" 159 c.Err.IsOAuth = false 160 } 161 162 if IsApiCall(c.App, r) || IsWebhookCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 { 163 w.WriteHeader(c.Err.StatusCode) 164 w.Write([]byte(c.Err.ToJson())) 165 } else { 166 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 167 } 168 169 if c.App.Metrics != nil { 170 c.App.Metrics.IncrementHttpError() 171 } 172 } 173 174 if c.App.Metrics != nil { 175 c.App.Metrics.IncrementHttpRequest() 176 177 if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { 178 elapsed := float64(time.Since(now)) / float64(time.Second) 179 c.App.Metrics.ObserveHttpRequestDuration(elapsed) 180 } 181 } 182 }