diff --git a/api/admin.go b/api/admin.go index 64659775506..ca66b7cb40a 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 e1778b5ac26..c74fbf6e53f 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 92fcbff9349..f0025fdbdc7 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 0a54b6a5d1b..352e1f0e1f8 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 38948641c2e..4b0c0fe5178 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 f9127719f1b..823e859cf18 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 3b333dbe14e..5240caf55d7 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 dd2c1797778..f3e93ef11a6 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 7cb17862677..9d3db964010 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 e8fb2585838..2e107f1ba7e 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 4e3db8f68ad..2707ce6b682 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 650a725163f..acf6db9dc79 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 ffc07a4ddb9..cb7bc883596 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 bf08e6508f6..91d79b91942 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 19c3b2d220c..08d5e2f48fb 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 d3107c5c712..cfd45edb371 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 41ac98303de..ee51debb7d9 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 66cde6ca277..895601bd2ca 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 830f622fa66..f78e0f37afa 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 427daf57731..d0e08f446be 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 112aaa3f2ae..cc7607187e5 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 c9eb09c00fc..b3fb28e9958 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 305e4f19996..2264d5053a5 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) }