Adding service settings to admin console

Этот коммит содержится в:
=Corey Hulen
2015-09-22 12:12:50 -07:00
родитель 08a3acbb44
Коммит 88e5a71e8c
37 изменённых файлов: 631 добавлений и 297 удалений

Просмотреть файл

@@ -35,7 +35,7 @@ func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
var lines []string
if utils.Cfg.LogSettings.FileEnable {
if utils.Cfg.LogSettings.EnableFile {
file, err := os.Open(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation))
if err != nil {
@@ -82,7 +82,7 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if len(cfg.ServiceSettings.Port) == 0 {
if len(cfg.ServiceSettings.ListenAddress) == 0 {
c.SetInvalidParam("saveConfig", "config")
return
}

Просмотреть файл

@@ -18,7 +18,7 @@ func Setup() {
NewServer()
StartServer()
InitApi()
Client = model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
Client = model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
}
}

Просмотреть файл

@@ -627,7 +627,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
currentEtag = cache_result.Etag
}
Client2 := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
Client2 := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "tester2@test.com", Nickname: "Tester 2", Password: "pwd"}
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)

Просмотреть файл

@@ -215,8 +215,8 @@ func joinCommand(c *Context, command *model.Command) bool {
func loadTestCommand(c *Context, command *model.Command) bool {
cmd := "/loadtest"
// This command is only available when AllowTesting is true
if !utils.Cfg.ServiceSettings.AllowTesting {
// This command is only available when EnableTesting is true
if !utils.Cfg.ServiceSettings.EnableTesting {
return false
}

Просмотреть файл

@@ -107,21 +107,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
isTokenFromQueryString = true
}
protocol := "http"
// If the request came from the ELB then assume this is produciton
// and redirect all http requests to https
if utils.Cfg.ServiceSettings.UseSSL {
forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO)
if forwardProto == "http" {
l4g.Info("redirecting http request to https for %v", r.URL.Path)
http.Redirect(w, r, "https://"+r.Host, http.StatusTemporaryRedirect)
return
} else {
protocol = "https"
}
}
protocol := GetProtocol(r)
c.setSiteURL(protocol + "://" + r.Host)
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
@@ -209,6 +195,14 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func GetProtocol(r *http.Request) string {
if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" {
return "https"
} else {
return "http"
}
}
func (c *Context) LogAudit(extraInfo string) {
audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
@@ -385,6 +379,11 @@ func (c *Context) GetSiteURL() string {
func GetIpAddress(r *http.Request) string {
address := r.Header.Get(model.HEADER_FORWARDED)
if len(address) == 0 {
address = r.Header.Get(model.HEADER_REAL_IP)
}
if len(address) == 0 {
address, _, _ = net.SplitHostPort(r.RemoteAddr)
}
@@ -458,14 +457,7 @@ func IsPrivateIpAddress(ipAddress string) bool {
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
protocol := "http"
if utils.Cfg.ServiceSettings.UseSSL {
forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO)
if forwardProto != "http" {
protocol = "https"
}
}
protocol := GetProtocol(r)
SiteURL := protocol + "://" + r.Host
m := make(map[string]string)

Просмотреть файл

@@ -399,7 +399,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
asyncGetFile(path, fileData)
if len(hash) > 0 && len(data) > 0 && len(teamId) == 26 {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ImageSettings.PublicLinkSalt)) {
c.Err = model.NewAppError("getFile", "The public link does not appear to be valid", "")
return
}
@@ -477,7 +477,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ImageSettings.PublicLinkSalt))
url := fmt.Sprintf("%s/api/v1/files/get/%s/%s/%s?d=%s&h=%s&t=%s", c.GetSiteURL(), channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId)

Просмотреть файл

@@ -38,7 +38,7 @@ func BenchmarkGetFile(b *testing.B) {
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ImageSettings.PublicLinkSalt))
// wait a bit for files to ready
time.Sleep(5 * time.Second)

Просмотреть файл

@@ -222,7 +222,7 @@ func TestGetFile(t *testing.T) {
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ImageSettings.PublicLinkSalt))
Client.LoginByEmail(team2.Name, user2.Email, "pwd")

Просмотреть файл

