From ed9a2da83b3b77e7dd0314eaa92082ac8a2a9a9c Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 15:11:56 -0700 Subject: [PATCH] Adding email to admin console --- api/admin.go | 27 + api/admin_test.go | 28 + api/team.go | 11 +- api/user.go | 4 +- config/config.json | 13 +- model/client.go | 9 + model/config.go | 21 +- utils/config.go | 29 +- utils/mail.go | 60 +- .../admin_console/email_settings.jsx | 546 +++++++++++------- .../components/admin_console/log_settings.jsx | 50 +- web/react/components/invite_member_modal.jsx | 2 +- web/react/components/login.jsx | 8 +- web/react/components/signup_team.jsx | 48 +- web/react/components/signup_user_complete.jsx | 9 +- .../components/team_signup_choose_auth.jsx | 20 +- .../team_signup_send_invites_page.jsx | 2 +- .../components/user_settings_general.jsx | 2 +- web/react/pages/login.jsx | 1 - web/react/pages/signup_team.jsx | 6 +- web/react/pages/signup_user_complete.jsx | 1 - web/react/utils/client.jsx | 15 + web/web.go | 4 - 23 files changed, 563 insertions(+), 353 deletions(-) diff --git a/api/admin.go b/api/admin.go index 6465977550..ca66b7cb40 100644 --- a/api/admin.go +++ b/api/admin.go @@ -24,6 +24,7 @@ func InitAdmin(r *mux.Router) { sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET") sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST") sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET") + sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST") } func getLogs(c *Context, w http.ResponseWriter, r *http.Request) { @@ -98,3 +99,29 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) { json := utils.Cfg.ToJson() w.Write([]byte(json)) } + +func testEmail(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.HasSystemAdminPermissions("testEmail") { + return + } + + cfg := model.ConfigFromJson(r.Body) + if cfg == nil { + c.SetInvalidParam("testEmail", "config") + return + } + + if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { + c.Err = result.Err + return + } else { + if err := utils.SendMailUsingConfig(result.Data.(*model.User).Email, "Mattermost - Testing Email Settings", "


It appears your Mattermost email is setup correctly!", cfg); err != nil { + c.Err = err + return + } + } + + m := make(map[string]string) + m["SUCCESS"] = "true" + w.Write([]byte(model.MapToJson(m))) +} diff --git a/api/admin_test.go b/api/admin_test.go index e1778b5ac2..c74fbf6e53 100644 --- a/api/admin_test.go +++ b/api/admin_test.go @@ -122,3 +122,31 @@ func TestSaveConfig(t *testing.T) { } } } + +func TestEmailTest(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user.Id)) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + if _, err := Client.TestEmail(utils.Cfg); err == nil { + t.Fatal("Shouldn't have permissions") + } + + c := &Context{} + c.RequestId = model.NewId() + c.IpAddress = "cmd_line" + UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) + + Client.LoginByEmail(team.Name, user.Email, "pwd") + + if _, err := Client.TestEmail(utils.Cfg); err != nil { + t.Fatal(err) + } +} diff --git a/api/team.go b/api/team.go index 92fcbff934..f0025fdbdc 100644 --- a/api/team.go +++ b/api/team.go @@ -38,7 +38,7 @@ func InitTeam(r *mux.Router) { } func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { - if utils.Cfg.ServiceSettings.DisableEmailSignUp { + if !utils.Cfg.EmailSettings.AllowSignUpWithEmail { c.Err = model.NewAppError("signupTeam", "Team sign-up with email is disabled.", "") c.Err.StatusCode = http.StatusNotImplemented return @@ -76,10 +76,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } - if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV || utils.Cfg.EmailSettings.ByPassEmail { - m["follow_link"] = bodyPage.Props["Link"] - } - + m["follow_link"] = bodyPage.Props["Link"] w.Header().Set("Access-Control-Allow-Origin", " *") w.Write([]byte(model.MapToJson(m))) } @@ -147,7 +144,7 @@ func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) { } func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { - if utils.Cfg.ServiceSettings.DisableEmailSignUp { + if !utils.Cfg.EmailSettings.AllowSignUpWithEmail { c.Err = model.NewAppError("createTeamFromSignup", "Team sign-up with email is disabled.", "") c.Err.StatusCode = http.StatusNotImplemented return @@ -257,7 +254,7 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { } func CreateTeam(c *Context, team *model.Team) *model.Team { - if utils.Cfg.ServiceSettings.DisableEmailSignUp { + if !utils.Cfg.EmailSettings.AllowSignUpWithEmail { c.Err = model.NewAppError("createTeam", "Team sign-up with email is disabled.", "") c.Err.StatusCode = http.StatusNotImplemented return nil diff --git a/api/user.go b/api/user.go index 0a54b6a5d1..352e1f0e1f 100644 --- a/api/user.go +++ b/api/user.go @@ -58,7 +58,7 @@ func InitUser(r *mux.Router) { } func createUser(c *Context, w http.ResponseWriter, r *http.Request) { - if utils.Cfg.ServiceSettings.DisableEmailSignUp { + if !utils.Cfg.EmailSettings.AllowSignUpWithEmail { c.Err = model.NewAppError("signupTeam", "User sign-up with email is disabled.", "") c.Err.StatusCode = http.StatusNotImplemented return @@ -324,7 +324,7 @@ func checkUserPassword(c *Context, user *model.User, password string) bool { func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { c.LogAuditWithUserId(user.Id, "attempt") - if !user.EmailVerified && !utils.Cfg.EmailSettings.ByPassEmail { + if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification { c.Err = model.NewAppError("Login", "Login failed because email address has not been verified", "user_id="+user.Id) c.Err.StatusCode = http.StatusForbidden return diff --git a/config/config.json b/config/config.json index 38948641c2..4b0c0fe517 100644 --- a/config/config.json +++ b/config/config.json @@ -21,7 +21,6 @@ "UseLocalStorage": true, "StorageDirectory": "./data/", "AllowedLoginAttempts": 10, - "DisableEmailSignUp": false, "EnableOAuthServiceProvider": false }, "SqlSettings": { @@ -51,14 +50,16 @@ "InitialFont": "luximbi.ttf" }, "EmailSettings": { - "ByPassEmail": true, + "AllowSignUpWithEmail": true, + "SendEmailNotifications": false, + "RequireEmailVerification": false, + "FeedbackName": "", + "FeedbackEmail": "", "SMTPUsername": "", "SMTPPassword": "", "SMTPServer": "", - "UseTLS": false, - "UseStartTLS": false, - "FeedbackEmail": "", - "FeedbackName": "", + "SMTPPort": "", + "ConnectionSecurity": "", "ApplePushServer": "", "ApplePushCertPublic": "", "ApplePushCertPrivate": "" diff --git a/model/client.go b/model/client.go index f9127719f1..823e859cf1 100644 --- a/model/client.go +++ b/model/client.go @@ -403,6 +403,15 @@ func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { } } +func (c *Client) TestEmail(config *Config) (*Result, *AppError) { + if r, err := c.DoApiPost("/admin/test_email", config.ToJson()); err != nil { + return nil, err + } else { + return &Result{r.Header.Get(HEADER_REQUEST_ID), + r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + } +} + func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil { return nil, err diff --git a/model/config.go b/model/config.go index 3b333dbe14..5240caf55d 100644 --- a/model/config.go +++ b/model/config.go @@ -22,7 +22,6 @@ type ServiceSettings struct { UseLocalStorage bool StorageDirectory string AllowedLoginAttempts int - DisableEmailSignUp bool EnableOAuthServiceProvider bool } @@ -73,14 +72,18 @@ type ImageSettings struct { } type EmailSettings struct { - ByPassEmail bool - SMTPUsername string - SMTPPassword string - SMTPServer string - UseTLS bool - UseStartTLS bool - FeedbackEmail string - FeedbackName string + AllowSignUpWithEmail bool + SendEmailNotifications bool + RequireEmailVerification bool + FeedbackName string + FeedbackEmail string + SMTPUsername string + SMTPPassword string + SMTPServer string + SMTPPort string + ConnectionSecurity string + + // For Future Use ApplePushServer string ApplePushCertPublic string ApplePushCertPrivate string diff --git a/utils/config.go b/utils/config.go index dd2c179777..f3e93ef11a 100644 --- a/utils/config.go +++ b/utils/config.go @@ -176,18 +176,24 @@ func getClientProperties(c *model.Config) map[string]string { props["BuildHash"] = model.BuildHash props["SiteName"] = c.ServiceSettings.SiteName - props["ByPassEmail"] = strconv.FormatBool(c.EmailSettings.ByPassEmail) + props["AnalyticsUrl"] = c.ServiceSettings.AnalyticsUrl + props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) + + props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications) + props["AllowSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.AllowSignUpWithEmail) props["FeedbackEmail"] = c.EmailSettings.FeedbackEmail + + props["AllowSignUpWithGitLab"] = strconv.FormatBool(false) + props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress) props["AllowPublicLink"] = strconv.FormatBool(c.TeamSettings.AllowPublicLink) + props["SegmentDeveloperKey"] = c.ClientSettings.SegmentDeveloperKey props["GoogleDeveloperKey"] = c.ClientSettings.GoogleDeveloperKey - props["AnalyticsUrl"] = c.ServiceSettings.AnalyticsUrl - props["ByPassEmail"] = strconv.FormatBool(c.EmailSettings.ByPassEmail) + props["ProfileHeight"] = fmt.Sprintf("%v", c.ImageSettings.ProfileHeight) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) - props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) return props } @@ -200,21 +206,6 @@ func IsS3Configured() bool { return true } -func GetAllowedAuthServices() []string { - authServices := []string{} - for name, service := range Cfg.SSOSettings { - if service.Allow { - authServices = append(authServices, name) - } - } - - if !Cfg.ServiceSettings.DisableEmailSignUp { - authServices = append(authServices, "email") - } - - return authServices -} - func IsServiceAllowed(s string) bool { if len(s) == 0 { return false diff --git a/utils/mail.go b/utils/mail.go index 7cb1786267..9d3db96401 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -15,43 +15,28 @@ import ( "time" ) -func CheckMailSettings() *model.AppError { - if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail { - return model.NewAppError("CheckMailSettings", "No email settings present, mail will not be sent", "") - } - conn, err := connectToSMTPServer() - if err != nil { - return err - } - defer conn.Close() - c, err2 := newSMTPClient(conn) - if err2 != nil { - return err - } - defer c.Quit() - defer c.Close() - - return nil -} - -func connectToSMTPServer() (net.Conn, *model.AppError) { - host, _, _ := net.SplitHostPort(Cfg.EmailSettings.SMTPServer) +const ( + CONN_SECURITY_NONE = "" + CONN_SECURITY_TLS = "TLS" + CONN_SECURITY_STARTTLS = "STARTTLS" +) +func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { var conn net.Conn var err error - if Cfg.EmailSettings.UseTLS { + if config.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS { tlsconfig := &tls.Config{ InsecureSkipVerify: true, - ServerName: host, + ServerName: config.EmailSettings.SMTPServer, } - conn, err = tls.Dial("tcp", Cfg.EmailSettings.SMTPServer, tlsconfig) + conn, err = tls.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort, tlsconfig) if err != nil { return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) } } else { - conn, err = net.Dial("tcp", Cfg.EmailSettings.SMTPServer) + conn, err = net.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) if err != nil { return nil, model.NewAppError("SendMail", "Failed to open connection", err.Error()) } @@ -60,24 +45,23 @@ func connectToSMTPServer() (net.Conn, *model.AppError) { return conn, nil } -func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) { - host, _, _ := net.SplitHostPort(Cfg.EmailSettings.SMTPServer) - c, err := smtp.NewClient(conn, host) +func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) { + c, err := smtp.NewClient(conn, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) if err != nil { l4g.Error("Failed to open a connection to SMTP server %v", err) return nil, model.NewAppError("SendMail", "Failed to open TLS connection", err.Error()) } // GO does not support plain auth over a non encrypted connection. // so if not tls then no auth - auth := smtp.PlainAuth("", Cfg.EmailSettings.SMTPUsername, Cfg.EmailSettings.SMTPPassword, host) - if Cfg.EmailSettings.UseTLS { + auth := smtp.PlainAuth("", config.EmailSettings.SMTPUsername, config.EmailSettings.SMTPPassword, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) + if config.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS { if err = c.Auth(auth); err != nil { return nil, model.NewAppError("SendMail", "Failed to authenticate on SMTP server", err.Error()) } - } else if Cfg.EmailSettings.UseStartTLS { + } else if config.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS { tlsconfig := &tls.Config{ InsecureSkipVerify: true, - ServerName: host, + ServerName: config.EmailSettings.SMTPServer, } c.StartTLS(tlsconfig) if err = c.Auth(auth); err != nil { @@ -88,12 +72,16 @@ func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) { } func SendMail(to, subject, body string) *model.AppError { + return SendMailUsingConfig(to, subject, body, Cfg) +} - if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail { +func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.AppError { + + if !config.EmailSettings.SendEmailNotifications { return nil } - fromMail := mail.Address{Cfg.EmailSettings.FeedbackName, Cfg.EmailSettings.FeedbackEmail} + fromMail := mail.Address{config.EmailSettings.FeedbackName, config.EmailSettings.FeedbackEmail} toMail := mail.Address{"", to} headers := make(map[string]string) @@ -110,13 +98,13 @@ func SendMail(to, subject, body string) *model.AppError { } message += "\r\n" + body + "" - conn, err1 := connectToSMTPServer() + conn, err1 := connectToSMTPServer(config) if err1 != nil { return err1 } defer conn.Close() - c, err2 := newSMTPClient(conn) + c, err2 := newSMTPClient(conn, config) if err2 != nil { return err2 } diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index e8fb258583..2e107f1ba7 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -1,15 +1,148 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + export default class EmailSettings extends React.Component { constructor(props) { super(props); + this.handleChange = this.handleChange.bind(this); + this.handleTestConnection = this.handleTestConnection.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.buildConfig = this.buildConfig.bind(this); + this.state = { + sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications, + saveNeeded: false, + serverError: null, + emailSuccess: null, + emailFail: null }; } + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'sendEmailNotifications_true') { + s.sendEmailNotifications = true; + } + + if (action === 'sendEmailNotifications_false') { + s.sendEmailNotifications = false; + } + + this.setState(s); + } + + buildConfig() { + var config = this.props.config; + config.EmailSettings.AllowSignUpWithEmail = React.findDOMNode(this.refs.allowSignUpWithEmail).checked; + config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.RequireEmailVerification = React.findDOMNode(this.refs.requireEmailVerification).checked; + config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.FeedbackName = React.findDOMNode(this.refs.feedbackName).value.trim(); + config.EmailSettings.FeedbackEmail = React.findDOMNode(this.refs.feedbackEmail).value.trim(); + config.EmailSettings.SMTPServer = React.findDOMNode(this.refs.SMTPServer).value.trim(); + config.EmailSettings.SMTPPort = React.findDOMNode(this.refs.SMTPPort).value.trim(); + config.EmailSettings.SMTPUsername = React.findDOMNode(this.refs.SMTPUsername).value.trim(); + config.EmailSettings.SMTPPassword = React.findDOMNode(this.refs.SMTPPassword).value.trim(); + config.EmailSettings.ConnectionSecurity = React.findDOMNode(this.refs.ConnectionSecurity).value.trim(); + return config; + } + + handleTestConnection(e) { + e.preventDefault(); + $('#connection-button').button('loading'); + + var config = this.buildConfig(); + + Client.testEmail( + config, + () => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: true, + emailSuccess: true, + emailFail: null + }); + }, + (err) => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: true, + emailSuccess: null, + emailFail: err.message + ' - ' + err.detailed_error + }); + $('#connection-button').button('reset'); + } + ); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.buildConfig(); + + Client.saveConfig( + config, + () => { + AsyncClient.getConfig(); + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: false, + emailSuccess: null, + emailFail: null + }); + $('#save-button').button('reset'); + }, + (err) => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: err.message, + saveNeeded: true, + emailSuccess: null, + emailFail: null + }); + $('#save-button').button('reset'); + } + ); + } + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + var emailSuccess = ''; + if (this.state.emailSuccess) { + emailSuccess = ( +
+ {'No errors were reported while sending an email. Please check your inbox to make sure.'} +
+ ); + } + + var emailFail = ''; + if (this.state.emailFail) { + emailSuccess = ( +
+ {'Connection unsuccessful: ' + this.state.emailFail} +
+ ); + } + return (

{'Email Settings'}

@@ -17,149 +150,130 @@ export default class EmailSettings extends React.Component { className='form-horizontal' role='form' > +
-

{'This is some sample help text for the Bypass Email field'}

+

{'Typically set to true in production. When true Mattermost will allow team creation and account signup utilizing email and password. You would set this to false if you only wanted to allow signup from a service like OAuth or LDAP.'}

+
- -
-
{' This is some error text for the Bypass Email field'}
-
-

{'This is some sample help text for the SMTP username field'}

+ + +

{'Typically set to true in production. When true Mattermost will attempt to send email notifications. Developers may set this field to false skipping sending emails for faster development.'}

+
- + + +

{'Typically set to true in production. When true Mattermost will not allow a user to login without first having recieved an email with a verification link. Developers may set this field to false so skip sending verification emails for faster development.'}

+
-
- - {'Test Connection'} - -
{' Connection successful'}
-
{' Connection unsuccessful'}
-
-
-
-
- -
- - -
-
-
- -
- - +

{'Name displayed on email account used when sending notification emails from Mattermost.'}

+
+
-
-
-
-
-
- -
+

{' Obtain this credential from administrator setting up your email server.'}

-
-
- + +
+ +
+ +

{'Location of SMTP email server.'}

+
+
+ +
+ +
+ +

{'Port of SMTP email server.'}

+
+
+ +
+ +
+ +
+ + + + +
{'None'}{'Mattermost will send email over an unsecure connection.'}
{'TLS'}{'Encrypts the communication between Mattermost and your email server.'}
{'STARTTLS'}{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}
-
-
-
- -
- -

{'This is some sample help text for the Apple push server field'}

-
-
-
- -
- -
-
-
- -
- -
-
-
+
+ + {emailSuccess} + {emailFail}
+ {serverError}
+
); } -} \ No newline at end of file +} + +EmailSettings.propTypes = { + config: React.PropTypes.object +}; \ No newline at end of file diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 4e3db8f68a..2707ce6b68 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -12,13 +12,33 @@ export default class LogSettings extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.state = { + consoleEnable: this.props.config.LogSettings.ConsoleEnable, + fileEnable: this.props.config.LogSettings.FileEnable, saveNeeded: false, serverError: null }; } - handleChange() { - this.setState({saveNeeded: true, serverError: this.state.serverError}); + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'console_true') { + s.consoleEnable = true; + } + + if (action === 'console_false') { + s.consoleEnable = false; + } + + if (action === 'file_true') { + s.fileEnable = true; + } + + if (action === 'file_false') { + s.fileEnable = false; + } + + this.setState(s); } handleSubmit(e) { @@ -37,11 +57,21 @@ export default class LogSettings extends React.Component { config, () => { AsyncClient.getConfig(); - this.setState({serverError: null, saveNeeded: false}); + this.setState({ + consoleEnable: config.LogSettings.ConsoleEnable, + fileEnable: config.LogSettings.FileEnable, + serverError: null, + saveNeeded: false + }); $('#save-button').button('reset'); }, (err) => { - this.setState({serverError: err.message, saveNeeded: true}); + this.setState({ + consoleEnable: config.LogSettings.ConsoleEnable, + fileEnable: config.LogSettings.FileEnable, + serverError: err.message, + saveNeeded: true + }); $('#save-button').button('reset'); } ); @@ -81,7 +111,7 @@ export default class LogSettings extends React.Component { value='true' ref='consoleEnable' defaultChecked={this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'console_true')} /> {'true'} @@ -91,7 +121,7 @@ export default class LogSettings extends React.Component { name='consoleEnable' value='false' defaultChecked={!this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'console_false')} /> {'false'} @@ -113,6 +143,7 @@ export default class LogSettings extends React.Component { ref='consoleLevel' defaultValue={this.props.config.LogSettings.consoleLevel} onChange={this.handleChange} + disabled={!this.state.consoleEnable} > @@ -136,7 +167,7 @@ export default class LogSettings extends React.Component { ref='fileEnable' value='true' defaultChecked={this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'file_true')} /> {'true'} @@ -146,7 +177,7 @@ export default class LogSettings extends React.Component { name='fileEnable' value='false' defaultChecked={!this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'file_false')} /> {'false'} @@ -168,6 +199,7 @@ export default class LogSettings extends React.Component { ref='fileLevel' defaultValue={this.props.config.LogSettings.FileLevel} onChange={this.handleChange} + disabled={!this.state.fileEnable} > @@ -193,6 +225,7 @@ export default class LogSettings extends React.Component { placeholder='Enter your file location' defaultValue={this.props.config.LogSettings.FileLocation} onChange={this.handleChange} + disabled={!this.state.fileEnable} />

{'File to which log files are written. If blank, will be set to ./logs/mattermost.log. Log rotation is enabled and new files may be created in the same directory.'}

@@ -214,6 +247,7 @@ export default class LogSettings extends React.Component { placeholder='Enter your file format' defaultValue={this.props.config.LogSettings.FileFormat} onChange={this.handleChange} + disabled={!this.state.fileEnable} />

{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 650a725163..acf6db9dc7 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -21,7 +21,7 @@ export default class InviteMemberModal extends React.Component { emailErrors: {}, firstNameErrors: {}, lastNameErrors: {}, - emailEnabled: !global.window.config.ByPassEmail + emailEnabled: !global.window.config.SendEmailNotifications }; } diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index ffc07a4ddb..cb7bc88359 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -95,10 +95,8 @@ export default class Login extends React.Component { focusEmail = true; } - const authServices = JSON.parse(this.props.authServices); - let loginMessage = []; - if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) { + if (global.window.config.AllowSignUpWithGitLab) { loginMessage.push(

@@ -206,10 +204,8 @@ export default class Login extends React.Component { Login.defaultProps = { teamName: '', teamDisplayName: '', - authServices: '' }; Login.propTypes = { teamName: React.PropTypes.string, teamDisplayName: React.PropTypes.string, - authServices: React.PropTypes.string }; diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index bf08e6508f..91d79b9194 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -4,7 +4,7 @@ const ChoosePage = require('./team_signup_choose_auth.jsx'); const EmailSignUpPage = require('./team_signup_with_email.jsx'); const SSOSignupPage = require('./team_signup_with_sso.jsx'); -const Constants = require('../utils/constants.jsx'); +var Constants = require('../utils/constants.jsx'); export default class TeamSignUp extends React.Component { constructor(props) { @@ -12,38 +12,32 @@ export default class TeamSignUp extends React.Component { this.updatePage = this.updatePage.bind(this); - if (props.services.length === 1) { - if (props.services[0] === Constants.EMAIL_SERVICE) { - this.state = {page: 'email', service: ''}; - } else { - this.state = {page: 'service', service: props.services[0]}; - } - } else { - this.state = {page: 'choose', service: ''}; + if (global.window.config.AllowSignUpWithEmail && global.window.config.AllowSignUpWithGitLab) { + this.state = {page: 'choose'}; + } else if (global.window.config.AllowSignUpWithEmail) { + this.state = {page: 'email'}; + } else if (global.window.config.AllowSignUpWithGitLab) { + this.state = {page: 'gitlab'}; } } - updatePage(page, service) { - this.setState({page: page, service: service}); + + updatePage(page) { + this.setState({page}); } + render() { + if (this.state.page === 'choose') { + return ( + + ); + } + if (this.state.page === 'email') { return ; - } else if (this.state.page === 'service' && this.state.service !== '') { - return ; + } else if (this.state.page === 'gitlab') { + return ; } - - return ( - - ); } } - -TeamSignUp.defaultProps = { - services: [] -}; -TeamSignUp.propTypes = { - services: React.PropTypes.array -}; diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 19c3b2d220..08d5e2f48f 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -161,11 +161,8 @@ export default class SignupUserComplete extends React.Component {
); - // add options to log in using another service - var authServices = JSON.parse(this.props.authServices); - var signupMessage = []; - if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) { + if (global.window.config.AllowSignUpWithGitLab) { signupMessage.push(
@@ -262,7 +259,6 @@ SignupUserComplete.defaultProps = { teamId: '', email: '', data: null, - authServices: '', teamDisplayName: '' }; SignupUserComplete.propTypes = { @@ -271,6 +267,5 @@ SignupUserComplete.propTypes = { teamId: React.PropTypes.string, email: React.PropTypes.string, data: React.PropTypes.string, - authServices: React.PropTypes.string, teamDisplayName: React.PropTypes.string }; diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index d3107c5c71..cfd45edb37 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Constants = require('../utils/constants.jsx'); - export default class ChooseAuthPage extends React.Component { constructor(props) { super(props); @@ -10,7 +8,7 @@ export default class ChooseAuthPage extends React.Component { } render() { var buttons = []; - if (this.props.services.indexOf(Constants.GITLAB_SERVICE) !== -1) { + if (global.window.config.AllowSignUpWithGitLab) { buttons.push( - Create new team with GitLab Account + {'Create new team with GitLab Account'} ); } - if (this.props.services.indexOf(Constants.EMAIL_SERVICE) !== -1) { + if (global.window.config.AllowSignUpWithEmail) { buttons.push( - Create new team with email address + {'Create new team with email address'} ); } if (buttons.length === 0) { - buttons = No sign-up methods configured, please contact your system administrator.; + buttons = {'No sign-up methods configured, please contact your system administrator.'}; } return ( @@ -61,10 +59,6 @@ export default class ChooseAuthPage extends React.Component { } } -ChooseAuthPage.defaultProps = { - services: [] -}; ChooseAuthPage.propTypes = { - services: React.PropTypes.array, updatePage: React.PropTypes.func }; diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 41ac98303d..ee51debb7d 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -13,7 +13,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { this.submitSkip = this.submitSkip.bind(this); this.keySubmit = this.keySubmit.bind(this); this.state = { - emailEnabled: !global.window.config.ByPassEmail + emailEnabled: !global.window.config.SendEmailNotifications }; if (!this.state.emailEnabled) { diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index 66cde6ca27..895601bd2c 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -208,7 +208,7 @@ export default class UserSettingsGeneralTab extends React.Component { } setupInitialState(props) { var user = props.user; - var emailEnabled = !global.window.config.ByPassEmail; + var emailEnabled = !global.window.config.SendEmailNotifications; return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled}; } diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx index 830f622fa6..f78e0f37af 100644 --- a/web/react/pages/login.jsx +++ b/web/react/pages/login.jsx @@ -8,7 +8,6 @@ function setupLoginPage(props) { , document.getElementById('login') ); diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index 427daf5773..d0e08f446b 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -3,11 +3,9 @@ var SignupTeam = require('../components/signup_team.jsx'); -function setupSignupTeamPage(props) { - var services = JSON.parse(props.AuthServices); - +function setupSignupTeamPage() { React.render( - , + , document.getElementById('signup-team') ); } diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx index 112aaa3f2a..cc7607187e 100644 --- a/web/react/pages/signup_user_complete.jsx +++ b/web/react/pages/signup_user_complete.jsx @@ -12,7 +12,6 @@ function setupSignupUserCompletePage(props) { email={props.Email} hash={props.Hash} data={props.Data} - authServices={props.AuthServices} />, document.getElementById('signup-user-complete') ); diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index c9eb09c00f..b3fb28e995 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -334,6 +334,21 @@ export function saveConfig(config, success, error) { }); } +export function testEmail(config, success, error) { + $.ajax({ + url: '/api/v1/admin/test_email', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(config), + success, + error: function onError(xhr, status, err) { + var e = handleError('testEmail', xhr, status, err); + error(e); + } + }); +} + export function getMeSynchronous(success, error) { var currentUser = null; $.ajax({ diff --git a/web/web.go b/web/web.go index 305e4f1999..2264d5053a 100644 --- a/web/web.go +++ b/web/web.go @@ -143,7 +143,6 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { if len(c.Session.UserId) == 0 { page := NewHtmlTemplatePage("signup_team", "Signup") - page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices()) page.Render(c, w) } else { page := NewHtmlTemplatePage("home", "Home") @@ -159,7 +158,6 @@ func signup(c *api.Context, w http.ResponseWriter, r *http.Request) { } page := NewHtmlTemplatePage("signup_team", "Signup") - page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices()) page.Render(c, w) } @@ -191,7 +189,6 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) { page := NewHtmlTemplatePage("login", "Login") page.Props["TeamDisplayName"] = team.DisplayName page.Props["TeamName"] = teamName - page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices()) page.Render(c, w) } @@ -277,7 +274,6 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) page.Props["TeamId"] = props["id"] page.Props["Data"] = data page.Props["Hash"] = hash - page.Props["AuthServices"] = model.ArrayToJson(utils.GetAllowedAuthServices()) page.Render(c, w) }