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
{'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 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.'}
{'Name displayed on email account used when sending notification emails from Mattermost.'}
{' Obtain this credential from administrator setting up your email server.'}
{' 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'}
-{'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.'}
{'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(