From ae83a3b337d5978baa7dca6b7663b733d9c1d98c Mon Sep 17 00:00:00 2001 From: ShenLin <773933146@qq.com> Date: Thu, 9 Jul 2026 12:28:51 +0800 Subject: [PATCH 1/7] Merge commit from fork * fix: replace strings.HasPrefix with utils.IsSubPath for path validation Signed-off-by: MadDogOwner * fix: re-validate shared paths to ensure they remain within the creator's base path Signed-off-by: MadDogOwner --------- Signed-off-by: MadDogOwner Co-authored-by: MadDogOwner --- internal/op/sharing.go | 10 ++++++++++ server/handles/search.go | 3 +-- server/handles/sharing.go | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/op/sharing.go b/internal/op/sharing.go index 5be0f8e6ac..234ca415c2 100644 --- a/internal/op/sharing.go +++ b/internal/op/sharing.go @@ -91,6 +91,16 @@ func GetSharingUnwrapPath(sharing *model.Sharing, path string) (unwrapPath strin if len(sharing.Files) == 0 { return "", errors.New("cannot get actual path of an invalid sharing") } + // Re-validate that the shared paths are still within the creator's current + // BasePath. This prevents access to files that fell out-of-scope after the + // creator's BasePath was changed by an admin. + if sharing.Creator != nil && !sharing.Creator.IsAdmin() { + for _, f := range sharing.Files { + if !utils.IsSubPath(sharing.Creator.BasePath, f) { + return "", errors.Errorf("sharing path [%s] is outside the creator's base path", f) + } + } + } if len(sharing.Files) == 1 { return stdpath.Join(sharing.Files[0], path), nil } diff --git a/server/handles/search.go b/server/handles/search.go index 32e4e95587..40b005ac13 100644 --- a/server/handles/search.go +++ b/server/handles/search.go @@ -2,7 +2,6 @@ package handles import ( "path" - "strings" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/errs" @@ -51,7 +50,7 @@ func Search(c *gin.Context) { } var filteredNodes []model.SearchNode for _, node := range nodes { - if !strings.HasPrefix(node.Parent, user.BasePath) { + if !utils.IsSubPath(user.BasePath, node.Parent) { continue } meta, err := op.GetNearestMeta(node.Parent) diff --git a/server/handles/sharing.go b/server/handles/sharing.go index 6090fcbd9e..ded8a20df3 100644 --- a/server/handles/sharing.go +++ b/server/handles/sharing.go @@ -461,7 +461,7 @@ func UpdateSharing(c *gin.Context) { for i, s := range req.Files { s = utils.FixAndCleanPath(s) req.Files[i] = s - if !reqUser.IsAdmin() && !strings.HasPrefix(s, user.BasePath) { + if !reqUser.IsAdmin() && !utils.IsSubPath(user.BasePath, s) { common.ErrorStrResp(c, fmt.Sprintf("permission denied to share path [%s]", s), 500) return } @@ -545,7 +545,7 @@ func CreateSharing(c *gin.Context) { for i, s := range req.Files { s = utils.FixAndCleanPath(s) req.Files[i] = s - if !reqUser.IsAdmin() && !strings.HasPrefix(s, user.BasePath) { + if !reqUser.IsAdmin() && !utils.IsSubPath(user.BasePath, s) { common.ErrorStrResp(c, fmt.Sprintf("permission denied to share path [%s]", s), 500) return } From 532fbdb5d285d0b4a0e18bbf0624dd7ac62280b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:48:09 +0800 Subject: [PATCH 2/7] fix(deps): update module gorm.io/driver/sqlite to v1.6.0 (#2757) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 489964584e..12c68dae09 100644 --- a/go.mod +++ b/go.mod @@ -85,8 +85,8 @@ require ( gopkg.in/ldap.v3 v3.1.0 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.9 - gorm.io/driver/sqlite v1.5.6 - gorm.io/gorm v1.25.11 + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.30.0 ) require ( diff --git a/go.sum b/go.sum index 2cdada87d0..a4bff9712c 100644 --- a/go.sum +++ b/go.sum @@ -868,9 +868,13 @@ gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= From ed4f8cf33df8d4972cf2a6e0e15b6849eccc02ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:00:50 +0800 Subject: [PATCH 3/7] fix(deps): update module gorm.io/driver/postgres to v1.6.0 (#2756) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 12c68dae09..3133a47fff 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( google.golang.org/appengine v1.6.8 gopkg.in/ldap.v3 v3.1.0 gorm.io/driver/mysql v1.5.7 - gorm.io/driver/postgres v1.5.9 + gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.30.0 ) @@ -165,7 +165,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hekmon/cunits/v2 v2.1.0 // indirect github.com/ipfs/boxo v0.12.0 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/matoous/go-nanoid/v2 v2.1.0 // indirect github.com/microcosm-cc/bluemonday v1.0.27 @@ -234,8 +234,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/go-cid v0.5.0 github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index a4bff9712c..d0ae278bc9 100644 --- a/go.sum +++ b/go.sum @@ -409,10 +409,16 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -866,6 +872,8 @@ gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= From d1bdd746c05ff662fd1e6035dea5072ced788a56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:06:13 +0800 Subject: [PATCH 4/7] fix(deps): update module google.golang.org/grpc to v1.82.0 (#2754) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3133a47fff..f74b7bc13d 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( golang.org/x/crypto v0.50.0 golang.org/x/image v0.29.0 golang.org/x/net v0.53.0 - golang.org/x/oauth2 v0.34.0 + golang.org/x/oauth2 v0.36.0 golang.org/x/time v0.14.0 google.golang.org/appengine v1.6.8 gopkg.in/ldap.v3 v3.1.0 @@ -304,8 +304,8 @@ require ( golang.org/x/term v0.42.0 // indirect golang.org/x/text v0.37.0 golang.org/x/tools v0.44.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect - google.golang.org/grpc v1.78.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.82.0 google.golang.org/protobuf v1.36.11 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index d0ae278bc9..f3bfa9ebb8 100644 --- a/go.sum +++ b/go.sum @@ -761,6 +761,8 @@ golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -842,8 +844,12 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU= +google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 1252c5fe3d73beaae7ce42bf7f524524105ed1bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:16:26 +0800 Subject: [PATCH 5/7] fix(deps): update module github.com/maruel/natural to v1.3.0 (#2739) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f74b7bc13d..3924c566c3 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3 github.com/json-iterator/go v1.1.12 github.com/kdomanski/iso9660 v0.4.0 - github.com/maruel/natural v1.1.1 + github.com/maruel/natural v1.3.0 github.com/meilisearch/meilisearch-go v0.32.0 github.com/mholt/archives v0.1.5 github.com/natefinch/lumberjack v2.0.0+incompatible diff --git a/go.sum b/go.sum index f3bfa9ebb8..fad75d1df0 100644 --- a/go.sum +++ b/go.sum @@ -487,6 +487,8 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg= +github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= From 8adaab1902a7a7cbcb38dcbb5ab34382a9276eeb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:52:29 +0800 Subject: [PATCH 6/7] fix(deps): update module github.com/quic-go/quic-go to v0.60.0 (#2743) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 3924c566c3..4c5652046d 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 - github.com/quic-go/quic-go v0.59.0 + github.com/quic-go/quic-go v0.60.0 github.com/rclone/rclone v1.70.3 github.com/shirou/gopsutil/v4 v4.25.5 github.com/sirupsen/logrus v1.9.4 @@ -76,9 +76,9 @@ require ( github.com/upyun/go-sdk/v3 v3.0.4 github.com/winfsp/cgofuse v1.6.0 github.com/zzzhr1990/go-common-entity v0.0.0-20250202070650-1a200048f0d3 - golang.org/x/crypto v0.50.0 + golang.org/x/crypto v0.51.0 golang.org/x/image v0.29.0 - golang.org/x/net v0.53.0 + golang.org/x/net v0.55.0 golang.org/x/oauth2 v0.36.0 golang.org/x/time v0.14.0 google.golang.org/appengine v1.6.8 @@ -300,8 +300,8 @@ require ( go.etcd.io/bbolt v1.4.0 // indirect golang.org/x/arch v0.23.0 // indirect golang.org/x/sync v0.20.0 - golang.org/x/sys v0.43.0 - golang.org/x/term v0.42.0 // indirect + golang.org/x/sys v0.45.0 + golang.org/x/term v0.43.0 // indirect golang.org/x/text v0.37.0 golang.org/x/tools v0.44.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect diff --git a/go.sum b/go.sum index fad75d1df0..0c561aac38 100644 --- a/go.sum +++ b/go.sum @@ -595,6 +595,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0= +github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk= github.com/rclone/rclone v1.70.3 h1:rg/WNh4DmSVZyKP2tHZ4lAaWEyMi7h/F0r7smOMA3IE= github.com/rclone/rclone v1.70.3/go.mod h1:nLyN+hpxAsQn9Rgt5kM774lcRDad82x/KqQeBZ83cMo= github.com/relvacode/iso8601 v1.6.0 h1:eFXUhMJN3Gz8Rcq82f9DTMW0svjtAVuIEULglM7QHTU= @@ -735,6 +737,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -761,6 +765,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= @@ -797,6 +803,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -809,6 +817,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From cc16024c431c1293a68fac74630c847ed1d84719 Mon Sep 17 00:00:00 2001 From: qiuxiuya Date: Fri, 10 Jul 2026 10:17:28 +0800 Subject: [PATCH 7/7] feat: add configurable sign-in attempts limit and IP allow/deny lists --- internal/bootstrap/data/setting.go | 6 ++ internal/conf/const.go | 6 ++ internal/model/user.go | 91 ++++++++++++++++++++++++++++-- server/ftp.go | 28 ++++++--- server/handles/auth.go | 37 +++++++++--- server/handles/ldap_login.go | 32 ++++++++--- server/sftp.go | 27 ++++++--- server/webdav.go | 27 +++++++-- 8 files changed, 214 insertions(+), 40 deletions(-) diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index 4526aa5234..8e5c3b363e 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -249,6 +249,12 @@ func InitialSettings() []model.SettingItem { {Key: conf.StreamMaxClientUploadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE}, {Key: conf.StreamMaxServerDownloadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE}, {Key: conf.StreamMaxServerUploadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE}, + + // login lock settings + {Key: conf.LoginLockDuration, Value: "5", Type: conf.TypeNumber, Group: model.SITE, Flag: model.PRIVATE}, + {Key: conf.LoginMaxRetries, Value: "5", Type: conf.TypeNumber, Group: model.SITE, Flag: model.PRIVATE}, + {Key: conf.LoginIPWhitelist, Value: "", Type: conf.TypeText, Group: model.SITE, Flag: model.PRIVATE}, + {Key: conf.LoginIPBlacklist, Value: "", Type: conf.TypeText, Group: model.SITE, Flag: model.PRIVATE}, } additionalSettingItems := tool.Tools.Items() // 固定顺序 diff --git a/internal/conf/const.go b/internal/conf/const.go index b99d8849cb..814cf6059d 100644 --- a/internal/conf/const.go +++ b/internal/conf/const.go @@ -161,6 +161,12 @@ const ( StreamMaxClientUploadSpeed = "max_client_upload_speed" StreamMaxServerDownloadSpeed = "max_server_download_speed" StreamMaxServerUploadSpeed = "max_server_upload_speed" + + // login lock + LoginLockDuration = "login_lock_duration" + LoginMaxRetries = "login_max_retries" + LoginIPWhitelist = "login_ip_whitelist" + LoginIPBlacklist = "login_ip_blacklist" ) const ( diff --git a/internal/model/user.go b/internal/model/user.go index 2dacd752eb..15613d6818 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -4,6 +4,8 @@ import ( "encoding/binary" "encoding/json" "fmt" + "net" + "strings" "time" "github.com/OpenListTeam/OpenList/v4/internal/errs" @@ -32,10 +34,91 @@ const ( var LoginCache = cache.NewMemCache[int]() -var ( - DefaultLockDuration = time.Minute * 5 - DefaultMaxAuthRetries = 5 -) +const DefaultLockDurationMinutes = 5 +const DefaultMaxAuthRetries = 5 + +// CheckLoginLocked checks if the IP has exceeded the max retry limit. +// If locked, it refreshes the lock expiry and returns true. +// maxRetries <= 0 means the lock feature is disabled. +func CheckLoginLocked(ip string, maxRetries int, lockDuration time.Duration) bool { + if maxRetries <= 0 { + return false + } + count, ok := LoginCache.Get(ip) + if ok && count >= maxRetries { + LoginCache.Expire(ip, lockDuration) + return true + } + return false +} + +// RecordLoginAttempt records a failed login attempt for the IP and returns the new count. +func RecordLoginAttempt(ip string) int { + count, _ := LoginCache.Get(ip) + LoginCache.Set(ip, count+1) + return count + 1 +} + +// ClearLoginAttempts clears the login attempt counter for the given IP. +func ClearLoginAttempts(ip string) { + LoginCache.Del(ip) +} + +// IsIPWhitelisted returns true if the given IP matches any entry in the whitelist. +// The whitelist supports single IPs and CIDR notation (e.g., 192.168.1.0/24). +func IsIPWhitelisted(ip string, whitelist []string) bool { + return matchIPList(ip, whitelist) +} + +// IsIPBlacklisted returns true if the given IP matches any entry in the blacklist. +// The blacklist supports single IPs and CIDR notation (e.g., 10.0.0.0/8). +func IsIPBlacklisted(ip string, blacklist []string) bool { + return matchIPList(ip, blacklist) +} + +// matchIPList checks if the given IP matches any entry in the list. +func matchIPList(ip string, list []string) bool { + if len(list) == 0 { + return false + } + parsedIP := net.ParseIP(strings.TrimSpace(ip)) + if parsedIP == nil { + return false + } + for _, entry := range list { + entry = strings.TrimSpace(entry) + if entry == "" { + continue + } + if strings.Contains(entry, "/") { + _, cidr, err := net.ParseCIDR(entry) + if err == nil && cidr.Contains(parsedIP) { + return true + } + } else { + if entry == ip { + return true + } + } + } + return false +} + +// ParseIPList parses a newline-separated string into a slice of IP/CIDR entries. +func ParseIPList(raw string) []string { + if raw == "" { + return nil + } + lines := strings.Split(raw, "\n") + var result []string + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + result = append(result, line) + } + } + return result +} type User struct { ID uint `json:"id" gorm:"primaryKey"` // unique key diff --git a/server/ftp.go b/server/ftp.go index 8999c1f544..9f8f1b03d9 100644 --- a/server/ftp.go +++ b/server/ftp.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/conf" @@ -114,11 +115,20 @@ func (d *FtpMainDriver) ClientDisconnected(cc ftpserver.ClientContext) { func (d *FtpMainDriver) AuthUser(cc ftpserver.ClientContext, user, pass string) (ftpserver.ClientDriver, error) { ip := cc.RemoteAddr().String() - count, ok := model.LoginCache.Get(ip) - if ok && count >= model.DefaultMaxAuthRetries { - model.LoginCache.Expire(ip, model.DefaultLockDuration) - return nil, errors.New("Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.") + maxRetries := setting.GetInt(conf.LoginMaxRetries, model.DefaultMaxAuthRetries) + lockDuration := time.Duration(setting.GetInt(conf.LoginLockDuration, model.DefaultLockDurationMinutes)) * time.Minute + + // check IP blacklist + if model.IsIPBlacklisted(ip, model.ParseIPList(setting.GetStr(conf.LoginIPBlacklist))) { + return nil, errors.New(model.TooManyAttempts) + } + + // check IP whitelist (bypasses lock) + whitelist := model.ParseIPList(setting.GetStr(conf.LoginIPWhitelist)) + if !model.IsIPWhitelisted(ip, whitelist) && model.CheckLoginLocked(ip, maxRetries, lockDuration) { + return nil, errors.New(model.TooManyAttempts) } + var userObj *model.User var err error if user == "anonymous" || user == "guest" { @@ -137,15 +147,19 @@ func (d *FtpMainDriver) AuthUser(cc ftpserver.ClientContext, user, pass string) userObj, err = tryLdapLoginAndRegister(user, pass) } if err != nil { - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return nil, err } } if userObj.Disabled || !userObj.CanFTPAccess() { - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return nil, errors.New("user is not allowed to access via FTP") } - model.LoginCache.Del(ip) + model.ClearLoginAttempts(ip) ctx := context.Background() ctx = context.WithValue(ctx, conf.UserKey, userObj) diff --git a/server/handles/auth.go b/server/handles/auth.go index 7800690918..943eaab2ab 100644 --- a/server/handles/auth.go +++ b/server/handles/auth.go @@ -4,10 +4,12 @@ import ( "bytes" "encoding/base64" "image/png" + "time" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/internal/setting" "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" @@ -41,25 +43,42 @@ func LoginHash(c *gin.Context) { } func loginHash(c *gin.Context, req *LoginReq) { - // check count of login + // check login lock ip := c.ClientIP() - count, ok := model.LoginCache.Get(ip) - if ok && count >= model.DefaultMaxAuthRetries { + maxRetries := setting.GetInt(conf.LoginMaxRetries, model.DefaultMaxAuthRetries) + lockDuration := time.Duration(setting.GetInt(conf.LoginLockDuration, model.DefaultLockDurationMinutes)) * time.Minute + + // check IP blacklist + blacklist := model.ParseIPList(setting.GetStr(conf.LoginIPBlacklist)) + if model.IsIPBlacklisted(ip, blacklist) { + common.ErrorStrResp(c, model.TooManyAttempts, 429) + return + } + + // check IP whitelist (bypasses lock) + whitelist := model.ParseIPList(setting.GetStr(conf.LoginIPWhitelist)) + if model.IsIPWhitelisted(ip, whitelist) { + // whitelisted IP, skip lock check + } else if model.CheckLoginLocked(ip, maxRetries, lockDuration) { common.ErrorStrResp(c, model.TooManyAttempts, 429) - model.LoginCache.Expire(ip, model.DefaultLockDuration) return } + // check username user, err := op.GetUserByName(req.Username) if err != nil { common.ErrorStrResp(c, model.InvalidUsernameOrPassword, 401) - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return } // validate password hash if err := user.ValidatePwdStaticHash(req.Password); err != nil { common.ErrorStrResp(c, model.InvalidUsernameOrPassword, 401) - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return } // check 2FA @@ -67,7 +86,9 @@ func loginHash(c *gin.Context, req *LoginReq) { if !totp.Validate(req.OtpCode, user.OtpSecret) { // 402 - need opt common.ErrorStrResp(c, model.Invalid2FACode, 402) - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return } } @@ -78,7 +99,7 @@ func loginHash(c *gin.Context, req *LoginReq) { return } common.SuccessResp(c, gin.H{"token": token}) - model.LoginCache.Del(ip) + model.ClearLoginAttempts(ip) } type UserResp struct { diff --git a/server/handles/ldap_login.go b/server/handles/ldap_login.go index ba44615f7d..6e680fba56 100644 --- a/server/handles/ldap_login.go +++ b/server/handles/ldap_login.go @@ -1,6 +1,8 @@ package handles import ( + "time" + "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/internal/op" @@ -27,19 +29,31 @@ func LoginLdap(c *gin.Context) { return } - // check count of login + // check login lock ip := c.ClientIP() - count, ok := model.LoginCache.Get(ip) - if ok && count >= model.DefaultMaxAuthRetries { - common.ErrorStrResp(c, "Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.", 429) - model.LoginCache.Expire(ip, model.DefaultLockDuration) + maxRetries := setting.GetInt(conf.LoginMaxRetries, model.DefaultMaxAuthRetries) + lockDuration := time.Duration(setting.GetInt(conf.LoginLockDuration, model.DefaultLockDurationMinutes)) * time.Minute + + // check IP blacklist + blacklist := model.ParseIPList(setting.GetStr(conf.LoginIPBlacklist)) + if model.IsIPBlacklisted(ip, blacklist) { + common.ErrorStrResp(c, model.TooManyAttempts, 429) + return + } + + // check IP whitelist (bypasses lock) + whitelist := model.ParseIPList(setting.GetStr(conf.LoginIPWhitelist)) + if !model.IsIPWhitelisted(ip, whitelist) && model.CheckLoginLocked(ip, maxRetries, lockDuration) { + common.ErrorStrResp(c, model.TooManyAttempts, 429) return } err = common.HandleLdapLogin(req.Username, req.Password) if err != nil { if errors.Is(err, common.ErrFailedLdapAuth) { - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } common.ErrorResp(c, err, 400) } else { common.ErrorResp(c, err, 500) @@ -51,7 +65,9 @@ func LoginLdap(c *gin.Context) { user, err = common.LdapRegister(req.Username) if err != nil { common.ErrorResp(c, err, 400) - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return } } @@ -63,5 +79,5 @@ func LoginLdap(c *gin.Context) { return } common.SuccessResp(c, gin.H{"token": token}) - model.LoginCache.Del(ip) + model.ClearLoginAttempts(ip) } diff --git a/server/sftp.go b/server/sftp.go index 37dc9870db..6141385d47 100644 --- a/server/sftp.go +++ b/server/sftp.go @@ -94,11 +94,20 @@ func (d *SftpDriver) NoClientAuth(conn ssh.ConnMetadata) (*ssh.Permissions, erro func (d *SftpDriver) PasswordAuth(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { ip := conn.RemoteAddr().String() - count, ok := model.LoginCache.Get(ip) - if ok && count >= model.DefaultMaxAuthRetries { - model.LoginCache.Expire(ip, model.DefaultLockDuration) - return nil, errors.New("Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.") + maxRetries := setting.GetInt(conf.LoginMaxRetries, model.DefaultMaxAuthRetries) + lockDuration := time.Duration(setting.GetInt(conf.LoginLockDuration, model.DefaultLockDurationMinutes)) * time.Minute + + // check IP blacklist + if model.IsIPBlacklisted(ip, model.ParseIPList(setting.GetStr(conf.LoginIPBlacklist))) { + return nil, errors.New(model.TooManyAttempts) + } + + // check IP whitelist (bypasses lock) + whitelist := model.ParseIPList(setting.GetStr(conf.LoginIPWhitelist)) + if !model.IsIPWhitelisted(ip, whitelist) && model.CheckLoginLocked(ip, maxRetries, lockDuration) { + return nil, errors.New(model.TooManyAttempts) } + pass := string(password) userObj, err := op.GetUserByName(conn.User()) if err == nil { @@ -110,14 +119,18 @@ func (d *SftpDriver) PasswordAuth(conn ssh.ConnMetadata, password []byte) (*ssh. userObj, err = tryLdapLoginAndRegister(conn.User(), pass) } if err != nil { - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return nil, err } if userObj.Disabled || !userObj.CanFTPAccess() { - model.LoginCache.Set(ip, count+1) + if !model.IsIPWhitelisted(ip, whitelist) { + model.RecordLoginAttempt(ip) + } return nil, errors.New("user is not allowed to access via SFTP") } - model.LoginCache.Del(ip) + model.ClearLoginAttempts(ip) return nil, nil } diff --git a/server/webdav.go b/server/webdav.go index 74523b0b30..06d18394c4 100644 --- a/server/webdav.go +++ b/server/webdav.go @@ -5,6 +5,7 @@ import ( "net/http" "path" "strings" + "time" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/model" @@ -48,11 +49,24 @@ func ServeWebDAV(c *gin.Context) { } func WebDAVAuth(c *gin.Context) { - // check count of login + // check login lock ip := c.ClientIP() guest, _ := op.GetGuest() - count, cok := model.LoginCache.Get(ip) - if cok && count >= model.DefaultMaxAuthRetries { + maxRetries := setting.GetInt(conf.LoginMaxRetries, model.DefaultMaxAuthRetries) + lockDuration := time.Duration(setting.GetInt(conf.LoginLockDuration, model.DefaultLockDurationMinutes)) * time.Minute + + // check IP blacklist + if model.IsIPBlacklisted(ip, model.ParseIPList(setting.GetStr(conf.LoginIPBlacklist))) { + c.Status(http.StatusTooManyRequests) + c.Abort() + return + } + + // check IP whitelist (bypasses lock) + whitelist := model.ParseIPList(setting.GetStr(conf.LoginIPWhitelist)) + isWhitelisted := model.IsIPWhitelisted(ip, whitelist) + + if !isWhitelisted && model.CheckLoginLocked(ip, maxRetries, lockDuration) { if c.Request.Method == "OPTIONS" { common.GinAppendValues(c, conf.UserKey, guest) c.Next() @@ -60,7 +74,6 @@ func WebDAVAuth(c *gin.Context) { } c.Status(http.StatusTooManyRequests) c.Abort() - model.LoginCache.Expire(ip, model.DefaultLockDuration) return } username, password, ok := c.Request.BasicAuth() @@ -100,13 +113,15 @@ func WebDAVAuth(c *gin.Context) { c.Next() return } - model.LoginCache.Set(ip, count+1) + if !isWhitelisted { + model.RecordLoginAttempt(ip) + } c.Status(http.StatusUnauthorized) c.Abort() return } // at least auth is successful till here - model.LoginCache.Del(ip) + model.ClearLoginAttempts(ip) if user.Disabled || !user.CanWebdavRead() { if c.Request.Method == "OPTIONS" { common.GinAppendValues(c, conf.UserKey, guest)