context.go 14 KB
Newer Older
1
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
=Corey Hulen's avatar
=Corey Hulen committed
2 3 4 5 6
// See License.txt for license information.

package api

import (
7
	"fmt"
=Corey Hulen's avatar
=Corey Hulen committed
8 9 10 11
	"net"
	"net/http"
	"net/url"
	"strings"
12 13 14 15 16

	l4g "code.google.com/p/log4go"
	"github.com/mattermost/platform/model"
	"github.com/mattermost/platform/store"
	"github.com/mattermost/platform/utils"
=Corey Hulen's avatar
=Corey Hulen committed
17 18 19 20 21
)

var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE)

type Context struct {
22 23 24 25 26 27 28 29
	Session      model.Session
	RequestId    string
	IpAddress    string
	Path         string
	Err          *model.AppError
	teamURLValid bool
	teamURL      string
	siteURL      string
=Corey Hulen's avatar
=Corey Hulen committed
30 31 32
}

type Page struct {
=Corey Hulen's avatar
=Corey Hulen committed
33 34 35 36 37 38
	TemplateName     string
	Props            map[string]string
	ClientCfg        map[string]string
	User             *model.User
	Team             *model.Team
	SessionTokenHash string
=Corey Hulen's avatar
=Corey Hulen committed
39 40 41
}

func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
42
	return &handler{h, false, false, true, false, false}
=Corey Hulen's avatar
=Corey Hulen committed
43 44 45
}

func AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
46 47 48 49 50
	return &handler{h, false, false, false, false, false}
}

func AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
	return &handler{h, false, false, false, false, true}
=Corey Hulen's avatar
=Corey Hulen committed
51 52 53
}

func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
54
	return &handler{h, true, false, true, true, false}
=Corey Hulen's avatar
=Corey Hulen committed
55 56 57
}

func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
58
	return &handler{h, true, false, true, isUserActivity, false}
=Corey Hulen's avatar
=Corey Hulen committed
59 60 61
}

func UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
62
	return &handler{h, true, false, false, false, false}
=Corey Hulen's avatar
=Corey Hulen committed
63 64 65
}

func ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
66
	return &handler{h, true, true, true, false, false}
=Corey Hulen's avatar
=Corey Hulen committed
67 68 69 70 71 72 73 74
}

type handler struct {
	handleFunc         func(*Context, http.ResponseWriter, *http.Request)
	requireUser        bool
	requireSystemAdmin bool
	isApi              bool
	isUserActivity     bool
75
	isTeamIndependent  bool
=Corey Hulen's avatar
=Corey Hulen committed
76 77 78 79 80 81 82 83 84 85
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	l4g.Debug("%v", r.URL.Path)

	c := &Context{}
	c.RequestId = model.NewId()
	c.IpAddress = GetIpAddress(r)

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	token := ""
	isTokenFromQueryString := false

	// Attempt to parse token out of the header
	authHeader := r.Header.Get(model.HEADER_AUTH)
	if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER {
		// Default session token
		token = authHeader[7:]

	} else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN {
		// OAuth token
		token = authHeader[6:]
	}

	// Attempt to parse the token from the cookie
	if len(token) == 0 {
=Corey Hulen's avatar
=Corey Hulen committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
		if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
			multiToken := cookie.Value

			fmt.Println(">>>>>>>> multiToken: " + multiToken)

			if len(multiToken) > 0 {
				tokens := strings.Split(multiToken, " ")

				// If there is only 1 token in the cookie then just use it like normal
				if len(tokens) == 1 {
					token = multiToken
				} else {
					// If it is a multi-session token then find the correct session
					sessionTokenHash := r.Header.Get(model.HEADER_MM_SESSION_TOKEN_HASH)
					fmt.Println(">>>>>>>> sessionHash: " + sessionTokenHash + " url=" + r.URL.Path)
					for _, t := range tokens {
						if sessionTokenHash == model.HashPassword(t) {
							token = token
							break
						}
					}
				}
			}
125 126 127 128 129 130 131 132 133
		}
	}

	// Attempt to parse token out of the query string
	if len(token) == 0 {
		token = r.URL.Query().Get("access_token")
		isTokenFromQueryString = true
	}

134
	protocol := GetProtocol(r)
135
	c.setSiteURL(protocol + "://" + r.Host)
=Corey Hulen's avatar
=Corey Hulen committed
136 137

	w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
=Corey Hulen's avatar
=Corey Hulen committed
138
	w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v", model.CurrentVersion, utils.CfgLastModified))
