From ef626befb1b8d1e28910033db3f5b8bb8b66ec0f Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Mon, 10 Jan 2022 19:44:16 +0100 Subject: [PATCH] web admin: simplify user page The page to add/edit users should be less less intimidating now. All the advanced settings are hidden by default. Permissions are set to any, so if you also have a users base dir set, to add a user you have to simply set username, password or public key and save Signed-off-by: Nicola Murino --- common/common_test.go | 5 +- dataprovider/dataprovider.go | 5 + dataprovider/user.go | 26 +- go.mod | 2 +- go.sum | 4 +- httpd/webadmin.go | 45 +- templates/webadmin/folder.html | 12 +- templates/webadmin/fsconfig.html | 35 +- templates/webadmin/user.html | 888 +++++++++++++++++-------------- 9 files changed, 574 insertions(+), 448 deletions(-) diff --git a/common/common_test.go b/common/common_test.go index 2b6b9590..eea8221d 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -831,7 +831,10 @@ func TestParseAllowedIPAndRanges(t *testing.T) { } func TestHideConfidentialData(t *testing.T) { - for _, provider := range sdk.ListProviders() { + for _, provider := range []sdk.FilesystemProvider{sdk.LocalFilesystemProvider, + sdk.CryptedFilesystemProvider, sdk.S3FilesystemProvider, sdk.GCSFilesystemProvider, + sdk.AzureBlobFilesystemProvider, sdk.SFTPFilesystemProvider, + } { u := dataprovider.User{ FsConfig: vfs.Filesystem{ Provider: provider, diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 9ea23217..f920c019 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -457,6 +457,11 @@ func GetQuotaTracking() int { return config.TrackQuota } +// HasUsersBaseDir returns true if users base dir is set +func HasUsersBaseDir() bool { + return config.UsersBaseDir != "" +} + // Provider defines the interface that data providers must implement. type Provider interface { validateUserAndPass(username, password, ip, protocol string) (User, error) diff --git a/dataprovider/user.go b/dataprovider/user.go index 8405a54d..22ba6ac0 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -561,19 +561,25 @@ func (u *User) AddVirtualDirs(list []os.FileInfo, virtualPath string) []os.FileI return list } + vdirs := make(map[string]bool) for dir := range u.GetVirtualFoldersInPath(virtualPath) { - fi := vfs.NewFileInfo(dir, true, 0, time.Now(), false) - found := false - for index := range list { - if list[index].Name() == fi.Name() { - list[index] = fi - found = true - break + vdirs[path.Base(dir)] = true + } + if len(vdirs) == 0 { + return list + } + + for index := range list { + for dir := range vdirs { + if list[index].Name() == dir { + delete(vdirs, dir) } } - if !found { - list = append(list, fi) - } + } + + for dir := range vdirs { + fi := vfs.NewFileInfo(dir, true, 0, time.Now(), false) + list = append(list, fi) } return list } diff --git a/go.mod b/go.mod index 135008c4..caaea430 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/rs/cors v1.8.2 github.com/rs/xid v1.3.0 github.com/rs/zerolog v1.26.2-0.20211219225053-665519c4da50 - github.com/sftpgo/sdk v0.0.0-20220106101837-50e87c59705a + github.com/sftpgo/sdk v0.0.0-20220110174344-ecf586dd8941 github.com/shirou/gopsutil/v3 v3.21.13-0.20220106132423-a3ae4bc40d26 github.com/spf13/afero v1.8.0 github.com/spf13/cobra v1.3.0 diff --git a/go.sum b/go.sum index 47ac017d..a438d5f6 100644 --- a/go.sum +++ b/go.sum @@ -747,8 +747,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY= -github.com/sftpgo/sdk v0.0.0-20220106101837-50e87c59705a h1:JJc19rE0eW2knPa/KIFYvqyu25CwzKltJ5Cw1kK3o4A= -github.com/sftpgo/sdk v0.0.0-20220106101837-50e87c59705a/go.mod h1:Bhgac6kiwIziILXLzH4wepT8lQXyhF83poDXqZorN6Q= +github.com/sftpgo/sdk v0.0.0-20220110174344-ecf586dd8941 h1:CxKFDSYekL6+dOZ9rSglYGwcXyhM4Aki6yDsdiPlJ5Y= +github.com/sftpgo/sdk v0.0.0-20220110174344-ecf586dd8941/go.mod h1:Bhgac6kiwIziILXLzH4wepT8lQXyhF83poDXqZorN6Q= github.com/shirou/gopsutil/v3 v3.21.13-0.20220106132423-a3ae4bc40d26 h1:nkvraEu1xs6D3AimiR9SkIOCG6lVvVZRfwbbQ7fX1DY= github.com/shirou/gopsutil/v3 v3.21.13-0.20220106132423-a3ae4bc40d26/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= diff --git a/httpd/webadmin.go b/httpd/webadmin.go index 144e3a0e..6bb1b581 100644 --- a/httpd/webadmin.go +++ b/httpd/webadmin.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "os" "path/filepath" "strconv" "strings" @@ -142,6 +143,13 @@ type statusPage struct { Status ServicesStatus } +type fsWrapper struct { + vfs.Filesystem + IsUserPage bool + HasUsersBaseDir bool + DirPath string +} + type userPage struct { basePage User *dataprovider.User @@ -155,6 +163,8 @@ type userPage struct { RedactedSecret string Mode userPageMode VirtualFolders []vfs.BaseVirtualFolder + CanImpersonate bool + FsWrapper fsWrapper } type adminPage struct { @@ -207,9 +217,10 @@ type setupPage struct { type folderPage struct { basePage - Folder vfs.BaseVirtualFolder - Error string - Mode folderPageMode + Folder vfs.BaseVirtualFolder + Error string + Mode folderPageMode + FsWrapper fsWrapper } type messagePage struct { @@ -307,7 +318,12 @@ func loadAdminTemplates(templatesPath string) { } fsBaseTpl := template.New("fsBaseTemplate").Funcs(template.FuncMap{ - "ListFSProviders": sdk.ListProviders, + "ListFSProviders": func() []sdk.FilesystemProvider { + return []sdk.FilesystemProvider{sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider, + sdk.S3FilesystemProvider, sdk.GCSFilesystemProvider, + sdk.AzureBlobFilesystemProvider, sdk.SFTPFilesystemProvider, + } + }, }) usersTmpl := util.LoadTemplate(nil, usersPaths...) userTmpl := util.LoadTemplate(fsBaseTpl, userPaths...) @@ -594,6 +610,13 @@ func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.U WebClientOptions: sdk.WebClientOptions, RootDirPerms: user.GetPermissionsForPath("/"), VirtualFolders: folders, + CanImpersonate: os.Getuid() == 0, + FsWrapper: fsWrapper{ + Filesystem: user.FsConfig, + IsUserPage: true, + HasUsersBaseDir: dataprovider.HasUsersBaseDir(), + DirPath: user.HomeDir, + }, } renderAdminTemplate(w, templateUser, data) } @@ -619,6 +642,12 @@ func renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVir Error: error, Folder: folder, Mode: mode, + FsWrapper: fsWrapper{ + Filesystem: folder.FsConfig, + IsUserPage: false, + HasUsersBaseDir: false, + DirPath: folder.MappedPath, + }, } renderAdminTemplate(w, templateFolder, data) } @@ -1708,6 +1737,7 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { user.ID = 0 user.Username = "" user.Password = "" + user.PublicKeys = nil user.SetEmptySecrets() renderUserPage(w, r, &user, userPageModeAdd, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { @@ -1716,7 +1746,12 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { renderInternalServerErrorPage(w, r, err) } } else { - user := dataprovider.User{BaseUser: sdk.BaseUser{Status: 1}} + user := dataprovider.User{BaseUser: sdk.BaseUser{ + Status: 1, + Permissions: map[string][]string{ + "/": {dataprovider.PermAny}, + }, + }} renderUserPage(w, r, &user, userPageModeAdd, "") } } diff --git a/templates/webadmin/folder.html b/templates/webadmin/folder.html index 6d9f9304..6c092991 100644 --- a/templates/webadmin/folder.html +++ b/templates/webadmin/folder.html @@ -76,18 +76,8 @@ -
- -
- - - Required for local providers. For Cloud providers, if set, it will store temporary files - -
-
- {{template "fshtml" .Folder.FsConfig}} + {{template "fshtml" .FsWrapper}} diff --git a/templates/webadmin/fsconfig.html b/templates/webadmin/fsconfig.html index b7aff5df..a6daa959 100644 --- a/templates/webadmin/fsconfig.html +++ b/templates/webadmin/fsconfig.html @@ -1,5 +1,8 @@ {{define "fshtml"}}
+
+ Filesystem +
@@ -12,7 +15,29 @@
- + {{if .IsUserPage}} +
+ +
+ + + {{if not .DirPath}}{{if .HasUsersBaseDir}}Leave blank for an appropriate default{{else}}Required for local storage providers. For non-local filesystems it will store temporary files, you can leave blank for an appropriate default{{end}}{{end}} + +
+
+ {{else}} +
+ +
+ + + Required for local storage providers. For non-local filesystems it will store temporary files, you can leave blank for an appropriate default + +
+
+ {{end}}
@@ -123,7 +148,7 @@
+ value="{{.S3Config.KeyPrefix}}" aria-describedby="S3KeyPrefixHelpBlock"> Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/". @@ -176,7 +201,7 @@
+ value="{{.GCSConfig.KeyPrefix}}" aria-describedby="GCSKeyPrefixHelpBlock"> Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/". @@ -268,7 +293,7 @@
+ value="{{.AzBlobConfig.KeyPrefix}}" aria-describedby="AzKeyPrefixHelpBlock"> Similar to a chroot for local filesystem. Cannot start with "/". Example: "somedir/subdir/". @@ -352,7 +377,7 @@
+ value="{{.SFTPConfig.Prefix}}" aria-describedby="SFTPPrefixHelpBlock"> Similar to a chroot for local filesystem. Example: "/somedir/subdir". diff --git a/templates/webadmin/user.html b/templates/webadmin/user.html index 96f01423..725be749 100644 --- a/templates/webadmin/user.html +++ b/templates/webadmin/user.html @@ -86,45 +86,6 @@
{{end}} -
- -
- -
-
- -
- -
- - - Optional description, for example the user full name - -
-
- -
- -
- -
-
- -
- -
- -
-
-
-
-
{{if ne .Mode 3}}
@@ -135,7 +96,7 @@
- Public keys + Public keys
@@ -177,43 +138,11 @@
{{end}} -
- -
- - - Defines the TLS certificate field to use as username. Ignored if mutual TLS is disabled - -
-
- -
- -
- -
-
- -
- -
- -
-
- - {{template "fshtml" .User.FsConfig}} + {{template "fshtml" .FsWrapper}} {{if .VirtualFolders}}
- Virtual folders + Virtual folders
Quota -1 means included within user quota, 0 unlimited. Don't set -1 for shared folders
@@ -298,385 +227,516 @@
{{end}} -
-
- Per-directory permissions -
-
-
-
- {{range $idx, $dirPerms := .User.GetSubDirPermissions -}} -
-
- -
-
- + +
-
- +
+ +
+ +
+ +
+
+
- {{else}} -
-
- + +
+ +
+
-
- + + + Allow to impersonate this user, in REST API, with an API key + +
+
+ +
+ +
+ + + Optional description, for example the user full name + +
+
+ +
+ +
+ + + Free form text field + +
+
+ +
+
+
+
+
+

+ +

+
+ +
+
+
+ +
+
-
- +
+ +
+
+ Per-directory permissions +
+
+
+
+ {{range $idx, $dirPerms := .User.GetSubDirPermissions -}} +
+
+ +
+
+ +
+
+ +
+
+ {{else}} +
+
+ +
+
+ +
+
+ +
+
+ {{end}} +
+
+ +
+ +
- {{end}} -
-
-
- -
-
-
+
+
+ Per-directory file patterns +
+
+
Comma separated denied or allowed files, based on shell patterns
+
+
+ {{range $idx, $pattern := .User.GetFlatFilePatterns -}} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {{else}} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {{end}} +
+
-
-
- - - - Disable checks for existence and automatic creation of home directory and virtual folders - -
-
+
+ +
+
+
-
- -
- -
-
- -
- -
-
+
+ +
+ + + Maximun number of concurrent sessions. 0 means no limit + +
+
-
- -
- - - 0 means no limit - -
-
- -
- - - 0 means no limit - -
-
+
+ +
+ +
+
-
- -
- - - 0 means no limit - -
-
- -
- - - 0 means no limit - -
-
+
+ +
+ +
+
-
- -
- - - 0 means no limit - -
-
- -
- - - 0 means no limit - -
-
+
+ +
+ +
+
-
-
- Per-source bandwidth limits -
-
-
-
- {{range $idx, $bwLimit := .User.Filters.BandwidthLimits -}} -
-
- - +
+ +
+ + Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
-
-
- - - UL (KB/s). 0 means no limit - -
-
- - - DL (KB/s). 0 means no limit - -
-
- -
- -
- {{else}} -
-
- - + +
+ +
+ + Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
-
-
- - - UL (KB/s). 0 means no limit - -
-
- - - DL (KB/s). 0 means no limit - -
-
-
- -
- {{end}} +
- -
- +
+
+
+

+ +

-
-
+
+
-
- -
- -
-
- -
- -
- -
-
- -
-
- - - - Allow to impersonate this user, in REST API, with an API key - -
-
- -
- -
- - - Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32" - -
-
- -
- -
- - - Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32" - -
-
- -
-
- Per-directory file patterns -
-
-
Comma separated denied or allowed files, based on shell patterns
-
-
- {{range $idx, $pattern := .User.GetFlatFilePatterns -}} -
-
- +
+ +
+ + + 0 means no limit +
-
- -
-
- -
-
- +
+ +
+ + + 0 means no limit +
- {{else}} -
-
- -
-
- -
-
- -
-
- + +
+ +
+ + + Maximum upload size for a single file. 0 means no limit +
- {{end}} + +
+ +
+ + + 0 means no limit + +
+
+ +
+ + + 0 means no limit + +
+
+ +
+
+ Per-source bandwidth limits +
+
+
+
+ {{range $idx, $bwLimit := .User.Filters.BandwidthLimits -}} +
+
+ + + Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32" + +
+
+
+ + + UL (KB/s). 0 means no limit + +
+
+ + + DL (KB/s). 0 means no limit + +
+
+ +
+ +
+
+ {{else}} +
+
+ + + Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32" + +
+
+
+ + + UL (KB/s). 0 means no limit + +
+
+ + + DL (KB/s). 0 means no limit + +
+
+
+ +
+
+ {{end}} +
+
+ +
+ +
+
+
+
+
-
- +
+
+

+ +

+
+
+
+ +
+ +
+ + + Defines the TLS certificate field to use as username. Ignored if mutual TLS is disabled + +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + + + Disable checks for existence and automatic creation of home directory and virtual folders + +
+
+ +
+ +
+ +
+
+ +
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- - - Free form text field - -
-
+
{{if eq .Mode 2}}
@@ -704,7 +764,9 @@