From 430806301da06e927b8d7d6dcba20ea4b6b6d6c1 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 1 Oct 2015 17:52:47 -0700 Subject: PLT-44 allow team switching without the need to login --- api/channel.go | 2 +- api/context.go | 24 ++++++++++++++++---- api/user.go | 39 ++++++++++++++++++++++++++++++-- model/session.go | 1 + web/react/components/navbar_dropdown.jsx | 10 ++++---- web/react/components/sidebar.jsx | 4 +++- web/react/components/sidebar_header.jsx | 3 +++ web/react/pages/channel.jsx | 4 ++++ web/web.go | 37 ++++++++++++++++++++++++++++-- 9 files changed, 109 insertions(+), 15 deletions(-) diff --git a/api/channel.go b/api/channel.go index 896e22793..28642ff67 100644 --- a/api/channel.go +++ b/api/channel.go @@ -282,7 +282,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) { // lets make sure the user is valid if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { c.Err = result.Err - c.RemoveSessionCookie(w) + c.RemoveSessionCookie(w, r) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId) return } diff --git a/api/context.go b/api/context.go index 02c3dc902..e80582b2a 100644 --- a/api/context.go +++ b/api/context.go @@ -137,7 +137,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if session == nil || session.IsExpired() { - c.RemoveSessionCookie(w) + c.RemoveSessionCookie(w, r) c.Err = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "token="+token) c.Err.StatusCode = http.StatusUnauthorized } else if !session.IsOAuth && isTokenFromQueryString { @@ -303,7 +303,6 @@ func (c *Context) HasSystemAdminPermissions(where string) bool { } func (c *Context) IsSystemAdmin() bool { - // TODO XXX FIXME && IsPrivateIpAddress(c.IpAddress) if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) { return true } @@ -317,7 +316,7 @@ func (c *Context) IsTeamAdmin() bool { return false } -func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { +func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) { sessionCache.Remove(c.Session.Token) @@ -330,6 +329,21 @@ func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { } http.SetCookie(w, cookie) + + multiToken := "" + if oldMultiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil { + multiToken = oldMultiCookie.Value + } + + multiCookie := &http.Cookie{ + Name: model.MULTI_SESSION_TOKEN, + Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)), + Path: "/", + MaxAge: model.SESSION_TIME_WEB_IN_SECS, + HttpOnly: true, + } + + http.SetCookie(w, multiCookie) } func (c *Context) SetInvalidParam(where string, name string) { @@ -346,7 +360,7 @@ func (c *Context) setTeamURL(url string, valid bool) { c.teamURLValid = valid } -func (c *Context) setTeamURLFromSession() { +func (c *Context) SetTeamURLFromSession() { if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil { c.setTeamURL(c.GetSiteURL()+"/"+result.Data.(*model.Team).Name, true) } @@ -362,7 +376,7 @@ func (c *Context) GetTeamURLFromTeam(team *model.Team) string { func (c *Context) GetTeamURL() string { if !c.teamURLValid { - c.setTeamURLFromSession() + c.SetTeamURLFromSession() 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") } diff --git a/api/user.go b/api/user.go index ed3576a30..2d7dd9ab1 100644 --- a/api/user.go +++ b/api/user.go @@ -394,6 +394,41 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, http.SetCookie(w, sessionCookie) + multiToken := "" + if originalMultiSessionCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil { + multiToken = originalMultiSessionCookie.Value + } + + // Attempt to clean all the old tokens or duplicate tokens + if len(multiToken) > 0 { + tokens := strings.Split(multiToken, " ") + + multiToken = "" + seen := make(map[string]string) + seen[session.TeamId] = session.TeamId + for _, token := range tokens { + if sr := <-Srv.Store.Session().Get(token); sr.Err == nil { + s := sr.Data.(*model.Session) + if !s.IsExpired() && seen[s.TeamId] == "" { + multiToken += " " + token + seen[s.TeamId] = s.TeamId + } + } + } + } + + multiToken = strings.TrimSpace(session.Token + " " + multiToken) + + multiSessionCookie := &http.Cookie{ + Name: model.MULTI_SESSION_TOKEN, + Value: multiToken, + Path: "/", + MaxAge: maxAge, + HttpOnly: true, + } + + http.SetCookie(w, multiSessionCookie) + c.Session = *session c.LogAuditWithUserId(user.Id, "success") } @@ -514,7 +549,7 @@ func logout(c *Context, w http.ResponseWriter, r *http.Request) { func Logout(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("") - c.RemoveSessionCookie(w) + c.RemoveSessionCookie(w, r) if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil { c.Err = result.Err return @@ -529,7 +564,7 @@ func getMe(c *Context, w http.ResponseWriter, r *http.Request) { if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { c.Err = result.Err - c.RemoveSessionCookie(w) + c.RemoveSessionCookie(w, r) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId) return } else if HandleEtag(result.Data.(*model.User).Etag(), w, r) { diff --git a/model/session.go b/model/session.go index 3c7c75eb4..bb4d987a7 100644 --- a/model/session.go +++ b/model/session.go @@ -10,6 +10,7 @@ import ( const ( SESSION_TOKEN = "MMSID" + MULTI_SESSION_TOKEN = "MMSIDMU" SESSION_TIME_WEB_IN_DAYS = 30 SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS SESSION_TIME_MOBILE_IN_DAYS = 30 diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 57a78a0d4..dc5983f1e 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -142,10 +142,10 @@ export default class NavbarDropdown extends React.Component { > ); - if (this.state.teams.length > 1 && this.state.currentTeam) { - var curTeamName = this.state.currentTeam.name; + + if (this.state.teams.length > 1) { this.state.teams.forEach((teamName) => { - if (teamName !== curTeamName) { + if (teamName !== this.props.teamName) { teams.push(
  • {'Switch to ' + teamName}
  • ); } }); @@ -234,5 +234,7 @@ NavbarDropdown.defaultProps = { teamType: '' }; NavbarDropdown.propTypes = { - teamType: React.PropTypes.string + teamType: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string }; diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 6033f200f..dfaa4c794 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -508,6 +508,7 @@ export default class Sidebar extends React.Component { /> @@ -587,5 +588,6 @@ Sidebar.defaultProps = { }; Sidebar.propTypes = { teamType: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string }; diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 072c14e0a..33de35064 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -52,6 +52,8 @@ export default class SidebarHeader extends React.Component { ); @@ -64,5 +66,6 @@ SidebarHeader.defaultProps = { }; SidebarHeader.propTypes = { teamDisplayName: React.PropTypes.string, + teamName: React.PropTypes.string, teamType: React.PropTypes.string }; diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 74259194a..c333fd57d 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -36,11 +36,14 @@ var RemovedFromChannelModal = require('../components/removed_from_channel_modal. var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx'); var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; function setupChannelPage(props) { + TeamStore.setCurrentId(props.TeamId); + AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, name: props.ChannelName, @@ -71,6 +74,7 @@ function setupChannelPage(props) { React.render( , document.getElementById('sidebar-left') diff --git a/web/web.go b/web/web.go index bf985a5a0..83b59ead4 100644 --- a/web/web.go +++ b/web/web.go @@ -189,9 +189,40 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) { return } + // We still might be able to switch to this team because we've logged in before + if multiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil { + multiToken := multiCookie.Value + + if len(multiToken) > 0 { + tokens := strings.Split(multiToken, " ") + + for _, token := range tokens { + if sr := <-api.Srv.Store.Session().Get(token); sr.Err == nil { + s := sr.Data.(*model.Session) + + if !s.IsExpired() && s.TeamId == team.Id { + w.Header().Set(model.HEADER_TOKEN, s.Token) + sessionCookie := &http.Cookie{ + Name: model.SESSION_TOKEN, + Value: s.Token, + Path: "/", + MaxAge: model.SESSION_TIME_WEB_IN_SECS, + HttpOnly: true, + } + + http.SetCookie(w, sessionCookie) + + http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect) + return + } + } + } + } + } + page := NewHtmlTemplatePage("login", "Login") page.Props["TeamDisplayName"] = team.DisplayName - page.Props["TeamName"] = teamName + page.Props["TeamName"] = team.Name page.Render(c, w) } @@ -319,7 +350,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { // lets make sure the user is valid if result := <-api.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { c.Err = result.Err - c.RemoveSessionCookie(w) + c.RemoveSessionCookie(w, r) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId) return } @@ -344,6 +375,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { page := NewHtmlTemplatePage("channel", "") page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientProps["SiteName"] page.Props["TeamDisplayName"] = team.DisplayName + page.Props["TeamName"] = team.Name page.Props["TeamType"] = team.Type page.Props["TeamId"] = team.Id page.Props["ChannelName"] = name @@ -451,6 +483,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) { page := NewHtmlTemplatePage("password_reset", "") page.Props["Title"] = "Reset Password " + page.ClientProps["SiteName"] page.Props["TeamDisplayName"] = teamDisplayName + page.Props["TeamName"] = teamName page.Props["Hash"] = hash page.Props["Data"] = data page.Props["TeamName"] = teamName -- cgit v1.2.3-1-g7c22 From 90635ad216f7658990dbd61eb209ecc65a9f28e0 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 1 Oct 2015 18:15:40 -0700 Subject: Fixing issue with being logged into other team --- web/web.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/web/web.go b/web/web.go index 83b59ead4..31101fa89 100644 --- a/web/web.go +++ b/web/web.go @@ -319,6 +319,10 @@ func logout(c *api.Context, w http.ResponseWriter, r *http.Request) { func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) name := params["channelname"] + teamName := params["team"] + + var team *model.Team + teamChan := api.Srv.Store.Team().Get(c.Session.TeamId) var channelId string if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil { @@ -328,6 +332,19 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { channelId = result.Data.(string) } + if tResult := <-teamChan; tResult.Err != nil { + c.Err = tResult.Err + return + } else { + team = tResult.Data.(*model.Team) + } + + if team.Name != teamName { + l4g.Error("It appears you are log into " + team.Name + ", but are trying to access " + teamName) + http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusFound) + return + } + if len(channelId) == 0 { if strings.Index(name, "__") > 0 { // It's a direct message channel that doesn't exist yet so let's create it @@ -363,15 +380,6 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { } } - var team *model.Team - - if tResult := <-api.Srv.Store.Team().Get(c.Session.TeamId); tResult.Err != nil { - c.Err = tResult.Err - return - } else { - team = tResult.Data.(*model.Team) - } - page := NewHtmlTemplatePage("channel", "") page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientProps["SiteName"] page.Props["TeamDisplayName"] = team.DisplayName -- cgit v1.2.3-1-g7c22 From cee6dc270b22d8cd8458f199c87d58f40b433ca5 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 2 Oct 2015 09:12:09 -0700 Subject: Fixing issues --- web/web.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/web.go b/web/web.go index 31101fa89..8952f36a3 100644 --- a/web/web.go +++ b/web/web.go @@ -340,8 +340,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { } if team.Name != teamName { - l4g.Error("It appears you are log into " + team.Name + ", but are trying to access " + teamName) - http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusFound) + l4g.Error("It appears you are logged into " + team.Name + ", but are trying to access " + teamName) + http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound) return } -- cgit v1.2.3-1-g7c22 From 4452bb798878ee3a803b018f817cb3259e341a82 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 2 Oct 2015 09:13:30 -0700 Subject: Fixing issues --- web/react/components/navbar_dropdown.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index dc5983f1e..78057d10b 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -9,7 +9,7 @@ var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); function getStateFromStores() { - return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; + return {teams: UserStore.getTeams()}; } export default class NavbarDropdown extends React.Component { -- cgit v1.2.3-1-g7c22 From 3b34e7313251c5c9b7dde8c5916f83ab9e9f2a31 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Fri, 2 Oct 2015 10:29:56 -0700 Subject: Fixing redirect issue --- web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/web.go b/web/web.go index 8952f36a3..e440699b2 100644 --- a/web/web.go +++ b/web/web.go @@ -341,7 +341,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { if team.Name != teamName { l4g.Error("It appears you are logged into " + team.Name + ", but are trying to access " + teamName) - http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound) + http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusFound) return } -- cgit v1.2.3-1-g7c22