From 01d681faa36e59059f715c4b56b3c3d0e9100694 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Mon, 8 Jun 2020 13:06:02 +0200 Subject: [PATCH] external auth: allow to map multiple login username to a single account some external auth users want to map multiple login usernames with a single SGTPGo account. For example an SFTP user logins using "user1" or "user2" and the external auth returns "user" in both cases, so we use the username returned from external auth and not the one used to login Fixes #125 --- dataprovider/dataprovider.go | 8 ++- sftpd/sftpd_test.go | 114 +++++++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index d3be252c..782d0a6e 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -1634,7 +1634,11 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) { user.PublicKeys = append(user.PublicKeys, pkey) } - u, err := provider.userExists(username) + // some users want to map multiple login usernames with a single SGTPGo account + // for example an SFTP user logins using "user1" or "user2" and the external auth + // returns "user" in both cases, so we use the username returned from + // external auth and not the one used to login + u, err := provider.userExists(user.Username) if err == nil { user.ID = u.ID user.UsedQuotaSize = u.UsedQuotaSize @@ -1648,7 +1652,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv if err != nil { return user, err } - return provider.userExists(username) + return provider.userExists(user.Username) } func providerLog(level logger.LogLevel, format string, v ...interface{}) { diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 029d13ce..4c8729ed 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -21,7 +21,6 @@ import ( "path" "path/filepath" "runtime" - "strconv" "strings" "testing" "time" @@ -1348,7 +1347,7 @@ func TestLoginExternalAuthPwdAndPubKey(t *testing.T) { err = config.LoadConfig(configDir, "") assert.NoError(t, err) providerConf := config.GetProviderConf() - err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false), 0755) + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), 0755) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = 0 @@ -1378,7 +1377,7 @@ func TestLoginExternalAuthPwdAndPubKey(t *testing.T) { usePubKey = false u = getTestUser(usePubKey) u.PublicKeys = []string{} - err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false), 0755) + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), 0755) assert.NoError(t, err) client, err = getSftpClient(u, usePubKey) if assert.NoError(t, err) { @@ -1387,16 +1386,95 @@ func TestLoginExternalAuthPwdAndPubKey(t *testing.T) { } users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK) assert.NoError(t, err) - assert.Equal(t, 1, len(users)) - user := users[0] - assert.Equal(t, 0, len(user.PublicKeys)) - assert.Equal(t, testFileSize, user.UsedQuotaSize) - assert.Equal(t, 1, user.UsedQuotaFiles) + if assert.Equal(t, 1, len(users)) { + user := users[0] + assert.Equal(t, 0, len(user.PublicKeys)) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + assert.Equal(t, 1, user.UsedQuotaFiles) - _, err = httpd.RemoveUser(user, http.StatusOK) + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + } + + dataProvider = dataprovider.GetProvider() + err = dataprovider.Close(dataProvider) assert.NoError(t, err) - err = os.RemoveAll(user.GetHomeDir()) + err = config.LoadConfig(configDir, "") assert.NoError(t, err) + providerConf = config.GetProviderConf() + err = dataprovider.Initialize(providerConf, configDir) + assert.NoError(t, err) + httpd.SetDataProvider(dataprovider.GetProvider()) + sftpd.SetDataProvider(dataprovider.GetProvider()) + err = os.Remove(extAuthPath) + assert.NoError(t, err) +} + +func TestExternalAuthDifferentUsername(t *testing.T) { + if runtime.GOOS == osWindows { + t.Skip("this test is not available on Windows") + } + usePubKey := false + extAuthUsername := "common_user" + u := getTestUser(usePubKey) + u.QuotaFiles = 1000 + dataProvider := dataprovider.GetProvider() + err := dataprovider.Close(dataProvider) + assert.NoError(t, err) + err = config.LoadConfig(configDir, "") + assert.NoError(t, err) + providerConf := config.GetProviderConf() + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, extAuthUsername), 0755) + assert.NoError(t, err) + providerConf.ExternalAuthHook = extAuthPath + providerConf.ExternalAuthScope = 0 + err = dataprovider.Initialize(providerConf, configDir) + assert.NoError(t, err) + httpd.SetDataProvider(dataprovider.GetProvider()) + sftpd.SetDataProvider(dataprovider.GetProvider()) + + // the user logins using "defaultUsername" and the external auth returns "extAuthUsername" + testFileSize := int64(65535) + client, err := getSftpClient(u, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + err = os.Remove(testFilePath) + assert.NoError(t, err) + } + + // logins again to test that used quota is preserved + client, err = getSftpClient(u, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + err = checkBasicSFTP(client) + assert.NoError(t, err) + } + + users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK) + assert.NoError(t, err) + assert.Equal(t, 0, len(users)) + + users, _, err = httpd.GetUsers(0, 0, extAuthUsername, http.StatusOK) + assert.NoError(t, err) + if assert.Equal(t, 1, len(users)) { + user := users[0] + assert.Equal(t, 0, len(user.PublicKeys)) + assert.Equal(t, testFileSize, user.UsedQuotaSize) + assert.Equal(t, 1, user.UsedQuotaFiles) + + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + } dataProvider = dataprovider.GetProvider() err = dataprovider.Close(dataProvider) @@ -1440,7 +1518,7 @@ func TestLoginExternalAuth(t *testing.T) { err = config.LoadConfig(configDir, "") assert.NoError(t, err) providerConf := config.GetProviderConf() - err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false), 0755) + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), 0755) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = authScope @@ -1510,7 +1588,7 @@ func TestLoginExternalAuthInteractive(t *testing.T) { err = config.LoadConfig(configDir, "") assert.NoError(t, err) providerConf := config.GetProviderConf() - err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false), 0755) + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), 0755) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = 4 @@ -1572,7 +1650,7 @@ func TestLoginExternalAuthErrors(t *testing.T) { err = config.LoadConfig(configDir, "") assert.NoError(t, err) providerConf := config.GetProviderConf() - err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, true), 0755) + err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, true, ""), 0755) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = 0 @@ -6320,10 +6398,13 @@ func getKeyboardInteractiveScriptContent(questions []string, sleepTime int, nonJ return content } -func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool) []byte { +func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte { extAuthContent := []byte("#!/bin/sh\n\n") - u, _ := json.Marshal(user) extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...) + if len(username) > 0 { + user.Username = username + } + u, _ := json.Marshal(user) if nonJSONResponse { extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...) } else { @@ -6333,8 +6414,7 @@ func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool) []byt if nonJSONResponse { extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...) } else { - json, _ := json.Marshal(user) - extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", strconv.Quote(string(json))))...) + extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...) } extAuthContent = append(extAuthContent, []byte("fi\n")...) return extAuthContent