summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--api/user.go23
-rw-r--r--config/config.json1
-rw-r--r--einterfaces/ldap.go2
-rw-r--r--i18n/en.json16
-rw-r--r--mattermost.go172
-rw-r--r--model/config.go12
-rw-r--r--model/job.go91
-rw-r--r--model/job_test.go128
-rw-r--r--store/sql_user_store.go21
-rw-r--r--store/store.go1
-rw-r--r--webapp/components/admin_console/ldap_settings.jsx20
-rw-r--r--webapp/i18n/en.json2
13 files changed, 416 insertions, 75 deletions
diff --git a/Makefile b/Makefile
index 1d808fd75..adf201ec2 100644
--- a/Makefile
+++ b/Makefile
@@ -160,7 +160,7 @@ test-server: start-docker prepare-enterprise
echo "mode: count" > cover.out
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=340s -covermode=count -coverprofile=capi.out ./api || exit 1
- $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s -covermode=count -coverprofile=cmodel.out ./model || exit 1
+ $(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=60s -covermode=count -coverprofile=cmodel.out ./model || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s -covermode=count -coverprofile=cstore.out ./store || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s -covermode=count -coverprofile=cutils.out ./utils || exit 1
$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s -covermode=count -coverprofile=cweb.out ./web || exit 1
diff --git a/api/user.go b/api/user.go
index 4d4518824..de7a560bf 100644
--- a/api/user.go
+++ b/api/user.go
@@ -704,6 +704,7 @@ func RevokeSessionById(c *Context, sessionId string) {
}
}
+// IF YOU UPDATE THIS PLEASE UPDATE BELOW
func RevokeAllSession(c *Context, userId string) {
if result := <-Srv.Store.Session().GetSessions(userId); result.Err != nil {
c.Err = result.Err
@@ -726,6 +727,28 @@ func RevokeAllSession(c *Context, userId string) {
}
}
+// UGH...
+// If you update this please update above
+func RevokeAllSessionsNoContext(userId string) *model.AppError {
+ if result := <-Srv.Store.Session().GetSessions(userId); result.Err != nil {
+ return result.Err
+ } else {
+ sessions := result.Data.([]*model.Session)
+
+ for _, session := range sessions {
+ if session.IsOAuth {
+ RevokeAccessToken(session.Token)
+ } else {
+ sessionCache.Remove(session.Token)
+ if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
+ return result.Err
+ }
+ }
+ }
+ }
+ return nil
+}
+
func getSessions(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
diff --git a/config/config.json b/config/config.json
index 582a7244c..726cb0d8c 100644
--- a/config/config.json
+++ b/config/config.json
@@ -147,6 +147,7 @@
"UsernameAttribute": "",
"NicknameAttribute": "",
"IdAttribute": "",
+ "SyncIntervalMinutes": 60,
"SkipCertificateVerification": false,
"QueryTimeout": 60,
"LoginFieldName": ""
diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go
index 25d591ce2..4f1b56119 100644
--- a/einterfaces/ldap.go
+++ b/einterfaces/ldap.go
@@ -13,6 +13,8 @@ type LdapInterface interface {
CheckPassword(id string, password string) *model.AppError
SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError
ValidateFilter(filter string) *model.AppError
+ Syncronize() *model.AppError
+ StartLdapSyncJob()
}
var theLdapInterface LdapInterface
diff --git a/i18n/en.json b/i18n/en.json
index 3666abd44..2a51826fa 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -2028,6 +2028,14 @@
"translation": "Invalid LDAP Filter"
},
{
+ "id": "ent.ldap.syncronize.get_all.app_error",
+ "translation": "Unable to get all users using LDAP"
+ },
+ {
+ "id": "ent.ldap.syncdone.info",
+ "translation": "LDAP Synchronization completed"
+ },
+ {
"id": "ent.mfa.activate.authenticate.app_error",
"translation": "Error attempting to authenticate MFA token"
},
@@ -2412,6 +2420,10 @@
"translation": "Invalid connection security for LDAP settings. Must be '', 'TLS', or 'STARTTLS'"
},
{
+ "id": "model.config.is_valid.ldap_sync_interval.app_error",
+ "translation": "Invalid sync interval time. Must be at least one minute."
+ },
+ {
"id": "model.config.is_valid.listen_address.app_error",
"translation": "Invalid listen address for service settings Must be set."
},
@@ -3556,6 +3568,10 @@
"translation": "We encountered an error trying to find the account by authentication type."
},
{
+ "id": "store.sql_user.get_all_using_auth_service.other.app_error",
+ "translation": "We encountered an error trying to find all the accounts using a specific authentication type."
+ },
+ {
"id": "store.sql_user.get_by_username.app_error",
"translation": "We couldn't find an existing account matching your username for this team. This team may require an invite from the team owner to join."
},
diff --git a/mattermost.go b/mattermost.go
index bebb55c3b..ddf20f19e 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -55,6 +55,7 @@ var flagCmdPermanentDeleteUser bool
var flagCmdPermanentDeleteTeam bool
var flagCmdPermanentDeleteAllUsers bool
var flagCmdResetDatabase bool
+var flagCmdRunLdapSync bool
var flagUsername string
var flagCmdUploadLicense bool
var flagConfigFile string
@@ -125,8 +126,12 @@ func main() {
setDiagnosticId()
go runSecurityAndDiagnosticsJob()
- if einterfaces.GetComplianceInterface() != nil {
- einterfaces.GetComplianceInterface().StartComplianceDailyJob()
+ if complianceI := einterfaces.GetComplianceInterface(); complianceI != nil {
+ complianceI.StartComplianceDailyJob()
+ }
+
+ if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
+ ldapI.StartLdapSyncJob()
}
// wait for kill signal before attempting to gracefully shutdown
@@ -154,96 +159,97 @@ func setDiagnosticId() {
}
}
-func runSecurityAndDiagnosticsJob() {
- for {
- if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
- if result := <-api.Srv.Store.System().Get(); result.Err == nil {
- props := result.Data.(model.StringMap)
- lastSecurityTime, _ := strconv.ParseInt(props[model.SYSTEM_LAST_SECURITY_TIME], 10, 0)
- currentTime := model.GetMillis()
-
- if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
- l4g.Debug(utils.T("mattermost.security_checks.debug"))
-
- v := url.Values{}
-
- v.Set(utils.PROP_DIAGNOSTIC_ID, utils.CfgDiagnosticId)
- v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
- v.Set(utils.PROP_DIAGNOSTIC_ENTERPRISE_READY, model.BuildEnterpriseReady)
- v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
- v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
- v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
-
- if len(props[model.SYSTEM_RAN_UNIT_TESTS]) > 0 {
- v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "1")
- } else {
- v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "0")
- }
+func doSecurityAndDiagnostics() {
+ if *utils.Cfg.ServiceSettings.EnableSecurityFixAlert {
+ if result := <-api.Srv.Store.System().Get(); result.Err == nil {
+ props := result.Data.(model.StringMap)
+ lastSecurityTime, _ := strconv.ParseInt(props[model.SYSTEM_LAST_SECURITY_TIME], 10, 0)
+ currentTime := model.GetMillis()
+
+ if (currentTime - lastSecurityTime) > 1000*60*60*24*1 {
+ l4g.Debug(utils.T("mattermost.security_checks.debug"))
+
+ v := url.Values{}
+
+ v.Set(utils.PROP_DIAGNOSTIC_ID, utils.CfgDiagnosticId)
+ v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
+ v.Set(utils.PROP_DIAGNOSTIC_ENTERPRISE_READY, model.BuildEnterpriseReady)
+ v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
+ v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
+ v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
+
+ if len(props[model.SYSTEM_RAN_UNIT_TESTS]) > 0 {
+ v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "1")
+ } else {
+ v.Set(utils.PROP_DIAGNOSTIC_UNIT_TESTS, "0")
+ }
- systemSecurityLastTime := &model.System{Name: model.SYSTEM_LAST_SECURITY_TIME, Value: strconv.FormatInt(currentTime, 10)}
- if lastSecurityTime == 0 {
- <-api.Srv.Store.System().Save(systemSecurityLastTime)
- } else {
- <-api.Srv.Store.System().Update(systemSecurityLastTime)
- }
+ systemSecurityLastTime := &model.System{Name: model.SYSTEM_LAST_SECURITY_TIME, Value: strconv.FormatInt(currentTime, 10)}
+ if lastSecurityTime == 0 {
+ <-api.Srv.Store.System().Save(systemSecurityLastTime)
+ } else {
+ <-api.Srv.Store.System().Update(systemSecurityLastTime)
+ }
- if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
- v.Set(utils.PROP_DIAGNOSTIC_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
- }
+ if ucr := <-api.Srv.Store.User().GetTotalUsersCount(); ucr.Err == nil {
+ v.Set(utils.PROP_DIAGNOSTIC_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
+ }
- if ucr := <-api.Srv.Store.User().GetTotalActiveUsersCount(); ucr.Err == nil {
- v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
- }
+ if ucr := <-api.Srv.Store.User().GetTotalActiveUsersCount(); ucr.Err == nil {
+ v.Set(utils.PROP_DIAGNOSTIC_ACTIVE_USER_COUNT, strconv.FormatInt(ucr.Data.(int64), 10))
+ }
- res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
- if err != nil {
- l4g.Error(utils.T("mattermost.security_info.error"))
- return
- }
+ res, err := http.Get(utils.DIAGNOSTIC_URL + "/security?" + v.Encode())
+ if err != nil {
+ l4g.Error(utils.T("mattermost.security_info.error"))
+ return
+ }
+
+ bulletins := model.SecurityBulletinsFromJson(res.Body)
+
+ for _, bulletin := range bulletins {
+ if bulletin.AppliesToVersion == model.CurrentVersion {
+ if props["SecurityBulletin_"+bulletin.Id] == "" {
+ if results := <-api.Srv.Store.User().GetSystemAdminProfiles(); results.Err != nil {
+ l4g.Error(utils.T("mattermost.system_admins.error"))
+ return
+ } else {
+ users := results.Data.(map[string]*model.User)
- bulletins := model.SecurityBulletinsFromJson(res.Body)
+ resBody, err := http.Get(utils.DIAGNOSTIC_URL + "/bulletins/" + bulletin.Id)
+ if err != nil {
+ l4g.Error(utils.T("mattermost.security_bulletin.error"))
+ return
+ }
- for _, bulletin := range bulletins {
- if bulletin.AppliesToVersion == model.CurrentVersion {
- if props["SecurityBulletin_"+bulletin.Id] == "" {
- if results := <-api.Srv.Store.User().GetSystemAdminProfiles(); results.Err != nil {
- l4g.Error(utils.T("mattermost.system_admins.error"))
+ body, err := ioutil.ReadAll(resBody.Body)
+ res.Body.Close()
+ if err != nil || resBody.StatusCode != 200 {
+ l4g.Error(utils.T("mattermost.security_bulletin_read.error"))
return
- } else {
- users := results.Data.(map[string]*model.User)
-
- resBody, err := http.Get(utils.DIAGNOSTIC_URL + "/bulletins/" + bulletin.Id)
- if err != nil {
- l4g.Error(utils.T("mattermost.security_bulletin.error"))
- return
- }
-
- body, err := ioutil.ReadAll(resBody.Body)
- res.Body.Close()
- if err != nil || resBody.StatusCode != 200 {
- l4g.Error(utils.T("mattermost.security_bulletin_read.error"))
- return
- }
-
- for _, user := range users {
- l4g.Info(utils.T("mattermost.send_bulletin.info"), bulletin.Id, user.Email)
- utils.SendMail(user.Email, utils.T("mattermost.bulletin.subject"), string(body))
- }
}
- bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
- <-api.Srv.Store.System().Save(bulletinSeen)
+ for _, user := range users {
+ l4g.Info(utils.T("mattermost.send_bulletin.info"), bulletin.Id, user.Email)
+ utils.SendMail(user.Email, utils.T("mattermost.bulletin.subject"), string(body))
+ }
}
+
+ bulletinSeen := &model.System{Name: "SecurityBulletin_" + bulletin.Id, Value: bulletin.Id}
+ <-api.Srv.Store.System().Save(bulletinSeen)
}
}
}
}
}
-
- time.Sleep(time.Hour * 4)
}
}
+func runSecurityAndDiagnosticsJob() {
+ doSecurityAndDiagnostics()
+ model.CreateRecurringTask("Security and Diagnostics", doSecurityAndDiagnostics, time.Hour*4)
+}
+
func parseCmds() {
flag.Usage = func() {
fmt.Fprintln(os.Stderr, usage)
@@ -272,6 +278,7 @@ func parseCmds() {
flag.BoolVar(&flagCmdPermanentDeleteTeam, "permanent_delete_team", false, "")
flag.BoolVar(&flagCmdPermanentDeleteAllUsers, "permanent_delete_all_users", false, "")
flag.BoolVar(&flagCmdResetDatabase, "reset_database", false, "")
+ flag.BoolVar(&flagCmdRunLdapSync, "ldap_sync", false, "")
flag.BoolVar(&flagCmdUploadLicense, "upload_license", false, "")
flag.Parse()
@@ -290,6 +297,7 @@ func parseCmds() {
flagCmdPermanentDeleteTeam ||
flagCmdPermanentDeleteAllUsers ||
flagCmdResetDatabase ||
+ flagCmdRunLdapSync ||
flagCmdUploadLicense)
}
@@ -308,6 +316,7 @@ func runCmds() {
cmdPermDeleteAllUsers()
cmdResetDatabase()
cmdUploadLicense()
+ cmdRunLdapSync()
}
type TeamForUpgrade struct {
@@ -1130,6 +1139,21 @@ func cmdResetDatabase() {
}
+func cmdRunLdapSync() {
+ if flagCmdRunLdapSync {
+ if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
+ if err := ldapI.Syncronize(); err != nil {
+ fmt.Println("ERROR: Ldap Syncronization Failed")
+ l4g.Error("%v", err.Error())
+ flushLogAndExit(1)
+ } else {
+ fmt.Println("SUCCESS: Ldap Syncronization Complete")
+ flushLogAndExit(0)
+ }
+ }
+ }
+}
+
func cmdUploadLicense() {
if flagCmdUploadLicense {
if model.BuildEnterpriseReady != "true" {
diff --git a/model/config.go b/model/config.go
index 08b00b90f..7e810be02 100644
--- a/model/config.go
+++ b/model/config.go
@@ -190,6 +190,9 @@ type LdapSettings struct {
NicknameAttribute *string
IdAttribute *string
+ // Syncronization
+ SyncIntervalMinutes *int
+
// Advanced
SkipCertificateVerification *bool
QueryTimeout *int
@@ -441,6 +444,11 @@ func (o *Config) SetDefaults() {
*o.LdapSettings.LoginFieldName = ""
}
+ if o.LdapSettings.SyncIntervalMinutes == nil {
+ o.LdapSettings.SyncIntervalMinutes = new(int)
+ *o.LdapSettings.SyncIntervalMinutes = 60
+ }
+
if o.ServiceSettings.SessionLengthWebInDays == nil {
o.ServiceSettings.SessionLengthWebInDays = new(int)
*o.ServiceSettings.SessionLengthWebInDays = 30
@@ -635,6 +643,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "")
}
+ if *o.LdapSettings.SyncIntervalMinutes <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "")
+ }
+
return nil
}
diff --git a/model/job.go b/model/job.go
new file mode 100644
index 000000000..bcae7a830
--- /dev/null
+++ b/model/job.go
@@ -0,0 +1,91 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "time"
+)
+
+type TaskFunc func()
+
+type ScheduledTask struct {
+ Name string `json:"name"`
+ Interval time.Duration `json:"interval"`
+ Recurring bool `json:"recurring"`
+ function TaskFunc `json:",omitempty"`
+ timer *time.Timer `json:",omitempty"`
+}
+
+var tasks = make(map[string]*ScheduledTask)
+
+func addTask(task *ScheduledTask) {
+ tasks[task.Name] = task
+}
+
+func removeTaskByName(name string) {
+ delete(tasks, name)
+}
+
+func getTaskByName(name string) *ScheduledTask {
+ return tasks[name]
+}
+
+func GetAllTasks() *map[string]*ScheduledTask {
+ return &tasks
+}
+
+func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
+ task := &ScheduledTask{
+ Name: name,
+ Interval: timeToExecution,
+ Recurring: false,
+ function: function,
+ }
+
+ taskRunner := func() {
+ go task.function()
+ removeTaskByName(task.Name)
+ }
+
+ task.timer = time.AfterFunc(timeToExecution, taskRunner)
+
+ addTask(task)
+
+ return task
+}
+
+func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
+ task := &ScheduledTask{
+ Name: name,
+ Interval: interval,
+ Recurring: true,
+ function: function,
+ }
+
+ taskRecurer := func() {
+ go task.function()
+ task.timer.Reset(task.Interval)
+ }
+
+ task.timer = time.AfterFunc(interval, taskRecurer)
+
+ addTask(task)
+
+ return task
+}
+
+func (task *ScheduledTask) Cancel() {
+ task.timer.Stop()
+ removeTaskByName(task.Name)
+}
+
+func (task *ScheduledTask) String() string {
+ return fmt.Sprintf(
+ "%s\nInterval: %s\nRecurring: %t\n",
+ task.Name,
+ task.Interval.String(),
+ task.Recurring,
+ )
+}
diff --git a/model/job_test.go b/model/job_test.go
new file mode 100644
index 000000000..2a307de1e
--- /dev/null
+++ b/model/job_test.go
@@ -0,0 +1,128 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "testing"
+ "time"
+)
+
+func TestCreateTask(t *testing.T) {
+ TASK_NAME := "Test Task"
+ TASK_TIME := time.Second * 3
+
+ testValue := 0
+ testFunc := func() {
+ testValue = 1
+ }
+
+ task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
+ if testValue != 0 {
+ t.Fatal("Unexpected execuition of task")
+ }
+
+ time.Sleep(TASK_TIME + time.Second)
+
+ if testValue != 1 {
+ t.Fatal("Task did not execute")
+ }
+
+ if task.Name != TASK_NAME {
+ t.Fatal("Bad name")
+ }
+
+ if task.Interval != TASK_TIME {
+ t.Fatal("Bad interval")
+ }
+
+ if task.Recurring != false {
+ t.Fatal("should not reccur")
+ }
+}
+
+func TestCreateRecurringTask(t *testing.T) {
+ TASK_NAME := "Test Recurring Task"
+ TASK_TIME := time.Second * 3
+
+ testValue := 0
+ testFunc := func() {
+ testValue += 1
+ }
+
+ task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME)
+ if testValue != 0 {
+ t.Fatal("Unexpected execuition of task")
+ }
+
+ time.Sleep(TASK_TIME + time.Second)
+
+ if testValue != 1 {
+ t.Fatal("Task did not execute")
+ }
+
+ time.Sleep(TASK_TIME)
+
+ if testValue != 2 {
+ t.Fatal("Task did not re-execute")
+ }
+
+ if task.Name != TASK_NAME {
+ t.Fatal("Bad name")
+ }
+
+ if task.Interval != TASK_TIME {
+ t.Fatal("Bad interval")
+ }
+
+ if task.Recurring != true {
+ t.Fatal("should reccur")
+ }
+
+ task.Cancel()
+}
+
+func TestCancelTask(t *testing.T) {
+ TASK_NAME := "Test Task"
+ TASK_TIME := time.Second * 3
+
+ testValue := 0
+ testFunc := func() {
+ testValue = 1
+ }
+
+ task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
+ if testValue != 0 {
+ t.Fatal("Unexpected execuition of task")
+ }
+ task.Cancel()
+
+ time.Sleep(TASK_TIME + time.Second)
+
+ if testValue != 0 {
+ t.Fatal("Unexpected execuition of task")
+ }
+}
+
+func TestGetAllTasks(t *testing.T) {
+ doNothing := func() {}
+
+ CreateTask("Task1", doNothing, time.Hour)
+ CreateTask("Task2", doNothing, time.Second)
+ CreateRecurringTask("Task3", doNothing, time.Second)
+ task4 := CreateRecurringTask("Task4", doNothing, time.Second)
+
+ task4.Cancel()
+
+ time.Sleep(time.Second * 3)
+
+ tasks := *GetAllTasks()
+ if len(tasks) != 2 {
+ t.Fatal("Wrong number of tasks got: ", len(tasks))
+ }
+ for _, task := range tasks {
+ if task.Name != "Task1" && task.Name != "Task3" {
+ t.Fatal("Wrong tasks")
+ }
+ }
+}
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 11a915055..07a801dc6 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -793,6 +793,27 @@ func (us SqlUserStore) GetByAuth(authData *string, authService string) StoreChan
return storeChannel
}
+func (us SqlUserStore) GetAllUsingAuthService(authService string) StoreChannel {
+
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+ var data []*model.User
+
+ if _, err := us.GetReplica().Select(&data, "SELECT * FROM Users WHERE AuthService = :AuthService", map[string]interface{}{"AuthService": authService}); err != nil {
+ result.Err = model.NewLocAppError("SqlUserStore.GetByAuth", "store.sql_user.get_by_auth.other.app_error", nil, "authService="+authService+", "+err.Error())
+ }
+
+ result.Data = data
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (us SqlUserStore) GetByUsername(username string) StoreChannel {
storeChannel := make(StoreChannel)
diff --git a/store/store.go b/store/store.go
index ebbd2e454..7f4db396c 100644
--- a/store/store.go
+++ b/store/store.go
@@ -139,6 +139,7 @@ type UserStore interface {
GetProfileByIds(userId []string) StoreChannel
GetByEmail(email string) StoreChannel
GetByAuth(authData *string, authService string) StoreChannel
+ GetAllUsingAuthService(authService string) StoreChannel
GetByUsername(username string) StoreChannel
GetForLogin(loginId string, allowSignInWithUsername, allowSignInWithEmail, ldapEnabled bool) StoreChannel
VerifyEmail(userId string) StoreChannel
diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx
index e4fd7f6cc..fb121f656 100644
--- a/webapp/components/admin_console/ldap_settings.jsx
+++ b/webapp/components/admin_console/ldap_settings.jsx
@@ -36,6 +36,7 @@ export default class LdapSettings extends AdminSettings {
emailAttribute: props.config.LdapSettings.EmailAttribute,
usernameAttribute: props.config.LdapSettings.UsernameAttribute,
idAttribute: props.config.LdapSettings.IdAttribute,
+ syncIntervalMinutes: props.config.LdapSettings.SyncIntervalMinutes,
skipCertificateVerification: props.config.LdapSettings.SkipCertificateVerification,
queryTimeout: props.config.LdapSettings.QueryTimeout,
loginFieldName: props.config.LdapSettings.LoginFieldName
@@ -57,6 +58,7 @@ export default class LdapSettings extends AdminSettings {
config.LdapSettings.EmailAttribute = this.state.emailAttribute;
config.LdapSettings.UsernameAttribute = this.state.usernameAttribute;
config.LdapSettings.IdAttribute = this.state.idAttribute;
+ config.LdapSettings.SyncIntervalMinutes = this.parseIntNonZero(this.state.syncIntervalMinutes);
config.LdapSettings.SkipCertificateVerification = this.state.skipCertificateVerification;
config.LdapSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
config.LdapSettings.LoginFieldName = this.state.loginFieldName;
@@ -339,6 +341,24 @@ export default class LdapSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enable}
/>
+ <TextSetting
+ id='syncIntervalMinutes'
+ label={
+ <FormattedMessage
+ id='admin.ldap.syncIntervalTitle'
+ defaultMessage='Synchronization Interval (In Minutes)'
+ />
+ }
+ helpText={
+ <FormattedMessage
+ id='admin.ldap.syncIntervalHelpText'
+ defaultMessage='LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour.'
+ />
+ }
+ value={this.state.syncIntervalMinutes}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
<BooleanSetting
id='skipCertificateVerification'
label={
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 089e3299f..1e1177ec0 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -286,6 +286,8 @@
"admin.ldap.userFilterTitle": "User Filter:",
"admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Username Attribute:",
+ "admin.ldap.syncIntervalTitle": "Synchronization Interval (In Minutes)",
+ "admin.ldap.syncIntervalHelpText": "LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour.",
"admin.license.choose": "Choose File",
"admin.license.chooseFile": "Choose File",
"admin.license.edition": "Edition: ",