139 140 141 142 143

	// Instruct the browser not to display us in an iframe for anti-clickjacking
	if !h.isApi {
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Content-Security-Policy", "frame-ancestors none")
144
	} else {
145
		// All api response bodies will be JSON formatted by default
146
		w.Header().Set("Content-Type", "application/json")
147
	}
=Corey Hulen's avatar
=Corey Hulen committed
148

149
	if len(token) != 0 {
150
		session := GetSession(token)
=Corey Hulen's avatar
=Corey Hulen committed
151 152

		if session == nil || session.IsExpired() {
153
			c.RemoveSessionCookie(w, r)
154 155 156 157
			c.Err = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "token="+token)
			c.Err.StatusCode = http.StatusUnauthorized
		} else if !session.IsOAuth && isTokenFromQueryString {
			c.Err = model.NewAppError("ServeHTTP", "Session is not OAuth but token was provided in the query string", "token="+token)
=Corey Hulen's avatar
=Corey Hulen committed
158 159 160 161 162 163
			c.Err.StatusCode = http.StatusUnauthorized
		} else {
			c.Session = *session
		}
	}

164 165 166 167 168 169 170 171 172
	if h.isApi || h.isTeamIndependent {
		c.setTeamURL(c.GetSiteURL(), false)
		c.Path = r.URL.Path
	} else {
		splitURL := strings.Split(r.URL.Path, "/")
		c.setTeamURL(protocol+"://"+r.Host+"/"+splitURL[1], true)
		c.Path = "/" + strings.Join(splitURL[2:], "/")
	}

=Corey Hulen's avatar
=Corey Hulen committed
173 174 175 176 177 178 179 180
	if c.Err == nil && h.requireUser {
		c.UserRequired()
	}

	if c.Err == nil && h.requireSystemAdmin {
		c.SystemAdminRequired()
	}

181
	if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 {
=Corey Hulen's avatar
=Corey Hulen committed
182
		go func() {
183 184
			if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, c.Session.Id, model.GetMillis())).Err; err != nil {
				l4g.Error("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", c.Session.UserId, c.Session.Id, err)
=Corey Hulen's avatar
=Corey Hulen committed
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
			}
		}()
	}

	if c.Err == nil {
		h.handleFunc(c, w, r)
	}

	if c.Err != nil {
		c.Err.RequestId = c.RequestId
		c.LogError(c.Err)
		c.Err.Where = r.URL.Path

		if h.isApi {
			w.WriteHeader(c.Err.StatusCode)
			w.Write([]byte(c.Err.ToJson()))
		} else {
			if c.Err.StatusCode == http.StatusUnauthorized {
=Corey Hulen's avatar
=Corey Hulen committed
203
				fmt.Println("!!!!!!!!!!!!!!!! url=" + r.URL.Path)
204
				http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
=Corey Hulen's avatar
=Corey Hulen committed
205 206 207 208 209 210 211
			} else {
				RenderWebError(c.Err, w, r)
			}
		}
	}
}

212 213 214 215 216 217 218 219
func GetProtocol(r *http.Request) string {
	if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" {
		return "https"
	} else {
		return "http"
	}
}

=Corey Hulen's avatar
=Corey Hulen committed
220
func (c *Context) LogAudit(extraInfo string) {
221
	audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
222 223 224
	if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
		c.LogError(r.Err)
	}
=Corey Hulen's avatar
=Corey Hulen committed
225 226 227 228
}

func (c *Context) LogAuditWithUserId(userId, extraInfo string) {

229 230 231
	if len(c.Session.UserId) > 0 {
		extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId)
	}
=Corey Hulen's avatar
=Corey Hulen committed
232

233
	audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
234 235 236
	if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
		c.LogError(r.Err)
	}
=Corey Hulen's avatar
=Corey Hulen committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
}

func (c *Context) LogError(err *model.AppError) {
	l4g.Error("%v:%v code=%v rid=%v uid=%v ip=%v %v [details: %v]", c.Path, err.Where, err.StatusCode,
		c.RequestId, c.Session.UserId, c.IpAddress, err.Message, err.DetailedError)
}

func (c *Context) UserRequired() {
	if len(c.Session.UserId) == 0 {
		c.Err = model.NewAppError("", "Invalid or expired session, please login again.", "UserRequired")
		c.Err.StatusCode = http.StatusUnauthorized
		return
	}
}

