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  }