feat(sidecar): enforce per-user BasePath on all filesystem mutations
Folder create/rename/delete/move, photo move, heap convert, and file rename now reject paths outside the caller's BasePath (403). Sources resolved via PhotoPrism UIDs are re-checked in movePhotoFiles. The USER_BASEPATHS reconciler also sets upload_path so client-app uploads land inside the user's subtree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -85,3 +87,35 @@ func ctxBasePath(c *gin.Context) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// userScopeRoot returns the absolute directory the session may mutate:
|
||||
// ORIGINALS_ROOT/<BasePath> for scoped users, the whole originals root for
|
||||
// admins (empty BasePath). Lexical join only — callers compare it against
|
||||
// paths built the same way from cfg.OriginalsRoot.
|
||||
func userScopeRoot(c *gin.Context, cfg *Config) string {
|
||||
base := strings.Trim(ctxBasePath(c), "/")
|
||||
if base == "" {
|
||||
return cfg.OriginalsRoot
|
||||
}
|
||||
return filepath.Join(cfg.OriginalsRoot, base)
|
||||
}
|
||||
|
||||
// requireUserScope guards an already-root-resolved absolute path against
|
||||
// the caller's BasePath. PhotoPrism scopes what a session can *see* by
|
||||
// BasePath, but the sidecar's filesystem endpoints accept raw paths, so
|
||||
// every mutation must re-check that boundary here. `strict` additionally
|
||||
// rejects the scope root itself — renaming/deleting/moving the user's own
|
||||
// base folder would detach their library from auth_users.base_path.
|
||||
// Writes the 403 response and returns false when out of bounds.
|
||||
func requireUserScope(c *gin.Context, cfg *Config, abs string, strict bool) bool {
|
||||
scope := userScopeRoot(c, cfg)
|
||||
if !sameOrUnder(abs, scope) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "path outside your library"})
|
||||
return false
|
||||
}
|
||||
if strict && abs == scope {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "cannot modify your library root"})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user