mirror of https://github.com/Xhofe/alist
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.pull/8491/merge v3.50.0
parent
aea3ba1499
commit
fcfb3369d1
|
@ -145,12 +145,15 @@ func (u *User) CheckPathLimit() bool {
|
|||
}
|
||||
|
||||
func (u *User) JoinPath(reqPath string) (string, error) {
|
||||
if reqPath == "/" {
|
||||
return utils.FixAndCleanPath(u.BasePath), nil
|
||||
}
|
||||
path, err := utils.JoinBasePath(u.BasePath, reqPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if u.CheckPathLimit() {
|
||||
if path != "/" && u.CheckPathLimit() {
|
||||
basePaths := GetAllBasePathsFromRoles(u)
|
||||
match := false
|
||||
for _, base := range basePaths {
|
||||
|
@ -206,12 +209,23 @@ func (u *User) WebAuthnIcon() string {
|
|||
return "https://alistgo.com/logo.svg"
|
||||
}
|
||||
|
||||
// FetchRole is used to load role details by id. It should be set by the op package
|
||||
// to avoid an import cycle between model and op.
|
||||
var FetchRole func(uint) (*Role, error)
|
||||
|
||||
// GetAllBasePathsFromRoles returns all permission paths from user's roles
|
||||
func GetAllBasePathsFromRoles(u *User) []string {
|
||||
basePaths := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, role := range u.RolesDetail {
|
||||
for _, rid := range u.Role {
|
||||
if FetchRole == nil {
|
||||
continue
|
||||
}
|
||||
role, err := FetchRole(uint(rid))
|
||||
if err != nil || role == nil {
|
||||
continue
|
||||
}
|
||||
for _, entry := range role.PermissionScopes {
|
||||
if entry.Path == "" {
|
||||
continue
|
||||
|
|
|
@ -15,6 +15,10 @@ import (
|
|||
var roleCache = cache.NewMemCache[*model.Role](cache.WithShards[*model.Role](2))
|
||||
var roleG singleflight.Group[*model.Role]
|
||||
|
||||
func init() {
|
||||
model.FetchRole = GetRole
|
||||
}
|
||||
|
||||
func GetRole(id uint) (*model.Role, error) {
|
||||
key := fmt.Sprint(id)
|
||||
if r, ok := roleCache.Get(key); ok {
|
||||
|
|
|
@ -43,17 +43,23 @@ func MergeRolePermissions(u *model.User, reqPath string) int32 {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if reqPath == "/" || utils.PathEqual(reqPath, u.BasePath) {
|
||||
for _, entry := range role.PermissionScopes {
|
||||
perm |= entry.Permission
|
||||
}
|
||||
} else {
|
||||
for _, entry := range role.PermissionScopes {
|
||||
if utils.IsSubPath(entry.Path, reqPath) {
|
||||
perm |= entry.Permission
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return perm
|
||||
}
|
||||
|
||||
func CanAccessWithRoles(u *model.User, meta *model.Meta, reqPath, password string) bool {
|
||||
if !canReadPathByRole(u, reqPath) {
|
||||
if !CanReadPathByRole(u, reqPath) {
|
||||
return false
|
||||
}
|
||||
perm := MergeRolePermissions(u, reqPath)
|
||||
|
@ -78,7 +84,30 @@ func CanAccessWithRoles(u *model.User, meta *model.Meta, reqPath, password strin
|
|||
return meta.Password == password
|
||||
}
|
||||
|
||||
func canReadPathByRole(u *model.User, reqPath string) bool {
|
||||
func CanReadPathByRole(u *model.User, reqPath string) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
if reqPath == "/" || utils.PathEqual(reqPath, u.BasePath) {
|
||||
return len(u.Role) > 0
|
||||
}
|
||||
for _, rid := range u.Role {
|
||||
role, err := op.GetRole(uint(rid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, entry := range role.PermissionScopes {
|
||||
if utils.PathEqual(entry.Path, reqPath) || utils.IsSubPath(entry.Path, reqPath) || utils.IsSubPath(reqPath, entry.Path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasChildPermission checks whether any child path under reqPath grants the
|
||||
// specified permission bit.
|
||||
func HasChildPermission(u *model.User, reqPath string, bit uint) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -88,7 +117,7 @@ func canReadPathByRole(u *model.User, reqPath string) bool {
|
|||
continue
|
||||
}
|
||||
for _, entry := range role.PermissionScopes {
|
||||
if utils.IsSubPath(entry.Path, reqPath) {
|
||||
if utils.IsSubPath(reqPath, entry.Path) && HasPermission(entry.Permission, bit) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +131,7 @@ func canReadPathByRole(u *model.User, reqPath string) bool {
|
|||
func CheckPathLimitWithRoles(u *model.User, reqPath string) bool {
|
||||
perm := MergeRolePermissions(u, reqPath)
|
||||
if HasPermission(perm, PermPathLimit) {
|
||||
return canReadPathByRole(u, reqPath)
|
||||
return CanReadPathByRole(u, reqPath)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -107,7 +107,14 @@ func FsList(c *gin.Context) {
|
|||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
total, objs := pagination(objs, &req.PageReq)
|
||||
filtered := make([]model.Obj, 0, len(objs))
|
||||
for _, obj := range objs {
|
||||
childPath := stdpath.Join(reqPath, obj.GetName())
|
||||
if common.CanReadPathByRole(user, childPath) {
|
||||
filtered = append(filtered, obj)
|
||||
}
|
||||
}
|
||||
total, objs := pagination(filtered, &req.PageReq)
|
||||
provider := "unknown"
|
||||
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||
if err == nil {
|
||||
|
@ -161,7 +168,14 @@ func FsDirs(c *gin.Context) {
|
|||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
dirs := filterDirs(objs)
|
||||
visible := make([]model.Obj, 0, len(objs))
|
||||
for _, obj := range objs {
|
||||
childPath := stdpath.Join(reqPath, obj.GetName())
|
||||
if common.CanReadPathByRole(user, childPath) {
|
||||
visible = append(visible, obj)
|
||||
}
|
||||
}
|
||||
dirs := filterDirs(visible)
|
||||
common.SuccessResp(c, dirs)
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,9 @@ func WebDAVAuth(c *gin.Context) {
|
|||
c.Abort()
|
||||
return
|
||||
}
|
||||
if roles, err := op.GetRolesByUserID(user.ID); err == nil {
|
||||
user.RolesDetail = roles
|
||||
}
|
||||
reqPath := c.Param("path")
|
||||
if reqPath == "" {
|
||||
reqPath = "/"
|
||||
|
@ -107,7 +110,8 @@ func WebDAVAuth(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
perm := common.MergeRolePermissions(user, reqPath)
|
||||
if user.Disabled || !common.HasPermission(perm, common.PermWebdavRead) {
|
||||
webdavRead := common.HasPermission(perm, common.PermWebdavRead)
|
||||
if user.Disabled || (!webdavRead && (c.Request.Method != "PROPFIND" || !common.HasChildPermission(user, reqPath, common.PermWebdavRead))) {
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.Set("user", guest)
|
||||
c.Next()
|
||||
|
|
|
@ -94,6 +94,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
|||
depth = 0
|
||||
}
|
||||
meta, _ := op.GetNearestMeta(name)
|
||||
user := ctx.Value("user").(*model.User)
|
||||
// Read directory names.
|
||||
objs, err := fs.List(context.WithValue(ctx, "meta", meta), name, &fs.ListArgs{})
|
||||
//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
|
|||
|
||||
for _, fileInfo := range objs {
|
||||
filename := path.Join(name, fileInfo.GetName())
|
||||
if !common.CanReadPathByRole(user, filename) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
|
|
|
@ -648,6 +648,98 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||
|
||||
mw := multistatusWriter{w: w}
|
||||
|
||||
if utils.PathEqual(reqPath, user.BasePath) {
|
||||
hasRootPerm := false
|
||||
for _, role := range user.RolesDetail {
|
||||
for _, entry := range role.PermissionScopes {
|
||||
if utils.PathEqual(entry.Path, user.BasePath) {
|
||||
hasRootPerm = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasRootPerm {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRootPerm {
|
||||
basePaths := model.GetAllBasePathsFromRoles(user)
|
||||
type infoItem struct {
|
||||
path string
|
||||
info model.Obj
|
||||
}
|
||||
infos := []infoItem{{reqPath, fi}}
|
||||
seen := make(map[string]struct{})
|
||||
for _, p := range basePaths {
|
||||
if !utils.IsSubPath(user.BasePath, p) {
|
||||
continue
|
||||
}
|
||||
rel := strings.TrimPrefix(
|
||||
strings.TrimPrefix(
|
||||
utils.FixAndCleanPath(p),
|
||||
utils.FixAndCleanPath(user.BasePath),
|
||||
),
|
||||
"/",
|
||||
)
|
||||
dir := strings.Split(rel, "/")[0]
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[dir]; ok {
|
||||
continue
|
||||
}
|
||||
seen[dir] = struct{}{}
|
||||
sp := utils.FixAndCleanPath(path.Join(user.BasePath, dir))
|
||||
info, err := fs.Get(ctx, sp, &fs.GetArgs{})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
infos = append(infos, infoItem{sp, info})
|
||||
}
|
||||
for _, item := range infos {
|
||||
var pstats []Propstat
|
||||
if pf.Propname != nil {
|
||||
pnames, err := propnames(ctx, h.LockSystem, item.info)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
pstat := Propstat{Status: http.StatusOK}
|
||||
for _, xmlname := range pnames {
|
||||
pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
|
||||
}
|
||||
pstats = append(pstats, pstat)
|
||||
} else if pf.Allprop != nil {
|
||||
pstats, err = allprop(ctx, h.LockSystem, item.info, pf.Prop)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
} else {
|
||||
pstats, err = props(ctx, h.LockSystem, item.info, pf.Prop)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
rel := strings.TrimPrefix(
|
||||
strings.TrimPrefix(
|
||||
utils.FixAndCleanPath(item.path),
|
||||
utils.FixAndCleanPath(user.BasePath),
|
||||
),
|
||||
"/",
|
||||
)
|
||||
href := utils.EncodePath(path.Join("/", h.Prefix, rel), true)
|
||||
if href != "/" && item.info.IsDir() {
|
||||
href += "/"
|
||||
}
|
||||
if err := mw.write(makePropstatResponse(href, pstats)); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
if err := mw.close(); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
walkFn := func(reqPath string, info model.Obj, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -671,7 +763,14 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
href := path.Join(h.Prefix, strings.TrimPrefix(reqPath, user.BasePath))
|
||||
rel := strings.TrimPrefix(
|
||||
strings.TrimPrefix(
|
||||
utils.FixAndCleanPath(reqPath),
|
||||
utils.FixAndCleanPath(user.BasePath),
|
||||
),
|
||||
"/",
|
||||
)
|
||||
href := utils.EncodePath(path.Join("/", h.Prefix, rel), true)
|
||||
if href != "/" && info.IsDir() {
|
||||
href += "/"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue