diff --git a/cmd/root.go b/cmd/root.go index 1beace66..5b314b61 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -312,9 +312,10 @@ func setupLog(logMethod string) { func quickSetup(flags *pflag.FlagSet, d pythonData) { set := &settings.Settings{ - Key: generateKey(), - Signup: false, - CreateUserDir: false, + Key: generateKey(), + Signup: false, + CreateUserDir: false, + UserHomeBasePath: settings.DefaultUsersHomeBasePath, Defaults: settings.UserDefaults{ Scope: ".", Locale: "en", @@ -330,6 +331,11 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) { Download: true, }, }, + AuthMethod: "", + Branding: settings.Branding{}, + Commands: nil, + Shell: nil, + Rules: nil, } var err error diff --git a/frontend/src/components/settings/UserForm.vue b/frontend/src/components/settings/UserForm.vue index 15504a11..b6bd3877 100644 --- a/frontend/src/components/settings/UserForm.vue +++ b/frontend/src/components/settings/UserForm.vue @@ -24,12 +24,18 @@

+

+ + {{ $t("settings.createUserHomeDirectory") }} +

@@ -69,17 +75,35 @@ import { enableExec } from "@/utils/constants"; export default { name: "user", + data: () => { + return { + createUserDirData: false, + originalUserScope: "/", + }; + }, components: { Permissions, Languages, Rules, Commands, }, - props: ["user", "isNew", "isDefault"], + props: ["user", "createUserDir", "isNew", "isDefault"], + created() { + this.originalUserScope = this.user.scope; + this.createUserDirData = this.createUserDir; + }, computed: { passwordPlaceholder() { return this.isNew ? "" : this.$t("settings.avoidChanges"); }, + scopePlaceholder() { + return this.createUserDir + ? this.$t("settings.userScopeGenerationPlaceholder") + : ""; + }, + displayHomeDirectoryCheckbox() { + return this.isNew && this.createUserDir; + }, isExecEnabled: () => enableExec, }, watch: { @@ -87,6 +111,9 @@ export default { if (!this.user.perm.admin) return; this.user.lockPassword = false; }, + createUserDirData() { + this.user.scope = this.createUserDirData ? "" : this.originalUserScope; + }, }, }; diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 182c6c08..6aaa6145 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -182,6 +182,9 @@ "commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.", "commandsUpdated": "Commands updated!", "createUserDir": "Auto create user home dir while adding new user", + "userHomeBasePath": "Base path for user home directories", + "userScopeGenerationPlaceholder": "The scope will be auto generated", + "createUserHomeDirectory": "Create user home directory", "customStylesheet": "Custom Stylesheet", "defaultUserDescription": "This are the default settings for new users.", "disableExternalLinks": "Disable external links (except documentation)", diff --git a/frontend/src/views/settings/Global.vue b/frontend/src/views/settings/Global.vue index 7d4e91fc..10993325 100644 --- a/frontend/src/views/settings/Global.vue +++ b/frontend/src/views/settings/Global.vue @@ -18,6 +18,15 @@ {{ $t("settings.createUserDir") }}

+
+

{{ $t("settings.userHomeBasePath") }}

+ +
+

{{ $t("settings.rules") }}

{{ $t("settings.globalRules") }}

diff --git a/frontend/src/views/settings/User.vue b/frontend/src/views/settings/User.vue index 09193e33..95d0352a 100644 --- a/frontend/src/views/settings/User.vue +++ b/frontend/src/views/settings/User.vue @@ -9,7 +9,12 @@
- +
@@ -73,6 +78,7 @@ export default { error: null, originalUser: null, user: {}, + createUserDir: false, }; }, created() { @@ -98,7 +104,8 @@ export default { try { if (this.isNew) { - let { defaults } = await settings.get(); + let { defaults, createUserDir } = await settings.get(); + this.createUserDir = createUserDir; this.user = { ...defaults, username: "", diff --git a/go.mod b/go.mod index 6253cbb5..f0c025fe 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/mholt/archiver/v3 v3.5.1 github.com/mitchellh/go-homedir v1.1.0 github.com/pelletier/go-toml/v2 v2.0.0 + github.com/shirou/gopsutil/v3 v3.22.5 github.com/spf13/afero v1.8.2 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 @@ -39,6 +40,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-acme/lego v2.5.0+incompatible // indirect github.com/go-errors/errors v1.1.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/geo v0.0.0-20200319012246-673a6f80352d // indirect github.com/golang/snappy v0.0.2 // indirect github.com/google/uuid v1.1.2 // indirect @@ -55,12 +57,13 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/shirou/gopsutil/v3 v3.22.5 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/ulikunitz/xz v0.5.9 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 1ef3e038..6cc2c5d4 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,7 @@ github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -245,6 +246,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -289,6 +291,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= diff --git a/http/settings.go b/http/settings.go index 0148b383..78ce9b0d 100644 --- a/http/settings.go +++ b/http/settings.go @@ -9,24 +9,26 @@ import ( ) type settingsData struct { - Signup bool `json:"signup"` - CreateUserDir bool `json:"createUserDir"` - Defaults settings.UserDefaults `json:"defaults"` - Rules []rules.Rule `json:"rules"` - Branding settings.Branding `json:"branding"` - Shell []string `json:"shell"` - Commands map[string][]string `json:"commands"` + Signup bool `json:"signup"` + CreateUserDir bool `json:"createUserDir"` + UserHomeBasePath string `json:"userHomeBasePath"` + Defaults settings.UserDefaults `json:"defaults"` + Rules []rules.Rule `json:"rules"` + Branding settings.Branding `json:"branding"` + Shell []string `json:"shell"` + Commands map[string][]string `json:"commands"` } var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { data := &settingsData{ - Signup: d.settings.Signup, - CreateUserDir: d.settings.CreateUserDir, - Defaults: d.settings.Defaults, - Rules: d.settings.Rules, - Branding: d.settings.Branding, - Shell: d.settings.Shell, - Commands: d.settings.Commands, + Signup: d.settings.Signup, + CreateUserDir: d.settings.CreateUserDir, + UserHomeBasePath: d.settings.UserHomeBasePath, + Defaults: d.settings.Defaults, + Rules: d.settings.Rules, + Branding: d.settings.Branding, + Shell: d.settings.Shell, + Commands: d.settings.Commands, } return renderJSON(w, r, data) @@ -41,6 +43,7 @@ var settingsPutHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d.settings.Signup = req.Signup d.settings.CreateUserDir = req.CreateUserDir + d.settings.UserHomeBasePath = req.UserHomeBasePath d.settings.Defaults = req.Defaults d.settings.Rules = req.Rules d.settings.Branding = req.Branding diff --git a/settings/dir.go b/settings/dir.go index a1971c05..25289ee4 100644 --- a/settings/dir.go +++ b/settings/dir.go @@ -2,9 +2,10 @@ package settings import ( "errors" + "fmt" "log" "os" - "path/filepath" + "path" "regexp" "strings" @@ -19,47 +20,23 @@ var ( // MakeUserDir makes the user directory according to settings. func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) { - var err error userScope = strings.TrimSpace(userScope) - if userScope == "" || userScope == "./" { - userScope = "." + if userScope == "" && s.CreateUserDir { + username = cleanUsername(username) + if username == "" || username == "-" || username == "." { + log.Printf("create user: invalid user for home dir creation: [%s]", username) + return "", errors.New("invalid user for home dir creation") + } + userScope = path.Join(s.UserHomeBasePath, username) } - if !s.CreateUserDir { - return userScope, nil - } + userScope = path.Join("/", userScope) fs := afero.NewBasePathFs(afero.NewOsFs(), serverRoot) - - // Use the default auto create logic only if specific scope is not the default scope - if userScope != s.Defaults.Scope { - // Try create the dir, for example: settings.Defaults.Scope == "." and userScope == "./foo" - if userScope != "." { - err = fs.MkdirAll(userScope, os.ModePerm) - if err != nil { - log.Printf("create user: failed to mkdir user home dir: [%s]", userScope) - } - } - return userScope, err + if err := fs.MkdirAll(userScope, os.ModePerm); err != nil { + return "", fmt.Errorf("failed to create user home dir: [%s]: %w", userScope, err) } - - // Clean username first - username = cleanUsername(username) - if username == "" || username == "-" || username == "." { - log.Printf("create user: invalid user for home dir creation: [%s]", username) - return "", errors.New("invalid user for home dir creation") - } - - // Create default user dir - userHomeBase := filepath.Join(s.Defaults.Scope, "users") - userHome := filepath.Join(userHomeBase, username) - err = fs.MkdirAll(userHome, os.ModePerm) - if err != nil { - log.Printf("create user: failed to mkdir user home dir: [%s]", userHome) - } else { - log.Printf("create user: mkdir user home dir: [%s] successfully.", userHome) - } - return userHome, err + return userScope, nil } func cleanUsername(s string) string { diff --git a/settings/settings.go b/settings/settings.go index 9cd45af6..9e0c4fe2 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -7,20 +7,23 @@ import ( "github.com/filebrowser/filebrowser/v2/rules" ) +const DefaultUsersHomeBasePath = "/users" + // AuthMethod describes an authentication method. type AuthMethod string // Settings contain the main settings of the application. type Settings struct { - Key []byte `json:"key"` - Signup bool `json:"signup"` - CreateUserDir bool `json:"createUserDir"` - 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"` + Key []byte `json:"key"` + Signup bool `json:"signup"` + CreateUserDir bool `json:"createUserDir"` + UserHomeBasePath string `json:"userHomeBasePath"` + 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"` } // GetRules implements rules.Provider. diff --git a/settings/storage.go b/settings/storage.go index d88f5c28..8498d3bf 100644 --- a/settings/storage.go +++ b/settings/storage.go @@ -26,7 +26,14 @@ func NewStorage(back StorageBackend) *Storage { // Get returns the settings for the current instance. func (s *Storage) Get() (*Settings, error) { - return s.back.Get() + set, err := s.back.Get() + if err != nil { + return nil, err + } + if set.UserHomeBasePath == "" { + set.UserHomeBasePath = DefaultUsersHomeBasePath + } + return set, nil } var defaultEvents = []string{ diff --git a/users/users.go b/users/users.go index ab5421bf..120e4599 100644 --- a/users/users.go +++ b/users/users.go @@ -92,11 +92,7 @@ func (u *User) Clean(baseScope string, fields ...string) error { if u.Fs == nil { scope := u.Scope - - if !filepath.IsAbs(scope) { - scope = filepath.Join(baseScope, scope) - } - + scope = filepath.Join(baseScope, filepath.Join("/", scope)) //nolint:gocritic u.Fs = afero.NewBasePathFs(afero.NewOsFs(), scope) }