From 799215ee2278b162d4e113c498424fdda817e83b Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 17 Jun 2015 12:07:35 -0400 Subject: [PATCH 1/6] move valet feature switch to DB from config --- api/command.go | 20 +++++++++++++++++--- api/post.go | 20 ++++++++++++++------ api/post_test.go | 2 +- api/team.go | 11 ++++++++++- api/user.go | 4 ---- config/config.json | 2 +- model/team.go | 1 + store/sql_team_store.go | 6 ++++++ utils/config.go | 2 +- 9 files changed, 51 insertions(+), 17 deletions(-) diff --git a/api/command.go b/api/command.go index 449483bbf9..94d2d8f60b 100644 --- a/api/command.go +++ b/api/command.go @@ -25,9 +25,7 @@ func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") r.Handle("/command", ApiUserRequired(command)).Methods("POST") - if utils.Cfg.TeamSettings.AllowValet { - commands = append(commands, echoCommand) - } + commands = append(commands, echoCommand) hub.Start() } @@ -59,6 +57,8 @@ func checkCommand(c *Context, command *model.Command) bool { return false } + tchan := Srv.Store.Team().Get(c.Session.TeamId) + if len(command.ChannelId) > 0 { cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) @@ -67,7 +67,21 @@ func checkCommand(c *Context, command *model.Command) bool { } } + allowValet := false + if tResult := <-tchan; tResult.Err != nil { + c.Err = model.NewAppError("checkCommand", "Could not find the team for this session, team_id="+c.Session.TeamId, "") + return false + } else { + allowValet = tResult.Data.(*model.Team).AllowValet + } + + var ec commandHandler + ec = echoCommand for _, v := range commands { + if !allowValet && &v == &ec { + continue + } + if v(c, command) { return true } else if c.Err != nil { diff --git a/api/post.go b/api/post.go index 36607c2315..6f500286f9 100644 --- a/api/post.go +++ b/api/post.go @@ -58,11 +58,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { } func createValetPost(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.TeamSettings.AllowValet { - c.Err = model.NewAppError("createValetPost", "The valet feature is currently turned off. Please contact your system administrator for details.", "") - c.Err.StatusCode = http.StatusNotImplemented - return - } + tchan := Srv.Store.Team().Get(c.Session.TeamId) post := model.PostFromJson(r.Body) if post == nil { @@ -70,13 +66,25 @@ func createValetPost(c *Context, w http.ResponseWriter, r *http.Request) { return } - // Any one with access to the team can post as valet to any open channel cchan := Srv.Store.Channel().CheckOpenChannelPermissions(c.Session.TeamId, post.ChannelId) + // Any one with access to the team can post as valet to any open channel if !c.HasPermissionsToChannel(cchan, "createValetPost") { return } + // Make sure this team has the valet feature enabled + if tResult := <-tchan; tResult.Err != nil { + c.Err = model.NewAppError("createValetPost", "Could not find the team for this session, team_id="+c.Session.TeamId, "") + return + } else { + if !tResult.Data.(*model.Team).AllowValet { + c.Err = model.NewAppError("createValetPost", "The valet feature is currently turned off. Please contact your team administrator for details.", "") + c.Err.StatusCode = http.StatusNotImplemented + return + } + } + if rp, err := CreateValetPost(c, post); err != nil { c.Err = err diff --git a/api/post_test.go b/api/post_test.go index b322a5017d..03f70bff7d 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -147,7 +147,7 @@ func TestCreateValetPost(t *testing.T) { channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - if utils.Cfg.TeamSettings.AllowValet { + if utils.Cfg.TeamSettings.AllowValetDefault { post1 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a"} rpost1, err := Client.CreateValetPost(post1) if err != nil { diff --git a/api/team.go b/api/team.go index cb60602c68..65f0879ffb 100644 --- a/api/team.go +++ b/api/team.go @@ -136,6 +136,8 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { } } + teamSignup.Team.AllowValet = utils.Cfg.TeamSettings.AllowValetDefault + if result := <-Srv.Store.Team().Save(&teamSignup.Team); result.Err != nil { c.Err = result.Err return @@ -157,7 +159,7 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { return } - if utils.Cfg.TeamSettings.AllowValet { + if teamSignup.Team.AllowValet { CreateValet(c, rteam) if c.Err != nil { return @@ -200,6 +202,13 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + if rteam.AllowValet { + CreateValet(c, rteam) + if c.Err != nil { + return + } + } + w.Write([]byte(rteam.ToJson())) } } diff --git a/api/user.go b/api/user.go index 83e29b28e5..c0ebc05e0b 100644 --- a/api/user.go +++ b/api/user.go @@ -145,10 +145,6 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { } func CreateValet(c *Context, team *model.Team) *model.User { - if !utils.Cfg.TeamSettings.AllowValet { - return &model.User{} - } - valet := &model.User{} valet.TeamId = team.Id valet.Email = utils.Cfg.EmailSettings.FeedbackEmail diff --git a/config/config.json b/config/config.json index 3d2c26716d..99f41af341 100644 --- a/config/config.json +++ b/config/config.json @@ -73,7 +73,7 @@ "TeamSettings": { "MaxUsersPerTeam": 150, "AllowPublicLink": true, - "AllowValet": false, + "AllowValetDefault": false, "TermsLink": "/static/help/configure_links.html", "PrivacyLink": "/static/help/configure_links.html", "AboutLink": "/static/help/configure_links.html", diff --git a/model/team.go b/model/team.go index a510cde783..5c66f3b1fb 100644 --- a/model/team.go +++ b/model/team.go @@ -24,6 +24,7 @@ type Team struct { Type string `json:"type"` CompanyName string `json:"company_name"` AllowedDomains string `json:"allowed_domains"` + AllowValet bool `json:"allow_valet"` } type Invites struct { diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 6e7fc1c1eb..ffb9f80938 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -5,6 +5,7 @@ package store import ( "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" "strings" ) @@ -29,6 +30,11 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { } func (s SqlTeamStore) UpgradeSchemaIfNeeded() { + defaultValue := "0" + if utils.Cfg.TeamSettings.AllowValetDefault { + defaultValue = "1" + } + s.CreateColumnIfNotExists("Teams", "AllowValet", "AllowedDomains", "tinyint(1)", defaultValue) } func (s SqlTeamStore) CreateIndexesIfNotExists() { diff --git a/utils/config.go b/utils/config.go index 745887c703..3c53e68755 100644 --- a/utils/config.go +++ b/utils/config.go @@ -97,7 +97,7 @@ type PrivacySettings struct { type TeamSettings struct { MaxUsersPerTeam int AllowPublicLink bool - AllowValet bool + AllowValetDefault bool TermsLink string PrivacyLink string AboutLink string From 1dc3a5f26d95ca87f2bfe1a57caa4ac9b077434a Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 18 Jun 2015 08:05:41 -0400 Subject: [PATCH 2/6] add API service to turn on valet feature --- api/team.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/api/team.go b/api/team.go index 65f0879ffb..40f75ec9e3 100644 --- a/api/team.go +++ b/api/team.go @@ -29,6 +29,7 @@ func InitTeam(r *mux.Router) { sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST") sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") sr.Handle("/update_name", ApiUserRequired(updateTeamName)).Methods("POST") + sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST") } func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { @@ -551,3 +552,53 @@ func updateTeamName(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } + +func updateValetFeature(c *Context, w http.ResponseWriter, r *http.Request) { + + props := model.MapFromJson(r.Body) + + allowValetStr := props["allow_valet"] + if len(allowValetStr) == 0 { + c.SetInvalidParam("updateValetFeature", "allow_valet") + return + } + + allowValet := allowValetStr == "true" + + teamId := props["team_id"] + if len(teamId) > 0 && len(teamId) != 26 { + c.SetInvalidParam("updateValetFeature", "team_id") + return + } else if len(teamId) == 0 { + teamId = c.Session.TeamId + } + + tchan := Srv.Store.Team().Get(teamId) + + if !c.HasPermissionsToTeam(teamId, "updateValetFeature") { + return + } + + if !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { + c.Err = model.NewAppError("updateValetFeature", "You do not have the appropriate permissions", "userId="+c.Session.UserId) + c.Err.StatusCode = http.StatusForbidden + return + } + + var team *model.Team + if tResult := <-tchan; tResult.Err != nil { + c.Err = tResult.Err + return + } else { + team = tResult.Data.(*model.Team) + } + + team.AllowValet = allowValet + + if result := <-Srv.Store.Team().Update(team); result.Err != nil { + c.Err = result.Err + return + } + + w.Write([]byte(model.MapToJson(props))) +} From 308c4f3ce9330f4ac04cc04495533e6f2ad87f53 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 18 Jun 2015 10:40:46 -0400 Subject: [PATCH 3/6] added team store, team settings menu, and the ability to turn on valet feature from client --- api/team.go | 20 +++ config/config.json | 4 +- web/react/components/channel_loader.jsx | 1 + web/react/components/settings_sidebar.jsx | 13 +- web/react/components/sidebar_header.jsx | 3 +- web/react/components/sidebar_right_menu.jsx | 2 +- web/react/components/team_settings.jsx | 151 ++++++++++++++++++ ...ings_modal.jsx => team_settings_modal.jsx} | 14 +- web/react/components/user_settings_modal.jsx | 68 ++++++++ web/react/pages/channel.jsx | 15 +- web/react/stores/team_store.jsx | 114 +++++++++++++ web/react/utils/async_client.jsx | 22 +++ web/react/utils/client.jsx | 31 ++++ web/react/utils/constants.jsx | 3 + web/templates/channel.html | 3 +- web/web.go | 1 + 16 files changed, 445 insertions(+), 20 deletions(-) create mode 100644 web/react/components/team_settings.jsx rename web/react/components/{settings_modal.jsx => team_settings_modal.jsx} (80%) create mode 100644 web/react/components/user_settings_modal.jsx create mode 100644 web/react/stores/team_store.jsx diff --git a/api/team.go b/api/team.go index 40f75ec9e3..775bc29ae2 100644 --- a/api/team.go +++ b/api/team.go @@ -30,6 +30,7 @@ func InitTeam(r *mux.Router) { sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") sr.Handle("/update_name", ApiUserRequired(updateTeamName)).Methods("POST") sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST") + sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") } func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { @@ -602,3 +603,22 @@ func updateValetFeature(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } + +func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { + + if len(c.Session.TeamId) == 0 { + return + } + + if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil { + c.Err = result.Err + return + } else if HandleEtag(result.Data.(*model.Team).Etag(), w, r) { + return + } else { + w.Header().Set(model.HEADER_ETAG_SERVER, result.Data.(*model.Team).Etag()) + w.Header().Set("Expires", "-1") + w.Write([]byte(result.Data.(*model.Team).ToJson())) + return + } +} diff --git a/config/config.json b/config/config.json index 99f41af341..3b8cb21446 100644 --- a/config/config.json +++ b/config/config.json @@ -1,6 +1,6 @@ { "LogSettings": { - "ConsoleEnable": false, + "ConsoleEnable": true, "ConsoleLevel": "DEBUG", "FileEnable": true, "FileLevel": "INFO", @@ -56,7 +56,7 @@ "EmailSettings": { "SMTPUsername": "", "SMTPPassword": "", - "SMTPServer": "localhost:25", + "SMTPServer": "", "UseTLS": false, "FeedbackEmail": "feedback@xxxxxxmustbefilledin.com", "FeedbackName": "", diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 5252f275c2..537a41d031 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -18,6 +18,7 @@ module.exports = React.createClass({ AsyncClient.getChannelExtraInfo(true); AsyncClient.findTeams(); AsyncClient.getStatuses(); + AsyncClient.getMyTeam(); /* End of async loads */ diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx index a1546890f7..ae8510cf22 100644 --- a/web/react/components/settings_sidebar.jsx +++ b/web/react/components/settings_sidebar.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var utils = require('../utils/utils.jsx'); + module.exports = React.createClass({ updateTab: function(tab) { this.props.updateTab(tab); @@ -11,16 +13,11 @@ module.exports = React.createClass({ return (
); - /* Temporarily removing sessions and activity logs -
  • Sessions
  • -
  • Activity Log
  • - */ } }); diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 5a872b7a0f..0b59d20364 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -94,7 +94,8 @@ var NavbarDropdown = React.createClass({
      -
    • Account Settings
    • +
    • Account Settings
    • + { isAdmin ?
    • Team Settings
    • : "" } { invite_link } { team_link } { manage_link } diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index d0c139d1ad..c523ce554d 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -59,7 +59,7 @@ module.exports = React.createClass({
        -
      • Account Settings
      • +
      • Account Settings
      • { invite_link } { team_link } { manage_link } diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx new file mode 100644 index 0000000000..8ad7d7043c --- /dev/null +++ b/web/react/components/team_settings.jsx @@ -0,0 +1,151 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); +var SettingItemMin = require('./setting_item_min.jsx'); +var SettingItemMax = require('./setting_item_max.jsx'); +var SettingPicture = require('./setting_picture.jsx'); +var utils = require('../utils/utils.jsx'); + +var client = require('../utils/client.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); +var Constants = require('../utils/constants.jsx'); + +var FeatureTab = React.createClass({ + submitValetFeature: function() { + data = {}; + data['allow_valet'] = this.state.allow_valet; + + client.updateValetFeature(data, + function(data) { + this.props.updateSection(""); + AsyncClient.getMyTeam(); + }.bind(this), + function(err) { + state = this.getInitialState(); + state.server_error = err; + this.setState(state); + }.bind(this) + ); + }, + handleValetRadio: function(val) { + this.setState({ allow_valet: val }); + this.refs.wrapper.getDOMNode().focus(); + }, + getInitialState: function() { + var team = this.props.team; + + var allow_valet = "false"; + if (team && team.allow_valet) { + allow_valet = "true"; + } + + return { allow_valet: allow_valet }; + }, + render: function() { + var team = this.props.team; + + var client_error = this.state.client_error ? this.state.client_error : null; + var server_error = this.state.server_error ? this.state.server_error : null; + + var valetSection; + var self = this; + + if (this.props.activeSection === 'valet') { + var valetActive = ["",""]; + if (this.state.allow_valet === "false") { + valetActive[1] = "active"; + } else { + valetActive[0] = "active"; + } + + var inputs = []; + + inputs.push( +
        +
        + + +
        +

        Warning: Turning on the Valet feature and using it with any third party software increases the risk of a security breach.
        +
        + ); + + valetSection = ( + + ); + } else { + var describe = ""; + if (this.state.allow_valet === "false") { + describe = "Off"; + } else { + describe = "On"; + } + + valetSection = ( + + ); + } + + return ( +
        +
        + +

        General Settings

        +
        +
        +

        Feature Settings

        +
        + {valetSection} +
        +
        +
        + ); + } +}); + +module.exports = React.createClass({ + componentDidMount: function() { + TeamStore.addChangeListener(this._onChange); + }, + componentWillUnmount: function() { + TeamStore.removeChangeListener(this._onChange); + }, + _onChange: function () { + var team = TeamStore.getCurrent(); + if (!utils.areStatesEqual(this.state.team, team)) { + this.setState({ team: team }); + } + }, + getInitialState: function() { + return { team: TeamStore.getCurrent() }; + }, + render: function() { + if (this.props.activeTab === 'general') { + return ( +
        +
        + ); + } else if (this.props.activeTab === 'feature') { + return ( +
        + +
        + ); + } else { + return
        ; + } + } +}); diff --git a/web/react/components/settings_modal.jsx b/web/react/components/team_settings_modal.jsx similarity index 80% rename from web/react/components/settings_modal.jsx rename to web/react/components/team_settings_modal.jsx index 57a869f93d..08a952d2eb 100644 --- a/web/react/components/settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. var SettingsSidebar = require('./settings_sidebar.jsx'); -var UserSettings = require('./user_settings.jsx'); +var TeamSettings = require('./team_settings.jsx'); module.exports = React.createClass({ componentDidMount: function() { @@ -22,27 +22,31 @@ module.exports = React.createClass({ this.setState({ active_section: section }); }, getInitialState: function() { - return { active_tab: "general", active_section: "" }; + return { active_tab: "feature", active_section: "" }; }, render: function() { + var tabs = []; + tabs.push({name: "feature", ui_name: "Features", icon: "glyphicon glyphicon-wrench"}); + return ( -