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  }