浏览代码

docs: clarify how multi-step auth works with external authentication

Fixes #617
Nicola Murino 3 年之前
父节点
当前提交
9fb43b2c46
共有 4 个文件被更改,包括 73 次插入3 次删除
  1. 2 2
      .github/workflows/development.yml
  2. 1 1
      .github/workflows/release.yml
  3. 2 0
      docs/external-auth.md
  4. 68 0
      sftpd/sftpd_test.go

+ 2 - 2
.github/workflows/development.yml

@@ -340,7 +340,7 @@ jobs:
             apt-get install -q -y curl gcc git
             apt-get install -q -y curl gcc git
             if [ ${{ matrix.go }} == 'latest' ]
             if [ ${{ matrix.go }} == 'latest' ]
             then
             then
-              GO_VERSION=$(curl https://golang.org/VERSION?m=text)
+              GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
             else
             else
               GO_VERSION=${{ matrix.go }}
               GO_VERSION=${{ matrix.go }}
             fi
             fi
@@ -349,7 +349,7 @@ jobs:
             then
             then
               GO_DOWNLOAD_ARCH=armv6l
               GO_DOWNLOAD_ARCH=armv6l
             fi
             fi
-            curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://golang.org/dl/${GO_VERSION}.linux-${GO_DOWNLOAD_ARCH}.tar.gz
+            curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/${GO_VERSION}.linux-${GO_DOWNLOAD_ARCH}.tar.gz
             tar -C /usr/local -xzf go.tar.gz
             tar -C /usr/local -xzf go.tar.gz
           run: |
           run: |
             export PATH=$PATH:/usr/local/go/bin
             export PATH=$PATH:/usr/local/go/bin

+ 1 - 1
.github/workflows/release.yml

@@ -293,7 +293,7 @@ jobs:
             then
             then
               GO_DOWNLOAD_ARCH=armv6l
               GO_DOWNLOAD_ARCH=armv6l
             fi
             fi
-            curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://golang.org/dl/go${{ steps.get_version.outputs.GO_VERSION }}.linux-${GO_DOWNLOAD_ARCH}.tar.gz
+            curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/go${{ steps.get_version.outputs.GO_VERSION }}.linux-${GO_DOWNLOAD_ARCH}.tar.gz
             tar -C /usr/local -xzf go.tar.gz
             tar -C /usr/local -xzf go.tar.gz
           run: |
           run: |
             export PATH=$PATH:/usr/local/go/bin
             export PATH=$PATH:/usr/local/go/bin

+ 2 - 0
docs/external-auth.md

@@ -39,6 +39,8 @@ If authentication succeeds the HTTP response code must be 200 and the response b
 
 
 If the authentication fails the HTTP response code must be != 200 or the returned SFTPGo user must have an empty username.
 If the authentication fails the HTTP response code must be != 200 or the returned SFTPGo user must have an empty username.
 
 
+If the hook returns a user who is only allowed to authenticate using public key + password (multi step authentication), your hook will be invoked for each authentication step, so it must validate the public key and password separately. SFTPGo will take care that the client uses the allowed sequence.
+
 Actions defined for users added/updated will not be executed in this case and an already logged in user with the same username will not be disconnected.
 Actions defined for users added/updated will not be executed in this case and an already logged in user with the same username will not be disconnected.
 
 
 The program hook must finish within 30 seconds, the HTTP hook timeout will use the global configuration for HTTP clients.
 The program hook must finish within 30 seconds, the HTTP hook timeout will use the global configuration for HTTP clients.

+ 68 - 0
sftpd/sftpd_test.go

@@ -2802,6 +2802,74 @@ func TestLoginExternalAuthPwdAndPubKey(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
 }
 
 
+func TestExternalAuthMultiStepLoginKeyAndPwd(t *testing.T) {
+	if runtime.GOOS == osWindows {
+		t.Skip("this test is not available on Windows")
+	}
+	u := getTestUser(true)
+	u.Password = defaultPassword
+	u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, []string{
+		dataprovider.SSHLoginMethodKeyAndKeyboardInt,
+		dataprovider.SSHLoginMethodPublicKey,
+		dataprovider.LoginMethodPassword,
+		dataprovider.SSHLoginMethodKeyboardInteractive,
+	}...)
+
+	err := dataprovider.Close()
+	assert.NoError(t, err)
+	err = config.LoadConfig(configDir, "")
+	assert.NoError(t, err)
+	providerConf := config.GetProviderConf()
+	err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
+	assert.NoError(t, err)
+	providerConf.ExternalAuthHook = extAuthPath
+	providerConf.ExternalAuthScope = 0
+	err = dataprovider.Initialize(providerConf, configDir, true)
+	assert.NoError(t, err)
+
+	signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
+	assert.NoError(t, err)
+	authMethods := []ssh.AuthMethod{
+		ssh.PublicKeys(signer),
+		ssh.Password(defaultPassword),
+	}
+	conn, client, err := getCustomAuthSftpClient(u, authMethods, "")
+	if assert.NoError(t, err) {
+		defer conn.Close()
+		defer client.Close()
+		assert.NoError(t, checkBasicSFTP(client))
+	}
+	// wrong sequence should fail
+	authMethods = []ssh.AuthMethod{
+		ssh.Password(defaultPassword),
+		ssh.PublicKeys(signer),
+	}
+	_, _, err = getCustomAuthSftpClient(u, authMethods, "")
+	assert.Error(t, err)
+
+	// public key only auth must fail
+	_, _, err = getSftpClient(u, true)
+	assert.Error(t, err)
+	// password only auth must fail
+	_, _, err = getSftpClient(u, false)
+	assert.Error(t, err)
+
+	_, err = httpdtest.RemoveUser(u, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(u.GetHomeDir())
+	assert.NoError(t, err)
+
+	err = dataprovider.Close()
+	assert.NoError(t, err)
+	err = config.LoadConfig(configDir, "")
+	assert.NoError(t, err)
+	providerConf = config.GetProviderConf()
+	err = dataprovider.Initialize(providerConf, configDir, true)
+	assert.NoError(t, err)
+	err = os.Remove(extAuthPath)
+	assert.NoError(t, err)
+}
+
 func TestExternalAuthEmptyResponse(t *testing.T) {
 func TestExternalAuthEmptyResponse(t *testing.T) {
 	if runtime.GOOS == osWindows {
 	if runtime.GOOS == osWindows {
 		t.Skip("this test is not available on Windows")
 		t.Skip("this test is not available on Windows")