@@ -38,7 +38,7 @@ func NewServer() {
func StartServer() {
l4g.Info("Starting Server...")
l4g.Info("Server is listening on " + utils.Cfg.ServiceSettings.Port)
l4g.Info("Server is listening on " + utils.Cfg.ServiceSettings.ListenAddress)
var handler http.Handler = Srv.Router
@@ -71,7 +71,7 @@ func StartServer() {
}
go func() {
err := Srv.Server.ListenAndServe(":"+utils.Cfg.ServiceSettings.Port, handler)
err := Srv.Server.ListenAndServe(utils.Cfg.ServiceSettings.ListenAddress, handler)
if err != nil {
l4g.Critical("Error starting server, err:%v", err)
time.Sleep(time.Second)

Просмотреть файл

@@ -38,7 +38,7 @@ func InitTeam(r *mux.Router) {
}
func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.EmailSettings.AllowSignUpWithEmail {
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
c.Err = model.NewAppError("signupTeam", "Team sign-up with email is disabled.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
@@ -66,7 +66,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(props)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_team_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash))
@@ -85,7 +85,7 @@ func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
service := params["service"]
sso := utils.Cfg.GetSSOService(service)
if sso != nil && !sso.Allow {
if sso != nil && !sso.Enable {
c.SetInvalidParam("createTeamFromSSO", "service")
return
}
@@ -142,7 +142,7 @@ func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
}
func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.EmailSettings.AllowSignUpWithEmail {
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
c.Err = model.NewAppError("createTeamFromSignup", "Team sign-up with email is disabled.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
@@ -183,7 +183,7 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
teamSignup.User.TeamId = ""
teamSignup.User.Password = password
if !model.ComparePassword(teamSignup.Hash, fmt.Sprintf("%v:%v", teamSignup.Data, utils.Cfg.ServiceSettings.InviteSalt)) {
if !model.ComparePassword(teamSignup.Hash, fmt.Sprintf("%v:%v", teamSignup.Data, utils.Cfg.EmailSettings.InviteSalt)) {
c.Err = model.NewAppError("createTeamFromSignup", "The signup link does not appear to be valid", "")
return
}
@@ -243,7 +243,7 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
func CreateTeam(c *Context, team *model.Team) *model.Team {
if !utils.Cfg.EmailSettings.AllowSignUpWithEmail {
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
c.Err = model.NewAppError("createTeam", "Team sign-up with email is disabled.", "")
c.Err.StatusCode = http.StatusNotImplemented
return nil
@@ -258,11 +258,6 @@ func CreateTeam(c *Context, team *model.Team) *model.Team {
return nil
}
if utils.Cfg.ServiceSettings.Mode != utils.MODE_DEV {
c.Err = model.NewAppError("CreateTeam", "The mode does not allow network creation without a valid invite", "")
return nil
}
if result := <-Srv.Store.Team().Save(team); result.Err != nil {
c.Err = result.Err
return nil
@@ -488,10 +483,10 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
props["name"] = team.Name
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(props)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash))
if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV {
if !utils.Cfg.EmailSettings.SendEmailNotifications {
l4g.Info("sending invitation to %v %v", invite, bodyPage.Props["Link"])
}

Просмотреть файл

@@ -30,7 +30,7 @@ func TestCreateFromSignupTeam(t *testing.T) {
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(props)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"}

Просмотреть файл

@@ -58,7 +58,7 @@ func InitUser(r *mux.Router) {
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.EmailSettings.AllowSignUpWithEmail {
if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
c.Err = model.NewAppError("signupTeam", "User sign-up with email is disabled.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
@@ -90,7 +90,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("d")
props := model.MapFromJson(strings.NewReader(data))
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
c.Err = model.NewAppError("createUser", "The signup link does not appear to be valid", "")
return
}
@@ -287,7 +287,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
func checkUserPassword(c *Context, user *model.User, password string) bool {
if user.FailedAttempts >= utils.Cfg.ServiceSettings.AllowedLoginAttempts {
if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
c.LogAuditWithUserId(user.Id, "fail")
c.Err = model.NewAppError("checkUserPassword", "Your account is locked because of too many failed password attempts. Please reset your password.", "user_id="+user.Id)
c.Err.StatusCode = http.StatusForbidden
@@ -1129,7 +1129,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.ResetSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt))
link := fmt.Sprintf("%s/reset_password?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash))
@@ -1208,7 +1208,7 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.ServiceSettings.ResetSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) {
c.Err = model.NewAppError("resetPassword", "The reset password link does not appear to be valid", "")
return
}
@@ -1357,7 +1357,7 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, teamName, service, redirectUri, loginHint string) {
sso := utils.Cfg.GetSSOService(service)
if sso != nil && !sso.Allow {
if sso != nil && !sso.Enable {
c.Err = model.NewAppError("GetAuthorizationCode", "Unsupported OAuth service provider", "service="+service)
c.Err.StatusCode = http.StatusBadRequest
return
@@ -1385,7 +1385,7 @@ func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, te
func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, *model.AppError) {
sso := utils.Cfg.GetSSOService(service)
if sso != nil && !sso.Allow {
if sso != nil && !sso.Enable {
return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Unsupported OAuth service provider", "service="+service)
}

Просмотреть файл

@@ -151,7 +151,7 @@ func TestLogin(t *testing.T) {
props["display_name"] = rteam2.Data.(*model.Team).DisplayName
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(props)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt))
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
ruser2, _ := Client.CreateUserFromSignup(&user2, data, hash)
@@ -814,7 +814,7 @@ func TestResetPassword(t *testing.T) {
props["user_id"] = user.Id
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data["data"] = model.MapToJson(props)
data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.ServiceSettings.ResetSalt))
data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.EmailSettings.PasswordResetSalt))
data["name"] = team.Name
if _, err := Client.ResetPassword(data); err != nil {

Просмотреть файл

@@ -16,7 +16,7 @@ import (
func TestSocket(t *testing.T) {
Setup()
url := "ws://localhost:" + utils.Cfg.ServiceSettings.Port + "/api/v1/websocket"
url := "ws://localhost" + utils.Cfg.ServiceSettings.ListenAddress + "/api/v1/websocket"
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)

Просмотреть файл

@@ -1,18 +1,11 @@
{
"ServiceSettings": {
"Mode": "dev",
"AllowTesting": false,
"UseSSL": false,
"Port": "8065",
"Version": "developer",
"InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
"PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "",
"AllowedLoginAttempts": 10,
"EnableOAuthServiceProvider": false,
"ListenAddress": ":8065",
"MaximumLoginAttempts": 10,
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": ""
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
"EnableTesting": false
},
"TeamSettings": {
"SiteName": "Mattermost",
@@ -32,9 +25,9 @@
"AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
},
"LogSettings": {
"ConsoleEnable": true,
"EnableConsole": true,
"ConsoleLevel": "DEBUG",
"FileEnable": true,
"EnableFile": true,
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": ""
@@ -43,6 +36,7 @@
"DriverName": "local",
"Directory": "./data/",
"EnablePublicLink": true,
"PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
"ThumbnailWidth": 120,
"ThumbnailHeight": 100,
"PreviewWidth": 1024,
@@ -56,7 +50,7 @@
"AmazonS3Region": ""
},
"EmailSettings": {
"AllowSignUpWithEmail": true,
"EnableSignUpWithEmail": true,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
@@ -66,6 +60,8 @@
"SMTPServer": "",
"SMTPPort": "",
"ConnectionSecurity": "",
"InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
"PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
"ApplePushServer": "",
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
@@ -82,7 +78,7 @@
"ShowFullName": true
},
"GitLabSettings": {
"Allow": false,
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "",

Просмотреть файл

@@ -1,81 +1,73 @@
{
"ServiceSettings": {
"ListenAddress": ":80",
"MaximumLoginAttempts": 10,
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
"EnableTesting": false
},
"TeamSettings": {
"SiteName": "Mattermost",
"MaxUsersPerTeam": 50,
"DefaultThemeColor": "#2389D7",
"EnableTeamCreation": true,
"EnableUserCreation": true,
"RestrictCreationToDomains": ""
},
"SqlSettings": {
"DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8",
"DataSourceReplicas": [],
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
"AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
},
"LogSettings": {
"ConsoleEnable": true,
"EnableConsole": false,
"ConsoleLevel": "INFO",
"FileEnable": true,
"EnableFile": true,
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": ""
},
"ServiceSettings": {
"SiteName": "Mattermost",
"Mode" : "dev",
"AllowTesting" : true,
"UseSSL": false,
"Port": "80",
"Version": "developer",
"Shards": {
},
"InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
"PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/",
"AllowedLoginAttempts": 10,
"DisableEmailSignUp": false,
"EnableOAuthServiceProvider": false
},
"SSOSettings": {
"gitlab": {
"Allow": false,
"Secret" : "",
"Id": "",
"Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
},
"SqlSettings": {
"DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8",
"DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8"],
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
"AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
},
"AWSSettings": {
"S3AccessKeyId": "",
"S3SecretAccessKey": "",
"S3Bucket": "",
"S3Region": ""
},
"ImageSettings": {
"DriverName": "local",
"Directory": "/mattermost/data/",
"EnablePublicLink": true,
"PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
"ThumbnailWidth": 120,
"ThumbnailHeight": 100,
"PreviewWidth": 1024,
"PreviewHeight": 0,
"ProfileWidth": 128,
"ProfileHeight": 128,
"InitialFont": "luximbi.ttf"
"InitialFont": "luximbi.ttf",
"AmazonS3AccessKeyId": "",
"AmazonS3SecretAccessKey": "",
"AmazonS3Bucket": "",
"AmazonS3Region": ""
},
"EmailSettings": {
"ByPassEmail" : true,
"EnableSignUpWithEmail": true,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
"FeedbackEmail": "",
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
"UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"SMTPPort": "",
"ConnectionSecurity": "",
"InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
"PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
"ApplePushServer": "",
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
},
"RateLimitSettings": {
"UseRateLimiter": true,
"EnableRateLimiter": true,
"PerSec": 10,
"MemoryStoreSize": 10000,
"VaryByRemoteAddr": true,
@@ -83,22 +75,15 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
"ShowPhoneNumber": true,
"ShowSkypeId": true,
"ShowFullName": true
},
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
"AllowValetDefault": false,
"TermsLink": "/static/help/configure_links.html",
"PrivacyLink": "/static/help/configure_links.html",
"AboutLink": "/static/help/configure_links.html",
"HelpLink": "/static/help/configure_links.html",
"ReportProblemLink": "/static/help/configure_links.html",
"TourLink": "/static/help/configure_links.html",
"DefaultThemeColor": "#2389D7",
"DisableTeamCreation": false,
"RestrictCreationToDomains": ""
"GitLabSettings": {
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
}
}

Просмотреть файл

@@ -1,81 +1,73 @@
{
"ServiceSettings": {
"ListenAddress": ":80",
"MaximumLoginAttempts": 10,
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
"EnableTesting": false
},
"TeamSettings": {
"SiteName": "Mattermost",
"MaxUsersPerTeam": 50,
"DefaultThemeColor": "#2389D7",
"EnableTeamCreation": true,
"EnableUserCreation": true,
"RestrictCreationToDomains": ""
},
"SqlSettings": {
"DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8",
"DataSourceReplicas": [],
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
"AtRestEncryptKey": "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"
},
"LogSettings": {
"ConsoleEnable": true,
"EnableConsole": false,
"ConsoleLevel": "INFO",
"FileEnable": true,
"EnableFile": true,
"FileLevel": "INFO",
"FileFormat": "",
"FileLocation": ""
},
"ServiceSettings": {
"SiteName": "Mattermost",
"Mode" : "dev",
"AllowTesting" : true,
"UseSSL": false,
"Port": "80",
"Version": "developer",
"Shards": {
},
"InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
"PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/",
"AllowedLoginAttempts": 10,
"DisableEmailSignUp": false,
"EnableOAuthServiceProvider": false
},
"SSOSettings": {
"gitlab": {
"Allow": false,
"Secret" : "",
"Id": "",
"Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
},
"SqlSettings": {
"DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8",
"DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8"],
"MaxIdleConns": 10,
"MaxOpenConns": 10,
"Trace": false,
"AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
},
"AWSSettings": {
"S3AccessKeyId": "",
"S3SecretAccessKey": "",
"S3Bucket": "",
"S3Region": ""
},
"ImageSettings": {
"DriverName": "local",
"Directory": "/mattermost/data/",
"EnablePublicLink": true,
"PublicLinkSalt": "LhaAWC6lYEKHTkBKsvyXNIOfUIT37AX",
"ThumbnailWidth": 120,
"ThumbnailHeight": 100,
"PreviewWidth": 1024,
"PreviewHeight": 0,
"ProfileWidth": 128,
"ProfileHeight": 128,
"InitialFont": "luximbi.ttf"
"InitialFont": "luximbi.ttf",
"AmazonS3AccessKeyId": "",
"AmazonS3SecretAccessKey": "",
"AmazonS3Bucket": "",
"AmazonS3Region": ""
},
"EmailSettings": {
"ByPassEmail" : true,
"EnableSignUpWithEmail": true,
"SendEmailNotifications": false,
"RequireEmailVerification": false,
"FeedbackName": "",
"FeedbackEmail": "",
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
"UseStartTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
"SMTPPort": "",
"ConnectionSecurity": "",
"InviteSalt": "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo",
"PasswordResetSalt": "vZ4DcKyVVRlKHHJpexcuXzojkE5PZ5e",
"ApplePushServer": "",
"ApplePushCertPublic": "",
"ApplePushCertPrivate": ""
},
"RateLimitSettings": {
"UseRateLimiter": true,
"EnableRateLimiter": true,
"PerSec": 10,
"MemoryStoreSize": 10000,
"VaryByRemoteAddr": true,
@@ -83,22 +75,15 @@
},
"PrivacySettings": {
"ShowEmailAddress": true,
"ShowPhoneNumber": true,
"ShowSkypeId": true,
"ShowFullName": true
},
"TeamSettings": {
"MaxUsersPerTeam": 150,
"AllowPublicLink": true,
"AllowValetDefault": false,
"TermsLink": "/static/help/configure_links.html",
"PrivacyLink": "/static/help/configure_links.html",
"AboutLink": "/static/help/configure_links.html",
"HelpLink": "/static/help/configure_links.html",
"ReportProblemLink": "/static/help/configure_links.html",
"TourLink": "/static/help/configure_links.html",
"DefaultThemeColor": "#2389D7",
"DisableTeamCreation": false,
"RestrictCreationToDomains": ""
"GitLabSettings": {
"Enable": false,
"Secret": "",
"Id": "",
"Scope": "",
"AuthEndpoint": "",
"TokenEndpoint": "",
"UserApiEndpoint": ""
}
}
}

Просмотреть файл

@@ -53,7 +53,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
// Create a client for tests to use
client := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
client := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
// Check for username parameter and create a user if present
username, ok1 := params["username"]

Просмотреть файл

@@ -57,7 +57,7 @@ func main() {
api.StartServer()
// If we allow testing then listen for manual testing URL hits
if utils.Cfg.ServiceSettings.AllowTesting {
if utils.Cfg.ServiceSettings.EnableTesting {
manualtesting.InitManualTesting()
}

Просмотреть файл

@@ -21,6 +21,7 @@ const (
HEADER_ETAG_SERVER = "ETag"
HEADER_ETAG_CLIENT = "If-None-Match"
HEADER_FORWARDED = "X-Forwarded-For"
HEADER_REAL_IP = "X-Real-IP"
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
HEADER_TOKEN = "token"
HEADER_BEARER = "BEARER"

Просмотреть файл

@@ -20,23 +20,16 @@ const (
)
type ServiceSettings struct {
Mode string
AllowTesting bool
UseSSL bool
Port string
Version string
InviteSalt string
PublicLinkSalt string
ResetSalt string
AnalyticsUrl string
AllowedLoginAttempts int
EnableOAuthServiceProvider bool
ListenAddress string
MaximumLoginAttempts int
SegmentDeveloperKey string
GoogleDeveloperKey string
EnableOAuthServiceProvider bool
EnableTesting bool
}
type SSOSettings struct {
Allow bool
Enable bool
Secret string
Id string
Scope string
@@ -56,9 +49,9 @@ type SqlSettings struct {
}
type LogSettings struct {
ConsoleEnable bool
EnableConsole bool
ConsoleLevel string
FileEnable bool
EnableFile bool
FileLevel string
FileFormat string
FileLocation string
@@ -68,6 +61,7 @@ type ImageSettings struct {
DriverName string
Directory string
EnablePublicLink bool
PublicLinkSalt string
ThumbnailWidth uint
ThumbnailHeight uint
PreviewWidth uint
@@ -82,7 +76,7 @@ type ImageSettings struct {
}
type EmailSettings struct {
AllowSignUpWithEmail bool
EnableSignUpWithEmail bool
SendEmailNotifications bool
RequireEmailVerification bool
FeedbackName string
@@ -92,6 +86,8 @@ type EmailSettings struct {
SMTPServer string
SMTPPort string
ConnectionSecurity string
InviteSalt string
PasswordResetSalt string
// For Future Use
ApplePushServer string

Просмотреть файл

@@ -58,9 +58,9 @@ func FindDir(dir string) string {
func ConfigureCmdLineLog() {
ls := model.LogSettings{}
ls.ConsoleEnable = true
ls.EnableConsole = true
ls.ConsoleLevel = "ERROR"
ls.FileEnable = false
ls.EnableFile = false
configureLog(&ls)
}
@@ -68,7 +68,7 @@ func configureLog(s *model.LogSettings) {
l4g.Close()
if s.ConsoleEnable {
if s.EnableConsole {
level := l4g.DEBUG
if s.ConsoleLevel == "INFO" {
level = l4g.INFO
@@ -79,7 +79,7 @@ func configureLog(s *model.LogSettings) {
l4g.AddFilter("stdout", level, l4g.NewConsoleLogWriter())
}
if s.FileEnable {
if s.EnableFile {
var fileFormat = s.FileFormat
@@ -174,16 +174,15 @@ func getClientProperties(c *model.Config) map[string]string {
props["BuildHash"] = model.BuildHash
props["SiteName"] = c.TeamSettings.SiteName
props["AnalyticsUrl"] = c.ServiceSettings.AnalyticsUrl
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey
props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["AllowSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.AllowSignUpWithEmail)
props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail)
props["FeedbackEmail"] = c.EmailSettings.FeedbackEmail
props["AllowSignUpWithGitLab"] = strconv.FormatBool(c.GitLabSettings.Allow)
props["EnableSignUpWithGitLab"] = strconv.FormatBool(c.GitLabSettings.Enable)
props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress)

Просмотреть файл

@@ -15,7 +15,7 @@ var RateSettingsTab = require('./rate_settings.jsx');
var GitLabSettingsTab = require('./gitlab_settings.jsx');
var SqlSettingsTab = require('./sql_settings.jsx');
var TeamSettingsTab = require('./team_settings.jsx');
var ServiceSettingsTab = require('./service_settings.jsx');
export default class AdminController extends React.Component {
constructor(props) {
@@ -26,7 +26,7 @@ export default class AdminController extends React.Component {
this.state = {
config: null,
selected: 'team_settings'
selected: 'service_settings'
};
}
@@ -72,6 +72,8 @@ export default class AdminController extends React.Component {
tab = <SqlSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_settings') {
tab = <TeamSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />;
}
}

Просмотреть файл

@@ -38,6 +38,15 @@ export default class AdminSidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<ul className='nav nav__sub-menu'>
<li>
<a
href='#'
className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings')}
>
{'Service Settings'}
</a>
</li>
<li>
<a
href='#'

Просмотреть файл

@@ -3,6 +3,7 @@
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var crypto = require('crypto');
export default class EmailSettings extends React.Component {
constructor(props) {
@@ -12,6 +13,8 @@ export default class EmailSettings extends React.Component {
this.handleTestConnection = this.handleTestConnection.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.buildConfig = this.buildConfig.bind(this);
this.handleGenerateInvite = this.handleGenerateInvite.bind(this);
this.handleGenerateReset = this.handleGenerateReset.bind(this);
this.state = {
sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications,
@@ -38,7 +41,7 @@ export default class EmailSettings extends React.Component {
buildConfig() {
var config = this.props.config;
config.EmailSettings.AllowSignUpWithEmail = React.findDOMNode(this.refs.allowSignUpWithEmail).checked;
config.EmailSettings.EnableSignUpWithEmail = 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;
@@ -49,9 +52,36 @@ export default class EmailSettings extends React.Component {
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();
config.EmailSettings.InviteSalt = React.findDOMNode(this.refs.InviteSalt).value.trim();
if (config.EmailSettings.InviteSalt === '') {
config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 31);
React.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt;
}
config.EmailSettings.PasswordResetSalt = React.findDOMNode(this.refs.PasswordResetSalt).value.trim();
if (config.EmailSettings.PasswordResetSalt === '') {
config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 31);
React.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt;
}
return config;
}
handleGenerateInvite(e) {
e.preventDefault();
React.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31);
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
handleGenerateReset(e) {
e.preventDefault();
React.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31);
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
handleTestConnection(e) {
e.preventDefault();
$('#connection-button').button('loading');
@@ -166,7 +196,7 @@ export default class EmailSettings extends React.Component {
name='allowSignUpWithEmail'
value='true'
ref='allowSignUpWithEmail'
defaultChecked={this.props.config.EmailSettings.AllowSignUpWithEmail}
defaultChecked={this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_true')}
/>
{'true'}
@@ -176,7 +206,7 @@ export default class EmailSettings extends React.Component {
type='radio'
name='allowSignUpWithEmail'
value='false'
defaultChecked={!this.props.config.EmailSettings.AllowSignUpWithEmail}
defaultChecked={!this.props.config.EmailSettings.EnableSignUpWithEmail}
onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_false')}
/>
{'false'}
@@ -431,6 +461,68 @@ export default class EmailSettings extends React.Component {
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='InviteSalt'
>
{'Invite Salt:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='InviteSalt'
ref='InviteSalt'
placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
defaultValue={this.props.config.EmailSettings.InviteSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<p className='help-text'>{'32-character salt added to signing of email invites.'}</p>
<div className='help-text'>
<button
className='help-link'
onClick={this.handleGenerateInvite}
disabled={!this.state.sendEmailNotifications}
>
{'Re-Generate'}
</button>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PasswordResetSalt'
>
{'Password Reset Salt:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PasswordResetSalt'
ref='PasswordResetSalt'
placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
defaultValue={this.props.config.EmailSettings.PasswordResetSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
/>
<p className='help-text'>{'32-character salt added to signing of password reset emails.'}</p>
<div className='help-text'>
<button
className='help-link'
onClick={this.handleGenerateReset}
disabled={!this.state.sendEmailNotifications}
>
{'Re-Generate'}
</button>
</div>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}

Просмотреть файл

@@ -3,6 +3,7 @@
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var crypto = require('crypto');
export default class ImageSettings extends React.Component {
constructor(props) {
@@ -10,6 +11,7 @@ export default class ImageSettings extends React.Component {
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleGenerate = this.handleGenerate.bind(this);
this.state = {
saveNeeded: false,
@@ -28,6 +30,13 @@ export default class ImageSettings extends React.Component {
this.setState(s);
}
handleGenerate(e) {
e.preventDefault();
React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31);
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
@@ -41,6 +50,13 @@ export default class ImageSettings extends React.Component {
config.ImageSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value;
config.ImageSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked;
config.ImageSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim();
if (config.ImageSettings.PublicLinkSalt === '') {
config.ImageSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 31);
React.findDOMNode(this.refs.PublicLinkSalt).value = config.ImageSettings.PublicLinkSalt;
}
var thumbnailWidth = 120;
if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) {
thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10);
@@ -424,6 +440,35 @@ export default class ImageSettings extends React.Component {
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PublicLinkSalt'
>
{'Public Link Salt:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PublicLinkSalt'
ref='PublicLinkSalt'
placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
defaultValue={this.props.config.ImageSettings.PublicLinkSalt}
onChange={this.handleChange}
/>
<p className='help-text'>{'32-character salt added to signing of public image links.'}</p>
<div className='help-text'>
<button
className='help-link'
onClick={this.handleGenerate}
>
{'Re-Generate'}
</button>
</div>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}

Просмотреть файл

@@ -12,8 +12,8 @@ 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,
consoleEnable: this.props.config.LogSettings.EnableConsole,
fileEnable: this.props.config.LogSettings.EnableFile,
saveNeeded: false,
serverError: null
};
@@ -46,9 +46,9 @@ export default class LogSettings extends React.Component {
$('#save-button').button('loading');
var config = this.props.config;
config.LogSettings.ConsoleEnable = React.findDOMNode(this.refs.consoleEnable).checked;
config.LogSettings.EnableConsole = React.findDOMNode(this.refs.consoleEnable).checked;
config.LogSettings.ConsoleLevel = React.findDOMNode(this.refs.consoleLevel).value;
config.LogSettings.FileEnable = React.findDOMNode(this.refs.fileEnable).checked;
config.LogSettings.EnableFile = React.findDOMNode(this.refs.fileEnable).checked;
config.LogSettings.FileLevel = React.findDOMNode(this.refs.fileLevel).value;
config.LogSettings.FileLocation = React.findDOMNode(this.refs.fileLocation).value.trim();
config.LogSettings.FileFormat = React.findDOMNode(this.refs.fileFormat).value.trim();
@@ -58,8 +58,8 @@ export default class LogSettings extends React.Component {
() => {
AsyncClient.getConfig();
this.setState({
consoleEnable: config.LogSettings.ConsoleEnable,
fileEnable: config.LogSettings.FileEnable,
consoleEnable: config.LogSettings.EnableConsole,
fileEnable: config.LogSettings.EnableFile,
serverError: null,
saveNeeded: false
});
@@ -67,8 +67,8 @@ export default class LogSettings extends React.Component {
},
(err) => {
this.setState({
consoleEnable: config.LogSettings.ConsoleEnable,
fileEnable: config.LogSettings.FileEnable,
consoleEnable: config.LogSettings.EnableConsole,
fileEnable: config.LogSettings.EnableFile,
serverError: err.message,
saveNeeded: true
});
@@ -110,7 +110,7 @@ export default class LogSettings extends React.Component {
name='consoleEnable'
value='true'
ref='consoleEnable'
defaultChecked={this.props.config.LogSettings.ConsoleEnable}
defaultChecked={this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_true')}
/>
{'true'}
@@ -120,7 +120,7 @@ export default class LogSettings extends React.Component {
type='radio'
name='consoleEnable'
value='false'
defaultChecked={!this.props.config.LogSettings.ConsoleEnable}
defaultChecked={!this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_false')}
/>
{'false'}
@@ -166,7 +166,7 @@ export default class LogSettings extends React.Component {
name='fileEnable'
ref='fileEnable'
value='true'
defaultChecked={this.props.config.LogSettings.FileEnable}
defaultChecked={this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_true')}
/>
{'true'}
@@ -176,7 +176,7 @@ export default class LogSettings extends React.Component {
type='radio'
name='fileEnable'
value='false'
defaultChecked={!this.props.config.LogSettings.FileEnable}
defaultChecked={!this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_false')}
/>
{'false'}

Просмотреть файл

@@ -0,0 +1,262 @@
// 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 ServiceSettings extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
saveNeeded: false,
serverError: null
};
}
handleChange() {
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.ServiceSettings.ListenAddress = React.findDOMNode(this.refs.ListenAddress).value.trim();
if (config.ServiceSettings.ListenAddress === '') {
config.ServiceSettings.ListenAddress = ':8065';
React.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress;
}
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked;
var MaximumLoginAttempts = 10;
if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) {
MaximumLoginAttempts = parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10);
}
config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts;
React.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts;
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
);
}
render() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return (
<div className='wrapper--fixed'>
<h3>{'Service Settings'}</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ListenAddress'
>
{'Listen Address:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ListenAddress'
ref='ListenAddress'
placeholder='Ex ":8065"'
defaultValue={this.props.config.ServiceSettings.ListenAddress}
onChange={this.handleChange}
/>
<p className='help-text'>{'The address to bind to and listen. ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='MaximumLoginAttempts'
>
{'Maximum Login Attempts:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='MaximumLoginAttempts'
ref='MaximumLoginAttempts'
placeholder='Ex "10"'
defaultValue={this.props.config.ServiceSettings.MaximumLoginAttempts}
onChange={this.handleChange}
/>
<p className='help-text'>{'Login attempts allowed before user is locked out and required to reset password via email.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='SegmentDeveloperKey'
>
{'Segment Developer Key:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='SegmentDeveloperKey'
ref='SegmentDeveloperKey'
placeholder='Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"'
defaultValue={this.props.config.ServiceSettings.SegmentDeveloperKey}
onChange={this.handleChange}
/>
<p className='help-text'>{'For users running a SaaS services, sign up for a key at Segment.com to track metrics.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='GoogleDeveloperKey'
>
{'Google Developer Key:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='GoogleDeveloperKey'
ref='GoogleDeveloperKey'
placeholder='Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"'
defaultValue={this.props.config.ServiceSettings.GoogleDeveloperKey}
onChange={this.handleChange}
/>
<p className='help-text'>{'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}<a href='https://www.youtube.com/watch?v=Im69kzhpR3I'>{'https://www.youtube.com/watch?v=Im69kzhpR3I'}</a>{'. Leaving field blank disables the automatic generation of YouTube video previews from links.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableOAuthServiceProvider'
>
{'Enable OAuth Service Provider: '}
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableOAuthServiceProvider'
value='true'
ref='EnableOAuthServiceProvider'
defaultChecked={this.props.config.ServiceSettings.EnableOAuthServiceProvider}
onChange={this.handleChange}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableOAuthServiceProvider'
value='false'
defaultChecked={!this.props.config.ServiceSettings.EnableOAuthServiceProvider}
onChange={this.handleChange}
/>
{'false'}
</label>
<p className='help-text'>{'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableTesting'
>
{'Enable Testing: '}
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableTesting'
value='true'
ref='EnableTesting'
defaultChecked={this.props.config.ServiceSettings.EnableTesting}
onChange={this.handleChange}
/>
{'true'}
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableTesting'
value='false'
defaultChecked={!this.props.config.ServiceSettings.EnableTesting}
onChange={this.handleChange}
/>
{'false'}
</label>
<p className='help-text'>{'When true slash commands like /loadtest are enabled in the add comment box. Changing this will require a server restart before taking effect. Typically used for development.'}</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
>
{'Save'}
</button>
</div>
</div>
</form>
</div>
);
}
}
ServiceSettings.propTypes = {
config: React.PropTypes.object
};

Просмотреть файл

@@ -207,7 +207,7 @@ export default class SqlSettings extends React.Component {
className='form-control'
id='AtRestEncryptKey'
ref='AtRestEncryptKey'
placeholder='Ex "10"'
placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
defaultValue={this.props.config.SqlSettings.AtRestEncryptKey}
onChange={this.handleChange}
/>

Просмотреть файл

@@ -95,7 +95,7 @@ export default class Login extends React.Component {
}
let loginMessage = [];
if (global.window.config.AllowSignUpWithGitLab === 'true') {
if (global.window.config.EnableSignUpWithGitLab === 'true') {
loginMessage.push(
<a
className='btn btn-custom-login gitlab'
@@ -113,7 +113,7 @@ export default class Login extends React.Component {
}
let emailSignup;
if (global.window.config.AllowSignUpWithEmail === 'true') {
if (global.window.config.EnableSignUpWithEmail === 'true') {
emailSignup = (
<div>
<div className={'form-group' + errorClass}>

Просмотреть файл

@@ -14,19 +14,19 @@ export default class TeamSignUp extends React.Component {
var count = 0;
if (global.window.config.AllowSignUpWithEmail === 'true') {
if (global.window.config.EnableSignUpWithEmail === 'true') {
count = count + 1;
}
if (global.window.config.AllowSignUpWithGitLab === 'true') {
if (global.window.config.EnableSignUpWithGitLab === 'true') {
count = count + 1;
}
if (count > 1) {
this.state = {page: 'choose'};
} else if (global.window.config.AllowSignUpWithEmail === 'true') {
} else if (global.window.config.EnableSignUpWithEmail === 'true') {
this.state = {page: 'email'};
} else if (global.window.config.AllowSignUpWithGitLab === 'true') {
} else if (global.window.config.EnableSignUpWithGitLab === 'true') {
this.state = {page: 'gitlab'};
}
}

Просмотреть файл

@@ -161,7 +161,7 @@ export default class SignupUserComplete extends React.Component {
);
var signupMessage = [];
if (global.window.config.AllowSignUpWithGitLab === 'true') {
if (global.window.config.EnableSignUpWithGitLab === 'true') {
signupMessage.push(
<a
className='btn btn-custom-login gitlab'
@@ -174,7 +174,7 @@ export default class SignupUserComplete extends React.Component {
}
var emailSignup;
if (global.window.config.AllowSignUpWithEmail === 'true') {
if (global.window.config.EnableSignUpWithEmail === 'true') {
emailSignup = (
<div>
<div className='inner__content'>

Просмотреть файл

@@ -8,7 +8,7 @@ export default class ChooseAuthPage extends React.Component {
}
render() {
var buttons = [];
if (global.window.config.AllowSignUpWithGitLab === 'true') {
if (global.window.config.EnableSignUpWithGitLab === 'true') {
buttons.push(
<a
className='btn btn-custom-login gitlab btn-full'
@@ -26,7 +26,7 @@ export default class ChooseAuthPage extends React.Component {
);
}
if (global.window.config.AllowSignUpWithEmail === 'true') {
if (global.window.config.EnableSignUpWithEmail === 'true') {
buttons.push(
<a
className='btn btn-custom-login email btn-full'

Просмотреть файл

@@ -4,12 +4,10 @@ var BrowserStore = require('../stores/browser_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
export function track(category, action, label, prop, val) {
global.window.snowplow('trackStructEvent', category, action, label, prop, val);
global.window.analytics.track(action, {category: category, label: label, property: prop, value: val});
}
export function trackPage() {
global.window.snowplow('trackPageView');
global.window.analytics.page();
}

Просмотреть файл

@@ -77,28 +77,5 @@
analytics.track = function(){};
}
</script>
<!-- Snowplow starts plowing -->
<script type="text/javascript">
if ('{{ .Props.AnalyticsUrl }}'.trim() !== '') {
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.4.2/sp.js","snowplow"));
window.snowplow('newTracker', 'cf', '{{ .Props.AnalyticsUrl }}', {
appId: window.config.SiteName
});
var user = window.UserStore.getCurrentUser(true);
if (user) {
window.snowplow('setUserId', user.id);
}
window.snowplow('trackPageView');
} else {
window.snowplow = function(){};
}
</script>
<!-- Snowplow stops plowing -->
</head>
{{end}}

Просмотреть файл

@@ -204,7 +204,7 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
data := r.FormValue("d")
hash := r.FormValue("h")
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "")
return
}
@@ -253,7 +253,7 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
}
} else {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
c.Err = model.NewAppError("signupTeamComplete", "The signup link does not appear to be valid", "")
return
}
@@ -414,7 +414,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
if len(hash) == 0 || len(data) == 0 {
isResetLink = false
} else {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.ResetSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt)) {
c.Err = model.NewAppError("resetPassword", "The reset link does not appear to be valid", "")
return
}
@@ -476,7 +476,7 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("d")
props := model.MapFromJson(strings.NewReader(data))
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
c.Err = model.NewAppError("signupWithOAuth", "The signup link does not appear to be valid", "")
return
}

Просмотреть файл

@@ -25,7 +25,7 @@ func Setup() {
api.StartServer()
api.InitApi()
InitWeb()
URL = "http://localhost:" + utils.Cfg.ServiceSettings.Port
URL = "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress
ApiClient = model.NewClient(URL)
}
}