func (c *Context) SystemAdminRequired() {
	if len(c.Session.UserId) == 0 {
		c.Err = model.NewAppError("", "Invalid or expired session, please login again.", "SystemAdminRequired")
		c.Err.StatusCode = http.StatusUnauthorized
		return
	} else if !c.IsSystemAdmin() {
		c.Err = model.NewAppError("", "You do not have the appropriate permissions", "AdminRequired")
		c.Err.StatusCode = http.StatusForbidden
		return
	}
}

func (c *Context) HasPermissionsToUser(userId string, where string) bool {

	// You are the user
	if c.Session.UserId == userId {
		return true
	}

	// You're a mattermost system admin and you're on the VPN
	if c.IsSystemAdmin() {
		return true
	}

	c.Err = model.NewAppError(where, "You do not have the appropriate permissions", "userId="+userId)
	c.Err.StatusCode = http.StatusForbidden
	return false
}

func (c *Context) HasPermissionsToTeam(teamId string, where string) bool {
	if c.Session.TeamId == teamId {
		return true
	}

	// You're a mattermost system admin and you're on the VPN
	if c.IsSystemAdmin() {
		return true
	}

	c.Err = model.NewAppError(where, "You do not have the appropriate permissions", "userId="+c.Session.UserId+", teamId="+teamId)
	c.Err.StatusCode = http.StatusForbidden
	return false
}

func (c *Context) HasPermissionsToChannel(sc store.StoreChannel, where string) bool {
	if cresult := <-sc; cresult.Err != nil {
		c.Err = cresult.Err
		return false
	} else if cresult.Data.(int64) != 1 {
		c.Err = model.NewAppError(where, "You do not have the appropriate permissions", "userId="+c.Session.UserId)
		c.Err.StatusCode = http.StatusForbidden
		return false
	}

	return true
}

=Corey Hulen's avatar
=Corey Hulen committed
309 310 311 312 313 314 315 316 317 318
func (c *Context) HasSystemAdminPermissions(where string) bool {
	if c.IsSystemAdmin() {
		return true
	}

	c.Err = model.NewAppError(where, "You do not have the appropriate permissions", "userId="+c.Session.UserId)
	c.Err.StatusCode = http.StatusForbidden
	return false
}

319 320 321 322 323 324 325
func (c *Context) IsSystemAdmin() bool {
	if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) {
		return true
	}
	return false
}

326
func (c *Context) IsTeamAdmin() bool {
327 328
	if model.IsInRole(c.Session.Roles, model.ROLE_TEAM_ADMIN) || c.IsSystemAdmin() {
		return true
329
	}
330
	return false
331 332
}

333
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
=Corey Hulen's avatar
=Corey Hulen committed
334

335
	multiToken := ""
=Corey Hulen's avatar
=Corey Hulen committed
336
	if oldMultiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
337 338 339 340
		multiToken = oldMultiCookie.Value
	}

	multiCookie := &http.Cookie{
=Corey Hulen's avatar
=Corey Hulen committed
341
		Name:     model.SESSION_COOKIE_TOKEN,
342 343 344 345 346 347 348
		Value:    strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
		Path:     "/",
		MaxAge:   model.SESSION_TIME_WEB_IN_SECS,
		HttpOnly: true,
	}

	http.SetCookie(w, multiCookie)
=Corey Hulen's avatar
=Corey Hulen committed
349 350 351 352 353 354 355 356 357 358 359
}

func (c *Context) SetInvalidParam(where string, name string) {
	c.Err = model.NewAppError(where, "Invalid "+name+" parameter", "")
	c.Err.StatusCode = http.StatusBadRequest
}

func (c *Context) SetUnknownError(where string, details string) {
	c.Err = model.NewAppError(where, "An unknown error has occured. Please contact support.", details)
}

360 361 362 363 364
func (c *Context) setTeamURL(url string, valid bool) {
	c.teamURL = url
	c.teamURLValid = valid
}

365
func (c *Context) SetTeamURLFromSession() {
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
	if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil {
		c.setTeamURL(c.GetSiteURL()+"/"+result.Data.(*model.Team).Name, true)
	}
}

func (c *Context) setSiteURL(url string) {
	c.siteURL = url
}

func (c *Context) GetTeamURLFromTeam(team *model.Team) string {
	return c.GetSiteURL() + "/" + team.Name
}

func (c *Context) GetTeamURL() string {
	if !c.teamURLValid {
381
		c.SetTeamURLFromSession()
382 383 384 385 386 387 388 389 390 391 392
		if !c.teamURLValid {
			l4g.Debug("TeamURL accessed when not valid. Team URL should not be used in api functions or those that are team independent")
		}
	}
	return c.teamURL
}

