Merge pull request #903 from mattermost/PLT-44

PLT-44 allow team switching without the need to login
Этот коммит содержится в:
Christopher Speller
2015-10-05 10:20:14 -04:00
родитель 8595fd85c7 3b34e73132
Коммит f4afabd679
9 изменённых файлов: 127 добавлений и 25 удалений

Просмотреть файл

@@ -281,7 +281,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
// lets make sure the user is valid // lets make sure the user is valid
if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
c.Err = result.Err 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) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
return return
} }

Просмотреть файл

@@ -137,7 +137,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
if session == nil || session.IsExpired() { 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 = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "token="+token)
c.Err.StatusCode = http.StatusUnauthorized c.Err.StatusCode = http.StatusUnauthorized
} else if !session.IsOAuth && isTokenFromQueryString { } else if !session.IsOAuth && isTokenFromQueryString {
@@ -303,7 +303,6 @@ func (c *Context) HasSystemAdminPermissions(where string) bool {
} }
func (c *Context) IsSystemAdmin() bool { func (c *Context) IsSystemAdmin() bool {
// TODO XXX FIXME && IsPrivateIpAddress(c.IpAddress)
if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) { if model.IsInRole(c.Session.Roles, model.ROLE_SYSTEM_ADMIN) {
return true return true
} }
@@ -317,7 +316,7 @@ func (c *Context) IsTeamAdmin() bool {
return false return false
} }
func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
sessionCache.Remove(c.Session.Token) sessionCache.Remove(c.Session.Token)
@@ -330,6 +329,21 @@ func (c *Context) RemoveSessionCookie(w http.ResponseWriter) {
} }
http.SetCookie(w, cookie) 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) { func (c *Context) SetInvalidParam(where string, name string) {
@@ -346,7 +360,7 @@ func (c *Context) setTeamURL(url string, valid bool) {
c.teamURLValid = valid c.teamURLValid = valid
} }
func (c *Context) setTeamURLFromSession() { func (c *Context) SetTeamURLFromSession() {
if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil { if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil {
c.setTeamURL(c.GetSiteURL()+"/"+result.Data.(*model.Team).Name, true) 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 { func (c *Context) GetTeamURL() string {
if !c.teamURLValid { if !c.teamURLValid {
c.setTeamURLFromSession() c.SetTeamURLFromSession()
if !c.teamURLValid { 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") l4g.Debug("TeamURL accessed when not valid. Team URL should not be used in api functions or those that are team independent")
} }

Просмотреть файл

@@ -394,6 +394,41 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
http.SetCookie(w, sessionCookie) 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.Session = *session
c.LogAuditWithUserId(user.Id, "success") 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) { func Logout(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("") c.LogAudit("")
c.RemoveSessionCookie(w) c.RemoveSessionCookie(w, r)
if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil { if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return 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 { if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
c.Err = result.Err 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) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
return return
} else if HandleEtag(result.Data.(*model.User).Etag(), w, r) { } else if HandleEtag(result.Data.(*model.User).Etag(), w, r) {

Просмотреть файл

@@ -10,6 +10,7 @@ import (
const ( const (
SESSION_TOKEN = "MMSID" SESSION_TOKEN = "MMSID"
MULTI_SESSION_TOKEN = "MMSIDMU"
SESSION_TIME_WEB_IN_DAYS = 30 SESSION_TIME_WEB_IN_DAYS = 30
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
SESSION_TIME_MOBILE_IN_DAYS = 30 SESSION_TIME_MOBILE_IN_DAYS = 30

Просмотреть файл

@@ -9,7 +9,7 @@ var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
function getStateFromStores() { function getStateFromStores() {
return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; return {teams: UserStore.getTeams()};
} }
export default class NavbarDropdown extends React.Component { export default class NavbarDropdown extends React.Component {
@@ -142,10 +142,10 @@ export default class NavbarDropdown extends React.Component {
> >
</li> </li>
); );
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) => { this.state.teams.forEach((teamName) => {
if (teamName !== curTeamName) { if (teamName !== this.props.teamName) {
teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>); teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>);
} }
}); });
@@ -234,5 +234,7 @@ NavbarDropdown.defaultProps = {
teamType: '' teamType: ''
}; };
NavbarDropdown.propTypes = { NavbarDropdown.propTypes = {
teamType: React.PropTypes.string teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string
}; };

Просмотреть файл

@@ -512,6 +512,7 @@ export default class Sidebar extends React.Component {
/> />
<SidebarHeader <SidebarHeader
teamDisplayName={this.props.teamDisplayName} teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
teamType={this.props.teamType} teamType={this.props.teamType}
/> />
<SearchBox /> <SearchBox />
@@ -591,5 +592,6 @@ Sidebar.defaultProps = {
}; };
Sidebar.propTypes = { Sidebar.propTypes = {
teamType: React.PropTypes.string, teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string
}; };

Просмотреть файл

@@ -52,6 +52,8 @@ export default class SidebarHeader extends React.Component {
<NavbarDropdown <NavbarDropdown
ref='dropdown' ref='dropdown'
teamType={this.props.teamType} teamType={this.props.teamType}
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
/> />
</div> </div>
); );
@@ -64,5 +66,6 @@ SidebarHeader.defaultProps = {
}; };
SidebarHeader.propTypes = { SidebarHeader.propTypes = {
teamDisplayName: React.PropTypes.string, teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string,
teamType: React.PropTypes.string teamType: React.PropTypes.string
}; };

Просмотреть файл

@@ -36,11 +36,14 @@ var RemovedFromChannelModal = require('../components/removed_from_channel_modal.
var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx');
var ImportThemeModal = require('../components/user_settings/import_theme_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 Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
function setupChannelPage(props) { function setupChannelPage(props) {
TeamStore.setCurrentId(props.TeamId);
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL, type: ActionTypes.CLICK_CHANNEL,
name: props.ChannelName, name: props.ChannelName,
@@ -71,6 +74,7 @@ function setupChannelPage(props) {
React.render( React.render(
<Sidebar <Sidebar
teamDisplayName={props.TeamDisplayName} teamDisplayName={props.TeamDisplayName}
teamName={props.TeamName}
teamType={props.TeamType} teamType={props.TeamType}
/>, />,
document.getElementById('sidebar-left') document.getElementById('sidebar-left')

Просмотреть файл

@@ -189,9 +189,40 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
return 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 := NewHtmlTemplatePage("login", "Login")
page.Props["TeamDisplayName"] = team.DisplayName page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamName"] = teamName page.Props["TeamName"] = team.Name
page.Render(c, w) page.Render(c, w)
} }
@@ -288,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) { func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r) params := mux.Vars(r)
name := params["channelname"] name := params["channelname"]
teamName := params["team"]
var team *model.Team
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
var channelId string var channelId string
if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil { if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil {
@@ -297,6 +332,19 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
channelId = result.Data.(string) 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 logged 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 len(channelId) == 0 {
if strings.Index(name, "__") > 0 { if strings.Index(name, "__") > 0 {
// It's a direct message channel that doesn't exist yet so let's create it // It's a direct message channel that doesn't exist yet so let's create it
@@ -319,7 +367,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
// lets make sure the user is valid // lets make sure the user is valid
if result := <-api.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { if result := <-api.Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
c.Err = result.Err 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) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
return return
} }
@@ -332,18 +380,10 @@ 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 := NewHtmlTemplatePage("channel", "")
page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientProps["SiteName"] page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientProps["SiteName"]
page.Props["TeamDisplayName"] = team.DisplayName page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamName"] = team.Name
page.Props["TeamType"] = team.Type page.Props["TeamType"] = team.Type
page.Props["TeamId"] = team.Id page.Props["TeamId"] = team.Id
page.Props["ChannelName"] = name page.Props["ChannelName"] = name
@@ -451,6 +491,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
page := NewHtmlTemplatePage("password_reset", "") page := NewHtmlTemplatePage("password_reset", "")
page.Props["Title"] = "Reset Password " + page.ClientProps["SiteName"] page.Props["Title"] = "Reset Password " + page.ClientProps["SiteName"]
page.Props["TeamDisplayName"] = teamDisplayName page.Props["TeamDisplayName"] = teamDisplayName
page.Props["TeamName"] = teamName
page.Props["Hash"] = hash page.Props["Hash"] = hash
page.Props["Data"] = data page.Props["Data"] = data
page.Props["TeamName"] = teamName page.Props["TeamName"] = teamName