allow to disable some hooks on a per-user basis
This way you can, for example, mix external and internal users
This commit is contained in:
parent
d92861a8e8
commit
fdf3f23df5
15 changed files with 211 additions and 66 deletions
|
@ -1601,23 +1601,25 @@ func checkUserAndPass(user *User, password, ip, protocol string) (User, error) {
|
|||
if user.Password == "" {
|
||||
return *user, errors.New("credentials cannot be null or empty")
|
||||
}
|
||||
hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelDebug, "error executing check password hook: %v", err)
|
||||
return *user, errors.New("unable to check credentials")
|
||||
}
|
||||
switch hookResponse.Status {
|
||||
case -1:
|
||||
// no hook configured
|
||||
case 1:
|
||||
providerLog(logger.LevelDebug, "password accepted by check password hook")
|
||||
return *user, nil
|
||||
case 2:
|
||||
providerLog(logger.LevelDebug, "partial success from check password hook")
|
||||
password = hookResponse.ToVerify
|
||||
default:
|
||||
providerLog(logger.LevelDebug, "password rejected by check password hook, status: %v", hookResponse.Status)
|
||||
return *user, ErrInvalidCredentials
|
||||
if !user.Filters.Hooks.CheckPasswordDisabled {
|
||||
hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelDebug, "error executing check password hook: %v", err)
|
||||
return *user, errors.New("unable to check credentials")
|
||||
}
|
||||
switch hookResponse.Status {
|
||||
case -1:
|
||||
// no hook configured
|
||||
case 1:
|
||||
providerLog(logger.LevelDebug, "password accepted by check password hook")
|
||||
return *user, nil
|
||||
case 2:
|
||||
providerLog(logger.LevelDebug, "partial success from check password hook")
|
||||
password = hookResponse.ToVerify
|
||||
default:
|
||||
providerLog(logger.LevelDebug, "password rejected by check password hook, status: %v", hookResponse.Status)
|
||||
return *user, ErrInvalidCredentials
|
||||
}
|
||||
}
|
||||
|
||||
match, err := isPasswordOK(user, password)
|
||||
|
@ -2142,6 +2144,9 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
|
|||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
if u.Filters.Hooks.PreLoginDisabled {
|
||||
return u, nil
|
||||
}
|
||||
startTime := time.Now()
|
||||
out, err := getPreLoginHookResponse(loginMethod, ip, protocol, userAsJSON)
|
||||
if err != nil {
|
||||
|
@ -2328,11 +2333,16 @@ func updateUserFromExtAuthResponse(user *User, password, pkey string) {
|
|||
func doExternalAuth(username, password string, pubKey []byte, keyboardInteractive, ip, protocol string, tlsCert *x509.Certificate) (User, error) {
|
||||
var user User
|
||||
|
||||
pkey, err := utils.GetSSHPublicKeyAsString(pubKey)
|
||||
u, userAsJSON, err := getUserAndJSONForHook(username)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
u, userAsJSON, err := getUserAndJSONForHook(username)
|
||||
|
||||
if u.Filters.Hooks.ExternalAuthDisabled {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
pkey, err := utils.GetSSHPublicKeyAsString(pubKey)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
@ -2397,7 +2407,6 @@ func getUserAndJSONForHook(username string) (User, []byte, error) {
|
|||
ID: 0,
|
||||
Username: username,
|
||||
}
|
||||
return u, userAsJSON, nil
|
||||
}
|
||||
userAsJSON, err = json.Marshal(u)
|
||||
if err != nil {
|
||||
|
|
|
@ -116,7 +116,15 @@ type PatternsFilter struct {
|
|||
DeniedPatterns []string `json:"denied_patterns,omitempty"`
|
||||
}
|
||||
|
||||
// HooksFilter defines user specific overrides for global hooks
|
||||
type HooksFilter struct {
|
||||
ExternalAuthDisabled bool `json:"external_auth_disabled"`
|
||||
PreLoginDisabled bool `json:"pre_login_disabled"`
|
||||
CheckPasswordDisabled bool `json:"check_password_disabled"`
|
||||
}
|
||||
|
||||
// UserFilters defines additional restrictions for a user
|
||||
// TODO: rename to UserOptions in v3
|
||||
type UserFilters struct {
|
||||
// only clients connecting from these IP/Mask are allowed.
|
||||
// IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291
|
||||
|
@ -142,6 +150,8 @@ type UserFilters struct {
|
|||
// For FTP clients it must match the name provided using the
|
||||
// "USER" command
|
||||
TLSUsername TLSUsername `json:"tls_username,omitempty"`
|
||||
// user specific hook overrides
|
||||
Hooks HooksFilter `json:"hooks,omitempty"`
|
||||
}
|
||||
|
||||
// User defines a SFTPGo user
|
||||
|
@ -1069,6 +1079,9 @@ func (u *User) getACopy() User {
|
|||
copy(filters.FilePatterns, u.Filters.FilePatterns)
|
||||
filters.DeniedProtocols = make([]string, len(u.Filters.DeniedProtocols))
|
||||
copy(filters.DeniedProtocols, u.Filters.DeniedProtocols)
|
||||
filters.Hooks.ExternalAuthDisabled = u.Filters.Hooks.ExternalAuthDisabled
|
||||
filters.Hooks.PreLoginDisabled = u.Filters.Hooks.PreLoginDisabled
|
||||
filters.Hooks.CheckPasswordDisabled = u.Filters.Hooks.CheckPasswordDisabled
|
||||
|
||||
return User{
|
||||
ID: u.ID,
|
||||
|
|
|
@ -42,4 +42,6 @@ You can also restrict the hook scope using the `check_password_scope` configurat
|
|||
|
||||
You can combine the scopes. For example, 6 means FTP and WebDAV.
|
||||
|
||||
You can disable the hook on a per-user basis.
|
||||
|
||||
An example check password program allowing 2FA using password + one time token can be found inside the source tree [checkpwd](../examples/OTP/authy/checkpwd) directory.
|
||||
|
|
|
@ -35,6 +35,8 @@ If an error happens while executing the hook then login will be denied.
|
|||
"Dynamic user creation or modification" and "External Authentication" are mutually exclusive, they are quite similar, the difference is that "External Authentication" returns an already authenticated user while using "Dynamic users modification" you simply create or update a user. The authentication will be checked inside SFTPGo.
|
||||
In other words while using "External Authentication" the external program receives the credentials of the user trying to login (for example the cleartext password) and it needs to validate them. While using "Dynamic users modification" the pre-login program receives the user stored inside the dataprovider (it includes the hashed password if any) and it can modify it, after the modification SFTPGo will check the credentials of the user trying to login.
|
||||
|
||||
You can disable the hook on a per-user basis.
|
||||
|
||||
Let's see a very basic example. Our sample program will grant access to the existing user `test_user` only in the time range 10:00-18:00. Other users will not be modified since the program will terminate with no output.
|
||||
|
||||
```shell
|
||||
|
|
|
@ -68,6 +68,8 @@ fi
|
|||
|
||||
The structure for SFTPGo users can be found within the [OpenAPI schema](../httpd/schema/openapi.yaml).
|
||||
|
||||
You can disable the hook on a per-user basis so that you can mix external and internal users.
|
||||
|
||||
An example authentication program allowing to authenticate against an LDAP server can be found inside the source tree [ldapauth](../examples/ldapauth) directory.
|
||||
|
||||
An example server, to use as HTTP authentication hook, allowing to authenticate against an LDAP server can be found inside the source tree [ldapauthserver](../examples/ldapauthserver) directory.
|
||||
|
|
18
go.mod
18
go.mod
|
@ -3,23 +3,23 @@ module github.com/drakkan/sftpgo
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.80.0 // indirect
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
cloud.google.com/go/storage v1.14.0
|
||||
github.com/Azure/azure-storage-blob-go v0.13.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||
github.com/alexedwards/argon2id v0.0.0-20210326052512-e2135f7c9c77
|
||||
github.com/aws/aws-sdk-go v1.38.7
|
||||
github.com/aws/aws-sdk-go v1.38.12
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d
|
||||
github.com/fclairamb/ftpserverlib v0.13.0
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.2
|
||||
github.com/go-chi/jwtauth/v5 v5.0.0
|
||||
github.com/go-chi/jwtauth/v5 v5.0.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
|
@ -31,7 +31,7 @@ require (
|
|||
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||
github.com/lestrrat-go/jwx v1.1.6
|
||||
github.com/lestrrat-go/jwx v1.1.7
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
|
@ -46,10 +46,10 @@ require (
|
|||
github.com/prometheus/client_golang v1.10.0
|
||||
github.com/prometheus/common v0.20.0 // indirect
|
||||
github.com/rs/cors v1.7.1-0.20200626170627-8b4a00bd362b
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/rs/zerolog v1.21.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.21.2
|
||||
github.com/shirou/gopsutil/v3 v3.21.3
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
|
@ -65,8 +65,8 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
|
||||
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
google.golang.org/api v0.43.0
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
|
|
48
go.sum
48
go.sum
|
@ -21,8 +21,8 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
|||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.80.0 h1:kAdyAMrj9CjqOSGiluseVjIgAyQ3uxADYtUYR6MwYeY=
|
||||
cloud.google.com/go v0.80.0/go.mod h1:fqpb6QRi1CFGAMXDoE72G+b+Ybv7dMB/T1tbExDHktI=
|
||||
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -118,8 +118,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
|||
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.36.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.7 h1:uOu2IrTiNhcSNAjBmA21t48lTx5mgGdcFKamDjXMscA=
|
||||
github.com/aws/aws-sdk-go v1.38.7/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.12 h1:khtODkUna3iF53Cg3dCF4e6oWgrAEbZDU4x1aq+G0WY=
|
||||
github.com/aws/aws-sdk-go v1.38.12/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
|
@ -219,8 +219,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
|||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-chi/chi/v5 v5.0.2 h1:4xKeALZdMEsuI5s05PU2Bm89Uc5iM04qFubUCl5LfAQ=
|
||||
github.com/go-chi/chi/v5 v5.0.2/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/jwtauth/v5 v5.0.0 h1:FjyoBQ0sH6/OSBCTXdRMMd7Eis3UjmsX2wKTSucFn+g=
|
||||
github.com/go-chi/jwtauth/v5 v5.0.0/go.mod h1:wdYCsXCBuihmcGwLdfVgZ4LhDLOZHfyF+Fd5mEjiGPM=
|
||||
github.com/go-chi/jwtauth/v5 v5.0.1 h1:eyJ6Yx5VphEfjkqpZ7+LJEWThzyIcF5aN2QVpgqSIu0=
|
||||
github.com/go-chi/jwtauth/v5 v5.0.1/go.mod h1:+JtcRYGZsnA4+ur1LFlb4Bei3O9WeUzoMfDZWfUJuoY=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
@ -246,14 +246,14 @@ github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEK
|
|||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/goccy/go-json v0.3.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.4.8 h1:TfwOxfSp8hXH+ivoOk36RyDNmXATUETRdaNWDaZglf8=
|
||||
github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
|
@ -517,15 +517,16 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
|
|||
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v1.0.0/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.1.0/go.mod h1:vn9FzD6gJtKkgYs7RTKV7CjWtEka8F/voUollhnn4QE=
|
||||
github.com/lestrrat-go/jwx v1.1.6 h1:VfyUo2PAU4lO/liwhdwiSZ55/QZDLTT3EYY5z9KfwZs=
|
||||
github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024=
|
||||
github.com/lestrrat-go/jwx v1.1.7 h1:+PNt2U7FfrK4xn+ZCG+9jPRq5eqyG30gwpVwcekrCjA=
|
||||
github.com/lestrrat-go/jwx v1.1.7/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc=
|
||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
|
@ -688,8 +689,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
|||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.1-0.20200626170627-8b4a00bd362b h1:LeFDRLtjhSBjIezNZvfN0CHsu2GfDS2CJAiEGaWBJ34=
|
||||
github.com/rs/cors v1.7.1-0.20200626170627-8b4a00bd362b/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
|
||||
|
@ -706,8 +708,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
|
|||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
|
||||
github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM=
|
||||
github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
|
||||
github.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8=
|
||||
github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
|
@ -857,8 +859,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 h1:D7nTwh4J0i+5mW4Zjzn5omvlr6YBcWywE6KOcatyNxY=
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -932,11 +934,10 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210314195730-07df6a141424/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 h1:64ChN/hjER/taL4YJuA+gpLfIMT+/NFherRZixbxOhg=
|
||||
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1053,7 +1054,6 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ
|
|||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.42.0/go.mod h1:+Oj4s6ch2SEGtPjGqfUfZonBH0GjQH89gTeKKAEGZKI=
|
||||
google.golang.org/api v0.43.0 h1:4sAyIHT6ZohtAQDoxws+ez7bROYmUlOVvsUscYCDTqA=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
|
@ -1112,10 +1112,9 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210312152112-fc591d9ea70f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210323160006-e668133fea6a h1:XVaQ1+BDKvrRcgppHhtAaniHCKyV5xJAvymwsPHHFaE=
|
||||
google.golang.org/genproto v0.0.0-20210323160006-e668133fea6a/go.mod h1:f2Bd7+2PlaVKmvKQ52aspJZXIDaRQBVdOOBfJ5i8OEs=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1 h1:E7wSQBXkH3T3diucK+9Z1kjn4+/9tNG7lZLr75oOhh8=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
@ -1140,8 +1139,9 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
|||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
|
@ -974,6 +974,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
u.UsedQuotaFiles = 1
|
||||
u.UsedQuotaSize = 2
|
||||
u.Filters.TLSUsername = dataprovider.TLSUsernameCN
|
||||
u.Filters.Hooks.CheckPasswordDisabled = true
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, user.UsedQuotaFiles)
|
||||
|
@ -991,6 +992,9 @@ func TestUpdateUser(t *testing.T) {
|
|||
user.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
|
||||
user.Filters.DeniedProtocols = []string{common.ProtocolWebDAV}
|
||||
user.Filters.TLSUsername = dataprovider.TLSUsernameNone
|
||||
user.Filters.Hooks.ExternalAuthDisabled = true
|
||||
user.Filters.Hooks.PreLoginDisabled = true
|
||||
user.Filters.Hooks.CheckPasswordDisabled = false
|
||||
user.Filters.FileExtensions = append(user.Filters.FileExtensions, dataprovider.ExtensionsFilter{
|
||||
Path: "/subdir",
|
||||
AllowedExtensions: []string{".zip", ".rar"},
|
||||
|
@ -1027,6 +1031,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
})
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "invalid")
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "0")
|
||||
|
@ -4852,6 +4857,7 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
form.Set("denied_patterns", "/dir1::*.zip\n/dir3::*.rar\n/dir2::*.mkv")
|
||||
form.Set("additional_info", user.AdditionalInfo)
|
||||
form.Set("description", user.Description)
|
||||
form.Add("hooks", "external_auth_disabled")
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
// test invalid url escape
|
||||
req, _ = http.NewRequest(http.MethodPost, webUserPath+"?a=%2", &b)
|
||||
|
@ -5011,6 +5017,9 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
assert.Equal(t, int64(1000), newUser.Filters.MaxUploadFileSize)
|
||||
assert.Equal(t, user.AdditionalInfo, newUser.AdditionalInfo)
|
||||
assert.Equal(t, user.Description, newUser.Description)
|
||||
assert.True(t, newUser.Filters.Hooks.ExternalAuthDisabled)
|
||||
assert.False(t, newUser.Filters.Hooks.PreLoginDisabled)
|
||||
assert.False(t, newUser.Filters.Hooks.CheckPasswordDisabled)
|
||||
assert.True(t, utils.IsStringInSlice(testPubKey, newUser.PublicKeys))
|
||||
if val, ok := newUser.Permissions["/subdir"]; ok {
|
||||
assert.True(t, utils.IsStringInSlice(dataprovider.PermListItems, val))
|
||||
|
@ -5416,6 +5425,8 @@ func TestUserTemplateMock(t *testing.T) {
|
|||
form.Set("allowed_extensions", "/dir1::.jpg,.png")
|
||||
form.Set("denied_extensions", "/dir2::.zip")
|
||||
form.Set("max_upload_file_size", "0")
|
||||
form.Add("hooks", "external_auth_disabled")
|
||||
form.Add("hooks", "check_password_disabled")
|
||||
// test invalid s3_upload_part_size
|
||||
form.Set("s3_upload_part_size", "a")
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
|
@ -5487,6 +5498,12 @@ func TestUserTemplateMock(t *testing.T) {
|
|||
err = user2.FsConfig.S3Config.AccessSecret.Decrypt()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "password2", user2.FsConfig.S3Config.AccessSecret.GetPayload())
|
||||
require.True(t, user1.Filters.Hooks.ExternalAuthDisabled)
|
||||
require.True(t, user1.Filters.Hooks.CheckPasswordDisabled)
|
||||
require.False(t, user1.Filters.Hooks.PreLoginDisabled)
|
||||
require.True(t, user2.Filters.Hooks.ExternalAuthDisabled)
|
||||
require.True(t, user2.Filters.Hooks.CheckPasswordDisabled)
|
||||
require.False(t, user2.Filters.Hooks.PreLoginDisabled)
|
||||
}
|
||||
|
||||
func TestFolderTemplateMock(t *testing.T) {
|
||||
|
@ -5663,6 +5680,7 @@ func TestWebUserS3Mock(t *testing.T) {
|
|||
form.Set("denied_extensions", "/dir2::.zip")
|
||||
form.Set("max_upload_file_size", "0")
|
||||
form.Set("description", user.Description)
|
||||
form.Add("hooks", "pre_login_disabled")
|
||||
// test invalid s3_upload_part_size
|
||||
form.Set("s3_upload_part_size", "a")
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
|
@ -5710,6 +5728,9 @@ func TestWebUserS3Mock(t *testing.T) {
|
|||
assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetKey())
|
||||
assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetAdditionalData())
|
||||
assert.Equal(t, user.Description, updateUser.Description)
|
||||
assert.True(t, updateUser.Filters.Hooks.PreLoginDisabled)
|
||||
assert.False(t, updateUser.Filters.Hooks.ExternalAuthDisabled)
|
||||
assert.False(t, updateUser.Filters.Hooks.CheckPasswordDisabled)
|
||||
// now check that a redacted password is not saved
|
||||
form.Set("s3_access_secret", redactedSecret)
|
||||
b, contentType, _ = getMultipartFormData(form, "", "")
|
||||
|
|
|
@ -1421,6 +1421,22 @@ components:
|
|||
description: 'list of, case insensitive, denied files extension. Denied file extensions are evaluated before the allowed ones'
|
||||
example:
|
||||
- .zip
|
||||
HooksFilter:
|
||||
type: object
|
||||
properties:
|
||||
external_auth_disabled:
|
||||
type: boolean
|
||||
example: false
|
||||
description: If true, the external auth hook, if defined, will not be executed
|
||||
pre_login_disabled:
|
||||
type: boolean
|
||||
example: false
|
||||
description: If true, the pre-login hook, if defined, will not be executed
|
||||
check_password_disabled:
|
||||
type: boolean
|
||||
example: false
|
||||
description: If true, the check password hook, if defined, will not be executed
|
||||
description: User specific hook overrides
|
||||
UserFilters:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1469,6 +1485,8 @@ components:
|
|||
- None
|
||||
- CommonName
|
||||
description: 'defines the TLS certificate field to use as username. For FTP clients it must match the name provided using the "USER" command. For WebDAV, if no username is provided, the CN will be used as username. For WebDAV clients it must match the implicit or provided username. Ignored if mutual TLS is disabled'
|
||||
hooks:
|
||||
$ref: '#/components/schemas/HooksFilter'
|
||||
description: Additional user restrictions
|
||||
Secret:
|
||||
type: object
|
||||
|
@ -1615,6 +1633,9 @@ components:
|
|||
description: Concurrent reads are safe to use and disabling them will degrade performance. Some servers automatically delete files once they are downloaded. Using concurrent reads is problematic with such servers.
|
||||
buffer_size:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 16
|
||||
example: 2
|
||||
description: The size of the buffer (in MB) to use for transfers. By enabling buffering, the reads and writes, from/to the remote SFTP server, are split in multiple concurrent requests and this allows data to be transferred at a faster rate, over high latency networks, by overlapping round-trip times. With buffering enabled, resuming uploads is not supported and a file cannot be opened for both reading and writing at the same time. 0 means disabled.
|
||||
FilesystemConfig:
|
||||
type: object
|
||||
|
|
10
httpd/web.go
10
httpd/web.go
|
@ -662,6 +662,16 @@ func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
|
|||
filters.FileExtensions = getFileExtensionsFromPostField(r.Form.Get("allowed_extensions"), r.Form.Get("denied_extensions"))
|
||||
filters.FilePatterns = getFilePatternsFromPostField(r.Form.Get("allowed_patterns"), r.Form.Get("denied_patterns"))
|
||||
filters.TLSUsername = dataprovider.TLSUsername(r.Form.Get("tls_username"))
|
||||
hooks := r.Form["hooks"]
|
||||
if utils.IsStringInSlice("external_auth_disabled", hooks) {
|
||||
filters.Hooks.ExternalAuthDisabled = true
|
||||
}
|
||||
if utils.IsStringInSlice("pre_login_disabled", hooks) {
|
||||
filters.Hooks.PreLoginDisabled = true
|
||||
}
|
||||
if utils.IsStringInSlice("check_password_disabled", hooks) {
|
||||
filters.Hooks.CheckPasswordDisabled = true
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
|
|
|
@ -1168,6 +1168,15 @@ func compareUserFilterSubStructs(expected *dataprovider.User, actual *dataprovid
|
|||
return errors.New("denied protocols contents mismatch")
|
||||
}
|
||||
}
|
||||
if expected.Filters.Hooks.ExternalAuthDisabled != actual.Filters.Hooks.ExternalAuthDisabled {
|
||||
return errors.New("external_auth_disabled hook mismatch")
|
||||
}
|
||||
if expected.Filters.Hooks.PreLoginDisabled != actual.Filters.Hooks.PreLoginDisabled {
|
||||
return errors.New("pre_login_disabled hook mismatch")
|
||||
}
|
||||
if expected.Filters.Hooks.CheckPasswordDisabled != actual.Filters.Hooks.CheckPasswordDisabled {
|
||||
return errors.New("check_password_disabled hook mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2084,6 +2084,20 @@ func TestPreLoginScript(t *testing.T) {
|
|||
if !assert.Error(t, err, "pre-login script returned a non json response, login must fail") {
|
||||
client.Close()
|
||||
}
|
||||
// now disable the the hook
|
||||
user.Filters.Hooks.PreLoginDisabled = true
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(u, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
}
|
||||
|
||||
user.Filters.Hooks.PreLoginDisabled = false
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
user.Status = 0
|
||||
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
|
@ -2091,6 +2105,7 @@ func TestPreLoginScript(t *testing.T) {
|
|||
if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
|
@ -2230,6 +2245,22 @@ func TestCheckPwdHook(t *testing.T) {
|
|||
client.Close()
|
||||
}
|
||||
|
||||
// now disable the the hook
|
||||
user.Filters.Hooks.CheckPasswordDisabled = true
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
err = checkBasicSFTP(client)
|
||||
assert.NoError(t, err)
|
||||
client.Close()
|
||||
}
|
||||
|
||||
// enable the hook again
|
||||
user.Filters.Hooks.CheckPasswordDisabled = false
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(checkPwdPath, getCheckPwdScriptsContents(1, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword + "1"
|
||||
|
@ -2323,6 +2354,23 @@ func TestLoginExternalAuthPwdAndPubKey(t *testing.T) {
|
|||
assert.Equal(t, testFileSize, user.UsedQuotaSize)
|
||||
assert.Equal(t, 1, user.UsedQuotaFiles)
|
||||
|
||||
u.Status = 0
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(u, usePubKey)
|
||||
if !assert.Error(t, err) {
|
||||
client.Close()
|
||||
}
|
||||
// now disable the the hook
|
||||
user.Filters.Hooks.ExternalAuthDisabled = true
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(u, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
|
|
|
@ -251,7 +251,7 @@
|
|||
<label for="idSFTPUploadBufferSize" class="col-sm-2 col-form-label">Buffer size (MB)</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="number" class="form-control" id="idSFTPBufferSize" name="sftp_buffer_size" placeholder=""
|
||||
value="{{.SFTPConfig.BufferSize}}" min="0" max="64" aria-describedby="SFTPBufferHelpBlock">
|
||||
value="{{.SFTPConfig.BufferSize}}" min="0" max="16" aria-describedby="SFTPBufferHelpBlock">
|
||||
<small id="SFTPBufferHelpBlock" class="form-text text-muted">
|
||||
A buffer size > 0 enables concurrent transfers
|
||||
</small>
|
||||
|
|
|
@ -360,6 +360,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idHooks" class="col-sm-2 col-form-label">Hooks</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" id="idHooks" name="hooks" multiple>
|
||||
<option value="external_auth_disabled" {{if .User.Filters.Hooks.ExternalAuthDisabled}}selected{{end}}>
|
||||
External auth disabled
|
||||
</option>
|
||||
<option value="pre_login_disabled" {{if .User.Filters.Hooks.PreLoginDisabled}}selected{{end}}>
|
||||
Pre-login disabled
|
||||
</option>
|
||||
<option value="check_password_disabled" {{if .User.Filters.Hooks.CheckPasswordDisabled}}selected{{end}}>
|
||||
Check password disabled
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "fshtml" .User.FsConfig}}
|
||||
|
||||
<div class="form-group row">
|
||||
|
|
|
@ -109,8 +109,8 @@ func (c *SFTPFsConfig) Validate() error {
|
|||
if c.Username == "" {
|
||||
return errors.New("username cannot be empty")
|
||||
}
|
||||
if c.BufferSize < 0 || c.BufferSize > 64 {
|
||||
return errors.New("invalid buffer_size, valid range is 0-64")
|
||||
if c.BufferSize < 0 || c.BufferSize > 16 {
|
||||
return errors.New("invalid buffer_size, valid range is 0-16")
|
||||
}
|
||||
if err := c.validateCredentials(); err != nil {
|
||||
return err
|
||||
|
@ -262,16 +262,7 @@ func (fs *SFTPFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, f
|
|||
return nil, nil, nil, err
|
||||
}
|
||||
go func() {
|
||||
var n int64
|
||||
var err error
|
||||
if fs.config.DisableCouncurrentReads {
|
||||
n, err = fs.copy(w, f)
|
||||
} else {
|
||||
br := bufio.NewReaderSize(f, int(fs.config.BufferSize)*1024*1024)
|
||||
// we don't use io.Copy since bufio.Reader implements io.ReadFrom and
|
||||
// so it calls the sftp.File ReadFrom method without buffering
|
||||
n, err = fs.copy(w, br)
|
||||
}
|
||||
n, err := io.Copy(w, f)
|
||||
w.CloseWithError(err) //nolint:errcheck
|
||||
f.Close()
|
||||
fsLog(fs, logger.LevelDebug, "download completed, path: %#v size: %v, err: %v", name, n, err)
|
||||
|
|
Loading…
Reference in a new issue