mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
webdav: fix caching with external auth/plugins
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
963cec124e
commit
d28a53a6cf
11 changed files with 181 additions and 60 deletions
|
@ -72,7 +72,7 @@ If you report an invalid issue or ask for step-by-step support, your issue will
|
|||
- Per-user authentication methods.
|
||||
- [Two-factor authentication](./docs/howto/two-factor-authentication.md) based on time-based one time passwords (RFC 6238) which works with Authy, Google Authenticator, Microsoft Authenticator and other compatible apps.
|
||||
- Simplified user administrations using [groups](./docs/groups.md).
|
||||
- [Roles](./docs/roles.md) allow you to create limited administrators who can only create and manage users with their role.
|
||||
- [Roles](./docs/roles.md) allow to create limited administrators who can only create and manage users with their role.
|
||||
- Custom authentication via [external programs/HTTP API](./docs/external-auth.md).
|
||||
- Web Client and Web Admin user interfaces support [OpenID Connect](https://openid.net/connect/) authentication and so they can be integrated with identity providers such as [Keycloak](https://www.keycloak.org/). You can find more details [here](./docs/oidc.md).
|
||||
- [Data At Rest Encryption](./docs/dare.md).
|
||||
|
|
6
go.mod
6
go.mod
|
@ -58,7 +58,7 @@ require (
|
|||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2
|
||||
github.com/subosito/gotenv v1.4.2
|
||||
github.com/unrolled/secure v1.13.0
|
||||
|
@ -113,7 +113,7 @@ require (
|
|||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/s2a-go v0.1.3 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
|
@ -153,7 +153,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1199,8 +1199,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
|
|||
github.com/google/pprof v0.0.0-20220318212150-b2ab0324ddda/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE=
|
||||
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
|
@ -1929,8 +1929,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
|||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyAOu7kJw6O/2jjG2UXLFoBzihqDU9Ofg9M=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
|
@ -1997,8 +1998,9 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
|
|
|
@ -77,7 +77,7 @@ func (cache *usersCache) updateLastLogin(username string) {
|
|||
// swapWebDAVUser updates an existing cached user with the specified one
|
||||
// preserving the lock fs if possible
|
||||
// FIXME: this could be racy in rare cases
|
||||
func (cache *usersCache) swap(userRef *User) {
|
||||
func (cache *usersCache) swap(userRef *User, plainPassword string) {
|
||||
user := userRef.getACopy()
|
||||
err := user.LoadAndApplyGroupSettings()
|
||||
|
||||
|
@ -85,19 +85,23 @@ func (cache *usersCache) swap(userRef *User) {
|
|||
defer cache.Unlock()
|
||||
|
||||
if cachedUser, ok := cache.users[user.Username]; ok {
|
||||
if cachedUser.User.Password != user.Password {
|
||||
providerLog(logger.LevelDebug, "current password different from the cached one for user %q, removing from cache",
|
||||
user.Username)
|
||||
// the password changed, the cached user is no longer valid
|
||||
delete(cache.users, user.Username)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
providerLog(logger.LevelDebug, "unable to load group settings, for user %q, removing from cache, err :%v",
|
||||
user.Username, err)
|
||||
delete(cache.users, user.Username)
|
||||
return
|
||||
}
|
||||
if plainPassword != "" {
|
||||
cachedUser.Password = plainPassword
|
||||
} else {
|
||||
if cachedUser.User.Password != user.Password {
|
||||
providerLog(logger.LevelDebug, "current password different from the cached one for user %q, removing from cache",
|
||||
user.Username)
|
||||
// the password changed, the cached user is no longer valid
|
||||
delete(cache.users, user.Username)
|
||||
return
|
||||
}
|
||||
}
|
||||
if cachedUser.User.isFsEqual(&user) {
|
||||
// the updated user has the same fs as the cached one, we can preserve the lock filesystem
|
||||
providerLog(logger.LevelDebug, "current password and fs unchanged for for user %q, swap cached one",
|
||||
|
@ -154,7 +158,10 @@ func (cache *usersCache) get(username string) (*CachedUser, bool) {
|
|||
defer cache.RUnlock()
|
||||
|
||||
cachedUser, ok := cache.users[username]
|
||||
return &cachedUser, ok
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return &cachedUser, true
|
||||
}
|
||||
|
||||
// CacheWebDAVUser add a user to the WebDAV cache
|
||||
|
|
|
@ -1103,38 +1103,47 @@ func CheckAdminAndPass(username, password, ip string) (Admin, error) {
|
|||
}
|
||||
|
||||
// CheckCachedUserCredentials checks the credentials for a cached user
|
||||
func CheckCachedUserCredentials(user *CachedUser, password, loginMethod, protocol string, tlsCert *x509.Certificate) error {
|
||||
func CheckCachedUserCredentials(user *CachedUser, password, ip, loginMethod, protocol string, tlsCert *x509.Certificate) (*CachedUser, *User, error) {
|
||||
if !user.User.skipExternalAuth() && isExternalAuthConfigured(loginMethod) {
|
||||
u, _, err := CheckCompositeCredentials(user.User.Username, password, ip, loginMethod, protocol, tlsCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
webDAVUsersCache.swap(&u, password)
|
||||
cu, _ := webDAVUsersCache.get(u.Username)
|
||||
return cu, &u, nil
|
||||
}
|
||||
if err := user.User.CheckLoginConditions(); err != nil {
|
||||
return err
|
||||
return user, nil, err
|
||||
}
|
||||
if loginMethod == LoginMethodPassword && user.User.Filters.IsAnonymous {
|
||||
return nil
|
||||
return user, nil, nil
|
||||
}
|
||||
if loginMethod != LoginMethodPassword {
|
||||
_, err := checkUserAndTLSCertificate(&user.User, protocol, tlsCert)
|
||||
if err != nil {
|
||||
return err
|
||||
return user, nil, err
|
||||
}
|
||||
if loginMethod == LoginMethodTLSCertificate {
|
||||
if !user.User.IsLoginMethodAllowed(LoginMethodTLSCertificate, protocol, nil) {
|
||||
return fmt.Errorf("certificate login method is not allowed for user %q", user.User.Username)
|
||||
return user, nil, fmt.Errorf("certificate login method is not allowed for user %q", user.User.Username)
|
||||
}
|
||||
return nil
|
||||
return user, nil, nil
|
||||
}
|
||||
}
|
||||
if password == "" {
|
||||
return ErrInvalidCredentials
|
||||
return user, nil, ErrInvalidCredentials
|
||||
}
|
||||
if user.Password != "" {
|
||||
if password == user.Password {
|
||||
return nil
|
||||
return user, nil, nil
|
||||
}
|
||||
} else {
|
||||
if ok, _ := isPasswordOK(&user.User, password); ok {
|
||||
return nil
|
||||
return user, nil, nil
|
||||
}
|
||||
}
|
||||
return ErrInvalidCredentials
|
||||
return user, nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
// CheckCompositeCredentials checks multiple credentials.
|
||||
|
@ -1689,7 +1698,7 @@ func DeleteRole(name string, executor, ipAddress, executorRole string) error {
|
|||
provider.setUpdatedAt(user)
|
||||
u, err := provider.userExists(user, "")
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
webDAVUsersCache.swap(&u, "")
|
||||
executeAction(operationUpdate, executor, ipAddress, actionObjectUser, u.Username, u.Role, &u)
|
||||
}
|
||||
}
|
||||
|
@ -1721,7 +1730,7 @@ func UpdateGroup(group *Group, users []string, executor, ipAddress, role string)
|
|||
provider.setUpdatedAt(user)
|
||||
u, err := provider.userExists(user, "")
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
webDAVUsersCache.swap(&u, "")
|
||||
} else {
|
||||
RemoveCachedWebDAVUser(user)
|
||||
}
|
||||
|
@ -2073,7 +2082,7 @@ func UpdateUserPassword(username, plainPwd, executor, ipAddress, role string) er
|
|||
if err := provider.updateUser(&user); err != nil {
|
||||
return err
|
||||
}
|
||||
webDAVUsersCache.swap(&user)
|
||||
webDAVUsersCache.swap(&user, plainPwd)
|
||||
executeAction(operationUpdate, executor, ipAddress, actionObjectUser, username, role, &user)
|
||||
return nil
|
||||
}
|
||||
|
@ -2085,7 +2094,7 @@ func UpdateUser(user *User, executor, ipAddress, role string) error {
|
|||
}
|
||||
err := provider.updateUser(user)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(user)
|
||||
webDAVUsersCache.swap(user, "")
|
||||
executeAction(operationUpdate, executor, ipAddress, actionObjectUser, user.Username, role, user)
|
||||
}
|
||||
return err
|
||||
|
@ -2252,7 +2261,7 @@ func UpdateFolder(folder *vfs.BaseVirtualFolder, users []string, groups []string
|
|||
provider.setUpdatedAt(user)
|
||||
u, err := provider.userExists(user, "")
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
webDAVUsersCache.swap(&u, "")
|
||||
executeAction(operationUpdate, executor, ipAddress, actionObjectUser, u.Username, u.Role, &u)
|
||||
} else {
|
||||
RemoveCachedWebDAVUser(user)
|
||||
|
@ -3950,7 +3959,7 @@ func executePreLoginHook(username, loginMethod, ip, protocol string, oidcTokenFi
|
|||
u.Filters.RecoveryCodes = recoveryCodes
|
||||
err = provider.updateUser(&u)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
webDAVUsersCache.swap(&u, "")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -4123,11 +4132,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
|
|||
return user, err
|
||||
}
|
||||
|
||||
if mergedUser.Filters.Hooks.ExternalAuthDisabled {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if mergedUser.isExternalAuthCached() {
|
||||
if mergedUser.skipExternalAuth() {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
|
@ -4184,7 +4189,9 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
|
|||
user.Filters.RecoveryCodes = u.Filters.RecoveryCodes
|
||||
err = provider.updateUser(&user)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&user)
|
||||
if protocol != protocolWebDAV {
|
||||
webDAVUsersCache.swap(&user, password)
|
||||
}
|
||||
cachedUserPasswords.Add(user.Username, password, user.Password)
|
||||
}
|
||||
return user, err
|
||||
|
@ -4206,11 +4213,7 @@ func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
|
|||
return user, err
|
||||
}
|
||||
|
||||
if mergedUser.Filters.Hooks.ExternalAuthDisabled {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if mergedUser.isExternalAuthCached() {
|
||||
if mergedUser.skipExternalAuth() {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
|
@ -4257,7 +4260,9 @@ func doPluginAuth(username, password string, pubKey []byte, ip, protocol string,
|
|||
user.Filters.RecoveryCodes = u.Filters.RecoveryCodes
|
||||
err = provider.updateUser(&user)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&user)
|
||||
if protocol != protocolWebDAV {
|
||||
webDAVUsersCache.swap(&user, password)
|
||||
}
|
||||
cachedUserPasswords.Add(user.Username, password, user.Password)
|
||||
}
|
||||
return user, err
|
||||
|
@ -4313,6 +4318,33 @@ func isLastActivityRecent(lastActivity int64, minDelay time.Duration) bool {
|
|||
return diff < minDelay
|
||||
}
|
||||
|
||||
func isExternalAuthConfigured(loginMethod string) bool {
|
||||
if config.ExternalAuthHook != "" {
|
||||
if config.ExternalAuthScope == 0 {
|
||||
return true
|
||||
}
|
||||
switch loginMethod {
|
||||
case LoginMethodPassword:
|
||||
return config.ExternalAuthScope&1 != 0
|
||||
case LoginMethodTLSCertificate:
|
||||
return config.ExternalAuthScope&8 != 0
|
||||
case LoginMethodTLSCertificateAndPwd:
|
||||
return config.ExternalAuthScope&1 != 0 || config.ExternalAuthScope&8 != 0
|
||||
}
|
||||
}
|
||||
switch loginMethod {
|
||||
case LoginMethodPassword:
|
||||
return plugin.Handler.HasAuthScope(plugin.AuthScopePassword)
|
||||
case LoginMethodTLSCertificate:
|
||||
return plugin.Handler.HasAuthScope(plugin.AuthScopeTLSCertificate)
|
||||
case LoginMethodTLSCertificateAndPwd:
|
||||
return plugin.Handler.HasAuthScope(plugin.AuthScopePassword) ||
|
||||
plugin.Handler.HasAuthScope(plugin.AuthScopeTLSCertificate)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getConfigPath(name, configDir string) string {
|
||||
if !util.IsFileInputValid(name) {
|
||||
return ""
|
||||
|
|
|
@ -130,7 +130,7 @@ func checkUserCache() {
|
|||
cachedUserPasswords.Remove(user.Username)
|
||||
delayedQuotaUpdater.resetUserQuota(user.Username)
|
||||
} else {
|
||||
webDAVUsersCache.swap(&user)
|
||||
webDAVUsersCache.swap(&user, "")
|
||||
}
|
||||
}
|
||||
lastUserCacheUpdate.Store(checkTime)
|
||||
|
|
|
@ -1039,14 +1039,16 @@ func (u *User) CanManageMFA() bool {
|
|||
return len(mfa.GetAvailableTOTPConfigs()) > 0
|
||||
}
|
||||
|
||||
func (u *User) isExternalAuthCached() bool {
|
||||
func (u *User) skipExternalAuth() bool {
|
||||
if u.Filters.Hooks.ExternalAuthDisabled {
|
||||
return true
|
||||
}
|
||||
if u.ID <= 0 {
|
||||
return false
|
||||
}
|
||||
if u.Filters.ExternalAuthCacheTime <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return isLastActivityRecent(u.LastLogin, time.Duration(u.Filters.ExternalAuthCacheTime)*time.Second)
|
||||
}
|
||||
|
||||
|
|
|
@ -1694,3 +1694,10 @@ func TestConfigsFromProvider(t *testing.T) {
|
|||
err = dataprovider.UpdateConfigs(nil, "", "", "")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetCacheExpirationTime(t *testing.T) {
|
||||
c := UsersCacheConfig{}
|
||||
assert.True(t, c.getExpirationTime().IsZero())
|
||||
c.ExpirationTime = 1
|
||||
assert.False(t, c.getExpirationTime().IsZero())
|
||||
}
|
||||
|
|
|
@ -299,8 +299,20 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
|
|||
tlsCert = nil
|
||||
loginMethod = dataprovider.LoginMethodPassword
|
||||
}
|
||||
if err := dataprovider.CheckCachedUserCredentials(cachedUser, password, loginMethod, common.ProtocolWebDAV, tlsCert); err == nil {
|
||||
return cachedUser.User, true, cachedUser.LockSystem, loginMethod, nil
|
||||
cu, u, err := dataprovider.CheckCachedUserCredentials(cachedUser, password, ip, loginMethod, common.ProtocolWebDAV, tlsCert)
|
||||
if err == nil {
|
||||
if cu != nil {
|
||||
return cu.User, true, cu.LockSystem, loginMethod, nil
|
||||
}
|
||||
lockSystem := webdav.NewMemLS()
|
||||
cachedUser = &dataprovider.CachedUser{
|
||||
User: *u,
|
||||
Password: password,
|
||||
LockSystem: lockSystem,
|
||||
Expiration: s.config.Cache.Users.getExpirationTime(),
|
||||
}
|
||||
dataprovider.CacheWebDAVUser(cachedUser)
|
||||
return cachedUser.User, false, cachedUser.LockSystem, loginMethod, nil
|
||||
}
|
||||
updateLoginMetrics(&cachedUser.User, ip, loginMethod, dataprovider.ErrInvalidCredentials)
|
||||
return user, false, nil, loginMethod, dataprovider.ErrInvalidCredentials
|
||||
|
@ -318,9 +330,7 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
|
|||
User: user,
|
||||
Password: password,
|
||||
LockSystem: lockSystem,
|
||||
}
|
||||
if s.config.Cache.Users.ExpirationTime > 0 {
|
||||
cachedUser.Expiration = time.Now().Add(time.Duration(s.config.Cache.Users.ExpirationTime) * time.Minute)
|
||||
Expiration: s.config.Cache.Users.getExpirationTime(),
|
||||
}
|
||||
dataprovider.CacheWebDAVUser(cachedUser)
|
||||
return user, false, lockSystem, loginMethod, nil
|
||||
|
|
|
@ -85,6 +85,13 @@ type UsersCacheConfig struct {
|
|||
MaxSize int `json:"max_size" mapstructure:"max_size"`
|
||||
}
|
||||
|
||||
func (c *UsersCacheConfig) getExpirationTime() time.Time {
|
||||
if c.ExpirationTime > 0 {
|
||||
return time.Now().Add(time.Duration(c.ExpirationTime) * time.Minute)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// MimeCacheConfig defines the cache configuration for mime types
|
||||
type MimeCacheConfig struct {
|
||||
Enabled bool `json:"enabled" mapstructure:"enabled"`
|
||||
|
|
|
@ -1186,7 +1186,7 @@ func TestLoginExternalAuth(t *testing.T) {
|
|||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
providerConf.ExternalAuthHook = extAuthPath
|
||||
providerConf.ExternalAuthScope = 0
|
||||
|
@ -1215,6 +1215,56 @@ func TestLoginExternalAuth(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestExternalAuthPasswordChange(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
}
|
||||
u := getTestUser()
|
||||
err := dataprovider.Close()
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, defaultPassword), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
providerConf.ExternalAuthHook = extAuthPath
|
||||
providerConf.ExternalAuthScope = 0
|
||||
err = dataprovider.Initialize(providerConf, configDir, true)
|
||||
assert.NoError(t, err)
|
||||
client := getWebDavClient(u, false, nil)
|
||||
assert.NoError(t, checkBasicFunc(client))
|
||||
u.Username = defaultUsername + "1"
|
||||
client = getWebDavClient(u, false, nil)
|
||||
assert.Error(t, checkBasicFunc(client))
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, defaultPassword+"1"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
client = getWebDavClient(u, false, nil)
|
||||
assert.Error(t, checkBasicFunc(client))
|
||||
u.Password = defaultPassword + "1"
|
||||
client = getWebDavClient(u, false, nil)
|
||||
assert.NoError(t, checkBasicFunc(client))
|
||||
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, defaultUsername, user.Username)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpdtest.GetUserByUsername(defaultUsername+"1", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
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 TestExternalAuthReturningAnonymousUser(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
|
@ -1228,7 +1278,7 @@ func TestExternalAuthReturningAnonymousUser(t *testing.T) {
|
|||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
providerConf.ExternalAuthHook = extAuthPath
|
||||
providerConf.ExternalAuthScope = 0
|
||||
|
@ -1320,7 +1370,7 @@ func TestExternalAuthAnonymousGroupInheritance(t *testing.T) {
|
|||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
providerConf.ExternalAuthHook = extAuthPath
|
||||
providerConf.ExternalAuthScope = 0
|
||||
|
@ -2880,7 +2930,7 @@ func TestExternatAuthWithClientCert(t *testing.T) {
|
|||
err = config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
providerConf := config.GetProviderConf()
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
|
||||
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, ""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
providerConf.ExternalAuthHook = extAuthPath
|
||||
providerConf.ExternalAuthScope = 0
|
||||
|
@ -3371,11 +3421,15 @@ func getEncryptedFileSize(size int64) (int64, error) {
|
|||
return int64(encSize) + 33, err
|
||||
}
|
||||
|
||||
func getExtAuthScriptContent(user dataprovider.User) []byte {
|
||||
func getExtAuthScriptContent(user dataprovider.User, password string) []byte {
|
||||
extAuthContent := []byte("#!/bin/sh\n\n")
|
||||
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
|
||||
if password != "" {
|
||||
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%s\" -a \"$SFTPGO_AUTHD_PASSWORD\" = \"%s\"; then\n", user.Username, password))...)
|
||||
} else {
|
||||
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%s\"; then\n", user.Username))...)
|
||||
}
|
||||
u, _ := json.Marshal(user)
|
||||
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
|
||||
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%s'\n", string(u)))...)
|
||||
extAuthContent = append(extAuthContent, []byte("else\n")...)
|
||||
extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
|
||||
extAuthContent = append(extAuthContent, []byte("fi\n")...)
|
||||
|
|
Loading…
Reference in a new issue