mirror of
https://github.com/hanzoai/team.git
synced 2026-10-30 14:49:35 +00:00
Fixed PatchChannelModerationsForChannel where AppError instances were created without setting StatusCode, causing zero-value StatusCode in error handling. Also added defensive hardening to handleContextError to detect and correct any future occurrences by logging diagnostic information and defaulting to 500.
579 lines
19 KiB
Go
579 lines
19 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/klauspost/compress/gzhttp"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
|
)
|
|
|
|
func GetHandlerName(h func(*Context, http.ResponseWriter, *http.Request)) string {
|
|
handlerName := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
|
|
pos := strings.LastIndex(handlerName, ".")
|
|
if pos != -1 && len(handlerName) > pos {
|
|
handlerName = handlerName[pos+1:]
|
|
}
|
|
return handlerName
|
|
}
|
|
|
|
func (w *Web) NewHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
|
return &Handler{
|
|
Srv: w.srv,
|
|
HandleFunc: h,
|
|
HandlerName: GetHandlerName(h),
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
IsLocal: false,
|
|
}
|
|
}
|
|
|
|
func (w *Web) NewStaticHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
|
// Determine the CSP SHA directive needed for subpath support, if any. This value is fixed
|
|
// on server start and intentionally requires a restart to take effect.
|
|
subpath, _ := utils.GetSubpathFromConfig(w.srv.Config())
|
|
|
|
return &Handler{
|
|
Srv: w.srv,
|
|
HandleFunc: h,
|
|
HandlerName: GetHandlerName(h),
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: true,
|
|
|
|
cspShaDirective: utils.GetSubpathScriptHash(subpath),
|
|
}
|
|
}
|
|
|
|
type Handler struct {
|
|
Srv *app.Server
|
|
HandleFunc func(*Context, http.ResponseWriter, *http.Request)
|
|
HandlerName string
|
|
RequireSession bool
|
|
RequireCloudKey bool
|
|
RequireRemoteClusterToken bool
|
|
TrustRequester bool
|
|
RequireMfa bool
|
|
IsStatic bool
|
|
IsLocal bool
|
|
DisableWhenBusy bool
|
|
FileAPI bool
|
|
|
|
cspShaDirective string
|
|
}
|
|
|
|
func generateDevCSP(c Context) string {
|
|
var devCSP []string
|
|
|
|
// Add unsafe-eval to the content security policy for faster source maps in development mode
|
|
if model.BuildNumber == "dev" {
|
|
devCSP = append(devCSP, "'unsafe-eval'")
|
|
}
|
|
|
|
// Add unsafe-inline to unlock extensions like React & Redux DevTools in Firefox
|
|
// see https://github.com/reduxjs/redux-devtools/issues/380
|
|
if model.BuildNumber == "dev" {
|
|
devCSP = append(devCSP, "'unsafe-inline'")
|
|
}
|
|
|
|
// Add supported flags for debugging during development, even if not on a dev build.
|
|
if *c.App.Config().ServiceSettings.DeveloperFlags != "" {
|
|
for devFlagKVStr := range strings.SplitSeq(*c.App.Config().ServiceSettings.DeveloperFlags, ",") {
|
|
devFlagKVSplit := strings.SplitN(devFlagKVStr, "=", 2)
|
|
if len(devFlagKVSplit) != 2 {
|
|
c.Logger.Warn("Unable to parse developer flag", mlog.String("developer_flag", devFlagKVStr))
|
|
continue
|
|
}
|
|
devFlagKey := devFlagKVSplit[0]
|
|
devFlagValue := devFlagKVSplit[1]
|
|
|
|
// Ignore disabled keys
|
|
if devFlagValue != "true" {
|
|
continue
|
|
}
|
|
|
|
// Honour only supported keys
|
|
switch devFlagKey {
|
|
case "unsafe-eval", "unsafe-inline":
|
|
if model.BuildNumber == "dev" {
|
|
// These flags are added automatically for dev builds
|
|
continue
|
|
}
|
|
|
|
devCSP = append(devCSP, "'"+devFlagKey+"'")
|
|
default:
|
|
c.Logger.Warn("Unrecognized developer flag", mlog.String("developer_flag", devFlagKVStr))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(devCSP) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return " " + strings.Join(devCSP, " ")
|
|
}
|
|
|
|
func (h Handler) basicSecurityChecks(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
maxURLCharacters := *c.App.Config().ServiceSettings.MaximumURLLength
|
|
if len(r.RequestURI) > maxURLCharacters {
|
|
c.Err = model.NewAppError("basicSecurityChecks", "basic_security_check.url.too_long_error", nil, "", http.StatusRequestURITooLong)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
w = newWrappedWriter(w)
|
|
now := time.Now()
|
|
|
|
appInstance := app.New(app.ServerConnector(h.Srv.Channels()))
|
|
|
|
c := &Context{
|
|
AppContext: &request.Context{},
|
|
App: appInstance,
|
|
}
|
|
|
|
requestID := model.NewId()
|
|
var rateLimitExceeded bool
|
|
defer func() {
|
|
responseLogFields := []mlog.Field{
|
|
mlog.String("method", r.Method),
|
|
mlog.String("url", r.URL.Path),
|
|
mlog.String("request_id", requestID),
|
|
}
|
|
// if there is a valid session and userID then include the user_id
|
|
if c.AppContext.Session() != nil && c.AppContext.Session().UserId != "" {
|
|
responseLogFields = append(responseLogFields, mlog.String("user_id", c.AppContext.Session().UserId))
|
|
}
|
|
|
|
statusCode := strconv.Itoa(w.(*responseWriterWrapper).StatusCode())
|
|
|
|
// Websockets are returning status code 0 to requests after closing the socket
|
|
if statusCode != "0" {
|
|
responseLogFields = append(responseLogFields, mlog.String("status_code", statusCode))
|
|
}
|
|
mlog.Debug("Received HTTP request", responseLogFields...)
|
|
|
|
if !rateLimitExceeded {
|
|
h.recordMetrics(c, r, now, statusCode)
|
|
}
|
|
}()
|
|
|
|
t, _ := i18n.GetTranslationsAndLocaleFromRequest(r)
|
|
c.AppContext = request.NewContext(
|
|
context.Background(),
|
|
requestID,
|
|
utils.GetIPAddress(r, c.App.Config().ServiceSettings.TrustedProxyIPHeader),
|
|
r.Header.Get("X-Forwarded-For"),
|
|
r.URL.Path,
|
|
r.UserAgent(),
|
|
r.Header.Get("Accept-Language"),
|
|
t,
|
|
)
|
|
|
|
c.Params = ParamsFromRequest(r)
|
|
c.Logger = c.App.Log()
|
|
|
|
h.basicSecurityChecks(c, w, r)
|
|
if c.Err != nil {
|
|
h.handleContextError(c, w, r)
|
|
return
|
|
}
|
|
|
|
var maxBytes int64
|
|
if h.FileAPI {
|
|
// We add a buffer of bytes.MinRead so that file sizes close to max file size
|
|
// do not get cut off.
|
|
maxBytes = *c.App.Config().FileSettings.MaxFileSize + bytes.MinRead
|
|
} else {
|
|
maxBytes = *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes + bytes.MinRead
|
|
}
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxBytes)
|
|
|
|
subpath, _ := utils.GetSubpathFromConfig(c.App.Config())
|
|
siteURLHeader := app.GetProtocol(r) + "://" + r.Host + subpath
|
|
if c.App.Channels().License().IsCloud() {
|
|
siteURLHeader = *c.App.Config().ServiceSettings.SiteURL + subpath
|
|
}
|
|
c.SetSiteURLHeader(siteURLHeader)
|
|
|
|
w.Header().Set(model.HeaderRequestId, c.AppContext.RequestId())
|
|
w.Header().Set(model.HeaderVersionId, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.Channels().License() != nil))
|
|
|
|
if *c.App.Config().ServiceSettings.TLSStrictTransport {
|
|
w.Header().Set("Strict-Transport-Security", fmt.Sprintf("max-age=%d", *c.App.Config().ServiceSettings.TLSStrictTransportMaxAge))
|
|
}
|
|
|
|
// Hardcoded sensible default values for these security headers. Feel free to override in proxy or ingress
|
|
w.Header().Set("Permissions-Policy", "")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
|
|
if h.IsStatic {
|
|
// Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
|
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
|
|
|
devCSP := generateDevCSP(*c)
|
|
|
|
// Set content security policy. This is also specified in the root.html of the webapp in a meta tag.
|
|
w.Header().Set("Content-Security-Policy", fmt.Sprintf(
|
|
"frame-ancestors 'self' %s; script-src 'self'%s%s",
|
|
*c.App.Config().ServiceSettings.FrameAncestors,
|
|
h.cspShaDirective,
|
|
devCSP,
|
|
))
|
|
} else {
|
|
// All api response bodies will be JSON formatted by default
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if r.Method == "GET" {
|
|
w.Header().Set("Expires", "0")
|
|
}
|
|
}
|
|
|
|
token, tokenLocation := app.ParseAuthTokenFromRequest(r)
|
|
|
|
if token != "" && tokenLocation != app.TokenLocationCloudHeader && tokenLocation != app.TokenLocationRemoteClusterHeader {
|
|
session, err := c.App.GetSession(token)
|
|
|
|
if err != nil {
|
|
c.Logger.Info("Invalid session", mlog.Err(err))
|
|
if err.StatusCode == http.StatusInternalServerError {
|
|
c.Err = err
|
|
} else if h.RequireSession {
|
|
c.RemoveSessionCookie(w, r)
|
|
c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized)
|
|
}
|
|
} else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString {
|
|
c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized)
|
|
} else {
|
|
c.AppContext = c.AppContext.WithSession(session)
|
|
}
|
|
|
|
// Rate limit by UserID
|
|
if c.App.Srv().RateLimiter != nil {
|
|
rateLimitExceeded = c.App.Srv().RateLimiter.UserIdRateLimit(c.AppContext.Session().UserId, w)
|
|
if rateLimitExceeded {
|
|
return
|
|
}
|
|
}
|
|
|
|
csrfChecked, csrfPassed := h.checkCSRFToken(c, r, tokenLocation, session)
|
|
if csrfChecked && !csrfPassed {
|
|
c.AppContext = c.AppContext.WithSession(&model.Session{})
|
|
c.RemoveSessionCookie(w, r)
|
|
c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
|
|
}
|
|
} else if token != "" && c.App.Channels().License().IsCloud() && tokenLocation == app.TokenLocationCloudHeader {
|
|
// Check to see if this provided token matches our CWS Token
|
|
session, err := c.App.GetCloudSession(token)
|
|
if err != nil {
|
|
c.Logger.Warn("Invalid CWS token", mlog.Err(err))
|
|
c.Err = err
|
|
} else {
|
|
c.AppContext = c.AppContext.WithSession(session)
|
|
}
|
|
} else if token != "" && c.App.Channels().License() != nil && c.App.Channels().License().HasRemoteClusterService() && tokenLocation == app.TokenLocationRemoteClusterHeader {
|
|
// Get the remote cluster
|
|
if remoteId := c.GetRemoteID(r); remoteId == "" {
|
|
c.Logger.Warn("Missing remote cluster id") //
|
|
c.Err = model.NewAppError("ServeHTTP", "api.context.remote_id_missing.app_error", nil, "", http.StatusUnauthorized)
|
|
} else {
|
|
// Check the token is correct for the remote cluster id.
|
|
session, err := c.App.GetRemoteClusterSession(token, remoteId)
|
|
if err != nil {
|
|
c.Logger.Warn("Invalid remote cluster token", mlog.Err(err))
|
|
c.Err = err
|
|
} else {
|
|
c.AppContext = c.AppContext.WithSession(session)
|
|
}
|
|
}
|
|
}
|
|
|
|
c.Logger = c.App.Log().With(
|
|
mlog.String("path", c.AppContext.Path()),
|
|
mlog.String("request_id", c.AppContext.RequestId()),
|
|
mlog.String("ip_addr", c.AppContext.IPAddress()),
|
|
mlog.String("user_id", c.AppContext.Session().UserId),
|
|
mlog.String("method", r.Method),
|
|
)
|
|
c.AppContext = c.AppContext.WithLogger(c.Logger)
|
|
|
|
if c.Err == nil && h.RequireSession {
|
|
c.SessionRequired()
|
|
}
|
|
|
|
if c.Err == nil && h.RequireMfa {
|
|
c.MfaRequired()
|
|
}
|
|
|
|
if c.Err == nil && h.DisableWhenBusy && c.App.Srv().Platform().Busy.IsBusy() {
|
|
c.SetServerBusyError()
|
|
}
|
|
|
|
if c.Err == nil && h.RequireCloudKey {
|
|
c.CloudKeyRequired()
|
|
}
|
|
|
|
if c.Err == nil && h.RequireRemoteClusterToken {
|
|
c.RemoteClusterTokenRequired()
|
|
}
|
|
|
|
if c.Err == nil && h.IsLocal {
|
|
// if the connection is local, RemoteAddr shouldn't have the
|
|
// shape IP:PORT (it will be "@" in Linux, for example)
|
|
isLocalOrigin := !strings.Contains(r.RemoteAddr, ":")
|
|
if *c.App.Config().ServiceSettings.EnableLocalMode && isLocalOrigin {
|
|
c.AppContext = c.AppContext.WithSession(&model.Session{Local: true})
|
|
} else if !isLocalOrigin {
|
|
c.Err = model.NewAppError("", "api.context.local_origin_required.app_error", nil, "LocalOriginRequired", http.StatusUnauthorized)
|
|
}
|
|
}
|
|
|
|
if c.Err == nil {
|
|
h.HandleFunc(c, w, r)
|
|
}
|
|
|
|
// Handle errors that have occurred
|
|
if c.Err != nil {
|
|
h.handleContextError(c, w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h Handler) recordMetrics(c *Context, r *http.Request, now time.Time, statusCode string) {
|
|
if c.App.Metrics() != nil {
|
|
c.App.Metrics().IncrementHTTPRequest()
|
|
|
|
if r.URL.Path != model.APIURLSuffix+"/websocket" {
|
|
elapsed := float64(time.Since(now)) / float64(time.Second)
|
|
|
|
pageLoadContext := r.Header.Get("X-Page-Load-Context")
|
|
if pageLoadContext != "page_load" && pageLoadContext != "reconnect" {
|
|
pageLoadContext = ""
|
|
}
|
|
|
|
c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, string(GetOriginClient(r)), pageLoadContext, elapsed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h Handler) handleContextError(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
if c.Err == nil {
|
|
return
|
|
}
|
|
|
|
// We're handling payload limit error here because it needs to be handled globally.
|
|
var maxBytesErr *http.MaxBytesError
|
|
// check if error is a MaxBytesError error, which occurs when you read more bytes from buffer than configured
|
|
if ok := errors.As(c.Err, &maxBytesErr); ok {
|
|
// replace the context error with this error if so,
|
|
newErr := model.NewAppError(c.Err.Where, "api.context.request_body_too_large.app_error", nil, "Use the setting `MaximumPayloadSizeBytes` in Mattermost config to configure allowed payload limit. Learn more about this setting in Mattermost docs at https://docs.mattermost.com/configure/environment-configuration-settings.html#maximum-payload-size", http.StatusRequestEntityTooLarge)
|
|
c.Err = newErr
|
|
}
|
|
|
|
// Detect and fix AppError with missing StatusCode to prevent panics
|
|
if c.Err.StatusCode == 0 {
|
|
c.Logger.Error("AppError with zero StatusCode detected",
|
|
mlog.String("error_id", c.Err.Id),
|
|
mlog.String("error_message", c.Err.Message),
|
|
mlog.String("error_where", c.Err.Where),
|
|
mlog.String("request_path", r.URL.Path),
|
|
mlog.String("request_method", r.Method),
|
|
mlog.String("detailed_error", c.Err.DetailedError),
|
|
)
|
|
c.Err.StatusCode = http.StatusInternalServerError
|
|
}
|
|
|
|
c.Err.RequestId = c.AppContext.RequestId()
|
|
c.LogErrorByCode(c.Err)
|
|
// The locale translation needs to happen after we have logged it.
|
|
// We don't want the server logs to be translated as per user locale.
|
|
c.Err.Translate(c.AppContext.T)
|
|
|
|
c.Err.Where = r.URL.Path
|
|
|
|
// Block out detailed error when not in developer mode
|
|
if !*c.App.Config().ServiceSettings.EnableDeveloper {
|
|
c.Err.WipeDetailed()
|
|
}
|
|
|
|
// Sanitize all 5xx error messages in hardened mode
|
|
if *c.App.Config().ServiceSettings.ExperimentalEnableHardenedMode && c.Err.StatusCode >= 500 {
|
|
c.Err.Id = ""
|
|
c.Err.Message = "Internal Server Error"
|
|
c.Err.WipeDetailed()
|
|
c.Err.StatusCode = 500
|
|
c.Err.Where = ""
|
|
}
|
|
|
|
if IsAPICall(c.App, r) || IsWebhookCall(c.App, r) || IsOAuthAPICall(c.App, r) || r.Header.Get("X-Mobile-App") != "" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(c.Err.StatusCode)
|
|
if _, err := w.Write([]byte(c.Err.ToJSON())); err != nil {
|
|
c.Logger.Warn("Failed to write error response", mlog.Err(err))
|
|
}
|
|
} else {
|
|
utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey())
|
|
}
|
|
|
|
if c.App.Metrics() != nil {
|
|
c.App.Metrics().IncrementHTTPError()
|
|
}
|
|
}
|
|
|
|
type OriginClient string
|
|
|
|
const (
|
|
OriginClientUnknown OriginClient = "unknown"
|
|
OriginClientWeb OriginClient = "web"
|
|
OriginClientMobile OriginClient = "mobile"
|
|
OriginClientDesktop OriginClient = "desktop"
|
|
)
|
|
|
|
// GetOriginClient returns the device from which the provided request was issued. The algorithm roughly looks like:
|
|
// - If the URL contains the query mobilev2=true, then it's mobile
|
|
// - If the first field of the user agent starts with either "rnbeta" or "Mattermost", then it's mobile
|
|
// - If the last field of the user agent starts with "Mattermost", then it's desktop
|
|
// - Otherwise, it's web
|
|
func GetOriginClient(r *http.Request) OriginClient {
|
|
userAgent := r.Header.Get("User-Agent")
|
|
fields := strings.Fields(userAgent)
|
|
if len(fields) < 1 {
|
|
return OriginClientUnknown
|
|
}
|
|
|
|
// Is mobile post v2?
|
|
queryParam := r.URL.Query().Get("mobilev2")
|
|
if queryParam == "true" {
|
|
return OriginClientMobile
|
|
}
|
|
|
|
// Is mobile pre v2?
|
|
clientAgent := fields[0]
|
|
if strings.HasPrefix(clientAgent, "rnbeta") || strings.HasPrefix(clientAgent, "Mattermost") {
|
|
return OriginClientMobile
|
|
}
|
|
|
|
// Is desktop?
|
|
if strings.HasPrefix(fields[len(fields)-1], "Mattermost") {
|
|
return OriginClientDesktop
|
|
}
|
|
|
|
// Default to web
|
|
return OriginClientWeb
|
|
}
|
|
|
|
// checkCSRFToken performs a CSRF check on the provided request with the given CSRF token. Returns whether
|
|
// a CSRF check occurred and whether it succeeded.
|
|
func (h *Handler) checkCSRFToken(c *Context, r *http.Request, tokenLocation app.TokenLocation, session *model.Session) (checked bool, passed bool) {
|
|
csrfCheckNeeded := session != nil && c.Err == nil && tokenLocation == app.TokenLocationCookie && !h.TrustRequester && r.Method != "GET"
|
|
csrfCheckPassed := false
|
|
|
|
if csrfCheckNeeded {
|
|
csrfHeader := r.Header.Get(model.HeaderCsrfToken)
|
|
|
|
if csrfHeader == session.GetCSRF() {
|
|
csrfCheckPassed = true
|
|
} else if r.Header.Get(model.HeaderRequestedWith) == model.HeaderRequestedWithXML {
|
|
// ToDo(DSchalla) 2019/01/04: Remove after deprecation period and only allow CSRF Header (MM-13657)
|
|
csrfErrorMessage := "CSRF Header check failed for request - Please upgrade your web application or custom app to set a CSRF Header"
|
|
|
|
fields := []mlog.Field{
|
|
mlog.String("path", r.URL.Path),
|
|
mlog.String("ip", r.RemoteAddr),
|
|
mlog.String("session_id", session.Id),
|
|
mlog.String("user_id", session.UserId),
|
|
}
|
|
|
|
if *c.App.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement {
|
|
c.Logger.Warn(csrfErrorMessage, fields...)
|
|
} else {
|
|
c.Logger.Debug(csrfErrorMessage, fields...)
|
|
csrfCheckPassed = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return csrfCheckNeeded, csrfCheckPassed
|
|
}
|
|
|
|
// APIHandler provides a handler for API endpoints which do not require the user to be logged in order for access to be
|
|
// granted.
|
|
func (w *Web) APIHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
|
handler := &Handler{
|
|
Srv: w.srv,
|
|
HandleFunc: h,
|
|
HandlerName: GetHandlerName(h),
|
|
RequireSession: false,
|
|
TrustRequester: false,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
IsLocal: false,
|
|
}
|
|
if *w.srv.Config().ServiceSettings.WebserverMode == "gzip" {
|
|
return gzhttp.GzipHandler(handler)
|
|
}
|
|
return handler
|
|
}
|
|
|
|
// APIHandlerTrustRequester provides a handler for API endpoints which do not require the user to be logged in and are
|
|
// allowed to be requested directly rather than via javascript/XMLHttpRequest, such as site branding images or the
|
|
// websocket.
|
|
func (w *Web) APIHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
|
handler := &Handler{
|
|
Srv: w.srv,
|
|
HandleFunc: h,
|
|
HandlerName: GetHandlerName(h),
|
|
RequireSession: false,
|
|
TrustRequester: true,
|
|
RequireMfa: false,
|
|
IsStatic: false,
|
|
IsLocal: false,
|
|
}
|
|
if *w.srv.Config().ServiceSettings.WebserverMode == "gzip" {
|
|
return gzhttp.GzipHandler(handler)
|
|
}
|
|
return handler
|
|
}
|
|
|
|
// APISessionRequired provides a handler for API endpoints which require the user to be logged in in order for access to
|
|
// be granted.
|
|
func (w *Web) APISessionRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
|
handler := &Handler{
|
|
Srv: w.srv,
|
|
HandleFunc: h,
|
|
HandlerName: GetHandlerName(h),
|
|
RequireSession: true,
|
|
TrustRequester: false,
|
|
RequireMfa: true,
|
|
IsStatic: false,
|
|
IsLocal: false,
|
|
}
|
|
if *w.srv.Config().ServiceSettings.WebserverMode == "gzip" {
|
|
return gzhttp.GzipHandler(handler)
|
|
}
|
|
return handler
|
|
}
|