From facdba7f0bc22f0e2c6fa532a2324e4387ff9d45 Mon Sep 17 00:00:00 2001 From: Grendgi Date: Tue, 16 Jun 2026 13:38:17 +0300 Subject: [PATCH] fix: validate parent folder access --- internal/handler/node.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/internal/handler/node.go b/internal/handler/node.go index 5cd2c7c..3b583bb 100644 --- a/internal/handler/node.go +++ b/internal/handler/node.go @@ -61,6 +61,9 @@ func (h *NodeHandler) CreateFolder(w http.ResponseWriter, r *http.Request) { return } userID := commonmw.GetUserID(r.Context()) + if !h.requireWritableParent(w, r, userID, req.ParentID) { + return + } node, err := h.repo.CreateFolder(r.Context(), req.Title, req.ParentID, userID) if err != nil { writeInternalError(w, r, err, "failed to create folder") @@ -97,13 +100,16 @@ func (h *NodeHandler) UploadFile(w http.ResponseWriter, r *http.Request) { title = strings.TrimSuffix(filename, filepath.Ext(filename)) } userID := commonmw.GetUserID(r.Context()) + parentID := emptyToNil(r.FormValue("parent_id")) + if !h.requireWritableParent(w, r, userID, parentID) { + return + } key := storage.GenerateKey(userID, filename) contentType := storage.GuessContentType(filename, header.Header.Get("Content-Type")) if err := h.store.PutObject(r.Context(), key, file, header.Size, contentType); err != nil { writeInternalError(w, r, err, "failed to upload file") return } - parentID := emptyToNil(r.FormValue("parent_id")) node, err := h.repo.CreateFile(r.Context(), &model.Node{ ParentID: parentID, Title: title, @@ -271,6 +277,30 @@ func (h *NodeHandler) requireNode(w http.ResponseWriter, r *http.Request) (*mode return node, true } +func (h *NodeHandler) requireWritableParent(w http.ResponseWriter, r *http.Request, userID string, parentID *string) bool { + if parentID == nil || strings.TrimSpace(*parentID) == "" { + return true + } + parent, err := h.repo.GetForUser(r.Context(), *parentID, userID, subordinates(r)) + if errors.Is(err, repository.ErrNotFound) { + writeError(w, http.StatusNotFound, "parent folder not found") + return false + } + if err != nil { + writeInternalError(w, r, err, "failed to check parent folder") + return false + } + if parent.NodeType != model.NodeTypeFolder { + writeError(w, http.StatusBadRequest, "parent_id must point to folder") + return false + } + if parent.EffectiveAccess != model.AccessEdit { + writeError(w, http.StatusForbidden, "edit access to parent folder required") + return false + } + return true +} + func (h *NodeHandler) publicNode(w http.ResponseWriter, r *http.Request) (*model.Node, bool) { node, err := h.repo.GetByPublicToken(r.Context(), chi.URLParam(r, "token")) if errors.Is(err, repository.ErrNotFound) {