From c830b80269f1cc04886e916b840937e4436f72ca Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 5 Jan 2019 23:56:22 +0000 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=A7=BC:=20remove=20comment=20[ci=20?= =?UTF-8?q?skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- settings/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/settings.go b/settings/settings.go index 11991a66..60285721 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -17,7 +17,7 @@ type Settings struct { 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 + Rules []rules.Rule `json:"rules"` } // Server settings. From 28b326ab1df32f41234c0d48f3555f1a18e2ffeb Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 09:24:09 +0000 Subject: [PATCH 02/10] fix: do not read whole file on listings License: MIT Signed-off-by: Henrique Dias --- files/file.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/files/file.go b/files/file.go index b50dc929..3dbb092a 100644 --- a/files/file.go +++ b/files/file.go @@ -76,7 +76,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,7 +126,7 @@ func (i *FileInfo) Checksum(algo string) error { return nil } -func (i *FileInfo) detectType(modify bool) error { +func (i *FileInfo) detectType(modify, saveContent bool) error { reader, err := i.Fs.Open(i.Path) if err != nil { return err @@ -160,17 +160,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 +241,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 } From 2d858e6738a1c3704cc5e894292b764b3fae7d9c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 09:29:23 +0000 Subject: [PATCH 03/10] fix: allow embedding into iframes (close #550) License: MIT Signed-off-by: Henrique Dias --- http/static.go | 2 -- 1 file changed, 2 deletions(-) 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) From d97bafb53b4a7bae1ba7dd507b62ab70e4aca017 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 10:06:39 +0000 Subject: [PATCH 04/10] feat: update frontend License: MIT Signed-off-by: Henrique Dias --- frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend b/frontend index 95fc3dfd..7a6397af 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 95fc3dfdfbe21b1d55538add66bf0d5f38197320 +Subproject commit 7a6397af22380eec65f20836d713256b5397d182 From 22f2287bc3416c3b241db92fd9f2173043c6942a Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 12:01:44 +0000 Subject: [PATCH 05/10] feat: update frontend License: MIT Signed-off-by: Henrique Dias --- frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend b/frontend index 7a6397af..0e7d4ef1 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 7a6397af22380eec65f20836d713256b5397d182 +Subproject commit 0e7d4ef110ee550375d4bf15dfa9ded70214076a From a8ff679ae3beec9df9e3f89d253002f8b1b4250a Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 12:26:48 +0000 Subject: [PATCH 06/10] feat: dont persist server data on database License: MIT Signed-off-by: Henrique Dias --- cmd/config.go | 12 --- cmd/config_init.go | 7 -- cmd/config_set.go | 10 --- cmd/root.go | 136 +++++++++++----------------------- cmd/utils.go | 9 +++ settings/settings.go | 10 --- storage/bolt/importer/conf.go | 5 -- 7 files changed, 53 insertions(+), 136 deletions(-) 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 cec455a0..f2e46c8a 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -43,17 +43,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 9444dc4c..c7f01598 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,6 @@ package cmd import ( - "crypto/rand" "crypto/tls" "errors" "io/ioutil" @@ -14,12 +13,10 @@ import ( "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" "github.com/spf13/cobra" - "github.com/spf13/pflag" lumberjack "gopkg.in/natefinch/lumberjack.v2" ) @@ -30,12 +27,12 @@ var ( func init() { rootCmd.PersistentFlags().StringVarP(&databasePath, "database", "d", "./filebrowser.db", "path to the database") - rootCmd.Flags().StringP("address", "a", "", "address to listen on (default comes from database)") - rootCmd.Flags().StringP("log", "l", "", "log output (default comes from database)") - rootCmd.Flags().IntP("port", "p", 0, "port to listen on (default comes from database)") - rootCmd.Flags().StringP("cert", "c", "", "tls certificate (default comes from database)") - rootCmd.Flags().StringP("key", "k", "", "tls key (default comes from database)") - rootCmd.Flags().StringP("scope", "s", "", "scope for users") + rootCmd.Flags().StringP("address", "a", "127.0.0.1", "address to listen on") + rootCmd.Flags().StringP("log", "l", "stdout", "log output") + rootCmd.Flags().IntP("port", "p", 80, "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") } var rootCmd = &cobra.Command{ @@ -46,29 +43,12 @@ 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) { - if _, err := os.Stat(databasePath); os.IsNotExist(err) { - quickSetup(cmd) - } - - db := getDB() - defer db.Close() - st := getStorage(db) - startServer(cmd, st) - }, +See 'filebrowser help config init' for more information.`, + Run: serveAndListen, } -func setupLogger(s *settings.Settings) { - switch s.Log { +func serveAndListen(cmd *cobra.Command, args []string) { + switch logMethod := mustGetString(cmd, "log"); logMethod { case "stdout": log.SetOutput(os.Stdout) case "stderr": @@ -77,29 +57,46 @@ func setupLogger(s *settings.Settings) { log.SetOutput(ioutil.Discard) default: log.SetOutput(&lumberjack.Logger{ - Filename: s.Log, + Filename: logMethod, MaxSize: 100, MaxAge: 14, MaxBackups: 10, }) } -} -func serverVisitAndReplace(cmd *cobra.Command, s *settings.Settings) { - cmd.Flags().Visit(func(flag *pflag.Flag) { - switch 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 "cert": - s.Server.TLSCert = mustGetString(cmd, flag.Name) - case "key": - s.Server.TLSKey = mustGetString(cmd, flag.Name) - } - }) + if _, err := os.Stat(databasePath); os.IsNotExist(err) { + quickSetup(cmd) + } + + db := getDB() + defer db.Close() + st := getStorage(db) + + port := mustGetInt(cmd, "port") + address := mustGetString(cmd, "address") + cert := mustGetString(cmd, "cert") + key := mustGetString(cmd, "key") + + 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 quickSetup(cmd *cobra.Command) { @@ -115,15 +112,8 @@ func quickSetup(cmd *cobra.Command) { set := &settings.Settings{ Key: generateRandomBytes(64), // 256 bit BaseURL: "", - Log: "stderr", Signup: false, AuthMethod: auth.MethodJSONAuth, - Server: settings.Server{ - Port: 0, - Address: "127.0.0.1", - TLSCert: mustGetString(cmd, "cert"), - TLSKey: mustGetString(cmd, "key"), - }, Defaults: settings.UserDefaults{ Scope: scope, Locale: "en", @@ -140,7 +130,6 @@ func quickSetup(cmd *cobra.Command) { }, } - serverVisitAndReplace(cmd, set) st := getStorage(db) err = st.Settings.Save(set) @@ -164,40 +153,3 @@ func quickSetup(cmd *cobra.Command) { err = st.Users.Save(user) checkErr(err) } - -func startServer(cmd *cobra.Command, st *storage.Storage) { - settings, err := st.Settings.Get() - checkErr(err) - - serverVisitAndReplace(cmd, 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/utils.go b/cmd/utils.go index 8b2520dd..70e63413 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,6 +1,7 @@ package cmd import ( + "crypto/rand" "errors" "os" @@ -53,3 +54,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/settings/settings.go b/settings/settings.go index 60285721..b287be8d 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -9,8 +9,6 @@ type AuthMethod string type Settings struct { Key []byte `json:"key"` BaseURL string `json:"baseURL"` - Log string `json:"log"` - Server Server `json:"server"` Signup bool `json:"signup"` Defaults UserDefaults `json:"defaults"` AuthMethod AuthMethod `json:"authMethod"` @@ -20,14 +18,6 @@ type Settings struct { Rules []rules.Rule `json:"rules"` } -// Server settings. -type Server struct { - Port int `json:"port"` - Address string `json:"address"` - TLSCert string `json:"tlsCert"` - TLSKey string `json:"tlsKey"` -} - // GetRules implements rules.Provider. func (s *Settings) GetRules() []rules.Rule { return s.Rules 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 From 12eb050cc22baeda292a25034821aa0d7640ca84 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 12:41:52 +0000 Subject: [PATCH 07/10] feat: simplify future changes License: MIT Signed-off-by: Henrique Dias --- cmd/users.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/users.go b/cmd/users.go index e497e82b..f49f46e4 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -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) } } From e22598a1260195653de23d0f972580f8d5bb020c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 12:57:35 +0000 Subject: [PATCH 08/10] feat: change default to 8080 License: MIT Signed-off-by: Henrique Dias --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index c7f01598..b895fa94 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,7 +29,7 @@ func init() { rootCmd.Flags().StringP("address", "a", "127.0.0.1", "address to listen on") rootCmd.Flags().StringP("log", "l", "stdout", "log output") - rootCmd.Flags().IntP("port", "p", 80, "port to listen on") + 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") From 07f3ee38e50ba6cef7a515b3fad1a37921fa1160 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 12:59:53 +0000 Subject: [PATCH 09/10] fix: dont fail when can't detect file type (#609) License: MIT Signed-off-by: Henrique Dias --- files/file.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/files/file.go b/files/file.go index 3dbb092a..f7253a1e 100644 --- a/files/file.go +++ b/files/file.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "hash" "io" + "log" "mime" "net/http" "os" @@ -127,16 +128,24 @@ func (i *FileInfo) Checksum(algo string) 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) From f55f205ced82d7e05e0ffc158b302b5300409769 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sun, 6 Jan 2019 13:01:42 +0000 Subject: [PATCH 10/10] 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