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.go b/cmd/config.go index e03fb354..bca203c5 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -35,12 +35,6 @@ func addConfigFlags(cmd *cobra.Command) { cmd.Flags().BoolP("signup", "s", false, "allow users to signup") cmd.Flags().String("shell", "", "shell command to which other commands should be appended") - cmd.Flags().StringP("address", "a", "127.0.0.1", "default address to listen to") - cmd.Flags().StringP("log", "l", "stderr", "log output") - cmd.Flags().IntP("port", "p", 0, "default port to listen to") - cmd.Flags().String("tls.cert", "", "tls certificate path") - cmd.Flags().String("tls.key", "", "tls key path") - cmd.Flags().String("auth.method", string(auth.MethodJSONAuth), "authentication type") cmd.Flags().String("auth.header", "", "HTTP header for auth.method=proxy") @@ -101,12 +95,6 @@ func printSettings(s *settings.Settings, auther auth.Auther) { fmt.Fprintf(w, "Sign up:\t%t\n", s.Signup) fmt.Fprintf(w, "Auth method:\t%s\n", s.AuthMethod) fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(s.Shell, " ")) - fmt.Fprintf(w, "Log:\t%s\t\n", s.Log) - fmt.Fprintln(w, "\nServer:") - fmt.Fprintf(w, "\tAddress:\t%s\n", s.Server.Address) - fmt.Fprintf(w, "\tPort:\t%d\n", s.Server.Port) - fmt.Fprintf(w, "\tTLS Cert:\t%s\n", s.Server.TLSCert) - fmt.Fprintf(w, "\tTLS Key:\t%s\n", s.Server.TLSKey) fmt.Fprintln(w, "\nBranding:") fmt.Fprintf(w, "\tName:\t%s\n", s.Branding.Name) fmt.Fprintf(w, "\tFiles override:\t%s\n", s.Branding.Files) diff --git a/cmd/config_init.go b/cmd/config_init.go index 0f7cb043..6a75bd1e 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -16,7 +16,6 @@ func init() { configCmd.AddCommand(configInitCmd) rootCmd.AddCommand(configInitCmd) addConfigFlags(configInitCmd) - configInitCmd.MarkFlagRequired("scope") } var configInitCmd = &cobra.Command{ @@ -45,17 +44,10 @@ override the options.`, s := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit BaseURL: mustGetString(cmd, "baseURL"), - Log: mustGetString(cmd, "log"), Signup: mustGetBool(cmd, "signup"), Shell: strings.Split(strings.TrimSpace(mustGetString(cmd, "shell")), " "), AuthMethod: authMethod, Defaults: defaults, - Server: settings.Server{ - Address: mustGetString(cmd, "address"), - Port: mustGetInt(cmd, "port"), - TLSCert: mustGetString(cmd, "tls.cert"), - TLSKey: mustGetString(cmd, "tls.key"), - }, Branding: settings.Branding{ Name: mustGetString(cmd, "branding.name"), DisableExternal: mustGetBool(cmd, "branding.disableExternal"), diff --git a/cmd/config_set.go b/cmd/config_set.go index b2d4658a..b5e7b510 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -44,16 +44,6 @@ you want to change.`, s.Branding.DisableExternal = mustGetBool(cmd, flag.Name) case "branding.files": s.Branding.Files = mustGetString(cmd, flag.Name) - case "log": - s.Log = mustGetString(cmd, flag.Name) - case "address": - s.Server.Address = mustGetString(cmd, flag.Name) - case "port": - s.Server.Port = mustGetInt(cmd, flag.Name) - case "tls.cert": - s.Server.TLSCert = mustGetString(cmd, flag.Name) - case "tls.key": - s.Server.TLSKey = mustGetString(cmd, flag.Name) } }) diff --git a/cmd/root.go b/cmd/root.go index a267abd6..9670d414 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,30 +1,53 @@ package cmd import ( - "crypto/rand" "crypto/tls" "io/ioutil" "log" "net" "net/http" "os" + "path/filepath" "strconv" "strings" "github.com/asdine/storm" "github.com/filebrowser/filebrowser/v2/auth" - "github.com/filebrowser/filebrowser/v2/settings" - "github.com/filebrowser/filebrowser/v2/storage" - "github.com/filebrowser/filebrowser/v2/users" - fbhttp "github.com/filebrowser/filebrowser/v2/http" - homedir "github.com/mitchellh/go-homedir" + "github.com/filebrowser/filebrowser/v2/settings" + "github.com/filebrowser/filebrowser/v2/users" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" - // "github.com/spf13/pflag" v "github.com/spf13/viper" lumberjack "gopkg.in/natefinch/lumberjack.v2" ) +var ( + cfgFile string +) + +func init() { + f := rootCmd.Flags() + pf := rootCmd.PersistentFlags() + + f.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')") + vaddP(pf, "database", "d", "./filebrowser.db", "path to the database") + vaddP(f, "address", "a", "127.0.0.1", "address to listen on") + vaddP(f, "log", "l", "stdout", "log output") + vaddP(f, "port", "p", 8080, "port to listen on") + vaddP(f, "cert", "t", "", "tls certificate") + vaddP(f, "key", "k", "", "tls key") + vaddP(f, "scope", "s", ".", "scope to prepend to a user's scope when it is relative") + + if err := v.BindPFlags(f); err != nil { + panic(err) + } + + if err := v.BindPFlags(pf); err != nil { + panic(err) + } +} + var rootCmd = &cobra.Command{ Use: "filebrowser", Short: "A stylish web-based file browser", @@ -33,89 +56,133 @@ manage your user and all the configurations without accessing the web interface. If you've never run File Browser, you will need to create the database. -See 'filebrowser help config init' for more information. - -This command is used to start up the server. By default it starts listening -on localhost on a random port unless specified otherwise in the database or -via flags. - -Use the available flags to override the database/default options. These flags -values won't be persisted to the database. To persist configuration to the database -use the command 'filebrowser config set'.`, - Run: func(cmd *cobra.Command, args []string) { - db := getDB() - defer db.Close() - st := getStorage(db) - startServer(st) - }, +See 'filebrowser help config init' for more information.`, + Run: serveAndListen, } -var ( - cfgFile string -) +func serveAndListen(cmd *cobra.Command, args []string) { + initConfig() -// POSSIBLE WORKAROUND TO IDENTIFY WHEN DEFAULT VALUES ARE BEING USED -var defaults = struct { - database string - address string - log string - port int - scope string - admin string -}{ - "./filebrowser.db", - "127.0.0.1", - "stderr", - 80, - "/srv", - "admin", + switch logMethod := v.GetString("log"); logMethod { + case "stdout": + log.SetOutput(os.Stdout) + case "stderr": + log.SetOutput(os.Stderr) + case "": + log.SetOutput(ioutil.Discard) + default: + log.SetOutput(&lumberjack.Logger{ + Filename: logMethod, + MaxSize: 100, + MaxAge: 14, + MaxBackups: 10, + }) + } + + if _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) { + quickSetup(cmd) + } + + db := getDB() + defer db.Close() + st := getStorage(db) + + port := v.GetInt("port") + address := v.GetString("address") + cert := v.GetString("cert") + key := v.GetString("key") + scope := v.GetString("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) + + var listener net.Listener + + if key != "" && cert != "" { + cer, err := tls.LoadX509KeyPair(cert, key) + checkErr(err) + config := &tls.Config{Certificates: []tls.Certificate{cer}} + listener, err = tls.Listen("tcp", address+":"+strconv.Itoa(port), config) + checkErr(err) + } else { + listener, err = net.Listen("tcp", address+":"+strconv.Itoa(port)) + checkErr(err) + } + + log.Println("Listening on", listener.Addr().String()) + if err := http.Serve(listener, handler); err != nil { + log.Fatal(err) + } } -func init() { - cobra.OnInitialize(initConfig) - //rootCmd.SetVersionTemplate("File Browser {{printf \"version %s\" .Version}}\n") +func quickSetup(cmd *cobra.Command) { + db, err := storm.Open(v.GetString("database")) + checkErr(err) + defer db.Close() - f := rootCmd.Flags() - pf := rootCmd.PersistentFlags() - - pf.StringVarP(&cfgFile, "config", "c", "", "config file (defaults are './.filebrowser[ext]', '$HOME/.filebrowser[ext]' or '/etc/filebrowser/.filebrowser[ext]')") - - vaddP(pf, "database", "d", "./filebrowser.db", "path to the database") - - vaddP(f, "address", "a", defaults.address, "address to listen on") - vaddP(f, "log", "l", defaults.log, "log output") - vaddP(f, "port", "p", defaults.port, "port to listen on") - vaddP(f, "cert", "t", "", "tls certificate (default comes from database)") - vaddP(f, "key", "k", "", "tls key (default comes from database)") - vaddP(f, "scope", "s", defaults.scope, "scope for users") - vaddP(f, "force", "f", false, "overwrite DB config with runtime params") - vaddP(f, "admin", "f", defaults.admin, "first username") - vaddP(f, "passwd", "f", "", "first username password hash") - vaddP(f, "baseurl", "b", "", "base URL") - - // Bind the full flag sets to the configuration - if err := v.BindPFlags(f); err != nil { - panic(err) + set := &settings.Settings{ + Key: generateRandomBytes(64), // 256 bit + BaseURL: "", + Signup: false, + AuthMethod: auth.MethodJSONAuth, + Defaults: settings.UserDefaults{ + Scope: ".", + Locale: "en", + Perm: users.Permissions{ + Admin: false, + Execute: true, + Create: true, + Rename: true, + Modify: true, + Delete: true, + Share: true, + Download: true, + }, + }, } - if err := v.BindPFlags(pf); err != nil { - panic(err) + + st := getStorage(db) + + err = st.Settings.Save(set) + checkErr(err) + + err = st.Auth.Save(&auth.JSONAuth{}) + checkErr(err) + + password, err := users.HashPwd("admin") + checkErr(err) + + user := &users.User{ + Username: "admin", + Password: password, + LockPassword: false, } + + set.Defaults.Apply(user) + user.Perm.Admin = true + + err = st.Users.Save(user) + checkErr(err) } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile == "" { - // Find home directory. home, err := homedir.Dir() - if err != nil { - panic(err) - } + checkErr(err) v.AddConfigPath(".") v.AddConfigPath(home) v.AddConfigPath("/etc/filebrowser/") v.SetConfigName(".filebrowser") } else { - // Use config file from the flag. v.SetConfigFile(cfgFile) } @@ -131,174 +198,4 @@ func initConfig() { } else { log.Println("Using config file:", v.ConfigFileUsed()) } - - log.Println("FORCE:", v.GetBool("force")) - - /* - if DB exists - if force false - database has highest priority, if undefined in DB use config params - else - config params overwrite existing and non-existing params in DB - else - (quick)Setup with provided config params - */ - - /* - DISPLAY WARNINGS WHEN DEFAULT VALUES ARE USED - - This allows to know if a CLI flag was provided: - - log.Println(rootCmd.Flags().Changed("database")) - - However, that is not enough in order to know if a value came from a config file or from envvars. - This should allow so. But it seems not to work as expected (see spf13/viper#323): - - log.Println(v.IsSet("database")) - */ - - if _, err := os.Stat(v.GetString("database")); os.IsNotExist(err) { - quickSetup() - } - -} - -/* -func serverVisitAndReplace(s *settings.Settings) { - rootCmd.Flags().Visit(func(flag *pflag.Flag) { - switch flag.Name { - case "log": - s.Log = v.GetString(flag.Name) - case "address": - s.Server.Address = v.GetString(flag.Name) - case "port": - s.Server.Port = v.GetInt(flag.Name) - case "cert": - s.Server.TLSCert = v.GetString(flag.Name) - case "key": - s.Server.TLSKey = v.GetString(flag.Name) - } - }) -} -*/ - -func quickSetup() { - scope := v.GetString("scope") - if scope == defaults.scope { - log.Println("[WARN] Using default value '/srv' as param 'scope'") - } - - db, err := storm.Open(v.GetString("database")) - checkErr(err) - defer db.Close() - - set := &settings.Settings{ - Key: generateRandomBytes(64), // 256 bit - BaseURL: v.GetString("baseurl"), - Log: v.GetString("log"), - Signup: false, - AuthMethod: auth.MethodJSONAuth, - Server: settings.Server{ - Port: v.GetInt("port"), - Address: v.GetString("address"), - TLSCert: v.GetString("cert"), - TLSKey: v.GetString("key"), - }, - Defaults: settings.UserDefaults{ - Scope: scope, - Locale: "en", - Perm: users.Permissions{ - Admin: false, - Execute: true, - Create: true, - Rename: true, - Modify: true, - Delete: true, - Share: true, - Download: true, - }, - }, - } - - // serverVisitAndReplace(set) - st := getStorage(db) - - err = st.Settings.Save(set) - checkErr(err) - - err = st.Auth.Save(&auth.JSONAuth{}) - checkErr(err) - - password := v.GetString("password") - if password == "" { - password, err = users.HashPwd("admin") - checkErr(err) - } - - user := &users.User{ - Username: v.GetString("admin"), - Password: password, - LockPassword: false, - } - - set.Defaults.Apply(user) - user.Perm.Admin = true - - err = st.Users.Save(user) - checkErr(err) -} - -func setupLogger(s *settings.Settings) { - switch s.Log { - case "stdout": - log.SetOutput(os.Stdout) - case "stderr": - log.SetOutput(os.Stderr) - case "": - log.SetOutput(ioutil.Discard) - default: - log.SetOutput(&lumberjack.Logger{ - Filename: s.Log, - MaxSize: 100, - MaxAge: 14, - MaxBackups: 10, - }) - } -} - -func startServer(st *storage.Storage) { - settings, err := st.Settings.Get() - checkErr(err) - - // serverVisitAndReplace(settings) - setupLogger(settings) - - handler, err := fbhttp.NewHandler(st) - checkErr(err) - - var listener net.Listener - - if settings.Server.TLSKey != "" && settings.Server.TLSCert != "" { - cer, err := tls.LoadX509KeyPair(settings.Server.TLSCert, settings.Server.TLSKey) - checkErr(err) - config := &tls.Config{Certificates: []tls.Certificate{cer}} - listener, err = tls.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port), config) - checkErr(err) - } else { - listener, err = net.Listen("tcp", settings.Server.Address+":"+strconv.Itoa(settings.Server.Port)) - checkErr(err) - } - - log.Println("Listening on", listener.Addr().String()) - if err := http.Serve(listener, handler); err != nil { - log.Fatal(err) - } -} - -func generateRandomBytes(n int) []byte { - b := make([]byte, n) - _, err := rand.Read(b) - checkErr(err) - // Note that err == nil only if we read len(b) bytes. - return b } 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 e497e82b..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") } @@ -94,35 +94,35 @@ func getUserDefaults(cmd *cobra.Command, defaults *settings.UserDefaults, all bo visit := func(flag *pflag.Flag) { switch flag.Name { case "scope": - defaults.Scope = mustGetString(cmd, "scope") + defaults.Scope = mustGetString(cmd, flag.Name) case "locale": - defaults.Locale = mustGetString(cmd, "locale") + defaults.Locale = mustGetString(cmd, flag.Name) case "viewMode": defaults.ViewMode = getViewMode(cmd) case "perm.admin": - defaults.Perm.Admin = mustGetBool(cmd, "perm.admin") + defaults.Perm.Admin = mustGetBool(cmd, flag.Name) case "perm.execute": - defaults.Perm.Execute = mustGetBool(cmd, "perm.execute") + defaults.Perm.Execute = mustGetBool(cmd, flag.Name) case "perm.create": - defaults.Perm.Create = mustGetBool(cmd, "perm.create") + defaults.Perm.Create = mustGetBool(cmd, flag.Name) case "perm.rename": - defaults.Perm.Rename = mustGetBool(cmd, "perm.rename") + defaults.Perm.Rename = mustGetBool(cmd, flag.Name) case "perm.modify": - defaults.Perm.Modify = mustGetBool(cmd, "perm.modify") + defaults.Perm.Modify = mustGetBool(cmd, flag.Name) case "perm.delete": - defaults.Perm.Delete = mustGetBool(cmd, "perm.delete") + defaults.Perm.Delete = mustGetBool(cmd, flag.Name) case "perm.share": - defaults.Perm.Share = mustGetBool(cmd, "perm.share") + defaults.Perm.Share = mustGetBool(cmd, flag.Name) case "perm.download": - defaults.Perm.Download = mustGetBool(cmd, "perm.download") + defaults.Perm.Download = mustGetBool(cmd, flag.Name) case "commands": - commands, err := cmd.Flags().GetStringSlice("commands") + commands, err := cmd.Flags().GetStringSlice(flag.Name) checkErr(err) defaults.Commands = commands case "sorting.by": - defaults.Sorting.By = mustGetString(cmd, "sorting.by") + defaults.Sorting.By = mustGetString(cmd, flag.Name) case "sorting.asc": - defaults.Sorting.Asc = mustGetBool(cmd, "sorting.asc") + defaults.Sorting.Asc = mustGetBool(cmd, flag.Name) } } 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/cmd/utils.go b/cmd/utils.go index 689912de..fe0ee06d 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,6 +1,7 @@ package cmd import ( + "crypto/rand" "errors" "os" @@ -80,3 +81,11 @@ func getDB() *storm.DB { func getStorage(db *storm.DB) *storage.Storage { return bolt.NewStorage(db) } + +func generateRandomBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + checkErr(err) + // Note that err == nil only if we read len(b) bytes. + return b +} diff --git a/files/file.go b/files/file.go index b50dc929..f7253a1e 100644 --- a/files/file.go +++ b/files/file.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "hash" "io" + "log" "mime" "net/http" "os" @@ -76,7 +77,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) { return file, file.readListing(opts.Checker) } - err = file.detectType(opts.Modify) + err = file.detectType(opts.Modify, true) if err != nil { return nil, err } @@ -126,17 +127,25 @@ func (i *FileInfo) Checksum(algo string) error { return nil } -func (i *FileInfo) detectType(modify bool) error { +func (i *FileInfo) detectType(modify, saveContent bool) error { + // failing to detect the type should not return error. + // imagine the situation where a file in a dir with thousands + // of files couldn't be opened: we'd have immediately + // a 500 even though it doesn't matter. So we just log it. reader, err := i.Fs.Open(i.Path) if err != nil { - return err + log.Print(err) + i.Type = "blob" + return nil } defer reader.Close() buffer := make([]byte, 512) n, err := reader.Read(buffer) if err != nil && err != io.EOF { - return err + log.Print(err) + i.Type = "blob" + return nil } mimetype := mime.TypeByExtension(i.Extension) @@ -160,17 +169,20 @@ func (i *FileInfo) detectType(modify bool) error { return nil default: i.Type = "text" - afs := &afero.Afero{Fs: i.Fs} - content, err := afs.ReadFile(i.Path) - if err != nil { - return err - } if !modify { i.Type = "textImmutable" } - i.Content = string(content) + if saveContent { + afs := &afero.Afero{Fs: i.Fs} + content, err := afs.ReadFile(i.Path) + if err != nil { + return err + } + + i.Content = string(content) + } } return nil @@ -238,7 +250,7 @@ func (i *FileInfo) readListing(checker rules.Checker) error { } else { listing.NumFiles++ - err := file.detectType(true) + err := file.detectType(true, false) if err != nil { return err } diff --git a/frontend b/frontend index 95fc3dfd..0e7d4ef1 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 95fc3dfdfbe21b1d55538add66bf0d5f38197320 +Subproject commit 0e7d4ef110ee550375d4bf15dfa9ded70214076a 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/static.go b/http/static.go index 6e84be85..bca3821e 100644 --- a/http/static.go +++ b/http/static.go @@ -85,9 +85,7 @@ func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler) { return http.StatusNotFound, nil } - w.Header().Set("x-frame-options", "SAMEORIGIN") w.Header().Set("x-xss-protection", "1; mode=block") - return handleWithStaticData(w, r, d, box, "index.html", "text/html; charset=utf-8") }, "", storage) 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 11991a66..57d4863d 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -9,23 +9,14 @@ type AuthMethod string type Settings struct { Key []byte `json:"key"` BaseURL string `json:"baseURL"` - Log string `json:"log"` - Server Server `json:"server"` + Scope string `json:"scope"` Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` Branding Branding `json:"branding"` Commands map[string][]string `json:"commands"` Shell []string `json:"shell"` - Rules []rules.Rule `json:"rules"` // TODO: use this add to cli -} - -// Server settings. -type Server struct { - Port int `json:"port"` - Address string `json:"address"` - TLSCert string `json:"tlsCert"` - TLSKey string `json:"tlsKey"` + Rules []rules.Rule `json:"rules"` } // GetRules implements rules.Provider. diff --git a/storage/bolt/importer/conf.go b/storage/bolt/importer/conf.go index efd98df7..335f4148 100644 --- a/storage/bolt/importer/conf.go +++ b/storage/bolt/importer/conf.go @@ -111,7 +111,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { s := &settings.Settings{ Key: key, BaseURL: cfg.BaseURL, - Log: cfg.Log, Signup: false, Defaults: settings.UserDefaults{ Scope: cfg.Defaults.Scope, @@ -129,10 +128,6 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error { Download: true, }, }, - Server: settings.Server{ - Address: cfg.Address, - Port: cfg.Port, - }, } var auther auth.Auther 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