From f55f205ced82d7e05e0ffc158b302b5300409769 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 13:01:42 +0000 Subject: [PATCH] feat: add global scope (#604) License: MIT Signed-off-by: Henrique Dias --- auth/auth.go | 5 ++--- auth/json.go | 10 ++-------- auth/none.go | 10 ++-------- auth/proxy.go | 12 +++--------- auth/storage.go | 10 ++-------- cmd/config_init.go | 1 - cmd/root.go | 20 ++++++++++++-------- cmd/rules.go | 2 +- cmd/users.go | 2 +- cmd/users_find.go | 10 ++++++---- cmd/users_update.go | 8 +++++--- http/auth.go | 4 ++-- http/public.go | 2 +- http/users.go | 6 +++--- settings/settings.go | 1 + storage/bolt/importer/users.go | 9 +-------- users/storage.go | 12 ++++++------ users/users.go | 17 ++++++++++------- 18 files changed, 60 insertions(+), 81 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index bcde03d7..86b56a04 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -3,13 +3,12 @@ package auth import ( "net/http" + "github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/users" ) // Auther is the authentication interface. type Auther interface { // Auth is called to authenticate a request. - Auth(*http.Request) (*users.User, error) - // SetStorage attaches the Storage instance. - SetStorage(*users.Storage) + Auth(*http.Request, *users.Storage, *settings.Settings) (*users.User, error) } diff --git a/auth/json.go b/auth/json.go index ecf067a2..9bd86fe8 100644 --- a/auth/json.go +++ b/auth/json.go @@ -23,11 +23,10 @@ type jsonCred struct { // JSONAuth is a json implementaion of an Auther. type JSONAuth struct { ReCaptcha *ReCaptcha - storage *users.Storage } // Auth authenticates the user via a json in content body. -func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) { +func (a *JSONAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) { var cred jsonCred if r.Body == nil { @@ -52,7 +51,7 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) { } } - u, err := a.storage.Get(cred.Username) + u, err := sto.Get(set.Scope, cred.Username) if err != nil || !users.CheckPwd(cred.Password, u.Password) { return nil, os.ErrPermission } @@ -60,11 +59,6 @@ func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) { return u, nil } -// SetStorage attaches the storage to the auther. -func (a *JSONAuth) SetStorage(s *users.Storage) { - a.storage = s -} - const reCaptchaAPI = "/recaptcha/api/siteverify" // ReCaptcha identifies a recaptcha conenction. diff --git a/auth/none.go b/auth/none.go index 0d3e2293..76312881 100644 --- a/auth/none.go +++ b/auth/none.go @@ -12,15 +12,9 @@ const MethodNoAuth settings.AuthMethod = "noauth" // NoAuth is no auth implementation of auther. type NoAuth struct { - storage *users.Storage } // Auth uses authenticates user 1. -func (a *NoAuth) Auth(r *http.Request) (*users.User, error) { - return a.storage.Get(1) -} - -// SetStorage attaches the storage to the auther. -func (a *NoAuth) SetStorage(s *users.Storage) { - a.storage = s +func (a *NoAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) { + return sto.Get(set.Scope, 1) } diff --git a/auth/proxy.go b/auth/proxy.go index f23b70fb..e3176bdd 100644 --- a/auth/proxy.go +++ b/auth/proxy.go @@ -14,22 +14,16 @@ const MethodProxyAuth settings.AuthMethod = "proxy" // ProxyAuth is a proxy implementation of an auther. type ProxyAuth struct { - Header string - storage *users.Storage + Header string } // Auth authenticates the user via an HTTP header. -func (a *ProxyAuth) Auth(r *http.Request) (*users.User, error) { +func (a *ProxyAuth) Auth(r *http.Request, sto *users.Storage, set *settings.Settings) (*users.User, error) { username := r.Header.Get(a.Header) - user, err := a.storage.Get(username) + user, err := sto.Get(set.Scope, username) if err == errors.ErrNotExist { return nil, os.ErrPermission } return user, err } - -// SetStorage attaches the storage to the auther. -func (a *ProxyAuth) SetStorage(s *users.Storage) { - a.storage = s -} diff --git a/auth/storage.go b/auth/storage.go index b5bd5e83..2cf63e05 100644 --- a/auth/storage.go +++ b/auth/storage.go @@ -22,15 +22,9 @@ func NewStorage(back StorageBackend, users *users.Storage) *Storage { return &Storage{back: back, users: users} } -// Get wraps a StorageBackend.Get and calls SetStorage on the auther. +// Get wraps a StorageBackend.Get. func (s *Storage) Get(t settings.AuthMethod) (Auther, error) { - auther, err := s.back.Get(t) - if err != nil { - return nil, err - } - - auther.SetStorage(s.users) - return auther, nil + return s.back.Get(t) } // Save wraps a StorageBackend.Save. diff --git a/cmd/config_init.go b/cmd/config_init.go index f2e46c8a..6c18628d 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -15,7 +15,6 @@ func init() { configCmd.AddCommand(configInitCmd) rootCmd.AddCommand(configInitCmd) addConfigFlags(configInitCmd) - configInitCmd.MarkFlagRequired("scope") } var configInitCmd = &cobra.Command{ diff --git a/cmd/root.go b/cmd/root.go index b895fa94..437acf2f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,12 +2,12 @@ package cmd import ( "crypto/tls" - "errors" "io/ioutil" "log" "net" "net/http" "os" + "path/filepath" "strconv" "github.com/asdine/storm" @@ -32,7 +32,7 @@ func init() { rootCmd.Flags().IntP("port", "p", 8080, "port to listen on") rootCmd.Flags().StringP("cert", "c", "", "tls certificate") rootCmd.Flags().StringP("key", "k", "", "tls key") - rootCmd.Flags().StringP("scope", "s", "", "root scope to which user's scope are relative too") + rootCmd.Flags().StringP("scope", "s", ".", "scope to prepend to a user's scope when it is relative") } var rootCmd = &cobra.Command{ @@ -76,6 +76,15 @@ func serveAndListen(cmd *cobra.Command, args []string) { address := mustGetString(cmd, "address") cert := mustGetString(cmd, "cert") key := mustGetString(cmd, "key") + scope := mustGetString(cmd, "scope") + + scope, err := filepath.Abs(scope) + checkErr(err) + settings, err := st.Settings.Get() + checkErr(err) + settings.Scope = scope + err = st.Settings.Save(settings) + checkErr(err) handler, err := fbhttp.NewHandler(st) checkErr(err) @@ -100,11 +109,6 @@ func serveAndListen(cmd *cobra.Command, args []string) { } func quickSetup(cmd *cobra.Command) { - scope := mustGetString(cmd, "scope") - if scope == "" { - panic(errors.New("scope flag must be set for quick setup")) - } - db, err := storm.Open(databasePath) checkErr(err) defer db.Close() @@ -115,7 +119,7 @@ func quickSetup(cmd *cobra.Command) { Signup: false, AuthMethod: auth.MethodJSONAuth, Defaults: settings.UserDefaults{ - Scope: scope, + Scope: ".", Locale: "en", Perm: users.Permissions{ Admin: false, diff --git a/cmd/rules.go b/cmd/rules.go index 99f20814..2a7d219d 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -39,7 +39,7 @@ func runRules(cmd *cobra.Command, users func(*users.User, *storage.Storage), glo id := getUserIdentifier(cmd) if id != nil { - user, err := st.Users.Get(id) + user, err := st.Users.Get("", id) checkErr(err) if users != nil { diff --git a/cmd/users.go b/cmd/users.go index f49f46e4..3afdba86 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -77,7 +77,7 @@ func addUserFlags(cmd *cobra.Command) { cmd.Flags().Bool("sorting.asc", false, "sorting by ascending order") cmd.Flags().Bool("lockPassword", false, "lock password") cmd.Flags().StringSlice("commands", nil, "a list of the commands a user can execute") - cmd.Flags().String("scope", "", "scope for users") + cmd.Flags().String("scope", ".", "scope for users") cmd.Flags().String("locale", "en", "locale for users") cmd.Flags().String("viewMode", string(users.ListViewMode), "view mode for users") } diff --git a/cmd/users_find.go b/cmd/users_find.go index 9fdd92b6..126c7354 100644 --- a/cmd/users_find.go +++ b/cmd/users_find.go @@ -32,19 +32,21 @@ var findUsers = func(cmd *cobra.Command, args []string) { defer db.Close() st := getStorage(db) + settings, err := st.Settings.Get() + checkErr(err) + username, _ := cmd.Flags().GetString("username") id, _ := cmd.Flags().GetUint("id") - var err error var list []*users.User var user *users.User if username != "" { - user, err = st.Users.Get(username) + user, err = st.Users.Get(settings.Scope, username) } else if id != 0 { - user, err = st.Users.Get(id) + user, err = st.Users.Get(settings.Scope, id) } else { - list, err = st.Users.Gets() + list, err = st.Users.Gets(settings.Scope) } checkErr(err) diff --git a/cmd/users_update.go b/cmd/users_update.go index ebeef419..f0aa27ba 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -26,17 +26,19 @@ options you want to change.`, defer db.Close() st := getStorage(db) + set, err := st.Settings.Get() + checkErr(err) + id, _ := cmd.Flags().GetUint("id") username := mustGetString(cmd, "username") password := mustGetString(cmd, "password") var user *users.User - var err error if id != 0 { - user, err = st.Users.Get(id) + user, err = st.Users.Get(set.Scope, id) } else { - user, err = st.Users.Get(username) + user, err = st.Users.Get(set.Scope, username) } checkErr(err) diff --git a/http/auth.go b/http/auth.go index 9ea0a889..2e01ddf0 100644 --- a/http/auth.go +++ b/http/auth.go @@ -67,7 +67,7 @@ func withUser(fn handleFunc) handleFunc { w.Header().Add("X-Renew-Token", "true") } - d.user, err = d.store.Users.Get(tk.User.ID) + d.user, err = d.store.Users.Get(d.settings.Scope, tk.User.ID) if err != nil { return http.StatusInternalServerError, err } @@ -91,7 +91,7 @@ var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, e return http.StatusInternalServerError, err } - user, err := auther.Auth(r) + user, err := auther.Auth(r, d.store.Users, d.Settings) if err == os.ErrPermission { return http.StatusForbidden, nil } else if err != nil { diff --git a/http/public.go b/http/public.go index 656e3698..afab24a6 100644 --- a/http/public.go +++ b/http/public.go @@ -13,7 +13,7 @@ var withHashFile = func(fn handleFunc) handleFunc { return errToStatus(err), err } - user, err := d.store.Users.Get(link.UserID) + user, err := d.store.Users.Get(d.settings.Scope, link.UserID) if err != nil { return errToStatus(err), err } diff --git a/http/users.go b/http/users.go index 545b2536..ad547435 100644 --- a/http/users.go +++ b/http/users.go @@ -61,7 +61,7 @@ func withSelfOrAdmin(fn handleFunc) handleFunc { } var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - users, err := d.store.Users.Gets() + users, err := d.store.Users.Gets(d.settings.Scope) if err != nil { return http.StatusInternalServerError, err } @@ -78,7 +78,7 @@ var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d * }) var userGetHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - u, err := d.store.Users.Get(d.raw.(uint)) + u, err := d.store.Users.Get(d.settings.Scope, d.raw.(uint)) if err == errors.ErrNotExist { return http.StatusNotFound, err } @@ -147,7 +147,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request req.Data.Password, err = users.HashPwd(req.Data.Password) } else { var suser *users.User - suser, err = d.store.Users.Get(d.raw.(uint)) + suser, err = d.store.Users.Get(d.settings.Scope, d.raw.(uint)) req.Data.Password = suser.Password } diff --git a/settings/settings.go b/settings/settings.go index b287be8d..57d4863d 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -9,6 +9,7 @@ type AuthMethod string type Settings struct { Key []byte `json:"key"` BaseURL string `json:"baseURL"` + Scope string `json:"scope"` Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` diff --git a/storage/bolt/importer/users.go b/storage/bolt/importer/users.go index 660403b4..5a98ffb9 100644 --- a/storage/bolt/importer/users.go +++ b/storage/bolt/importer/users.go @@ -3,7 +3,6 @@ package importer import ( "encoding/json" "fmt" - "path/filepath" "github.com/asdine/storm" "github.com/filebrowser/filebrowser/v2/rules" @@ -52,7 +51,6 @@ func readOldUsers(db *storm.DB) ([]*oldUser, error) { } func convertUsersToNew(old []*oldUser) ([]*users.User, error) { - var err error list := []*users.User{} for _, oldUser := range old { @@ -82,12 +80,7 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) { user.Rules = append(user.Rules, *rule) } - user.Scope, err = filepath.Abs(user.Scope) - if err != nil { - return nil, err - } - - err = user.Clean() + err := user.Clean("") if err != nil { return nil, err } diff --git a/users/storage.go b/users/storage.go index 1366b968..ce3e7514 100644 --- a/users/storage.go +++ b/users/storage.go @@ -36,7 +36,7 @@ func NewStorage(back StorageBackend) *Storage { // Get allows you to get a user by its name or username. The provided // id must be a string for username lookup or a uint for id lookup. If id // is neither, a ErrInvalidDataType will be returned. -func (s *Storage) Get(id interface{}) (*User, error) { +func (s *Storage) Get(baseScope string, id interface{}) (*User, error) { var ( user *User err error @@ -55,19 +55,19 @@ func (s *Storage) Get(id interface{}) (*User, error) { return nil, err } - user.Clean() + user.Clean(baseScope) return user, err } // Gets gets a list of all users. -func (s *Storage) Gets() ([]*User, error) { +func (s *Storage) Gets(baseScope string) ([]*User, error) { users, err := s.back.Gets() if err != nil { return nil, err } for _, user := range users { - user.Clean() + user.Clean(baseScope) } return users, err @@ -75,7 +75,7 @@ func (s *Storage) Gets() ([]*User, error) { // Update updates a user in the database. func (s *Storage) Update(user *User, fields ...string) error { - err := user.Clean(fields...) + err := user.Clean("", fields...) if err != nil { return err } @@ -93,7 +93,7 @@ func (s *Storage) Update(user *User, fields ...string) error { // Save saves the user in a storage. func (s *Storage) Save(user *User) error { - if err := user.Clean(); err != nil { + if err := user.Clean(""); err != nil { return err } diff --git a/users/users.go b/users/users.go index 8d530048..387a4eaa 100644 --- a/users/users.go +++ b/users/users.go @@ -1,10 +1,11 @@ package users import ( - "github.com/filebrowser/filebrowser/v2/errors" "path/filepath" "regexp" + "github.com/filebrowser/filebrowser/v2/errors" + "github.com/filebrowser/filebrowser/v2/files" "github.com/filebrowser/filebrowser/v2/rules" "github.com/spf13/afero" @@ -51,7 +52,7 @@ var checkableFields = []string{ // Clean cleans up a user and verifies if all its fields // are alright to be saved. -func (u *User) Clean(fields ...string) error { +func (u *User) Clean(baseScope string, fields ...string) error { if len(fields) == 0 { fields = checkableFields } @@ -66,10 +67,6 @@ func (u *User) Clean(fields ...string) error { if u.Password == "" { return errors.ErrEmptyPassword } - case "Scope": - if !filepath.IsAbs(u.Scope) { - return errors.ErrScopeIsRelative - } case "ViewMode": if u.ViewMode == "" { u.ViewMode = ListViewMode @@ -90,7 +87,13 @@ func (u *User) Clean(fields ...string) error { } if u.Fs == nil { - u.Fs = afero.NewBasePathFs(afero.NewOsFs(), u.Scope) + scope := u.Scope + + if !filepath.IsAbs(scope) { + scope = filepath.Join(baseScope, scope) + } + + u.Fs = afero.NewBasePathFs(afero.NewOsFs(), scope) } return nil