Skip to content

Commit fcfb336

Browse files
authored
fix: webdav error location (#9266)
* feat: improve WebDAV permission handling and user role fetching - Added logic to handle root permissions in WebDAV requests. - Improved the user role fetching mechanism. - Enhanced path checks and permission scopes in role_perm.go. - Set FetchRole function to avoid import cycles between modules. * fix(webdav): resolve connection reset issue by encoding paths - Adjust path encoding in webdav.go to prevent connection reset. - Utilize utils.EncodePath for correct path formatting. - Ensure proper handling of directory paths with trailing slash. * fix(webdav): resolve connection reset issue by encoding paths - Adjust path encoding in webdav.go to prevent connection reset. - Utilize utils.FixAndCleanPath for correct path formatting. - Ensure proper handling of directory paths with trailing slash. * fix: resolve webdav handshake error in permission checks - Updated role permission logic to handle bidirectional subpaths. - This adjustment fixes the issue where remote host terminates the handshake due to improper path matching. * fix: resolve webdav handshake error in permission checks (fix/fix-webdav-error) - Updated role permission logic to handle bidirectional subpaths, fixing handshake termination by remote host due to path mismatch. - Refactored function naming for consistency and clarity. - Enhanced filtering of objects based on user permissions. * fix: resolve webdav handshake error in permission checks - Updated role permission logic to handle bidirectional subpaths, fixing handshake termination by remote host due to path mismatch. - Refactored function naming for consistency and clarity. - Enhanced filtering of objects based on user permissions.
1 parent aea3ba1 commit fcfb336

File tree

7 files changed

+180
-12
lines changed

7 files changed

+180
-12
lines changed

internal/model/user.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,15 @@ func (u *User) CheckPathLimit() bool {
145145
}
146146

147147
func (u *User) JoinPath(reqPath string) (string, error) {
148+
if reqPath == "/" {
149+
return utils.FixAndCleanPath(u.BasePath), nil
150+
}
148151
path, err := utils.JoinBasePath(u.BasePath, reqPath)
149152
if err != nil {
150153
return "", err
151154
}
152155

153-
if u.CheckPathLimit() {
156+
if path != "/" && u.CheckPathLimit() {
154157
basePaths := GetAllBasePathsFromRoles(u)
155158
match := false
156159
for _, base := range basePaths {
@@ -206,12 +209,23 @@ func (u *User) WebAuthnIcon() string {
206209
return "https://alistgo.com/logo.svg"
207210
}
208211

212+
// FetchRole is used to load role details by id. It should be set by the op package
213+
// to avoid an import cycle between model and op.
214+
var FetchRole func(uint) (*Role, error)
215+
209216
// GetAllBasePathsFromRoles returns all permission paths from user's roles
210217
func GetAllBasePathsFromRoles(u *User) []string {
211218
basePaths := make([]string, 0)
212219
seen := make(map[string]struct{})
213220

214-
for _, role := range u.RolesDetail {
221+
for _, rid := range u.Role {
222+
if FetchRole == nil {
223+
continue
224+
}
225+
role, err := FetchRole(uint(rid))
226+
if err != nil || role == nil {
227+
continue
228+
}
215229
for _, entry := range role.PermissionScopes {
216230
if entry.Path == "" {
217231
continue

internal/op/role.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import (
1515
var roleCache = cache.NewMemCache[*model.Role](cache.WithShards[*model.Role](2))
1616
var roleG singleflight.Group[*model.Role]
1717

18+
func init() {
19+
model.FetchRole = GetRole
20+
}
21+
1822
func GetRole(id uint) (*model.Role, error) {
1923
key := fmt.Sprint(id)
2024
if r, ok := roleCache.Get(key); ok {

server/common/role_perm.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,23 @@ func MergeRolePermissions(u *model.User, reqPath string) int32 {
4343
if err != nil {
4444
continue
4545
}
46-
for _, entry := range role.PermissionScopes {
47-
if utils.IsSubPath(entry.Path, reqPath) {
46+
if reqPath == "/" || utils.PathEqual(reqPath, u.BasePath) {
47+
for _, entry := range role.PermissionScopes {
4848
perm |= entry.Permission
4949
}
50+
} else {
51+
for _, entry := range role.PermissionScopes {
52+
if utils.IsSubPath(entry.Path, reqPath) {
53+
perm |= entry.Permission
54+
}
55+
}
5056
}
5157
}
5258
return perm
5359
}
5460

5561
func CanAccessWithRoles(u *model.User, meta *model.Meta, reqPath, password string) bool {
56-
if !canReadPathByRole(u, reqPath) {
62+
if !CanReadPathByRole(u, reqPath) {
5763
return false
5864
}
5965
perm := MergeRolePermissions(u, reqPath)
@@ -78,7 +84,30 @@ func CanAccessWithRoles(u *model.User, meta *model.Meta, reqPath, password strin
7884
return meta.Password == password
7985
}
8086

81-
func canReadPathByRole(u *model.User, reqPath string) bool {
87+
func CanReadPathByRole(u *model.User, reqPath string) bool {
88+
if u == nil {
89+
return false
90+
}
91+
if reqPath == "/" || utils.PathEqual(reqPath, u.BasePath) {
92+
return len(u.Role) > 0
93+
}
94+
for _, rid := range u.Role {
95+
role, err := op.GetRole(uint(rid))
96+
if err != nil {
97+
continue
98+
}
99+
for _, entry := range role.PermissionScopes {
100+
if utils.PathEqual(entry.Path, reqPath) || utils.IsSubPath(entry.Path, reqPath) || utils.IsSubPath(reqPath, entry.Path) {
101+
return true
102+
}
103+
}
104+
}
105+
return false
106+
}
107+
108+
// HasChildPermission checks whether any child path under reqPath grants the
109+
// specified permission bit.
110+
func HasChildPermission(u *model.User, reqPath string, bit uint) bool {
82111
if u == nil {
83112
return false
84113
}
@@ -88,7 +117,7 @@ func canReadPathByRole(u *model.User, reqPath string) bool {
88117
continue
89118
}
90119
for _, entry := range role.PermissionScopes {
91-
if utils.IsSubPath(entry.Path, reqPath) {
120+
if utils.IsSubPath(reqPath, entry.Path) && HasPermission(entry.Permission, bit) {
92121
return true
93122
}
94123
}
@@ -102,7 +131,7 @@ func canReadPathByRole(u *model.User, reqPath string) bool {
102131
func CheckPathLimitWithRoles(u *model.User, reqPath string) bool {
103132
perm := MergeRolePermissions(u, reqPath)
104133
if HasPermission(perm, PermPathLimit) {
105-
return canReadPathByRole(u, reqPath)
134+
return CanReadPathByRole(u, reqPath)
106135
}
107136
return true
108137
}

server/handles/fsread.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,14 @@ func FsList(c *gin.Context) {
107107
common.ErrorResp(c, err, 500)
108108
return
109109
}
110-
total, objs := pagination(objs, &req.PageReq)
110+
filtered := make([]model.Obj, 0, len(objs))
111+
for _, obj := range objs {
112+
childPath := stdpath.Join(reqPath, obj.GetName())
113+
if common.CanReadPathByRole(user, childPath) {
114+
filtered = append(filtered, obj)
115+
}
116+
}
117+
total, objs := pagination(filtered, &req.PageReq)
111118
provider := "unknown"
112119
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
113120
if err == nil {
@@ -161,7 +168,14 @@ func FsDirs(c *gin.Context) {
161168
common.ErrorResp(c, err, 500)
162169
return
163170
}
164-
dirs := filterDirs(objs)
171+
visible := make([]model.Obj, 0, len(objs))
172+
for _, obj := range objs {
173+
childPath := stdpath.Join(reqPath, obj.GetName())
174+
if common.CanReadPathByRole(user, childPath) {
175+
visible = append(visible, obj)
176+
}
177+
}
178+
dirs := filterDirs(visible)
165179
common.SuccessResp(c, dirs)
166180
}
167181

server/webdav.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ func WebDAVAuth(c *gin.Context) {
9595
c.Abort()
9696
return
9797
}
98+
if roles, err := op.GetRolesByUserID(user.ID); err == nil {
99+
user.RolesDetail = roles
100+
}
98101
reqPath := c.Param("path")
99102
if reqPath == "" {
100103
reqPath = "/"
@@ -107,7 +110,8 @@ func WebDAVAuth(c *gin.Context) {
107110
return
108111
}
109112
perm := common.MergeRolePermissions(user, reqPath)
110-
if user.Disabled || !common.HasPermission(perm, common.PermWebdavRead) {
113+
webdavRead := common.HasPermission(perm, common.PermWebdavRead)
114+
if user.Disabled || (!webdavRead && (c.Request.Method != "PROPFIND" || !common.HasChildPermission(user, reqPath, common.PermWebdavRead))) {
111115
if c.Request.Method == "OPTIONS" {
112116
c.Set("user", guest)
113117
c.Next()

server/webdav/file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
9494
depth = 0
9595
}
9696
meta, _ := op.GetNearestMeta(name)
97+
user := ctx.Value("user").(*model.User)
9798
// Read directory names.
9899
objs, err := fs.List(context.WithValue(ctx, "meta", meta), name, &fs.ListArgs{})
99100
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
@@ -108,6 +109,9 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
108109

109110
for _, fileInfo := range objs {
110111
filename := path.Join(name, fileInfo.GetName())
112+
if !common.CanReadPathByRole(user, filename) {
113+
continue
114+
}
111115
if err != nil {
112116
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
113117
return err

server/webdav/webdav.go

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,98 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
648648

649649
mw := multistatusWriter{w: w}
650650

651+
if utils.PathEqual(reqPath, user.BasePath) {
652+
hasRootPerm := false
653+
for _, role := range user.RolesDetail {
654+
for _, entry := range role.PermissionScopes {
655+
if utils.PathEqual(entry.Path, user.BasePath) {
656+
hasRootPerm = true
657+
break
658+
}
659+
}
660+
if hasRootPerm {
661+
break
662+
}
663+
}
664+
if !hasRootPerm {
665+
basePaths := model.GetAllBasePathsFromRoles(user)
666+
type infoItem struct {
667+
path string
668+
info model.Obj
669+
}
670+
infos := []infoItem{{reqPath, fi}}
671+
seen := make(map[string]struct{})
672+
for _, p := range basePaths {
673+
if !utils.IsSubPath(user.BasePath, p) {
674+
continue
675+
}
676+
rel := strings.TrimPrefix(
677+
strings.TrimPrefix(
678+
utils.FixAndCleanPath(p),
679+
utils.FixAndCleanPath(user.BasePath),
680+
),
681+
"/",
682+
)
683+
dir := strings.Split(rel, "/")[0]
684+
if dir == "" {
685+
continue
686+
}
687+
if _, ok := seen[dir]; ok {
688+
continue
689+
}
690+
seen[dir] = struct{}{}
691+
sp := utils.FixAndCleanPath(path.Join(user.BasePath, dir))
692+
info, err := fs.Get(ctx, sp, &fs.GetArgs{})
693+
if err != nil {
694+
continue
695+
}
696+
infos = append(infos, infoItem{sp, info})
697+
}
698+
for _, item := range infos {
699+
var pstats []Propstat
700+
if pf.Propname != nil {
701+
pnames, err := propnames(ctx, h.LockSystem, item.info)
702+
if err != nil {
703+
return http.StatusInternalServerError, err
704+
}
705+
pstat := Propstat{Status: http.StatusOK}
706+
for _, xmlname := range pnames {
707+
pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
708+
}
709+
pstats = append(pstats, pstat)
710+
} else if pf.Allprop != nil {
711+
pstats, err = allprop(ctx, h.LockSystem, item.info, pf.Prop)
712+
if err != nil {
713+
return http.StatusInternalServerError, err
714+
}
715+
} else {
716+
pstats, err = props(ctx, h.LockSystem, item.info, pf.Prop)
717+
if err != nil {
718+
return http.StatusInternalServerError, err
719+
}
720+
}
721+
rel := strings.TrimPrefix(
722+
strings.TrimPrefix(
723+
utils.FixAndCleanPath(item.path),
724+
utils.FixAndCleanPath(user.BasePath),
725+
),
726+
"/",
727+
)
728+
href := utils.EncodePath(path.Join("/", h.Prefix, rel), true)
729+
if href != "/" && item.info.IsDir() {
730+
href += "/"
731+
}
732+
if err := mw.write(makePropstatResponse(href, pstats)); err != nil {
733+
return http.StatusInternalServerError, err
734+
}
735+
}
736+
if err := mw.close(); err != nil {
737+
return http.StatusInternalServerError, err
738+
}
739+
return 0, nil
740+
}
741+
}
742+
651743
walkFn := func(reqPath string, info model.Obj, err error) error {
652744
if err != nil {
653745
return err
@@ -671,7 +763,14 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
671763
if err != nil {
672764
return err
673765
}
674-
href := path.Join(h.Prefix, strings.TrimPrefix(reqPath, user.BasePath))
766+
rel := strings.TrimPrefix(
767+
strings.TrimPrefix(
768+
utils.FixAndCleanPath(reqPath),
769+
utils.FixAndCleanPath(user.BasePath),
770+
),
771+
"/",
772+
)
773+
href := utils.EncodePath(path.Join("/", h.Prefix, rel), true)
675774
if href != "/" && info.IsDir() {
676775
href += "/"
677776
}

0 commit comments

Comments
 (0)