github.com/demisto/mattermost-server@v4.9.0-rc3+incompatible/api4/context.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	l4g "github.com/alecthomas/log4go"
    14  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    15  
    16  	"github.com/mattermost/mattermost-server/app"
    17  	"github.com/mattermost/mattermost-server/model"
    18  	"github.com/mattermost/mattermost-server/utils"
    19  )
    20  
    21  type Context struct {
    22  	App           *app.App
    23  	Session       model.Session
    24  	Params        *ApiParams
    25  	Err           *model.AppError
    26  	T             goi18n.TranslateFunc
    27  	RequestId     string
    28  	IpAddress     string
    29  	Path          string
    30  	siteURLHeader string
    31  }
    32  
    33  func (api *API) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    34  	return &handler{
    35  		app:            api.App,
    36  		handleFunc:     h,
    37  		requireSession: false,
    38  		trustRequester: false,
    39  		requireMfa:     false,
    40  	}
    41  }
    42  
    43  func (api *API) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    44  	return &handler{
    45  		app:            api.App,
    46  		handleFunc:     h,
    47  		requireSession: true,
    48  		trustRequester: false,
    49  		requireMfa:     true,
    50  	}
    51  }
    52  
    53  func (api *API) ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    54  	return &handler{
    55  		app:            api.App,
    56  		handleFunc:     h,
    57  		requireSession: true,
    58  		trustRequester: false,
    59  		requireMfa:     false,
    60  	}
    61  }
    62  
    63  func (api *API) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    64  	return &handler{
    65  		app:            api.App,
    66  		handleFunc:     h,
    67  		requireSession: false,
    68  		trustRequester: true,
    69  		requireMfa:     false,
    70  	}
    71  }
    72  
    73  func (api *API) ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    74  	return &handler{
    75  		app:            api.App,
    76  		handleFunc:     h,
    77  		requireSession: true,
    78  		trustRequester: true,
    79  		requireMfa:     true,
    80  	}
    81  }
    82  
    83  type handler struct {
    84  	app            *app.App
    85  	handleFunc     func(*Context, http.ResponseWriter, *http.Request)
    86  	requireSession bool
    87  	trustRequester bool
    88  	requireMfa     bool
    89  }
    90  
    91  func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    92  	now := time.Now()
    93  	l4g.Debug("%v - %v", r.Method, r.URL.Path)
    94  
    95  	c := &Context{}
    96  	c.App = h.app
    97  	c.T, _ = utils.GetTranslationsAndLocale(w, r)
    98  	c.RequestId = model.NewId()
    99  	c.IpAddress = utils.GetIpAddress(r)
   100  	c.Params = ApiParamsFromRequest(r)
   101  
   102  	token, tokenLocation := app.ParseAuthTokenFromRequest(r)
   103  
   104  	// CSRF Check
   105  	if tokenLocation == app.TokenLocationCookie && h.requireSession && !h.trustRequester {
   106  		if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML {
   107  			c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
   108  			token = ""
   109  		}
   110  	}
   111  
   112  	c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
   113  
   114  	w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
   115  	w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil))
   116  
   117  	w.Header().Set("Content-Type", "application/json")
   118  
   119  	if r.Method == "GET" {
   120  		w.Header().Set("Expires", "0")
   121  	}
   122  
   123  	if len(token) != 0 {
   124  		session, err := c.App.GetSession(token)
   125  
   126  		if err != nil {
   127  			l4g.Info(utils.T("api.context.invalid_session.error"), err.Error())
   128  			if err.StatusCode == http.StatusInternalServerError {
   129  				c.Err = err
   130  			} else if h.requireSession {
   131  				c.RemoveSessionCookie(w, r)
   132  				c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized)
   133  			}
   134  		} else if !session.IsOAuth && tokenLocation == app.TokenLocationQueryString {
   135  			c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized)
   136  		} else {
   137  			c.Session = *session
   138  		}
   139  
   140  		// Rate limit by UserID
   141  		if c.App.Srv.RateLimiter != nil && c.App.Srv.RateLimiter.UserIdRateLimit(c.Session.UserId, w) {
   142  			return
   143  		}
   144  	}
   145  
   146  	c.Path = r.URL.Path
   147  
   148  	if c.Err == nil && h.requireSession {
   149  		c.SessionRequired()
   150  	}
   151  
   152  	if c.Err == nil && h.requireMfa {
   153  		c.MfaRequired()
   154  	}
   155  
   156  	if c.Err == nil {
   157  		h.handleFunc(c, w, r)
   158  	}
   159  
   160  	// Handle errors that have occurred
   161  	if c.Err != nil {
   162  		c.Err.Translate(c.T)
   163  		c.Err.RequestId = c.RequestId
   164  
   165  		if c.Err.Id == "api.context.session_expired.app_error" {
   166  			c.LogInfo(c.Err)
   167  		} else {
   168  			c.LogError(c.Err)
   169  		}
   170  
   171  		c.Err.Where = r.URL.Path
   172  
   173  		// Block out detailed error when not in developer mode
   174  		if !*c.App.Config().ServiceSettings.EnableDeveloper {
   175  			c.Err.DetailedError = ""
   176  		}
   177  
   178  		w.WriteHeader(c.Err.StatusCode)
   179  		w.Write([]byte(c.Err.ToJson()))
   180  
   181  		if c.App.Metrics != nil {
   182  			c.App.Metrics.IncrementHttpError()
   183  		}
   184  	}
   185  
   186  	if c.App.Metrics != nil {
   187  		c.App.Metrics.IncrementHttpRequest()
   188  
   189  		if r.URL.Path != model.API_URL_SUFFIX+"/websocket" {
   190  			elapsed := float64(time.Since(now)) / float64(time.Second)
   191  			c.App.Metrics.ObserveHttpRequestDuration(elapsed)
   192  		}
   193  	}
   194  }
   195  
   196  func (c *Context) LogAudit(extraInfo string) {
   197  	audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
   198  	if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil {
   199  		c.LogError(r.Err)
   200  	}
   201  }
   202  
   203  func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
   204  
   205  	if len(c.Session.UserId) > 0 {
   206  		extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId)
   207  	}
   208  
   209  	audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
   210  	if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil {
   211  		c.LogError(r.Err)
   212  	}
   213  }
   214  
   215  func (c *Context) LogError(err *model.AppError) {
   216  
   217  	// Filter out 404s, endless reconnects and browser compatibility errors
   218  	if err.StatusCode == http.StatusNotFound ||
   219  		(c.Path == "/api/v3/users/websocket" && err.StatusCode == 401) ||
   220  		err.Id == "web.check_browser_compatibility.app_error" {
   221  		c.LogDebug(err)
   222  	} else {
   223  		l4g.Error(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode,
   224  			c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError)
   225  	}
   226  }
   227  
   228  func (c *Context) LogInfo(err *model.AppError) {
   229  	l4g.Info(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode,
   230  		c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError)
   231  }
   232  
   233  func (c *Context) LogDebug(err *model.AppError) {
   234  	l4g.Debug(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode,
   235  		c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError)
   236  }
   237  
   238  func (c *Context) IsSystemAdmin() bool {
   239  	return c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM)
   240  }
   241  
   242  func (c *Context) SessionRequired() {
   243  	if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN {
   244  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized)
   245  		return
   246  	}
   247  
   248  	if len(c.Session.UserId) == 0 {
   249  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized)
   250  		return
   251  	}
   252  }
   253  
   254  func (c *Context) MfaRequired() {
   255  	// Must be licensed for MFA and have it configured for enforcement
   256  	if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
   257  		return
   258  	}
   259  
   260  	// OAuth integrations are excepted
   261  	if c.Session.IsOAuth {
   262  		return
   263  	}
   264  
   265  	if user, err := c.App.GetUser(c.Session.UserId); err != nil {
   266  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized)
   267  		return
   268  	} else {
   269  		// Only required for email and ldap accounts
   270  		if user.AuthService != "" &&
   271  			user.AuthService != model.USER_AUTH_SERVICE_EMAIL &&
   272  			user.AuthService != model.USER_AUTH_SERVICE_LDAP {
   273  			return
   274  		}
   275  
   276  		// Special case to let user get themself
   277  		if c.Path == "/api/v4/users/me" {
   278  			return
   279  		}
   280  
   281  		if !user.MfaActive {
   282  			c.Err = model.NewAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired", http.StatusForbidden)
   283  			return
   284  		}
   285  	}
   286  }
   287  
   288  func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
   289  	cookie := &http.Cookie{
   290  		Name:     model.SESSION_COOKIE_TOKEN,
   291  		Value:    "",
   292  		Path:     "/",
   293  		MaxAge:   -1,
   294  		HttpOnly: true,
   295  	}
   296  
   297  	http.SetCookie(w, cookie)
   298  }
   299  
   300  func (c *Context) SetInvalidParam(parameter string) {
   301  	c.Err = NewInvalidParamError(parameter)
   302  }
   303  
   304  func (c *Context) SetInvalidUrlParam(parameter string) {
   305  	c.Err = NewInvalidUrlParamError(parameter)
   306  }
   307  
   308  func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool {
   309  	metrics := c.App.Metrics
   310  	if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 {
   311  		if et == etag {
   312  			w.Header().Set(model.HEADER_ETAG_SERVER, etag)
   313  			w.WriteHeader(http.StatusNotModified)
   314  			if metrics != nil {
   315  				metrics.IncrementEtagHitCounter(routeName)
   316  			}
   317  			return true
   318  		}
   319  	}
   320  
   321  	if metrics != nil {
   322  		metrics.IncrementEtagMissCounter(routeName)
   323  	}
   324  
   325  	return false
   326  }
   327  
   328  func NewInvalidParamError(parameter string) *model.AppError {
   329  	err := model.NewAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest)
   330  	return err
   331  }
   332  func NewInvalidUrlParamError(parameter string) *model.AppError {
   333  	err := model.NewAppError("Context", "api.context.invalid_url_param.app_error", map[string]interface{}{"Name": parameter}, "", http.StatusBadRequest)
   334  	return err
   335  }
   336  
   337  func (c *Context) SetPermissionError(permission *model.Permission) {
   338  	c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden)
   339  }
   340  
   341  func (c *Context) SetSiteURLHeader(url string) {
   342  	c.siteURLHeader = strings.TrimRight(url, "/")
   343  }
   344  
   345  func (c *Context) GetSiteURLHeader() string {
   346  	return c.siteURLHeader
   347  }
   348  
   349  func (c *Context) RequireUserId() *Context {
   350  	if c.Err != nil {
   351  		return c
   352  	}
   353  
   354  	if c.Params.UserId == model.ME {
   355  		c.Params.UserId = c.Session.UserId
   356  	}
   357  
   358  	if len(c.Params.UserId) != 26 {
   359  		c.SetInvalidUrlParam("user_id")
   360  	}
   361  	return c
   362  }
   363  
   364  func (c *Context) RequireTeamId() *Context {
   365  	if c.Err != nil {
   366  		return c
   367  	}
   368  
   369  	if len(c.Params.TeamId) != 26 {
   370  		c.SetInvalidUrlParam("team_id")
   371  	}
   372  	return c
   373  }
   374  
   375  func (c *Context) RequireInviteId() *Context {
   376  	if c.Err != nil {
   377  		return c
   378  	}
   379  
   380  	if len(c.Params.InviteId) == 0 {
   381  		c.SetInvalidUrlParam("invite_id")
   382  	}
   383  	return c
   384  }
   385  
   386  func (c *Context) RequireTokenId() *Context {
   387  	if c.Err != nil {
   388  		return c
   389  	}
   390  
   391  	if len(c.Params.TokenId) != 26 {
   392  		c.SetInvalidUrlParam("token_id")
   393  	}
   394  	return c
   395  }
   396  
   397  func (c *Context) RequireChannelId() *Context {
   398  	if c.Err != nil {
   399  		return c
   400  	}
   401  
   402  	if len(c.Params.ChannelId) != 26 {
   403  		c.SetInvalidUrlParam("channel_id")
   404  	}
   405  	return c
   406  }
   407  
   408  func (c *Context) RequireUsername() *Context {
   409  	if c.Err != nil {
   410  		return c
   411  	}
   412  
   413  	if !model.IsValidUsername(c.Params.Username) {
   414  		c.SetInvalidParam("username")
   415  	}
   416  
   417  	return c
   418  }
   419  
   420  func (c *Context) RequirePostId() *Context {
   421  	if c.Err != nil {
   422  		return c
   423  	}
   424  
   425  	if len(c.Params.PostId) != 26 {
   426  		c.SetInvalidUrlParam("post_id")
   427  	}
   428  	return c
   429  }
   430  
   431  func (c *Context) RequireAppId() *Context {
   432  	if c.Err != nil {
   433  		return c
   434  	}
   435  
   436  	if len(c.Params.AppId) != 26 {
   437  		c.SetInvalidUrlParam("app_id")
   438  	}
   439  	return c
   440  }
   441  
   442  func (c *Context) RequireFileId() *Context {
   443  	if c.Err != nil {
   444  		return c
   445  	}
   446  
   447  	if len(c.Params.FileId) != 26 {
   448  		c.SetInvalidUrlParam("file_id")
   449  	}
   450  
   451  	return c
   452  }
   453  
   454  func (c *Context) RequireFilename() *Context {
   455  	if c.Err != nil {
   456  		return c
   457  	}
   458  
   459  	if len(c.Params.Filename) == 0 {
   460  		c.SetInvalidUrlParam("filename")
   461  	}
   462  
   463  	return c
   464  }
   465  
   466  func (c *Context) RequirePluginId() *Context {
   467  	if c.Err != nil {
   468  		return c
   469  	}
   470  
   471  	if len(c.Params.PluginId) == 0 {
   472  		c.SetInvalidUrlParam("plugin_id")
   473  	}
   474  
   475  	return c
   476  }
   477  
   478  func (c *Context) RequireReportId() *Context {
   479  	if c.Err != nil {
   480  		return c
   481  	}
   482  
   483  	if len(c.Params.ReportId) != 26 {
   484  		c.SetInvalidUrlParam("report_id")
   485  	}
   486  	return c
   487  }
   488  
   489  func (c *Context) RequireEmojiId() *Context {
   490  	if c.Err != nil {
   491  		return c
   492  	}
   493  
   494  	if len(c.Params.EmojiId) != 26 {
   495  		c.SetInvalidUrlParam("emoji_id")
   496  	}
   497  	return c
   498  }
   499  
   500  func (c *Context) RequireTeamName() *Context {
   501  	if c.Err != nil {
   502  		return c
   503  	}
   504  
   505  	if !model.IsValidTeamName(c.Params.TeamName) {
   506  		c.SetInvalidUrlParam("team_name")
   507  	}
   508  
   509  	return c
   510  }
   511  
   512  func (c *Context) RequireChannelName() *Context {
   513  	if c.Err != nil {
   514  		return c
   515  	}
   516  
   517  	if !model.IsValidChannelIdentifier(c.Params.ChannelName) {
   518  		c.SetInvalidUrlParam("channel_name")
   519  	}
   520  
   521  	return c
   522  }
   523  
   524  func (c *Context) RequireEmail() *Context {
   525  	if c.Err != nil {
   526  		return c
   527  	}
   528  
   529  	if !model.IsValidEmail(c.Params.Email) {
   530  		c.SetInvalidUrlParam("email")
   531  	}
   532  
   533  	return c
   534  }
   535  
   536  func (c *Context) RequireCategory() *Context {
   537  	if c.Err != nil {
   538  		return c
   539  	}
   540  
   541  	if !model.IsValidAlphaNumHyphenUnderscore(c.Params.Category, true) {
   542  		c.SetInvalidUrlParam("category")
   543  	}
   544  
   545  	return c
   546  }
   547  
   548  func (c *Context) RequireService() *Context {
   549  	if c.Err != nil {
   550  		return c
   551  	}
   552  
   553  	if len(c.Params.Service) == 0 {
   554  		c.SetInvalidUrlParam("service")
   555  	}
   556  
   557  	return c
   558  }
   559  
   560  func (c *Context) RequirePreferenceName() *Context {
   561  	if c.Err != nil {
   562  		return c
   563  	}
   564  
   565  	if !model.IsValidAlphaNumHyphenUnderscore(c.Params.PreferenceName, true) {
   566  		c.SetInvalidUrlParam("preference_name")
   567  	}
   568  
   569  	return c
   570  }
   571  
   572  func (c *Context) RequireEmojiName() *Context {
   573  	if c.Err != nil {
   574  		return c
   575  	}
   576  
   577  	validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`)
   578  
   579  	if len(c.Params.EmojiName) == 0 || len(c.Params.EmojiName) > model.EMOJI_NAME_MAX_LENGTH || !validName.MatchString(c.Params.EmojiName) {
   580  		c.SetInvalidUrlParam("emoji_name")
   581  	}
   582  
   583  	return c
   584  }
   585  
   586  func (c *Context) RequireHookId() *Context {
   587  	if c.Err != nil {
   588  		return c
   589  	}
   590  
   591  	if len(c.Params.HookId) != 26 {
   592  		c.SetInvalidUrlParam("hook_id")
   593  	}
   594  
   595  	return c
   596  }
   597  
   598  func (c *Context) RequireCommandId() *Context {
   599  	if c.Err != nil {
   600  		return c
   601  	}
   602  
   603  	if len(c.Params.CommandId) != 26 {
   604  		c.SetInvalidUrlParam("command_id")
   605  	}
   606  	return c
   607  }
   608  
   609  func (c *Context) RequireJobId() *Context {
   610  	if c.Err != nil {
   611  		return c
   612  	}
   613  
   614  	if len(c.Params.JobId) != 26 {
   615  		c.SetInvalidUrlParam("job_id")
   616  	}
   617  	return c
   618  }
   619  
   620  func (c *Context) RequireJobType() *Context {
   621  	if c.Err != nil {
   622  		return c
   623  	}
   624  
   625  	if len(c.Params.JobType) == 0 || len(c.Params.JobType) > 32 {
   626  		c.SetInvalidUrlParam("job_type")
   627  	}
   628  	return c
   629  }
   630  
   631  func (c *Context) RequireActionId() *Context {
   632  	if c.Err != nil {
   633  		return c
   634  	}
   635  
   636  	if len(c.Params.ActionId) != 26 {
   637  		c.SetInvalidUrlParam("action_id")
   638  	}
   639  	return c
   640  }
   641  
   642  func (c *Context) RequireRoleId() *Context {
   643  	if c.Err != nil {
   644  		return c
   645  	}
   646  
   647  	if len(c.Params.RoleId) != 26 {
   648  		c.SetInvalidUrlParam("role_id")
   649  	}
   650  	return c
   651  }
   652  
   653  func (c *Context) RequireRoleName() *Context {
   654  	if c.Err != nil {
   655  		return c
   656  	}
   657  
   658  	if !model.IsValidRoleName(c.Params.RoleName) {
   659  		c.SetInvalidUrlParam("role_name")
   660  	}
   661  
   662  	return c
   663  }