func (c *Context) GetSiteURL() string {
	return c.siteURL
}

=Corey Hulen's avatar
=Corey Hulen committed
393 394
func GetIpAddress(r *http.Request) string {
	address := r.Header.Get(model.HEADER_FORWARDED)
395 396 397 398 399

	if len(address) == 0 {
		address = r.Header.Get(model.HEADER_REAL_IP)
	}

=Corey Hulen's avatar
=Corey Hulen committed
400 401 402 403 404 405 406 407 408 409 410 411 412
	if len(address) == 0 {
		address, _, _ = net.SplitHostPort(r.RemoteAddr)
	}

	return address
}

func IsTestDomain(r *http.Request) bool {

	if strings.Index(r.Host, "localhost") == 0 {
		return true
	}

=Corey Hulen's avatar
=Corey Hulen committed
413 414 415 416
	if strings.Index(r.Host, "dockerhost") == 0 {
		return true
	}

=Corey Hulen's avatar
=Corey Hulen committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
	if strings.Index(r.Host, "test") == 0 {
		return true
	}

	if strings.Index(r.Host, "127.0.") == 0 {
		return true
	}

	if strings.Index(r.Host, "192.168.") == 0 {
		return true
	}

	if strings.Index(r.Host, "10.") == 0 {
		return true
	}

	if strings.Index(r.Host, "176.") == 0 {
		return true
	}

	return false
}

func IsBetaDomain(r *http.Request) bool {

	if strings.Index(r.Host, "beta") == 0 {
		return true
	}

	if strings.Index(r.Host, "ci") == 0 {
		return true
	}

	return false
}

var privateIpAddress = []*net.IPNet{
454 455 456 457
	{IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)},
	{IP: net.IPv4(176, 16, 0, 1), Mask: net.IPv4Mask(255, 255, 0, 0)},
	{IP: net.IPv4(192, 168, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)},
	{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 252)},
=Corey Hulen's avatar
=Corey Hulen committed
458 459 460 461 462 463 464 465 466 467 468 469 470 471
}

func IsPrivateIpAddress(ipAddress string) bool {

	for _, pips := range privateIpAddress {
		if pips.Contains(net.ParseIP(ipAddress)) {
			return true
		}
	}

	return false
}

func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
Christopher Speller's avatar
Christopher Speller committed
472 473 474
	props := make(map[string]string)
	props["Message"] = err.Message
	props["Details"] = err.DetailedError
=Corey Hulen's avatar
=Corey Hulen committed
475 476 477 478 479 480 481

	pathParts := strings.Split(r.URL.Path, "/")
	if len(pathParts) > 1 {
		props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
	} else {
		props["SiteURL"] = GetProtocol(r) + "://" + r.Host
	}
=Corey Hulen's avatar
=Corey Hulen committed
482 483

	w.WriteHeader(err.StatusCode)
484
	ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientCfg: utils.ClientCfg})
=Corey Hulen's avatar
=Corey Hulen committed
485 486 487 488 489 490 491 492
}

func Handle404(w http.ResponseWriter, r *http.Request) {
	err := model.NewAppError("Handle404", "Sorry, we could not find the page.", "")
	err.StatusCode = http.StatusNotFound
	l4g.Error("%v: code=404 ip=%v", r.URL.Path, GetIpAddress(r))
	RenderWebError(err, w, r)
}
493

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
func GetSession(token string) *model.Session {
	var session *model.Session
	if ts, ok := sessionCache.Get(token); ok {
		session = ts.(*model.Session)
	}

	if session == nil {
		if sessionResult := <-Srv.Store.Session().Get(token); sessionResult.Err != nil {
			l4g.Error("Invalid session token=" + token + ", err=" + sessionResult.Err.DetailedError)
		} else {
			session = sessionResult.Data.(*model.Session)
		}
	}

	return session
}

func FindMultiSessionForTeamId(r *http.Request, teamId string) *model.Session {

=Corey Hulen's avatar
=Corey Hulen committed
513
	if multiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
		multiToken := multiCookie.Value

		if len(multiToken) > 0 {
			tokens := strings.Split(multiToken, " ")

			for _, token := range tokens {
				s := GetSession(token)
				if s != nil && !s.IsExpired() && s.TeamId == teamId {
					return s
				}
			}
		}
	}

	return nil
}

531 532 533
func AddSessionToCache(session *model.Session) {
	sessionCache.Add(session.Token, session)
}