From 0338a7b9475426093f565703890eb70c71697f1f Mon Sep 17 00:00:00 2001 From: Graham Steffaniak Date: Sat, 2 Sep 2023 12:24:55 -0500 Subject: [PATCH] updated --- backend/cmd/cmd.go | 12 +++ backend/cmd/cmds.go | 26 +++++++ backend/cmd/cmds_add.go | 27 +++++++ backend/cmd/cmds_ls.go | 31 ++++++++ backend/cmd/cmds_rm.go | 56 ++++++++++++++ backend/cmd/config.go | 18 +++++ backend/cmd/config_cat.go | 25 +++++++ backend/cmd/config_export.go | 37 ++++++++++ backend/cmd/config_import.go | 94 +++++++++++++++++++++++ backend/cmd/config_init.go | 1 + backend/cmd/config_set.go | 74 +++++++++++++++++++ backend/cmd/docs.go | 139 +++++++++++++++++++++++++++++++++++ backend/cmd/hash.go | 25 +++++++ backend/cmd/rule_rm.go | 66 +++++++++++++++++ backend/cmd/rules.go | 92 +++++++++++++++++++++++ backend/cmd/rules_add.go | 58 +++++++++++++++ backend/cmd/rules_ls.go | 19 +++++ backend/cmd/users_add.go | 51 +++++++++++++ backend/cmd/users_export.go | 24 ++++++ backend/cmd/users_find.go | 51 +++++++++++++ backend/cmd/users_import.go | 89 ++++++++++++++++++++++ backend/cmd/users_rm.go | 31 ++++++++ backend/cmd/users_update.go | 75 +++++++++++++++++++ backend/cmd/utils.go | 4 +- backend/cmd/version.go | 21 ++++++ backend/filebrowser.yml | 28 +++---- 26 files changed, 1158 insertions(+), 16 deletions(-) create mode 100644 backend/cmd/cmd.go create mode 100644 backend/cmd/cmds.go create mode 100644 backend/cmd/cmds_add.go create mode 100644 backend/cmd/cmds_ls.go create mode 100644 backend/cmd/cmds_rm.go create mode 100644 backend/cmd/config_cat.go create mode 100644 backend/cmd/config_export.go create mode 100644 backend/cmd/config_import.go create mode 100644 backend/cmd/config_set.go create mode 100644 backend/cmd/docs.go create mode 100644 backend/cmd/hash.go create mode 100644 backend/cmd/rule_rm.go create mode 100644 backend/cmd/rules.go create mode 100644 backend/cmd/rules_add.go create mode 100644 backend/cmd/rules_ls.go create mode 100644 backend/cmd/users_add.go create mode 100644 backend/cmd/users_export.go create mode 100644 backend/cmd/users_find.go create mode 100644 backend/cmd/users_import.go create mode 100644 backend/cmd/users_rm.go create mode 100644 backend/cmd/users_update.go create mode 100644 backend/cmd/version.go diff --git a/backend/cmd/cmd.go b/backend/cmd/cmd.go new file mode 100644 index 00000000..18f52337 --- /dev/null +++ b/backend/cmd/cmd.go @@ -0,0 +1,12 @@ +package cmd + +import ( + "log" +) + +// Execute executes the commands. +func Execute() { + if err := rootCmd.Execute(); err != nil { + log.Fatal(err) + } +} diff --git a/backend/cmd/cmds.go b/backend/cmd/cmds.go new file mode 100644 index 00000000..cf951995 --- /dev/null +++ b/backend/cmd/cmds.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(cmdsCmd) +} + +var cmdsCmd = &cobra.Command{ + Use: "cmds", + Short: "Command runner management utility", + Long: `Command runner management utility.`, + Args: cobra.NoArgs, +} + +func printEvents(m map[string][]string) { + for evt, cmds := range m { + for i, cmd := range cmds { + fmt.Printf("%s(%d): %s\n", evt, i, cmd) + } + } +} diff --git a/backend/cmd/cmds_add.go b/backend/cmd/cmds_add.go new file mode 100644 index 00000000..fd2c0dc5 --- /dev/null +++ b/backend/cmd/cmds_add.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "strings" + + "github.com/spf13/cobra" +) + +func init() { + cmdsCmd.AddCommand(cmdsAddCmd) +} + +var cmdsAddCmd = &cobra.Command{ + Use: "add ", + Short: "Add a command to run on a specific event", + Long: `Add a command to run on a specific event.`, + Args: cobra.MinimumNArgs(2), //nolint:gomnd + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + s, err := d.store.Settings.Get() + checkErr(err) + command := strings.Join(args[1:], " ") + s.Commands[args[0]] = append(s.Commands[args[0]], command) + err = d.store.Settings.Save(s) + checkErr(err) + printEvents(s.Commands) + }, pythonConfig{}), +} diff --git a/backend/cmd/cmds_ls.go b/backend/cmd/cmds_ls.go new file mode 100644 index 00000000..71f36382 --- /dev/null +++ b/backend/cmd/cmds_ls.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + cmdsCmd.AddCommand(cmdsLsCmd) + cmdsLsCmd.Flags().StringP("event", "e", "", "event name, without 'before' or 'after'") +} + +var cmdsLsCmd = &cobra.Command{ + Use: "ls", + Short: "List all commands for each event", + Long: `List all commands for each event.`, + Args: cobra.NoArgs, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + s, err := d.store.Settings.Get() + checkErr(err) + evt := mustGetString(cmd.Flags(), "event") + + if evt == "" { + printEvents(s.Commands) + } else { + show := map[string][]string{} + show["before_"+evt] = s.Commands["before_"+evt] + show["after_"+evt] = s.Commands["after_"+evt] + printEvents(show) + } + }, pythonConfig{}), +} diff --git a/backend/cmd/cmds_rm.go b/backend/cmd/cmds_rm.go new file mode 100644 index 00000000..e1f78863 --- /dev/null +++ b/backend/cmd/cmds_rm.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "strconv" + + "github.com/spf13/cobra" +) + +func init() { + cmdsCmd.AddCommand(cmdsRmCmd) +} + +var cmdsRmCmd = &cobra.Command{ + Use: "rm [index_end]", + Short: "Removes a command from an event hooker", + Long: `Removes a command from an event hooker. The provided index +is the same that's printed when you run 'cmds ls'. Note +that after each removal/addition, the index of the +commands change. So be careful when removing them after each +other. + +You can also specify an optional parameter (index_end) so +you can remove all commands from 'index' to 'index_end', +including 'index_end'.`, + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.RangeArgs(2, 3)(cmd, args); err != nil { //nolint:gomnd + return err + } + + for _, arg := range args[1:] { + if _, err := strconv.Atoi(arg); err != nil { + return err + } + } + + return nil + }, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + s, err := d.store.Settings.Get() + checkErr(err) + evt := args[0] + + i, err := strconv.Atoi(args[1]) + checkErr(err) + f := i + if len(args) == 3 { //nolint:gomnd + f, err = strconv.Atoi(args[2]) + checkErr(err) + } + + s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][f+1:]...) + err = d.store.Settings.Save(s) + checkErr(err) + printEvents(s.Commands) + }, pythonConfig{}), +} diff --git a/backend/cmd/config.go b/backend/cmd/config.go index cb2b98b0..9ee88b85 100644 --- a/backend/cmd/config.go +++ b/backend/cmd/config.go @@ -9,6 +9,7 @@ import ( "text/tabwriter" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/gtsteffaniak/filebrowser/auth" "github.com/gtsteffaniak/filebrowser/errors" @@ -26,6 +27,22 @@ var configCmd = &cobra.Command{ Args: cobra.NoArgs, } +func addConfigFlags(flags *pflag.FlagSet) { + addUserFlags(flags) + flags.BoolP("signup", "s", false, "allow users to signup") + flags.String("shell", "", "shell command to which other commands should be appended") + + flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China") + flags.String("recaptcha.key", "", "ReCaptcha site key") + flags.String("recaptcha.secret", "", "ReCaptcha secret") + + flags.String("frontend.name", "", "replace 'File Browser' by this name") + flags.String("frontend.color", "", "set the theme color") + flags.String("frontend.files", "", "path to directory with images and custom styles") + flags.Bool("frontend.disableExternal", false, "disable external links such as GitHub links") + flags.Bool("frontend.disableUsedPercentage", false, "disable used disk percentage graph") +} + //nolint:gocyclo func getAuthentication() (string, auth.Auther) { method := settings.GlobalConfiguration.Auth.Method @@ -112,6 +129,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut fmt.Fprintf(w, "\tColor:\t%s\n", set.Frontend.Color) fmt.Fprintln(w, "\nServer:") fmt.Fprintf(w, "\tLog:\t%s\n", ser.Log) + fmt.Fprintf(w, "\tPort:\t%s\n", ser.Port) fmt.Fprintf(w, "\tBase URL:\t%s\n", ser.BaseURL) fmt.Fprintf(w, "\tRoot:\t%s\n", ser.Root) fmt.Fprintf(w, "\tSocket:\t%s\n", ser.Socket) diff --git a/backend/cmd/config_cat.go b/backend/cmd/config_cat.go new file mode 100644 index 00000000..3f06eb9f --- /dev/null +++ b/backend/cmd/config_cat.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + configCmd.AddCommand(configCatCmd) +} + +var configCatCmd = &cobra.Command{ + Use: "cat", + Short: "Prints the configuration", + Long: `Prints the configuration.`, + Args: cobra.NoArgs, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + set, err := d.store.Settings.Get() + checkErr(err) + ser, err := d.store.Settings.GetServer() + checkErr(err) + auther, err := d.store.Auth.Get(set.Auth.Method) + checkErr(err) + printSettings(ser, set, auther) + }, pythonConfig{}), +} diff --git a/backend/cmd/config_export.go b/backend/cmd/config_export.go new file mode 100644 index 00000000..a4d8ecff --- /dev/null +++ b/backend/cmd/config_export.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + configCmd.AddCommand(configExportCmd) +} + +var configExportCmd = &cobra.Command{ + Use: "export ", + Short: "Export the configuration to a file", + Long: `Export the configuration to a file. The path must be for a +json or yaml file. This exported configuration can be changed, +and imported again with 'config import' command.`, + Args: jsonYamlArg, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + settings, err := d.store.Settings.Get() + checkErr(err) + + server, err := d.store.Settings.GetServer() + checkErr(err) + + auther, err := d.store.Auth.Get(settings.Auth.Method) + checkErr(err) + + data := &settingsFile{ + Settings: settings, + Auther: auther, + Server: server, + } + + err = marshal(args[0], data) + checkErr(err) + }, pythonConfig{}), +} diff --git a/backend/cmd/config_import.go b/backend/cmd/config_import.go new file mode 100644 index 00000000..08ff806d --- /dev/null +++ b/backend/cmd/config_import.go @@ -0,0 +1,94 @@ +package cmd + +import ( + "encoding/json" + "errors" + "path/filepath" + "reflect" + "log" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/auth" + "github.com/gtsteffaniak/filebrowser/settings" +) + +func init() { + configCmd.AddCommand(configImportCmd) +} + +type settingsFile struct { + Settings *settings.Settings `json:"settings"` + Server *settings.Server `json:"server"` + Auther interface{} `json:"auther"` +} + +var configImportCmd = &cobra.Command{ + Use: "import ", + Short: "Import a configuration file", + Long: `Import a configuration file. This will replace all the existing +configuration. Can be used with or without unexisting databases. + +If used with a nonexisting database, a key will be generated +automatically. Otherwise the key will be kept the same as in the +database. + +The path must be for a json or yaml file.`, + Args: jsonYamlArg, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + var key []byte + if d.hadDB { + settings, err := d.store.Settings.Get() + checkErr(err) + key = settings.Key + } else { + key = generateKey() + } + file := settingsFile{} + err := unmarshal(args[0], &file) + checkErr(err) + log.Println(file.Settings) + file.Settings.Key = key + err = d.store.Settings.Save(file.Settings) + checkErr(err) + + err = d.store.Settings.SaveServer(file.Server) + checkErr(err) + + var rawAuther interface{} + if filepath.Ext(args[0]) != ".json" { //nolint:goconst + rawAuther = cleanUpInterfaceMap(file.Auther.(map[interface{}]interface{})) + } else { + rawAuther = file.Auther + } + log.Println("config_import",file.Settings.Auth) + var auther auth.Auther + switch file.Settings.Auth.Method { + case "password": + auther = getAuther(auth.JSONAuth{}, rawAuther).(*auth.JSONAuth) + case "noauth": + auther = getAuther(auth.NoAuth{}, rawAuther).(*auth.NoAuth) + case "proxy": + auther = getAuther(auth.ProxyAuth{}, rawAuther).(*auth.ProxyAuth) + case "hook": + auther = getAuther(&auth.HookAuth{}, rawAuther).(*auth.HookAuth) + default: + checkErr(errors.New("invalid auth method")) + } + + err = d.store.Auth.Save(auther) + checkErr(err) + + printSettings(file.Server, file.Settings, auther) + }, pythonConfig{allowNoDB: true}), +} + +func getAuther(sample auth.Auther, data interface{}) interface{} { + authType := reflect.TypeOf(sample) + auther := reflect.New(authType).Interface() + bytes, err := json.Marshal(data) + checkErr(err) + err = json.Unmarshal(bytes, &auther) + checkErr(err) + return auther +} diff --git a/backend/cmd/config_init.go b/backend/cmd/config_init.go index b627762f..47bf8df7 100644 --- a/backend/cmd/config_init.go +++ b/backend/cmd/config_init.go @@ -10,6 +10,7 @@ import ( func init() { configCmd.AddCommand(configInitCmd) + addConfigFlags(configInitCmd.Flags()) } var configInitCmd = &cobra.Command{ diff --git a/backend/cmd/config_set.go b/backend/cmd/config_set.go new file mode 100644 index 00000000..f30dd088 --- /dev/null +++ b/backend/cmd/config_set.go @@ -0,0 +1,74 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func init() { + configCmd.AddCommand(configSetCmd) + addConfigFlags(configSetCmd.Flags()) +} + +var configSetCmd = &cobra.Command{ + Use: "set", + Short: "Updates the configuration", + Long: `Updates the configuration. Set the flags for the options +you want to change. Other options will remain unchanged.`, + Args: cobra.NoArgs, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + flags := cmd.Flags() + set, err := d.store.Settings.Get() + checkErr(err) + + ser, err := d.store.Settings.GetServer() + checkErr(err) + + flags.Visit(func(flag *pflag.Flag) { + switch flag.Name { + case "baseurl": + ser.BaseURL = mustGetString(flags, flag.Name) + case "root": + ser.Root = mustGetString(flags, flag.Name) + case "socket": + ser.Socket = mustGetString(flags, flag.Name) + case "cert": + ser.TLSCert = mustGetString(flags, flag.Name) + case "key": + ser.TLSKey = mustGetString(flags, flag.Name) + case "address": + ser.Address = mustGetString(flags, flag.Name) + case "port": + ser.Port = 8080 + case "log": + ser.Log = mustGetString(flags, flag.Name) + case "signup": + set.Signup = mustGetBool(flags, flag.Name) + case "shell": + set.Shell = convertCmdStrToCmdArray(mustGetString(flags, flag.Name)) + case "frontend.name": + set.Frontend.Name = mustGetString(flags, flag.Name) + case "frontend.color": + set.Frontend.Color = mustGetString(flags, flag.Name) + case "frontend.disableExternal": + set.Frontend.DisableExternal = mustGetBool(flags, flag.Name) + case "frontend.disableUsedPercentage": + set.Frontend.DisableUsedPercentage = mustGetBool(flags, flag.Name) + case "frontend.files": + set.Frontend.Files = mustGetString(flags, flag.Name) + } + }) + + getUserDefaults(flags, &set.UserDefaults, false) + + // read the defaults + _, auther := getAuthentication() + err = d.store.Auth.Save(auther) + checkErr(err) + err = d.store.Settings.Save(set) + checkErr(err) + err = d.store.Settings.SaveServer(ser) + checkErr(err) + printSettings(ser, set, auther) + }, pythonConfig{}), +} diff --git a/backend/cmd/docs.go b/backend/cmd/docs.go new file mode 100644 index 00000000..9ebb9573 --- /dev/null +++ b/backend/cmd/docs.go @@ -0,0 +1,139 @@ +package cmd + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func init() { + rootCmd.AddCommand(docsCmd) + docsCmd.Flags().StringP("path", "p", "./docs", "path to save the docs") +} + +func printToc(names []string) { + for i, name := range names { + name = strings.TrimSuffix(name, filepath.Ext(name)) + name = strings.Replace(name, "-", " ", -1) + names[i] = name + } + + sort.Strings(names) + + toc := "" + for _, name := range names { + toc += "* [" + name + "](cli/" + strings.Replace(name, " ", "-", -1) + ".md)\n" + } + + fmt.Println(toc) +} + +var docsCmd = &cobra.Command{ + Use: "docs", + Hidden: true, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + dir := mustGetString(cmd.Flags(), "path") + generateDocs(rootCmd, dir) + names := []string{} + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + + if !strings.HasPrefix(info.Name(), "filebrowser") { + return nil + } + + names = append(names, info.Name()) + return nil + }) + + checkErr(err) + printToc(names) + }, +} + +func generateDocs(cmd *cobra.Command, dir string) { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + continue + } + + generateDocs(c, dir) + } + + basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md" + filename := filepath.Join(dir, basename) + f, err := os.Create(filename) + checkErr(err) + defer f.Close() + generateMarkdown(cmd, f) +} + +func generateMarkdown(cmd *cobra.Command, w io.Writer) { + cmd.InitDefaultHelpCmd() + cmd.InitDefaultHelpFlag() + + buf := new(bytes.Buffer) + name := cmd.CommandPath() + + short := cmd.Short + long := cmd.Long + if long == "" { + long = short + } + + buf.WriteString("---\ndescription: " + short + "\n---\n\n") + buf.WriteString("# " + name + "\n\n") + buf.WriteString("## Synopsis\n\n") + buf.WriteString(long + "\n\n") + + if cmd.Runnable() { + buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) + } + + if len(cmd.Example) > 0 { + buf.WriteString("## Examples\n\n") + buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) + } + + printOptions(buf, cmd) + _, err := buf.WriteTo(w) + checkErr(err) +} + +func generateFlagsTable(fs *pflag.FlagSet, buf io.StringWriter) { + _, _ = buf.WriteString("| Name | Shorthand | Usage |\n") + _, _ = buf.WriteString("|------|-----------|-------|\n") + + fs.VisitAll(func(f *pflag.Flag) { + _, _ = buf.WriteString("|" + f.Name + "|" + f.Shorthand + "|" + f.Usage + "|\n") + }) +} + +func printOptions(buf *bytes.Buffer, cmd *cobra.Command) { + flags := cmd.NonInheritedFlags() + flags.SetOutput(buf) + if flags.HasAvailableFlags() { + buf.WriteString("## Options\n\n") + generateFlagsTable(flags, buf) + buf.WriteString("\n") + } + + parentFlags := cmd.InheritedFlags() + parentFlags.SetOutput(buf) + if parentFlags.HasAvailableFlags() { + buf.WriteString("### Inherited\n\n") + generateFlagsTable(parentFlags, buf) + buf.WriteString("\n") + } +} diff --git a/backend/cmd/hash.go b/backend/cmd/hash.go new file mode 100644 index 00000000..41718afa --- /dev/null +++ b/backend/cmd/hash.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + rootCmd.AddCommand(hashCmd) +} + +var hashCmd = &cobra.Command{ + Use: "hash ", + Short: "Hashes a password", + Long: `Hashes a password using bcrypt algorithm.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + pwd, err := users.HashPwd(args[0]) + checkErr(err) + fmt.Println(pwd) + }, +} diff --git a/backend/cmd/rule_rm.go b/backend/cmd/rule_rm.go new file mode 100644 index 00000000..0aea796c --- /dev/null +++ b/backend/cmd/rule_rm.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "strconv" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + rulesCmd.AddCommand(rulesRmCommand) + rulesRmCommand.Flags().Uint("index", 0, "index of rule to remove") + _ = rulesRmCommand.MarkFlagRequired("index") +} + +var rulesRmCommand = &cobra.Command{ + Use: "rm [index_end]", + Short: "Remove a global rule or user rule", + Long: `Remove a global rule or user rule. The provided index +is the same that's printed when you run 'rules ls'. Note +that after each removal/addition, the index of the +commands change. So be careful when removing them after each +other. + +You can also specify an optional parameter (index_end) so +you can remove all commands from 'index' to 'index_end', +including 'index_end'.`, + Args: func(cmd *cobra.Command, args []string) error { + if err := cobra.RangeArgs(1, 2)(cmd, args); err != nil { //nolint:gomnd + return err + } + + for _, arg := range args { + if _, err := strconv.Atoi(arg); err != nil { + return err + } + } + + return nil + }, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + i, err := strconv.Atoi(args[0]) + checkErr(err) + f := i + if len(args) == 2 { //nolint:gomnd + f, err = strconv.Atoi(args[1]) + checkErr(err) + } + + user := func(u *users.User) { + u.Rules = append(u.Rules[:i], u.Rules[f+1:]...) + err := d.store.Users.Save(u) + checkErr(err) + } + + global := func(s *settings.Settings) { + s.Rules = append(s.Rules[:i], s.Rules[f+1:]...) + err := d.store.Settings.Save(s) + checkErr(err) + } + + runRules(d.store, cmd, user, global) + }, pythonConfig{}), +} diff --git a/backend/cmd/rules.go b/backend/cmd/rules.go new file mode 100644 index 00000000..69df0d33 --- /dev/null +++ b/backend/cmd/rules.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/gtsteffaniak/filebrowser/rules" + "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/storage" + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + rootCmd.AddCommand(rulesCmd) + rulesCmd.PersistentFlags().StringP("username", "u", "", "username of user to which the rules apply") + rulesCmd.PersistentFlags().UintP("id", "i", 0, "id of user to which the rules apply") +} + +var rulesCmd = &cobra.Command{ + Use: "rules", + Short: "Rules management utility", + Long: `On each subcommand you'll have available at least two flags: +"username" and "id". You must either set only one of them +or none. If you set one of them, the command will apply to +an user, otherwise it will be applied to the global set or +rules.`, + Args: cobra.NoArgs, +} + +func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User), globalFn func(*settings.Settings)) { + id := getUserIdentifier(cmd.Flags()) + if id != nil { + user, err := st.Users.Get("", id) + checkErr(err) + + if usersFn != nil { + usersFn(user) + } + + printRules(user.Rules, id) + return + } + + s, err := st.Settings.Get() + checkErr(err) + + if globalFn != nil { + globalFn(s) + } + + printRules(s.Rules, id) +} + +func getUserIdentifier(flags *pflag.FlagSet) interface{} { + id := mustGetUint(flags, "id") + username := mustGetString(flags, "username") + + if id != 0 { + return id + } else if username != "" { + return username + } + + return nil +} + +func printRules(rulez []rules.Rule, id interface{}) { + if id == nil { + fmt.Printf("Global Rules:\n\n") + } else { + fmt.Printf("Rules for user %v:\n\n", id) + } + + for id, rule := range rulez { + fmt.Printf("(%d) ", id) + if rule.Regex { + if rule.Allow { + fmt.Printf("Allow Regex: \t%s\n", rule.Regexp.Raw) + } else { + fmt.Printf("Disallow Regex: \t%s\n", rule.Regexp.Raw) + } + } else { + if rule.Allow { + fmt.Printf("Allow Path: \t%s\n", rule.Path) + } else { + fmt.Printf("Disallow Path: \t%s\n", rule.Path) + } + } + } +} diff --git a/backend/cmd/rules_add.go b/backend/cmd/rules_add.go new file mode 100644 index 00000000..768717bd --- /dev/null +++ b/backend/cmd/rules_add.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "regexp" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/rules" + "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + rulesCmd.AddCommand(rulesAddCmd) + rulesAddCmd.Flags().BoolP("allow", "a", false, "indicates this is an allow rule") + rulesAddCmd.Flags().BoolP("regex", "r", false, "indicates this is a regex rule") +} + +var rulesAddCmd = &cobra.Command{ + Use: "add ", + Short: "Add a global rule or user rule", + Long: `Add a global rule or user rule.`, + Args: cobra.ExactArgs(1), + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + allow := mustGetBool(cmd.Flags(), "allow") + regex := mustGetBool(cmd.Flags(), "regex") + exp := args[0] + + if regex { + regexp.MustCompile(exp) + } + + rule := rules.Rule{ + Allow: allow, + Regex: regex, + } + + if regex { + rule.Regexp = &rules.Regexp{Raw: exp} + } else { + rule.Path = exp + } + + user := func(u *users.User) { + u.Rules = append(u.Rules, rule) + err := d.store.Users.Save(u) + checkErr(err) + } + + global := func(s *settings.Settings) { + s.Rules = append(s.Rules, rule) + err := d.store.Settings.Save(s) + checkErr(err) + } + + runRules(d.store, cmd, user, global) + }, pythonConfig{}), +} diff --git a/backend/cmd/rules_ls.go b/backend/cmd/rules_ls.go new file mode 100644 index 00000000..e0e5f8f8 --- /dev/null +++ b/backend/cmd/rules_ls.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + rulesCmd.AddCommand(rulesLsCommand) +} + +var rulesLsCommand = &cobra.Command{ + Use: "ls", + Short: "List global rules or user specific rules", + Long: `List global rules or user specific rules.`, + Args: cobra.NoArgs, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + runRules(d.store, cmd, nil, nil) + }, pythonConfig{}), +} diff --git a/backend/cmd/users_add.go b/backend/cmd/users_add.go new file mode 100644 index 00000000..eac81240 --- /dev/null +++ b/backend/cmd/users_add.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + usersCmd.AddCommand(usersAddCmd) + addUserFlags(usersAddCmd.Flags()) +} + +var usersAddCmd = &cobra.Command{ + Use: "add ", + Short: "Create a new user", + Long: `Create a new user and add it to the database.`, + Args: cobra.ExactArgs(2), //nolint:gomnd + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + s, err := d.store.Settings.Get() + checkErr(err) + getUserDefaults(cmd.Flags(), &s.UserDefaults, false) + + password, err := users.HashPwd(args[1]) + checkErr(err) + + user := &users.User{ + Username: args[0], + Password: password, + LockPassword: mustGetBool(cmd.Flags(), "lockPassword"), + } + + s.UserDefaults.Apply(user) + + servSettings, err := d.store.Settings.GetServer() + checkErr(err) + // since getUserDefaults() polluted s.Defaults.Scope + // which makes the Scope not the one saved in the db + // we need the right s.Defaults.Scope here + s2, err := d.store.Settings.Get() + checkErr(err) + + userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root) + checkErr(err) + user.Scope = userHome + + err = d.store.Users.Save(user) + checkErr(err) + printUsers([]*users.User{user}) + }, pythonConfig{}), +} diff --git a/backend/cmd/users_export.go b/backend/cmd/users_export.go new file mode 100644 index 00000000..c0160f68 --- /dev/null +++ b/backend/cmd/users_export.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + usersCmd.AddCommand(usersExportCmd) +} + +var usersExportCmd = &cobra.Command{ + Use: "export ", + Short: "Export all users to a file.", + Long: `Export all users to a json or yaml file. Please indicate the +path to the file where you want to write the users.`, + Args: jsonYamlArg, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + list, err := d.store.Users.Gets("") + checkErr(err) + + err = marshal(args[0], list) + checkErr(err) + }, pythonConfig{}), +} diff --git a/backend/cmd/users_find.go b/backend/cmd/users_find.go new file mode 100644 index 00000000..b00959c4 --- /dev/null +++ b/backend/cmd/users_find.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + usersCmd.AddCommand(usersFindCmd) + usersCmd.AddCommand(usersLsCmd) +} + +var usersFindCmd = &cobra.Command{ + Use: "find ", + Short: "Find a user by username or id", + Long: `Find a user by username or id. If no flag is set, all users will be printed.`, + Args: cobra.ExactArgs(1), + Run: findUsers, +} + +var usersLsCmd = &cobra.Command{ + Use: "ls", + Short: "List all users.", + Args: cobra.NoArgs, + Run: findUsers, +} + +var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) { + var ( + list []*users.User + user *users.User + err error + ) + + if len(args) == 1 { + username, id := parseUsernameOrID(args[0]) + if username != "" { + user, err = d.store.Users.Get("", username) + } else { + user, err = d.store.Users.Get("", id) + } + + list = []*users.User{user} + } else { + list, err = d.store.Users.Gets("") + } + + checkErr(err) + printUsers(list) +}, pythonConfig{}) diff --git a/backend/cmd/users_import.go b/backend/cmd/users_import.go new file mode 100644 index 00000000..d5be6e9c --- /dev/null +++ b/backend/cmd/users_import.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + usersCmd.AddCommand(usersImportCmd) + usersImportCmd.Flags().Bool("overwrite", false, "overwrite users with the same id/username combo") + usersImportCmd.Flags().Bool("replace", false, "replace the entire user base") +} + +var usersImportCmd = &cobra.Command{ + Use: "import ", + Short: "Import users from a file", + Long: `Import users from a file. The path must be for a json or yaml +file. You can use this command to import new users to your +installation. For that, just don't place their ID on the files +list or set it to 0.`, + Args: jsonYamlArg, + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + fd, err := os.Open(args[0]) + checkErr(err) + defer fd.Close() + + list := []*users.User{} + err = unmarshal(args[0], &list) + checkErr(err) + + for _, user := range list { + err = user.Clean("") + checkErr(err) + } + + if mustGetBool(cmd.Flags(), "replace") { + oldUsers, err := d.store.Users.Gets("") + checkErr(err) + + err = marshal("users.backup.json", list) + checkErr(err) + + for _, user := range oldUsers { + err = d.store.Users.Delete(user.ID) + checkErr(err) + } + } + + overwrite := mustGetBool(cmd.Flags(), "overwrite") + + for _, user := range list { + onDB, err := d.store.Users.Get("", user.ID) + + // User exists in DB. + if err == nil { + if !overwrite { + checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred")) + } + + // If the usernames mismatch, check if there is another one in the DB + // with the new username. If there is, print an error and cancel the + // operation + if user.Username != onDB.Username { + if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet + checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID)) + } + } + } else { + // If it doesn't exist, set the ID to 0 to automatically get a new + // one that make sense in this DB. + user.ID = 0 + } + + err = d.store.Users.Save(user) + checkErr(err) + } + }, pythonConfig{}), +} + +func usernameConflictError(username string, originalID, newID uint) error { + return fmt.Errorf(`can't import user with ID %d and username "%s" because the username is already registred with the user %d`, + newID, username, originalID) +} diff --git a/backend/cmd/users_rm.go b/backend/cmd/users_rm.go new file mode 100644 index 00000000..e3fef01b --- /dev/null +++ b/backend/cmd/users_rm.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + usersCmd.AddCommand(usersRmCmd) +} + +var usersRmCmd = &cobra.Command{ + Use: "rm ", + Short: "Delete a user by username or id", + Long: `Delete a user by username or id`, + Args: cobra.ExactArgs(1), + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + username, id := parseUsernameOrID(args[0]) + var err error + + if username != "" { + err = d.store.Users.Delete(username) + } else { + err = d.store.Users.Delete(id) + } + + checkErr(err) + fmt.Println("user deleted successfully") + }, pythonConfig{}), +} diff --git a/backend/cmd/users_update.go b/backend/cmd/users_update.go new file mode 100644 index 00000000..09bb4b8d --- /dev/null +++ b/backend/cmd/users_update.go @@ -0,0 +1,75 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/users" +) + +func init() { + usersCmd.AddCommand(usersUpdateCmd) + + usersUpdateCmd.Flags().StringP("password", "p", "", "new password") + usersUpdateCmd.Flags().StringP("username", "u", "", "new username") + addUserFlags(usersUpdateCmd.Flags()) +} + +var usersUpdateCmd = &cobra.Command{ + Use: "update ", + Short: "Updates an existing user", + Long: `Updates an existing user. Set the flags for the +options you want to change.`, + Args: cobra.ExactArgs(1), + Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + username, id := parseUsernameOrID(args[0]) + flags := cmd.Flags() + password := mustGetString(flags, "password") + newUsername := mustGetString(flags, "username") + + var ( + err error + user *users.User + ) + + if id != 0 { + user, err = d.store.Users.Get("", id) + } else { + user, err = d.store.Users.Get("", username) + } + + checkErr(err) + + defaults := settings.UserDefaults{ + Scope: user.Scope, + Locale: user.Locale, + ViewMode: user.ViewMode, + SingleClick: user.SingleClick, + Perm: user.Perm, + Sorting: user.Sorting, + Commands: user.Commands, + } + getUserDefaults(flags, &defaults, false) + user.Scope = defaults.Scope + user.Locale = defaults.Locale + user.ViewMode = defaults.ViewMode + user.SingleClick = defaults.SingleClick + user.Perm = defaults.Perm + user.Commands = defaults.Commands + user.Sorting = defaults.Sorting + user.LockPassword = mustGetBool(flags, "lockPassword") + + if newUsername != "" { + user.Username = newUsername + } + + if password != "" { + user.Password, err = users.HashPwd(password) + checkErr(err) + } + + err = d.store.Users.Update(user) + checkErr(err) + printUsers([]*users.User{user}) + }, pythonConfig{}), +} diff --git a/backend/cmd/utils.go b/backend/cmd/utils.go index ef0b9a7c..0ae50265 100644 --- a/backend/cmd/utils.go +++ b/backend/cmd/utils.go @@ -85,8 +85,7 @@ func dbExists(path string) (bool, error) { func python(fn pythonFunc, cfg pythonConfig) cobraFunc { return func(cmd *cobra.Command, args []string) { data := pythonData{hadDB: true} - - path := getParam(cmd.Flags(), "database") + path := settings.GlobalConfiguration.Server.Database exists, err := dbExists(path) if err != nil { @@ -100,6 +99,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { data.hadDB = exists db, err := storm.Open(path) checkErr(err) + defer db.Close() data.store, err = bolt.NewStorage(db) checkErr(err) diff --git a/backend/cmd/version.go b/backend/cmd/version.go new file mode 100644 index 00000000..2e085103 --- /dev/null +++ b/backend/cmd/version.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/gtsteffaniak/filebrowser/version" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA) + }, +} diff --git a/backend/filebrowser.yml b/backend/filebrowser.yml index f68917f4..8a5d8ddf 100644 --- a/backend/filebrowser.yml +++ b/backend/filebrowser.yml @@ -4,8 +4,8 @@ server: socket: "" tlsKey: "" tlsCert: "" - enableThumbnails: true - resizePreview: false + enableThumbnails: false + resizePreview: true typeDetectionByHeader: true port: 8080 baseURL: "/" @@ -26,7 +26,7 @@ auth: frontend: name: "" disableExternal: false - disableUsedPercentage: false + disableUsedPercentage: true files: "" theme: "" color: "" @@ -34,19 +34,19 @@ userDefaults: scope: "" locale: "" viewMode: "" - singleClick: false + singleClick: true sorting: by: "" - asc: false + asc: true perm: - admin: false - execute: false - create: false - rename: false - modify: false - delete: false - share: false - download: false + admin: true + execute: true + create: true + rename: true + modify: true + delete: true + share: true + download: true commands: [] - hideDotfiles: false + hideDotfiles: true dateFormat: false