fix: refactoring previous fix in attempt to address ongoing issues

See #211 and #215
This commit is contained in:
Henrique Dias
2025-01-15 19:35:03 +01:00
parent 59c5f1343f
commit 6aeb3f8a30
3 changed files with 74 additions and 24 deletions
+7 -13
View File
@@ -2,7 +2,6 @@ package lib
import ( import (
"net/http" "net/http"
"net/url"
"os" "os"
"strings" "strings"
@@ -116,21 +115,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr)) zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr))
} }
// Cleanup destination header if it's present by stripping out the prefix // Convert the HTTP request into an internal request type
// and only keeping the path. req, err := newRequest(r, h.user.Prefix)
if destination := r.Header.Get("Destination"); destination != "" { if err != nil {
u, err := url.Parse(destination) zap.L().Info("invalid request path or destination", zap.Error(err))
if err == nil { http.Error(w, "Invalid request path or destination", http.StatusBadRequest)
destination = strings.TrimPrefix(u.Path, user.Prefix) return
if !strings.HasPrefix(destination, "/") {
destination = "/" + destination
}
r.Header.Set("Destination", destination)
}
} }
// Checks for user permissions relatively to this PATH. // Checks for user permissions relatively to this PATH.
allowed := user.Allowed(r, func(filename string) bool { allowed := user.Allowed(req, func(filename string) bool {
_, err := user.FileSystem.Stat(r.Context(), filename) _, err := user.FileSystem.Stat(r.Context(), filename)
return !os.IsNotExist(err) return !os.IsNotExist(err)
}) })
+10 -11
View File
@@ -3,7 +3,6 @@ package lib
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@@ -51,12 +50,12 @@ type UserPermissions struct {
} }
// Allowed checks if the user has permission to access a directory/file // Allowed checks if the user has permission to access a directory/file
func (p UserPermissions) Allowed(r *http.Request, fileExists func(string) bool) bool { func (p UserPermissions) Allowed(r *request, fileExists func(string) bool) bool {
// For COPY and MOVE requests, we first check the permissions for the destination // For COPY and MOVE requests, we first check the permissions for the destination
// path. As soon as a rule matches and does not allow the operation at the destination, // path. As soon as a rule matches and does not allow the operation at the destination,
// we fail immediately. If no rule matches, we check the global permissions. // we fail immediately. If no rule matches, we check the global permissions.
if r.Method == "COPY" || r.Method == "MOVE" { if r.method == "COPY" || r.method == "MOVE" {
dst := r.Header.Get("Destination") dst := r.destination
for i := len(p.Rules) - 1; i >= 0; i-- { for i := len(p.Rules) - 1; i >= 0; i-- {
if p.Rules[i].Matches(dst) { if p.Rules[i].Matches(dst) {
@@ -77,7 +76,7 @@ func (p UserPermissions) Allowed(r *http.Request, fileExists func(string) bool)
// Go through rules beginning from the last one, and check the permissions at // Go through rules beginning from the last one, and check the permissions at
// the source. The first matched rule returns. // the source. The first matched rule returns.
for i := len(p.Rules) - 1; i >= 0; i-- { for i := len(p.Rules) - 1; i >= 0; i-- {
if p.Rules[i].Matches(r.URL.Path) { if p.Rules[i].Matches(r.path) {
return p.Rules[i].Permissions.Allowed(r, fileExists) return p.Rules[i].Permissions.Allowed(r, fileExists)
} }
} }
@@ -142,8 +141,8 @@ func (p *Permissions) UnmarshalText(data []byte) error {
// Allowed returns whether this permission set has permissions to execute this // Allowed returns whether this permission set has permissions to execute this
// request in the source directory. This applies to all requests with all methods. // request in the source directory. This applies to all requests with all methods.
func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool { func (p Permissions) Allowed(r *request, fileExists func(string) bool) bool {
switch r.Method { switch r.method {
case "GET", "HEAD", "OPTIONS", "POST", "PROPFIND": case "GET", "HEAD", "OPTIONS", "POST", "PROPFIND":
// Note: POST backend implementation just returns the same thing as GET. // Note: POST backend implementation just returns the same thing as GET.
return p.Read return p.Read
@@ -152,7 +151,7 @@ func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool
case "PROPPATCH": case "PROPPATCH":
return p.Update return p.Update
case "PUT": case "PUT":
if fileExists(r.URL.Path) { if fileExists(r.path) {
return p.Update return p.Update
} else { } else {
return p.Create return p.Create
@@ -172,10 +171,10 @@ func (p Permissions) Allowed(r *http.Request, fileExists func(string) bool) bool
// AllowedDestination returns whether this permissions set has permissions to execute this // AllowedDestination returns whether this permissions set has permissions to execute this
// request in the destination directory. This only applies for COPY and MOVE requests. // request in the destination directory. This only applies for COPY and MOVE requests.
func (p Permissions) AllowedDestination(r *http.Request, fileExists func(string) bool) bool { func (p Permissions) AllowedDestination(r *request, fileExists func(string) bool) bool {
switch r.Method { switch r.method {
case "COPY", "MOVE": case "COPY", "MOVE":
if fileExists(r.Header.Get("Destination")) { if fileExists(r.destination) {
return p.Update return p.Update
} else { } else {
return p.Create return p.Create
+57
View File
@@ -0,0 +1,57 @@
package lib
import (
"errors"
"net/http"
"net/url"
"strings"
)
type request struct {
method string
path string
destination string
}
func newRequest(r *http.Request, prefix string) (*request, error) {
ctx := &request{
method: r.Method,
}
if destination := r.Header.Get("Destination"); destination != "" {
u, err := url.Parse(destination)
if err != nil {
return nil, errors.New("invalid destination header")
}
if prefix != "" {
destination = strings.TrimPrefix(u.Path, prefix)
if len(destination) >= len(u.Path) {
return nil, errors.New("invalid url prefix")
}
}
if !strings.HasPrefix(destination, "/") {
destination = "/" + destination
}
ctx.destination = destination
}
path := r.URL.Path
if prefix != "" {
path = strings.TrimPrefix(r.URL.Path, prefix)
if len(path) >= len(r.URL.Path) {
return nil, errors.New("invalid url prefix")
}
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
ctx.path = path
return ctx, nil
}