feat: enforce CHFS permissions in WebDAV
This commit is contained in:
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -598,21 +599,106 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
vp := strings.TrimPrefix(r.URL.Path, "/webdav")
|
||||
vp = strings.Trim(vp, "/")
|
||||
if !account(r, s).MaskFor(vp).HasRead() {
|
||||
http.Error(w, "forbidden", 403)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead && !account(r, s).MaskFor(vp).HasWrite() {
|
||||
http.Error(w, "forbidden", 403)
|
||||
return
|
||||
}
|
||||
vp := strings.Trim(strings.TrimPrefix(r.URL.Path, "/webdav"), "/")
|
||||
old := r.URL.Path
|
||||
r.URL.Path = "/" + vp
|
||||
defer func() { r.URL.Path = old }()
|
||||
h := &webdav.Handler{FileSystem: webdav.LocalFileSystem(s.WebDAVRoot)}
|
||||
h.ServeHTTP(w, r)
|
||||
fs := &webdavAuthFS{inner: webdav.LocalFileSystem(s.WebDAVRoot), account: account(r, s)}
|
||||
(&webdav.Handler{FileSystem: fs}).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type webdavAuthFS struct {
|
||||
inner webdav.FileSystem
|
||||
account *auth.Account
|
||||
}
|
||||
|
||||
func (f *webdavAuthFS) allow(path string, p auth.PermMask) error {
|
||||
path = strings.Trim(path, "/")
|
||||
if f.account == nil || f.account.MaskFor(path)&p != p {
|
||||
return webdav.NewHTTPError(http.StatusForbidden, fmt.Errorf("forbidden"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (f *webdavAuthFS) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||
if err := f.allow(name, auth.Read); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.inner.Open(ctx, name)
|
||||
}
|
||||
func (f *webdavAuthFS) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
|
||||
if err := f.allow(name, auth.Read); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.inner.Stat(ctx, name)
|
||||
}
|
||||
func (f *webdavAuthFS) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) {
|
||||
if err := f.allow(name, auth.Read); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := f.inner.ReadDir(ctx, name, recursive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]webdav.FileInfo, 0, len(items))
|
||||
for _, item := range items {
|
||||
p := strings.Trim(item.Path, "/")
|
||||
if f.account.MaskFor(p).HasRead() {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (f *webdavAuthFS) Create(ctx context.Context, name string, body io.ReadCloser, opts *webdav.CreateOptions) (*webdav.FileInfo, bool, error) {
|
||||
parent := path.Dir(strings.Trim(name, "/"))
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
}
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return f.inner.Create(ctx, name, body, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) RemoveAll(ctx context.Context, name string, opts *webdav.RemoveAllOptions) error {
|
||||
if err := f.allow(name, auth.Delete); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.inner.RemoveAll(ctx, name, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) Mkdir(ctx context.Context, name string) error {
|
||||
parent := path.Dir(strings.Trim(name, "/"))
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
}
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.inner.Mkdir(ctx, name)
|
||||
}
|
||||
func (f *webdavAuthFS) Copy(ctx context.Context, name, dest string, opts *webdav.CopyOptions) (bool, error) {
|
||||
if err := f.allow(name, auth.Read); err != nil {
|
||||
return false, err
|
||||
}
|
||||
parent := path.Dir(strings.Trim(dest, "/"))
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
}
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.inner.Copy(ctx, name, dest, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) {
|
||||
if err := f.allow(name, auth.Write); err != nil {
|
||||
return false, err
|
||||
}
|
||||
parent := path.Dir(strings.Trim(dest, "/"))
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
}
|
||||
if err := f.allow(parent, auth.Write); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.inner.Move(ctx, name, dest, opts)
|
||||
}
|
||||
|
||||
func (s *Server) ipFilter(next http.Handler) http.Handler {
|
||||
|
||||
Reference in New Issue
Block a user