github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/api/context.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  	"sync/atomic"
    12  	"time"
    13  
    14  	l4g "github.com/alecthomas/log4go"
    15  	"github.com/gorilla/mux"
    16  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    17  
    18  	"github.com/mattermost/mattermost-server/app"
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/utils"
    21  )
    22  
    23  type Context struct {
    24  	App           *app.App
    25  	Session       model.Session
    26  	RequestId     string
    27  	IpAddress     string
    28  	Path          string
    29  	Err           *model.AppError
    30  	siteURLHeader string
    31  	teamURLValid  bool
    32  	teamURL       string
    33  	T             goi18n.TranslateFunc
    34  	Locale        string
    35  	TeamId        string
    36  }
    37  
    38  func (api *API) ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    39  	return &handler{api.App, h, false, false, true, false, false, false, false}
    40  }
    41  
    42  func (api *API) AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    43  	return &handler{api.App, h, false, false, false, false, false, false, false}
    44  }
    45  
    46  func (api *API) AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    47  	return &handler{api.App, h, false, false, false, false, true, false, false}
    48  }
    49  
    50  func (api *API) ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    51  	return &handler{api.App, h, true, false, true, false, false, false, true}
    52  }
    53  
    54  func (api *API) ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
    55  	return &handler{api.App, h, true, false, true, isUserActivity, false, false, true}
    56  }
    57  
    58  func (api *API) ApiUserRequiredMfa(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    59  	return &handler{api.App, h, true, false, true, false, false, false, false}
    60  }
    61  
    62  func (api *API) UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    63  	return &handler{api.App, h, true, false, false, false, false, false, true}
    64  }
    65  
    66  func (api *API) AppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    67  	return &handler{api.App, h, false, false, false, false, false, true, false}
    68  }
    69  
    70  func (api *API) ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    71  	return &handler{api.App, h, true, true, true, false, false, false, true}
    72  }
    73  
    74  func (api *API) ApiAdminSystemRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    75  	return &handler{api.App, h, true, true, true, false, false, true, true}
    76  }
    77  
    78  func (api *API) ApiAppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    79  	return &handler{api.App, h, false, false, true, false, false, true, false}
    80  }
    81  
    82  func (api *API) ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    83  	return &handler{api.App, h, true, false, true, false, false, true, true}
    84  }
    85  
    86  func (api *API) ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
    87  	return &handler{api.App, h, false, false, true, false, true, true, false}
    88  }
    89  
    90  type handler struct {
    91  	app                *app.App
    92  	handleFunc         func(*Context, http.ResponseWriter, *http.Request)
    93  	requireUser        bool
    94  	requireSystemAdmin bool
    95  	isApi              bool
    96  	isUserActivity     bool
    97  	isTeamIndependent  bool
    98  	trustRequester     bool
    99  	requireMfa         bool
   100  }
   101  
   102  func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   103  	now := time.Now()
   104  	l4g.Debug("%v", r.URL.Path)
   105  
   106  	c := &Context{}
   107  	c.App = h.app
   108  	c.T, c.Locale = utils.GetTranslationsAndLocale(w, r)
   109  	c.RequestId = model.NewId()
   110  	c.IpAddress = utils.GetIpAddress(r)
   111  	c.TeamId = mux.Vars(r)["team_id"]
   112  
   113  	if metrics := c.App.Metrics; metrics != nil && h.isApi {
   114  		metrics.IncrementHttpRequest()
   115  	}
   116  
   117  	token := ""
   118  	isTokenFromQueryString := false
   119  
   120  	// Attempt to parse token out of the header
   121  	authHeader := r.Header.Get(model.HEADER_AUTH)
   122  	if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER {
   123  		// Default session token
   124  		token = authHeader[7:]
   125  
   126  	} else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN {
   127  		// OAuth token
   128  		token = authHeader[6:]
   129  	}
   130  
   131  	// Attempt to parse the token from the cookie
   132  	if len(token) == 0 {
   133  		if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
   134  			token = cookie.Value
   135  
   136  			if (h.requireSystemAdmin || h.requireUser) && !h.trustRequester {
   137  				if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML {
   138  					c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
   139  					token = ""
   140  				}
   141  			}
   142  		}
   143  	}
   144  
   145  	// Attempt to parse token out of the query string
   146  	if len(token) == 0 {
   147  		token = r.URL.Query().Get("access_token")
   148  		isTokenFromQueryString = true
   149  	}
   150  
   151  	c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
   152  
   153  	w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
   154  	w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), utils.IsLicensed()))
   155  
   156  	// Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
   157  	if !h.isApi {
   158  		w.Header().Set("X-Frame-Options", "SAMEORIGIN")
   159  		w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'")
   160  	} else {
   161  		// All api response bodies will be JSON formatted by default
   162  		w.Header().Set("Content-Type", "application/json")
   163  
   164  		if r.Method == "GET" {
   165  			w.Header().Set("Expires", "0")
   166  		}
   167  	}
   168  
   169  	if len(token) != 0 {
   170  		session, err := c.App.GetSession(token)
   171  
   172  		if err != nil {
   173  			l4g.Error(utils.T("api.context.invalid_session.error"), err.Error())
   174  			c.RemoveSessionCookie(w, r)
   175  			if h.requireUser || h.requireSystemAdmin {
   176  				c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized)
   177  			}
   178  		} else if !session.IsOAuth && isTokenFromQueryString {
   179  			c.Err = model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized)
   180  		} else {
   181  			c.Session = *session
   182  		}
   183  	}
   184  
   185  	if h.isApi || h.isTeamIndependent {
   186  		c.setTeamURL(c.GetSiteURLHeader(), false)
   187  		c.Path = r.URL.Path
   188  	} else {
   189  		splitURL := strings.Split(r.URL.Path, "/")
   190  		c.setTeamURL(c.GetSiteURLHeader()+"/"+splitURL[1], true)
   191  		c.Path = "/" + strings.Join(splitURL[2:], "/")
   192  	}
   193  
   194  	if h.isApi && !*c.App.Config().ServiceSettings.EnableAPIv3 {
   195  		c.Err = model.NewAppError("ServeHTTP", "api.context.v3_disabled.app_error", nil, "", http.StatusNotImplemented)
   196  	}
   197  
   198  	if c.Err == nil && h.requireUser {
   199  		c.UserRequired()
   200  	}
   201  
   202  	if c.Err == nil && h.requireMfa {
   203  		c.MfaRequired()
   204  	}
   205  
   206  	if c.Err == nil && h.requireSystemAdmin {
   207  		c.SystemAdminRequired()
   208  	}
   209  
   210  	if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 {
   211  		c.App.SetStatusOnline(c.Session.UserId, c.Session.Id, false)
   212  		c.App.UpdateLastActivityAtIfNeeded(c.Session)
   213  	}
   214  
   215  	if c.Err == nil && (h.requireUser || h.requireSystemAdmin) {
   216  		//check if teamId exist
   217  		c.CheckTeamId()
   218  	}
   219  
   220  	if h.isApi {
   221  		atomic.StoreInt32(model.UsedApiV3, 1)
   222  	}
   223  
   224  	if c.Err == nil {
   225  		h.handleFunc(c, w, r)
   226  	}
   227  
   228  	// Handle errors that have occoured
   229  	if c.Err != nil {
   230  		c.Err.Translate(c.T)
   231  		c.Err.RequestId = c.RequestId
   232  		c.LogError(c.Err)
   233  		c.Err.Where = r.URL.Path
   234  
   235  		// Block out detailed error when not in developer mode
   236  		if !*c.App.Config().ServiceSettings.EnableDeveloper {
   237  			c.Err.DetailedError = ""
   238  		}
   239  
   240  		if h.isApi {
   241  			w.WriteHeader(c.Err.StatusCode)
   242  			w.Write([]byte(c.Err.ToJson()))
   243  
   244  			if c.App.Metrics != nil {
   245  				c.App.Metrics.IncrementHttpError()
   246  			}
   247  		} else {
   248  			if c.Err.StatusCode == http.StatusUnauthorized {
   249  				http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
   250  			} else {
   251  				utils.RenderWebError(c.Err, w, r)
   252  			}
   253  		}
   254  
   255  	}
   256  
   257  	if h.isApi && c.App.Metrics != nil {
   258  		if r.URL.Path != model.API_URL_SUFFIX_V3+"/users/websocket" {
   259  			elapsed := float64(time.Since(now)) / float64(time.Second)
   260  			c.App.Metrics.ObserveHttpRequestDuration(elapsed)
   261  		}
   262  	}
   263  }
   264  
   265  func (c *Context) LogAudit(extraInfo string) {
   266  	audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
   267  	if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil {
   268  		c.LogError(r.Err)
   269  	}
   270  }
   271  
   272  func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
   273  
   274  	if len(c.Session.UserId) > 0 {
   275  		extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId)
   276  	}
   277  
   278  	audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
   279  	if r := <-c.App.Srv.Store.Audit().Save(audit); r.Err != nil {
   280  		c.LogError(r.Err)
   281  	}
   282  }
   283  
   284  func (c *Context) LogError(err *model.AppError) {
   285  
   286  	// filter out endless reconnects
   287  	if c.Path == "/api/v3/users/websocket" && err.StatusCode == 401 || err.Id == "web.check_browser_compatibility.app_error" {
   288  		c.LogDebug(err)
   289  	} else if err.Id != "api.post.create_post.town_square_read_only" {
   290  		l4g.Error(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode,
   291  			c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError)
   292  	}
   293  }
   294  
   295  func (c *Context) LogDebug(err *model.AppError) {
   296  	l4g.Debug(utils.TDefault("api.context.log.error"), c.Path, err.Where, err.StatusCode,
   297  		c.RequestId, c.Session.UserId, c.IpAddress, err.SystemMessage(utils.TDefault), err.DetailedError)
   298  }
   299  
   300  func (c *Context) UserRequired() {
   301  	if !*c.App.Config().ServiceSettings.EnableUserAccessTokens && c.Session.Props[model.SESSION_PROP_TYPE] == model.SESSION_TYPE_USER_ACCESS_TOKEN {
   302  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserAccessToken", http.StatusUnauthorized)
   303  		return
   304  	}
   305  
   306  	if len(c.Session.UserId) == 0 {
   307  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized)
   308  		return
   309  	}
   310  }
   311  
   312  func (c *Context) MfaRequired() {
   313  	// Must be licensed for MFA and have it configured for enforcement
   314  	if !utils.IsLicensed() || !*utils.License().Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
   315  		return
   316  	}
   317  
   318  	// OAuth integrations are excepted
   319  	if c.Session.IsOAuth {
   320  		return
   321  	}
   322  
   323  	if result := <-c.App.Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
   324  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "MfaRequired", http.StatusUnauthorized)
   325  		return
   326  	} else {
   327  		user := result.Data.(*model.User)
   328  
   329  		// Only required for email and ldap accounts
   330  		if user.AuthService != "" &&
   331  			user.AuthService != model.USER_AUTH_SERVICE_EMAIL &&
   332  			user.AuthService != model.USER_AUTH_SERVICE_LDAP {
   333  			return
   334  		}
   335  
   336  		if !user.MfaActive {
   337  			c.Err = model.NewAppError("", "api.context.mfa_required.app_error", nil, "MfaRequired", http.StatusUnauthorized)
   338  			return
   339  		}
   340  	}
   341  }
   342  
   343  func (c *Context) SystemAdminRequired() {
   344  	if len(c.Session.UserId) == 0 {
   345  		c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "SystemAdminRequired", http.StatusUnauthorized)
   346  		return
   347  	} else if !c.IsSystemAdmin() {
   348  		c.Err = model.NewAppError("", "api.context.permissions.app_error", nil, "AdminRequired", http.StatusForbidden)
   349  		return
   350  	}
   351  }
   352  
   353  func (c *Context) IsSystemAdmin() bool {
   354  	return c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM)
   355  }
   356  
   357  func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
   358  	cookie := &http.Cookie{
   359  		Name:     model.SESSION_COOKIE_TOKEN,
   360  		Value:    "",
   361  		Path:     "/",
   362  		MaxAge:   -1,
   363  		HttpOnly: true,
   364  	}
   365  
   366  	userCookie := &http.Cookie{
   367  		Name:   model.SESSION_COOKIE_USER,
   368  		Value:  "",
   369  		Path:   "/",
   370  		MaxAge: -1,
   371  	}
   372  
   373  	http.SetCookie(w, cookie)
   374  	http.SetCookie(w, userCookie)
   375  }
   376  
   377  func (c *Context) SetInvalidParam(where string, name string) {
   378  	c.Err = NewInvalidParamError(where, name)
   379  }
   380  
   381  func NewInvalidParamError(where string, name string) *model.AppError {
   382  	err := model.NewAppError(where, "api.context.invalid_param.app_error", map[string]interface{}{"Name": name}, "", http.StatusBadRequest)
   383  	return err
   384  }
   385  
   386  func (c *Context) SetUnknownError(where string, details string) {
   387  	c.Err = model.NewAppError(where, "api.context.unknown.app_error", nil, details, http.StatusInternalServerError)
   388  }
   389  
   390  func (c *Context) SetPermissionError(permission *model.Permission) {
   391  	c.Err = model.NewAppError("Permissions", "api.context.permissions.app_error", nil, "userId="+c.Session.UserId+", "+"permission="+permission.Id, http.StatusForbidden)
   392  }
   393  
   394  func (c *Context) setTeamURL(url string, valid bool) {
   395  	c.teamURL = url
   396  	c.teamURLValid = valid
   397  }
   398  
   399  func (c *Context) SetTeamURLFromSession() {
   400  	if result := <-c.App.Srv.Store.Team().Get(c.TeamId); result.Err == nil {
   401  		c.setTeamURL(c.GetSiteURLHeader()+"/"+result.Data.(*model.Team).Name, true)
   402  	}
   403  }
   404  
   405  func (c *Context) SetSiteURLHeader(url string) {
   406  	c.siteURLHeader = strings.TrimRight(url, "/")
   407  }
   408  
   409  // TODO see where these are used
   410  func (c *Context) GetTeamURLFromTeam(team *model.Team) string {
   411  	return c.GetSiteURLHeader() + "/" + team.Name
   412  }
   413  
   414  func (c *Context) GetTeamURL() string {
   415  	if !c.teamURLValid {
   416  		c.SetTeamURLFromSession()
   417  		if !c.teamURLValid {
   418  			l4g.Debug(utils.T("api.context.invalid_team_url.debug"))
   419  		}
   420  	}
   421  	return c.teamURL
   422  }
   423  
   424  func (c *Context) GetSiteURLHeader() string {
   425  	return c.siteURLHeader
   426  }
   427  
   428  func (c *Context) GetCurrentTeamMember() *model.TeamMember {
   429  	return c.Session.GetTeamByTeamId(c.TeamId)
   430  }
   431  
   432  func (c *Context) HandleEtag(etag string, routeName string, w http.ResponseWriter, r *http.Request) bool {
   433  	metrics := c.App.Metrics
   434  	if et := r.Header.Get(model.HEADER_ETAG_CLIENT); len(etag) > 0 {
   435  		if et == etag {
   436  			w.Header().Set(model.HEADER_ETAG_SERVER, etag)
   437  			w.WriteHeader(http.StatusNotModified)
   438  			if metrics != nil {
   439  				metrics.IncrementEtagHitCounter(routeName)
   440  			}
   441  			return true
   442  		}
   443  	}
   444  
   445  	if metrics != nil {
   446  		metrics.IncrementEtagMissCounter(routeName)
   447  	}
   448  
   449  	return false
   450  }
   451  
   452  func IsApiCall(r *http.Request) bool {
   453  	return strings.Index(r.URL.Path, "/api/") == 0
   454  }
   455  
   456  func Handle404(w http.ResponseWriter, r *http.Request) {
   457  	err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound)
   458  
   459  	l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))
   460  
   461  	if IsApiCall(r) {
   462  		w.WriteHeader(err.StatusCode)
   463  		err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'.  Typo? are you missing a team_id or user_id as part of the url?"
   464  		w.Write([]byte(err.ToJson()))
   465  	} else {
   466  		utils.RenderWebError(err, w, r)
   467  	}
   468  }
   469  
   470  func (c *Context) CheckTeamId() {
   471  	if c.TeamId != "" && c.Session.GetTeamByTeamId(c.TeamId) == nil {
   472  		if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
   473  			if result := <-c.App.Srv.Store.Team().Get(c.TeamId); result.Err != nil {
   474  				c.Err = result.Err
   475  				c.Err.StatusCode = http.StatusBadRequest
   476  				return
   477  			}
   478  		} else {
   479  			c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
   480  			return
   481  		}
   482  	}
   483  }