From f5375254f90053bd9b688d36f758aca309ec3735 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 26 Jul 2016 17:39:51 -0400 Subject: Adding migration support to LDAP from other account types (#3655) --- Makefile | 5 +++- api/admin.go | 6 +--- einterfaces/account_migration.go | 20 ++++++++++++++ einterfaces/ldap.go | 2 ++ i18n/en.json | 8 ++++++ mattermost.go | 53 +++++++++++++++++++++++++++++++++++ model/job.go | 6 ++++ model/job_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 einterfaces/account_migration.go diff --git a/Makefile b/Makefile index 766cb6dd7..3ece21004 100644 --- a/Makefile +++ b/Makefile @@ -182,16 +182,19 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/compliance && ./compliance.test -test.v -test.timeout=120s -test.coverprofile=ccompliance.out || exit 1 $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/emoji && ./emoji.test -test.v -test.timeout=120s -test.coverprofile=cemoji.out || exit 1 $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/saml && ./saml.test -test.v -test.timeout=60s -test.coverprofile=csaml.out || exit 1 + $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/account_migration && ./account_migration.test -test.v -test.timeout=60s -test.coverprofile=caccount_migration.out || exit 1 tail -n +2 cldap.out >> ecover.out tail -n +2 ccompliance.out >> ecover.out tail -n +2 cemoji.out >> ecover.out tail -n +2 csaml.out >> ecover.out - rm -f cldap.out ccompliance.out cemoji.out csaml.out + tail -n +2 caccount_migration.out >> ecover.out + rm -f cldap.out ccompliance.out cemoji.out csaml.out caccount_migration.out rm -r ldap.test rm -r compliance.test rm -r emoji.test rm -r saml.test + rm -r account_migration.test rm -f config/*.crt rm -f config/*.key endif diff --git a/api/admin.go b/api/admin.go index 2771e5491..bd3955195 100644 --- a/api/admin.go +++ b/api/admin.go @@ -572,11 +572,7 @@ func ldapSyncNow(c *Context, w http.ResponseWriter, r *http.Request) { go func() { if utils.IsLicensed && *utils.License.Features.LDAP && *utils.Cfg.LdapSettings.Enable { if ldapI := einterfaces.GetLdapInterface(); ldapI != nil { - if err := ldapI.Syncronize(); err != nil { - l4g.Error("%v", err.Error()) - } else { - l4g.Info(utils.T("ent.ldap.syncdone.info")) - } + ldapI.SyncNow() } else { l4g.Error("%v", model.NewLocAppError("saveComplianceReport", "ent.compliance.licence_disable.app_error", nil, "").Error()) } diff --git a/einterfaces/account_migration.go b/einterfaces/account_migration.go new file mode 100644 index 000000000..4824de6d5 --- /dev/null +++ b/einterfaces/account_migration.go @@ -0,0 +1,20 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package einterfaces + +import "github.com/mattermost/platform/model" + +type AccountMigrationInterface interface { + MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError +} + +var theAccountMigrationInterface AccountMigrationInterface + +func RegisterAccountMigrationInterface(newInterface AccountMigrationInterface) { + theAccountMigrationInterface = newInterface +} + +func GetAccountMigrationInterface() AccountMigrationInterface { + return theAccountMigrationInterface +} diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go index 4f1b56119..fb14a8f02 100644 --- a/einterfaces/ldap.go +++ b/einterfaces/ldap.go @@ -15,6 +15,8 @@ type LdapInterface interface { ValidateFilter(filter string) *model.AppError Syncronize() *model.AppError StartLdapSyncJob() + SyncNow() + GetAllLdapUsers() ([]*model.User, *model.AppError) } var theLdapInterface LdapInterface diff --git a/i18n/en.json b/i18n/en.json index 7ddb99b3d..3937e5514 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2099,6 +2099,14 @@ "id": "cli.license.critical", "translation": "Feature requires an enterprise license. Please contact your system administrator about upgrading your enterprise license." }, + { + "id": "ent.migration.migratetoldap.duplicate_field", + "translation": "Unable to migrate LDAP users with specified field. Duplicate entry detected. Please remove all duplcates and try again." + }, + { + "id": "ent.migration.migratetoldap.user_not_found", + "translation": "Unable to find user on LDAP server: " + }, { "id": "ent.brand.save_brand_image.decode.app_error", "translation": "Unable to decode image." diff --git a/mattermost.go b/mattermost.go index f2047580b..68f7f26da 100644 --- a/mattermost.go +++ b/mattermost.go @@ -61,6 +61,7 @@ var flagCmdPermanentDeleteTeam bool var flagCmdPermanentDeleteAllUsers bool var flagCmdResetDatabase bool var flagCmdRunLdapSync bool +var flagCmdMigrateAccounts bool var flagUsername string var flagCmdUploadLicense bool var flagConfigFile string @@ -73,6 +74,9 @@ var flagSiteURL string var flagConfirmBackup string var flagRole string var flagRunCmds bool +var flagFromAuth string +var flagToAuth string +var flagMatchField string func doLoadConfig(filename string) (err string) { defer func() { @@ -288,6 +292,9 @@ func parseCmds() { flag.StringVar(&flagChannelName, "channel_name", "", "") flag.StringVar(&flagSiteURL, "site_url", "", "") flag.StringVar(&flagConfirmBackup, "confirm_backup", "", "") + flag.StringVar(&flagFromAuth, "from_auth", "", "") + flag.StringVar(&flagToAuth, "to_auth", "", "") + flag.StringVar(&flagMatchField, "match_field", "email", "") flag.StringVar(&flagRole, "role", "", "") flag.BoolVar(&flagCmdUpdateDb30, "upgrade_db_30", false, "") @@ -311,6 +318,7 @@ func parseCmds() { flag.BoolVar(&flagCmdPermanentDeleteAllUsers, "permanent_delete_all_users", false, "") flag.BoolVar(&flagCmdResetDatabase, "reset_database", false, "") flag.BoolVar(&flagCmdRunLdapSync, "ldap_sync", false, "") + flag.BoolVar(&flagCmdMigrateAccounts, "migrate_accounts", false, "") flag.BoolVar(&flagCmdUploadLicense, "upload_license", false, "") flag.Parse() @@ -335,6 +343,7 @@ func parseCmds() { flagCmdPermanentDeleteAllUsers || flagCmdResetDatabase || flagCmdRunLdapSync || + flagCmdMigrateAccounts || flagCmdUploadLicense) } @@ -359,6 +368,7 @@ func runCmds() { cmdResetDatabase() cmdUploadLicense() cmdRunLdapSync() + cmdRunMigrateAccounts() } type TeamForUpgrade struct { @@ -1475,6 +1485,44 @@ func cmdRunLdapSync() { } } +func cmdRunMigrateAccounts() { + if flagCmdMigrateAccounts { + if len(flagFromAuth) == 0 || (flagFromAuth != "email" && flagFromAuth != "gitlab" && flagFromAuth != "saml") { + fmt.Fprintln(os.Stderr, "flag needs an argument: -from_auth") + flag.Usage() + os.Exit(1) + } + + if len(flagToAuth) == 0 || flagToAuth != "ldap" { + fmt.Fprintln(os.Stderr, "flag needs an argument: -from_auth") + flag.Usage() + os.Exit(1) + } + + // Email auth in Mattermost system is represented by "" + if flagFromAuth == "email" { + flagFromAuth = "" + } + + if len(flagMatchField) == 0 || (flagMatchField != "email" && flagMatchField != "username") { + fmt.Fprintln(os.Stderr, "flag needs an argument: -match_field") + flag.Usage() + os.Exit(1) + } + + if migrate := einterfaces.GetAccountMigrationInterface(); migrate != nil { + if err := migrate.MigrateToLdap(flagFromAuth, flagMatchField); err != nil { + fmt.Println("ERROR: Account migration failed.") + l4g.Error("%v", err.Error()) + flushLogAndExit(1) + } else { + fmt.Println("SUCCESS: Account migration complete.") + flushLogAndExit(0) + } + } + } +} + func cmdUploadLicense() { if flagCmdUploadLicense { if model.BuildEnterpriseReady != "true" { @@ -1651,6 +1699,11 @@ COMMANDS: Example: platform -upload_license -license="/path/to/license/example.mattermost-license" + + -migrate_accounts Migrates accounts from one authentication provider to anouther. Requires -from_auth -to_auth and -match_field flags. Supported options for -from_auth: email, gitlab, saml. Supported options for -to_auth ldap. Supported options for -match_field email, username. Will display any accounts that are not migrated succesfully. + + Example: + platform -migrate_accounts -from_auth email -to_auth ldap -match_field username -upgrade_db_30 Upgrades the database from a version 2.x schema to version 3 see http://www.mattermost.org/upgrading-to-mattermost-3-0/ diff --git a/model/job.go b/model/job.go index b6c68dce4..229d5efd3 100644 --- a/model/job.go +++ b/model/job.go @@ -84,6 +84,12 @@ func (task *ScheduledTask) Cancel() { removeTaskByName(task.Name) } +// Executes the task immediatly. A recurring task will be run regularally after interval. +func (task *ScheduledTask) Execute() { + task.function() + task.timer.Reset(task.Interval) +} + func (task *ScheduledTask) String() string { return fmt.Sprintf( "%s\nInterval: %s\nRecurring: %t\n", diff --git a/model/job_test.go b/model/job_test.go index 2a307de1e..8908fed58 100644 --- a/model/job_test.go +++ b/model/job_test.go @@ -126,3 +126,63 @@ func TestGetAllTasks(t *testing.T) { } } } + +func TestExecuteTask(t *testing.T) { + TASK_NAME := "Test Task" + TASK_TIME := time.Second * 5 + + testValue := 0 + testFunc := func() { + testValue += 1 + } + + task := CreateTask(TASK_NAME, testFunc, TASK_TIME) + if testValue != 0 { + t.Fatal("Unexpected execuition of task") + } + + task.Execute() + + if testValue != 1 { + t.Fatal("Task did not execute") + } + + time.Sleep(TASK_TIME + time.Second) + + if testValue != 2 { + t.Fatal("Task re-executed") + } +} + +func TestExecuteTaskRecurring(t *testing.T) { + TASK_NAME := "Test Recurring Task" + TASK_TIME := time.Second * 5 + + testValue := 0 + testFunc := func() { + testValue += 1 + } + + task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME) + if testValue != 0 { + t.Fatal("Unexpected execuition of task") + } + + time.Sleep(time.Second * 3) + + task.Execute() + if testValue != 1 { + t.Fatal("Task did not execute") + } + + time.Sleep(time.Second * 3) + if testValue != 1 { + t.Fatal("Task should not have executed before 5 seconds") + } + + time.Sleep(time.Second * 3) + + if testValue != 2 { + t.Fatal("Task did not re-execute after forced execution") + } +} -- cgit v1.2.3-1-g7c22