diff --git a/api/team.go b/api/team.go index d39d8ed60c..7d746d922d 100644 --- a/api/team.go +++ b/api/team.go @@ -30,7 +30,7 @@ func InitTeam(r *mux.Router) { sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST") sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST") sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") - sr.Handle("/update_name", ApiUserRequired(updateTeamDisplayName)).Methods("POST") + sr.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST") sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") // These should be moved to the global admain console sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST") @@ -541,40 +541,47 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str } } -func updateTeamDisplayName(c *Context, w http.ResponseWriter, r *http.Request) { +func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) + team := model.TeamFromJson(r.Body) - new_name := props["new_name"] - if len(new_name) == 0 { - c.SetInvalidParam("updateTeamDisplayName", "new_name") + if team == nil { + c.SetInvalidParam("updateTeam", "team") return } - teamId := props["team_id"] - if len(teamId) > 0 && len(teamId) != 26 { - c.SetInvalidParam("updateTeamDisplayName", "team_id") - return - } else if len(teamId) == 0 { - teamId = c.Session.TeamId - } - - if !c.HasPermissionsToTeam(teamId, "updateTeamDisplayName") { - return - } + team.Id = c.Session.TeamId if !c.IsTeamAdmin() { - c.Err = model.NewAppError("updateTeamDisplayName", "You do not have the appropriate permissions", "userId="+c.Session.UserId) + c.Err = model.NewAppError("updateTeam", "You do not have the appropriate permissions", "userId="+c.Session.UserId) c.Err.StatusCode = http.StatusForbidden return } - if result := <-Srv.Store.Team().UpdateDisplayName(new_name, c.Session.TeamId); result.Err != nil { + var oldTeam *model.Team + if result := <-Srv.Store.Team().Get(team.Id); result.Err != nil { + c.Err = result.Err + return + } else { + oldTeam = result.Data.(*model.Team) + } + + oldTeam.DisplayName = team.DisplayName + oldTeam.InviteId = team.InviteId + oldTeam.AllowOpenInvite = team.AllowOpenInvite + oldTeam.AllowTeamListing = team.AllowTeamListing + oldTeam.CompanyName = team.CompanyName + oldTeam.AllowedDomains = team.AllowedDomains + //oldTeam.Type = team.Type + + if result := <-Srv.Store.Team().Update(oldTeam); result.Err != nil { c.Err = result.Err return } - w.Write([]byte(model.MapToJson(props))) + oldTeam.Sanitize() + + w.Write([]byte(oldTeam.ToJson())) } func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api/team_test.go b/api/team_test.go index 507f4252a3..7a3b092ce0 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -281,41 +281,23 @@ func TestUpdateTeamDisplayName(t *testing.T) { Client.LoginByEmail(team.Name, user2.Email, "pwd") - data := make(map[string]string) - data["new_name"] = "NewName" - if _, err := Client.UpdateTeamDisplayName(data); err == nil { + vteam := &model.Team{DisplayName: team.DisplayName, Name: team.Name, Email: team.Email, Type: team.Type} + vteam.DisplayName = "NewName" + if _, err := Client.UpdateTeam(vteam); err == nil { t.Fatal("Should have errored, not admin") } Client.LoginByEmail(team.Name, user.Email, "pwd") - data["new_name"] = "" - if _, err := Client.UpdateTeamDisplayName(data); err == nil { + vteam.DisplayName = "" + if _, err := Client.UpdateTeam(vteam); err == nil { t.Fatal("Should have errored, empty name") } - data["new_name"] = "NewName" - if _, err := Client.UpdateTeamDisplayName(data); err != nil { + vteam.DisplayName = "NewName" + if _, err := Client.UpdateTeam(vteam); err != nil { t.Fatal(err) } - // No GET team web service, so hard to confirm here that team name updated - - data["team_id"] = "junk" - if _, err := Client.UpdateTeamDisplayName(data); err == nil { - t.Fatal("Should have errored, junk team id") - } - - data["team_id"] = "12345678901234567890123456" - if _, err := Client.UpdateTeamDisplayName(data); err == nil { - t.Fatal("Should have errored, bad team id") - } - - data["team_id"] = team.Id - data["new_name"] = "NewNameAgain" - if _, err := Client.UpdateTeamDisplayName(data); err != nil { - t.Fatal(err) - } - // No GET team web service, so hard to confirm here that team name updated } func TestFuzzyTeamCreate(t *testing.T) { diff --git a/api/user_test.go b/api/user_test.go index b54e030c55..0ad3541bc5 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -661,12 +661,6 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Should have errored, not admin") } - name := make(map[string]string) - name["new_name"] = "NewName" - if _, err := Client.UpdateTeamDisplayName(name); err == nil { - t.Fatal("should have errored - user not admin yet") - } - team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) @@ -707,12 +701,6 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Roles did not update properly") } } - - Client.LoginByEmail(team.Name, user2.Email, "pwd") - - if _, err := Client.UpdateTeamDisplayName(name); err != nil { - t.Fatal(err) - } } func TestUserUpdateActive(t *testing.T) { diff --git a/config/config.json b/config/config.json index 7bac58df7b..a927620b5c 100644 --- a/config/config.json +++ b/config/config.json @@ -18,7 +18,8 @@ "EnableTeamCreation": true, "EnableUserCreation": true, "RestrictCreationToDomains": "", - "RestrictTeamNames": true + "RestrictTeamNames": true, + "EnableTeamListing": false }, "SqlSettings": { "DriverName": "mysql", diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index 00729395e8..80e6ab14ed 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -17,7 +17,9 @@ "MaxUsersPerTeam": 50, "EnableTeamCreation": true, "EnableUserCreation": true, - "RestrictCreationToDomains": "" + "RestrictCreationToDomains": "", + "RestrictTeamNames": true, + "EnableTeamListing": false }, "SqlSettings": { "DriverName": "mysql", diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index 00729395e8..80e6ab14ed 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -17,7 +17,9 @@ "MaxUsersPerTeam": 50, "EnableTeamCreation": true, "EnableUserCreation": true, - "RestrictCreationToDomains": "" + "RestrictCreationToDomains": "", + "RestrictTeamNames": true, + "EnableTeamListing": false }, "SqlSettings": { "DriverName": "mysql", diff --git a/model/client.go b/model/client.go index 5533c117f2..19183098e0 100644 --- a/model/client.go +++ b/model/client.go @@ -211,8 +211,8 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { } } -func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/teams/update_name", MapToJson(data)); err != nil { +func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { + if r, err := c.DoApiPost("/teams/update", team.ToJson()); err != nil { return nil, err } else { return &Result{r.Header.Get(HEADER_REQUEST_ID), diff --git a/model/config.go b/model/config.go index 216b1de860..50a8dc133d 100644 --- a/model/config.go +++ b/model/config.go @@ -123,6 +123,7 @@ type TeamSettings struct { EnableUserCreation bool RestrictCreationToDomains string RestrictTeamNames *bool + EnableTeamListing *bool } type Config struct { @@ -175,6 +176,11 @@ func (o *Config) SetDefaults() { o.TeamSettings.RestrictTeamNames = new(bool) *o.TeamSettings.RestrictTeamNames = true } + + if o.TeamSettings.EnableTeamListing == nil { + o.TeamSettings.EnableTeamListing = new(bool) + *o.TeamSettings.EnableTeamListing = false + } } func (o *Config) IsValid() *AppError { diff --git a/model/team.go b/model/team.go index 9da2cd5b2a..4d14ec2ee4 100644 --- a/model/team.go +++ b/model/team.go @@ -17,16 +17,19 @@ const ( ) type Team struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - DisplayName string `json:"display_name"` - Name string `json:"name"` - Email string `json:"email"` - Type string `json:"type"` - CompanyName string `json:"company_name"` - AllowedDomains string `json:"allowed_domains"` + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + DisplayName string `json:"display_name"` + Name string `json:"name"` + Email string `json:"email"` + Type string `json:"type"` + CompanyName string `json:"company_name"` + AllowedDomains string `json:"allowed_domains"` + InviteId string `json:"invite_id"` + AllowOpenInvite bool `json:"allow_open_invite"` + AllowTeamListing bool `json:"allow_team_listing"` } type Invites struct { @@ -119,7 +122,7 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError { return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id) } - if len(o.DisplayName) > 64 { + if len(o.DisplayName) == 0 || len(o.DisplayName) > 64 { return NewAppError("Team.IsValid", "Invalid name", "id="+o.Id) } @@ -157,6 +160,10 @@ func (o *Team) PreSave() { o.CreateAt = GetMillis() o.UpdateAt = o.CreateAt + + if len(o.InviteId) == 0 { + o.InviteId = NewId() + } } func (o *Team) PreUpdate() { diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 8700a9d04d..ebf982ec44 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -23,6 +23,7 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { table.ColMap("Email").SetMaxSize(128) table.ColMap("CompanyName").SetMaxSize(64) table.ColMap("AllowedDomains").SetMaxSize(500) + table.ColMap("InviteId").SetMaxSize(32) } return s @@ -31,10 +32,14 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { func (s SqlTeamStore) UpgradeSchemaIfNeeded() { // REMOVE AFTER 1.2 SHIP see PLT-828 s.RemoveColumnIfExists("Teams", "AllowValet") + s.CreateColumnIfNotExists("Teams", "InviteId", "varchar(26)", "varchar(26)", "") + s.CreateColumnIfNotExists("Teams", "AllowOpenInvite", "tinyint(1)", "boolean", "0") + s.CreateColumnIfNotExists("Teams", "AllowTeamListing", "tinyint(1)", "boolean", "0") } func (s SqlTeamStore) CreateIndexesIfNotExists() { s.CreateIndexIfNotExists("idx_teams_name", "Teams", "Name") + s.CreateIndexIfNotExists("idx_teams_invite_id", "Teams", "InviteId") } func (s SqlTeamStore) Save(team *model.Team) StoreChannel { @@ -98,6 +103,7 @@ func (s SqlTeamStore) Update(team *model.Team) StoreChannel { } else { oldTeam := oldResult.(*model.Team) team.CreateAt = oldTeam.CreateAt + team.UpdateAt = model.GetMillis() team.Name = oldTeam.Name if count, err := s.GetMaster().Update(team); err != nil { @@ -147,7 +153,12 @@ func (s SqlTeamStore) Get(id string) StoreChannel { } else if obj == nil { result.Err = model.NewAppError("SqlTeamStore.Get", "We couldn't find the existing team", "id="+id) } else { - result.Data = obj.(*model.Team) + team := obj.(*model.Team) + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + + result.Data = team } storeChannel <- result @@ -157,6 +168,35 @@ func (s SqlTeamStore) Get(id string) StoreChannel { return storeChannel } +func (s SqlTeamStore) GetByInviteId(inviteId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + team := model.Team{} + + if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Id = :InviteId OR InviteId = :InviteId", map[string]interface{}{"InviteId": inviteId}); err != nil { + result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "We couldn't find the existing team", "inviteId="+inviteId+", "+err.Error()) + } + + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + + if len(inviteId) == 0 || team.InviteId != inviteId { + result.Err = model.NewAppError("SqlTeamStore.GetByInviteId", "We couldn't find the existing team", "inviteId="+inviteId) + } + + result.Data = &team + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (s SqlTeamStore) GetByName(name string) StoreChannel { storeChannel := make(StoreChannel) @@ -169,6 +209,10 @@ func (s SqlTeamStore) GetByName(name string) StoreChannel { result.Err = model.NewAppError("SqlTeamStore.GetByName", "We couldn't find the existing team", "name="+name+", "+err.Error()) } + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + result.Data = &team storeChannel <- result @@ -189,6 +233,12 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel { result.Err = model.NewAppError("SqlTeamStore.GetTeamsForEmail", "We encounted a problem when looking up teams", "email="+email+", "+err.Error()) } + for _, team := range data { + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + } + result.Data = data storeChannel <- result @@ -209,6 +259,44 @@ func (s SqlTeamStore) GetAll() StoreChannel { result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "We could not get all teams", err.Error()) } + for _, team := range data { + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + } + + result.Data = data + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlTeamStore) GetAllTeamListing() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + query := "SELECT * FROM Teams WHERE AllowTeamListing = 1" + + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { + query = "SELECT * FROM Teams WHERE AllowTeamListing = true" + } + + var data []*model.Team + if _, err := s.GetReplica().Select(&data, query); err != nil { + result.Err = model.NewAppError("SqlTeamStore.GetAllTeams", "We could not get all teams", err.Error()) + } + + for _, team := range data { + if len(team.InviteId) == 0 { + team.InviteId = team.Id + } + } + result.Data = data storeChannel <- result diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go index 3d9b4d4355..71740f7e7f 100644 --- a/store/sql_team_store_test.go +++ b/store/sql_team_store_test.go @@ -132,6 +132,54 @@ func TestTeamStoreGetByName(t *testing.T) { } } +func TestTeamStoreGetByIniviteId(t *testing.T) { + Setup() + + o1 := model.Team{} + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" + o1.Email = model.NewId() + "@nowhere.com" + o1.Type = model.TEAM_OPEN + o1.InviteId = model.NewId() + + if err := (<-store.Team().Save(&o1)).Err; err != nil { + t.Fatal(err) + } + + o2 := model.Team{} + o2.DisplayName = "DisplayName" + o2.Name = "a" + model.NewId() + "b" + o2.Email = model.NewId() + "@nowhere.com" + o2.Type = model.TEAM_OPEN + + if err := (<-store.Team().Save(&o2)).Err; err != nil { + t.Fatal(err) + } + + if r1 := <-store.Team().GetByInviteId(o1.InviteId); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.(*model.Team).ToJson() != o1.ToJson() { + t.Fatal("invalid returned team") + } + } + + o2.InviteId = "" + <-store.Team().Update(&o2) + + if r1 := <-store.Team().GetByInviteId(o2.Id); r1.Err != nil { + t.Fatal(r1.Err) + } else { + if r1.Data.(*model.Team).Id != o2.Id { + t.Fatal("invalid returned team") + } + } + + if err := (<-store.Team().GetByInviteId("")).Err; err == nil { + t.Fatal("Missing id should have failed") + } +} + func TestTeamStoreGetForEmail(t *testing.T) { Setup() @@ -161,3 +209,32 @@ func TestTeamStoreGetForEmail(t *testing.T) { t.Fatal(r1.Err) } } + +func TestAllTeamListing(t *testing.T) { + Setup() + + o1 := model.Team{} + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" + o1.Email = model.NewId() + "@nowhere.com" + o1.Type = model.TEAM_OPEN + o1.AllowTeamListing = true + Must(store.Team().Save(&o1)) + + o2 := model.Team{} + o2.DisplayName = "DisplayName" + o2.Name = "a" + model.NewId() + "b" + o2.Email = model.NewId() + "@nowhere.com" + o2.Type = model.TEAM_OPEN + Must(store.Team().Save(&o2)) + + if r1 := <-store.Team().GetAllTeamListing(); r1.Err != nil { + t.Fatal(r1.Err) + } else { + teams := r1.Data.([]*model.Team) + + if len(teams) == 0 { + t.Fatal("failed team listing") + } + } +} diff --git a/store/store.go b/store/store.go index 42329b0366..53a6e053b0 100644 --- a/store/store.go +++ b/store/store.go @@ -50,6 +50,8 @@ type TeamStore interface { GetByName(name string) StoreChannel GetTeamsForEmail(domain string) StoreChannel GetAll() StoreChannel + GetAllTeamListing() StoreChannel + GetByInviteId(inviteId string) StoreChannel } type ChannelStore interface { diff --git a/utils/config.go b/utils/config.go index 6b34c76eda..13b7b6b647 100644 --- a/utils/config.go +++ b/utils/config.go @@ -190,6 +190,7 @@ func getClientConfig(c *model.Config) map[string]string { props["SiteName"] = c.TeamSettings.SiteName props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation) props["RestrictTeamNames"] = strconv.FormatBool(*c.TeamSettings.RestrictTeamNames) + props["EnableTeamListing"] = strconv.FormatBool(*c.TeamSettings.EnableTeamListing) props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx index 9ecd14a1e9..6587184ea2 100644 --- a/web/react/components/admin_console/team_settings.jsx +++ b/web/react/components/admin_console/team_settings.jsx @@ -32,6 +32,7 @@ export default class TeamSettings extends React.Component { config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked; config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked; config.TeamSettings.RestrictTeamNames = ReactDOM.findDOMNode(this.refs.RestrictTeamNames).checked; + config.TeamSettings.EnableTeamListing = ReactDOM.findDOMNode(this.refs.EnableTeamListing).checked; var MaxUsersPerTeam = 50; if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { @@ -242,6 +243,39 @@ export default class TeamSettings extends React.Component { +
{'When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.'}
+