github.com/ashishbhate/mattermost-server@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  }