diff options
Diffstat (limited to 'api/context.go')
-rw-r--r-- | api/context.go | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/api/context.go b/api/context.go new file mode 100644 index 000000000..16105c8af --- /dev/null +++ b/api/context.go @@ -0,0 +1,375 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" + "net" + "net/http" + "net/url" + "strings" +) + +var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE) + +type Context struct { + Session model.Session + RequestId string + IpAddress string + TeamUrl string + Path string + Err *model.AppError +} + +type Page struct { + TemplateName string + Title string + SiteName string + FeedbackEmail string + TeamUrl string + Props map[string]string +} + +func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, false, false, true, false} +} + +func AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, false, false, false, false} +} + +func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, true, false, true, true} +} + +func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler { + return &handler{h, true, false, true, isUserActivity} +} + +func UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, true, false, false, false} +} + +func ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, true, true, true, false} +} + +type handler struct { + handleFunc func(*Context, http.ResponseWriter, *http.Request) + requireUser bool + requireSystemAdmin bool + isApi bool + isUserActivity bool +} + +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) + c.Path = r.URL.Path + + protocol := "http" + + // if the request came from the ELB then assume this is produciton + // and redirect all http requests to https + if utils.Cfg.ServiceSettings.UseSSL { + forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO) + if forwardProto == "http" { + l4g.Info("redirecting http request to https for %v", r.URL.Path) + http.Redirect(w, r, "https://"+r.Host, http.StatusTemporaryRedirect) + } else { + protocol = "https" + } + } + + c.TeamUrl = protocol + "://" + r.Host + + w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) + w.Header().Set(model.HEADER_VERSION_ID, utils.Cfg.ServiceSettings.Version) + + sessionId := "" + + // attempt to parse the session token from the header + if ah := r.Header.Get(model.HEADER_AUTH); ah != "" { + if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { + sessionId = ah[7:] + } + } + + // attempt to parse the session token from the cookie + if sessionId == "" { + if cookie, err := r.Cookie(model.SESSION_TOKEN); err == nil { + sessionId = cookie.Value + } + } + + if sessionId != "" { + + var session *model.Session + if ts, ok := sessionCache.Get(sessionId); ok { + session = ts.(*model.Session) + } + + if session == nil { + if sessionResult := <-Srv.Store.Session().Get(sessionId); sessionResult.Err != nil { + c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "id="+sessionId+", err="+sessionResult.Err.DetailedError)) + } else { + session = sessionResult.Data.(*model.Session) + } + } + + if session == nil || session.IsExpired() { + c.RemoveSessionCookie(w) + c.Err = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "id="+sessionId) + c.Err.StatusCode = http.StatusUnauthorized + } else { + c.Session = *session + } + } + + if c.Err == nil && h.requireUser { + c.UserRequired() + } + + if c.Err == nil && h.requireSystemAdmin { + c.SystemAdminRequired() + } + + if c.Err == nil && h.isUserActivity && sessionId != "" && len(c.Session.UserId) > 0 { + go func() { + if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, sessionId, model.GetMillis())).Err; err != nil { + l4g.Error("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", c.Session.UserId, sessionId, err) + } + }() + } + + 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 { + http.Redirect(w, r, "/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) + } else { + RenderWebError(c.Err, w, r) + } + } + } +} + +func (c *Context) LogAudit(extraInfo string) { + audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId} + Srv.Store.Audit().Save(audit) +} + +func (c *Context) LogAuditWithUserId(userId, extraInfo string) { + + if len(c.Session.UserId) > 0 { + extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId) + } + + audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId} + Srv.Store.Audit().Save(audit) +} + +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 +} + +func (c *Context) IsSystemAdmin() bool { + if strings.Contains(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) && IsPrivateIpAddress(c.IpAddress) { + return true + } + return false +} + +func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { + + sessionCache.Remove(c.Session.Id) + + cookie := &http.Cookie{ + Name: model.SESSION_TOKEN, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + } + + http.SetCookie(w, cookie) +} + +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) +} + +func GetIpAddress(r *http.Request) string { + address := r.Header.Get(model.HEADER_FORWARDED) + 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 + } + + 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{ + &net.IPNet{IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)}, + &net.IPNet{IP: net.IPv4(176, 16, 0, 1), Mask: net.IPv4Mask(255, 255, 0, 0)}, + &net.IPNet{IP: net.IPv4(192, 168, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}, + &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 252)}, +} + +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) { + m := make(map[string]string) + m["Message"] = err.Message + m["Details"] = err.DetailedError + m["SiteName"] = utils.Cfg.ServiceSettings.SiteName + + w.WriteHeader(err.StatusCode) + ServerTemplates.ExecuteTemplate(w, "error.html", m) +} + +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) +} |