diff --git a/frontend b/frontend index 82abdaf3..9977299d 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 82abdaf3900aae349f172cc1f5494fa5b6e72a02 +Subproject commit 9977299d4e3dd201cd3bf648fb70b03e09d6e4e3 diff --git a/http/auth.go b/http/auth.go index 25210e7b..970bf872 100644 --- a/http/auth.go +++ b/http/auth.go @@ -79,10 +79,11 @@ func (e *Env) signupHandler(w http.ResponseWriter, r *http.Request) { } type userInfo struct { - ID uint `json:"id"` - Locale string `json:"locale"` - ViewMode types.ViewMode `json:"viewMode"` - Perm types.Permissions `json:"perm"` + ID uint `json:"id"` + Locale string `json:"locale"` + ViewMode types.ViewMode `json:"viewMode"` + Perm types.Permissions `json:"perm"` + LockPassword bool `json:"lockPassword"` } type authToken struct { @@ -136,10 +137,11 @@ func (e *Env) auth(next http.HandlerFunc) http.HandlerFunc { func (e *Env) printToken(w http.ResponseWriter, r *http.Request, user *types.User) { claims := &authToken{ User: userInfo{ - ID: user.ID, - Locale: user.Locale, - ViewMode: user.ViewMode, - Perm: user.Perm, + ID: user.ID, + Locale: user.Locale, + ViewMode: user.ViewMode, + Perm: user.Perm, + LockPassword: user.LockPassword, }, StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), diff --git a/http/http.go b/http/http.go index 54354dfc..768ada51 100644 --- a/http/http.go +++ b/http/http.go @@ -16,6 +16,11 @@ const ( keyUserID key = iota ) +type modifyRequest struct { + What string `json:"what"` // Answer to: what data type? + Which []string `json:"which"` // Answer to: which fields? +} + // Env ... type Env struct { Auther types.Auther diff --git a/http/resource.go b/http/resource.go index 8d16858d..c71fc8e1 100644 --- a/http/resource.go +++ b/http/resource.go @@ -62,15 +62,7 @@ func (e *Env) resourceGetHandler(w http.ResponseWriter, r *http.Request) { } if file.IsDir { - scope := "/" - - if sort, order, err := handleSortOrder(w, r, scope); err == nil { - file.Listing.Sort = sort - file.Listing.Order = order - } else { - httpErr(w, r, http.StatusBadRequest, err) - return - } + file.Listing.Sorting = user.Sorting file.Listing.ApplySort() renderJSON(w, r, file) return @@ -240,42 +232,3 @@ func (e *Env) resourcePatchHandler(w http.ResponseWriter, r *http.Request) { httpErr(w, r, httpFsErr(err), err) } - -func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) { - sort = r.URL.Query().Get("sort") - order = r.URL.Query().Get("order") - - switch sort { - case "": - sort = "name" - if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil { - sort = sortCookie.Value - } - case "name", "size": - http.SetCookie(w, &http.Cookie{ - Name: "sort", - Value: sort, - MaxAge: 31536000, - Path: scope, - Secure: r.TLS != nil, - }) - } - - switch order { - case "": - order = "asc" - if orderCookie, orderErr := r.Cookie("order"); orderErr == nil { - order = orderCookie.Value - } - case "asc", "desc": - http.SetCookie(w, &http.Cookie{ - Name: "order", - Value: order, - MaxAge: 31536000, - Path: scope, - Secure: r.TLS != nil, - }) - } - - return -} diff --git a/http/users.go b/http/users.go index 6cf181ec..1bb8d6fc 100644 --- a/http/users.go +++ b/http/users.go @@ -1,9 +1,11 @@ package http import ( + "encoding/json" "net/http" "sort" "strconv" + "strings" "github.com/filebrowser/filebrowser/types" "github.com/gorilla/mux" @@ -18,6 +20,32 @@ func getUserID(r *http.Request) (uint, error) { return uint(i), err } +type modifyUserRequest struct { + modifyRequest + Data *types.User `json:"data"` +} + +func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, bool) { + if r.Body == nil { + httpErr(w, r, http.StatusBadRequest, nil) + return nil, false + } + + req := &modifyUserRequest{} + err := json.NewDecoder(r.Body).Decode(req) + if err != nil { + httpErr(w, r, http.StatusBadRequest, err) + return nil, false + } + + if req.What != "user" { + httpErr(w, r, http.StatusBadRequest, nil) + return nil, false + } + + return req, true +} + func (e *Env) usersGetHandler(w http.ResponseWriter, r *http.Request) { user, ok := e.getUser(w, r) if !ok { @@ -109,5 +137,69 @@ func (e *Env) userPostHandler(w http.ResponseWriter, r *http.Request) { } func (e *Env) userPutHandler(w http.ResponseWriter, r *http.Request) { - // TODO: fill me + sessionUser, modifiedID, ok := e.userSelfOrAdmin(w, r) + if !ok { + return + } + + req, ok := getUser(w, r) + if !ok { + return + } + + if req.Data.ID != modifiedID { + httpErr(w, r, http.StatusBadRequest, nil) + return + } + + var err error + + if len(req.Which) == 1 && req.Which[0] == "all" { + if !sessionUser.Perm.Admin { + httpErr(w, r, http.StatusForbidden, nil) + return + } + + if req.Data.Password != "" { + req.Data.Password, err = types.HashPwd(req.Data.Password) + } else { + var suser *types.User + suser, err = e.Store.Users.Get(modifiedID) + req.Data.Password = suser.Password + } + + if err != nil { + httpErr(w, r, http.StatusInternalServerError, err) + return + } + + req.Which = []string{} + } + + for k, v := range req.Which { + if v == "password" { + if !sessionUser.Perm.Admin && sessionUser.LockPassword { + httpErr(w, r, http.StatusForbidden, nil) + return + } + + req.Data.Password, err = types.HashPwd(req.Data.Password) + if err != nil { + httpErr(w, r, http.StatusInternalServerError, err) + return + } + } + + if !sessionUser.Perm.Admin && (v == "scope" || v == "perm" || v == "username") { + httpErr(w, r, http.StatusForbidden, nil) + return + } + + req.Which[k] = strings.Title(v) + } + + err = e.Store.Users.Update(req.Data, req.Which...) + if err != nil { + httpErr(w, r, http.StatusInternalServerError, err) + } } diff --git a/types/listing.go b/types/listing.go index 77756bba..ecfc8c90 100644 --- a/types/listing.go +++ b/types/listing.go @@ -11,15 +11,14 @@ type Listing struct { Items []*File `json:"items"` NumDirs int `json:"numDirs"` NumFiles int `json:"numFiles"` - Sort string `json:"sort"` - Order string `json:"order"` + Sorting Sorting `json:"sorting"` } // ApplySort applies the sort order using .Order and .Sort func (l Listing) ApplySort() { // Check '.Order' to know how to sort - if l.Order == "desc" { - switch l.Sort { + if !l.Sorting.Asc { + switch l.Sorting.By { case "name": sort.Sort(sort.Reverse(byName(l))) case "size": @@ -31,7 +30,7 @@ func (l Listing) ApplySort() { return } } else { // If we had more Orderings we could add them here - switch l.Sort { + switch l.Sorting.By { case "name": sort.Sort(byName(l)) case "size": diff --git a/types/rule.go b/types/rule.go index 331f45de..9155e132 100644 --- a/types/rule.go +++ b/types/rule.go @@ -2,14 +2,15 @@ package types import ( "regexp" + "strings" ) // Rule is a allow/disallow rule. type Rule struct { - Regex bool - Allow bool - Path string - Regexp *Regexp + Regex bool `json:"regex"` + Allow bool `json:"allow"` + Path string `json:"path"` + Regexp *Regexp `json:"regexp"` } // Regexp is a wrapper to the native regexp type where we @@ -27,3 +28,17 @@ func (r *Regexp) MatchString(s string) bool { return r.regexp.MatchString(s) } + +func isAllowed(path string, rules []Rule) bool { + for _, rule := range rules { + if rule.Regex { + if rule.Regexp.MatchString(path) { + return rule.Allow + } + } else if strings.HasPrefix(path, rule.Path) { + return rule.Allow + } + } + + return true +} diff --git a/types/settings.go b/types/settings.go index 9d8488e2..6e965546 100644 --- a/types/settings.go +++ b/types/settings.go @@ -10,7 +10,19 @@ type Settings struct { Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` - Branding Branding `json:"Branding"` + Branding Branding `json:"branding"` + Rules []Rule `json:"rules"` // TODO: use this add to cli +} + +// IsAllowed matches the rules against the url. +func (e Settings) IsAllowed(url string) bool { + return isAllowed(url, e.Rules) +} + +// Sorting contains a sorting order. +type Sorting struct { + By string `json:"by"` + Asc bool `json:"asc"` } // Branding contains the branding settings of the app. @@ -26,5 +38,6 @@ type UserDefaults struct { Scope string `json:"scope"` Locale string `json:"locale"` ViewMode ViewMode `json:"viewMode"` + Sorting Sorting `json:"sorting"` // TODO: add to cli Perm Permissions `json:"perm"` } diff --git a/types/user.go b/types/user.go index 13b8b3dc..7f2900e2 100644 --- a/types/user.go +++ b/types/user.go @@ -1,8 +1,6 @@ package types import ( - "strings" - "github.com/spf13/afero" "golang.org/x/crypto/bcrypt" ) @@ -30,15 +28,17 @@ type Permissions struct { // User describes a user. type User struct { - ID uint `storm:"id,increment" json:"id"` - Username string `storm:"unique" json:"username"` - Password string `json:"password"` - Scope string `json:"scope"` - Locale string `json:"locale"` - ViewMode ViewMode `json:"viewMode"` - Perm Permissions `json:"perm"` - Fs afero.Fs `json:"-"` - Rules []Rule `json:"rules"` + ID uint `storm:"id,increment" json:"id"` + Username string `storm:"unique" json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` + Locale string `json:"locale"` + LockPassword bool `json:"lockPassword"` // TODO: add to cli + ViewMode ViewMode `json:"viewMode"` + Perm Permissions `json:"perm"` + Sorting Sorting `json:"sorting"` // TODO: add to cli + Fs afero.Fs `json:"-"` + Rules []Rule `json:"rules"` } // BuildFs builds the FileSystem property of the user, @@ -51,24 +51,7 @@ func (u *User) BuildFs() { // IsAllowed checks if an user is allowed to go to a certain path. func (u User) IsAllowed(url string) bool { - var rule *Rule - i := len(u.Rules) - 1 - - for i >= 0 { - rule = &u.Rules[i] - - if rule.Regex { - if rule.Regexp.MatchString(url) { - return rule.Allow - } - } else if strings.HasPrefix(url, rule.Path) { - return rule.Allow - } - - i-- - } - - return true + return isAllowed(url, u.Rules) } // HashPwd hashes a password.