diff --git a/dataprovider/user.go b/dataprovider/user.go index 22ba6ac0..5636aca3 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -344,16 +344,10 @@ func (u *User) IsTLSUsernameVerificationEnabled() bool { // SetEmptySecrets sets to empty any user secret func (u *User) SetEmptySecrets() { - u.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret() - u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret() - u.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret() - u.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret() - u.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret() - u.FsConfig.SFTPConfig.Password = kms.NewEmptySecret() - u.FsConfig.SFTPConfig.PrivateKey = kms.NewEmptySecret() + u.FsConfig.SetEmptySecrets() for idx := range u.VirtualFolders { folder := &u.VirtualFolders[idx] - folder.FsConfig.SetEmptySecretsIfNil() + folder.FsConfig.SetEmptySecrets() } u.Filters.TOTPConfig.Secret = kms.NewEmptySecret() } @@ -572,6 +566,9 @@ func (u *User) AddVirtualDirs(list []os.FileInfo, virtualPath string) []os.FileI for index := range list { for dir := range vdirs { if list[index].Name() == dir { + if !list[index].IsDir() { + list[index] = vfs.NewFileInfo(dir, true, 0, time.Now(), false) + } delete(vdirs, dir) } } diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index 26c79c9e..8344a270 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -4104,11 +4104,6 @@ func TestProviderErrors(t *testing.T) { setJWTCookieForReq(req, testServerToken) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr) - req, err = http.NewRequest(http.MethodGet, webUserPath+"?clone-from=user", nil) - assert.NoError(t, err) - setJWTCookieForReq(req, testServerToken) - rr = executeRequest(req) - checkResponseCode(t, http.StatusInternalServerError, rr) req, err = http.NewRequest(http.MethodGet, webTemplateUser+"?from=auser", nil) assert.NoError(t, err) setJWTCookieForReq(req, testServerToken) @@ -13596,28 +13591,6 @@ func TestRenderUserTemplateMock(t *testing.T) { assert.NoError(t, err) } -func TestRenderWebCloneUserMock(t *testing.T) { - token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass) - assert.NoError(t, err) - user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) - assert.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?clone-from=%v", user.Username), nil) - assert.NoError(t, err) - setJWTCookieForReq(req, token) - rr := executeRequest(req) - checkResponseCode(t, http.StatusOK, rr) - - req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?clone-from=%v", altAdminPassword), nil) - assert.NoError(t, err) - setJWTCookieForReq(req, token) - rr = executeRequest(req) - checkResponseCode(t, http.StatusNotFound, rr) - - _, err = httpdtest.RemoveUser(user, http.StatusOK) - assert.NoError(t, err) -} - func TestUserTemplateWithFoldersMock(t *testing.T) { folder := vfs.BaseVirtualFolder{ Name: "vfolder", @@ -13659,6 +13632,7 @@ func TestUserTemplateWithFoldersMock(t *testing.T) { form.Add("tpl_username", "auser1") form.Add("tpl_password", "password") form.Add("tpl_public_keys", "") + form.Set("form_action", "export_from_template") b, contentType, _ := getMultipartFormData(form, "", "") req, _ := http.NewRequest(http.MethodPost, path.Join(webTemplateUser), &b) setJWTCookieForReq(req, token) @@ -13714,6 +13688,73 @@ func TestUserTemplateWithFoldersMock(t *testing.T) { assert.NoError(t, err) } +func TestUserSaveFromTemplateMock(t *testing.T) { + token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass) + assert.NoError(t, err) + csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) + assert.NoError(t, err) + user1 := "u1" + user2 := "u2" + form := make(url.Values) + form.Set("username", "") + form.Set("home_dir", filepath.Join(os.TempDir(), "%username%")) + form.Set("upload_bandwidth", "0") + form.Set("download_bandwidth", "0") + form.Set("uid", "0") + form.Set("gid", "0") + form.Set("max_sessions", "0") + form.Set("quota_size", "0") + form.Set("quota_files", "0") + form.Set("permissions", "*") + form.Set("status", "1") + form.Set("expiration_date", "") + form.Set("fs_provider", "0") + form.Set("max_upload_file_size", "0") + form.Add("tpl_username", user1) + form.Add("tpl_password", "password1") + form.Add("tpl_public_keys", " ") + form.Add("tpl_username", user2) + form.Add("tpl_public_keys", testPubKey) + form.Set(csrfFormToken, csrfToken) + b, contentType, _ := getMultipartFormData(form, "", "") + req, _ := http.NewRequest(http.MethodPost, webTemplateUser, &b) + setJWTCookieForReq(req, token) + req.Header.Set("Content-Type", contentType) + rr := executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + + u1, _, err := httpdtest.GetUserByUsername(user1, http.StatusOK) + assert.NoError(t, err) + u2, _, err := httpdtest.GetUserByUsername(user2, http.StatusOK) + assert.NoError(t, err) + + _, err = httpdtest.RemoveUser(u1, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveUser(u2, http.StatusOK) + assert.NoError(t, err) + + err = dataprovider.Close() + assert.NoError(t, err) + + b, contentType, _ = getMultipartFormData(form, "", "") + req, err = http.NewRequest(http.MethodPost, webTemplateUser, &b) + assert.NoError(t, err) + setJWTCookieForReq(req, token) + req.Header.Set("Content-Type", contentType) + rr = executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, rr) + assert.Contains(t, rr.Body.String(), "Cannot save the defined users") + + err = config.LoadConfig(configDir, "") + assert.NoError(t, err) + providerConf := config.GetProviderConf() + providerConf.CredentialsPath = credentialsPath + err = os.RemoveAll(credentialsPath) + assert.NoError(t, err) + err = dataprovider.Initialize(providerConf, configDir, true) + assert.NoError(t, err) +} + func TestUserTemplateMock(t *testing.T) { token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass) assert.NoError(t, err) @@ -13760,6 +13801,7 @@ func TestUserTemplateMock(t *testing.T) { form.Set("s3_download_part_max_time", "0") // test invalid s3_upload_part_size form.Set("s3_upload_part_size", "a") + form.Set("form_action", "export_from_template") b, contentType, _ := getMultipartFormData(form, "", "") req, _ := http.NewRequest(http.MethodPost, webTemplateUser, &b) setJWTCookieForReq(req, token) @@ -13798,7 +13840,7 @@ func TestUserTemplateMock(t *testing.T) { req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr) - require.Contains(t, rr.Body.String(), "No valid users found, export is not possible") + require.Contains(t, rr.Body.String(), "No valid users defined, unable to complete the requested action") form.Set("tpl_username", "user1") form.Set("tpl_password", "password1") @@ -13853,6 +13895,59 @@ func TestUserTemplateMock(t *testing.T) { require.True(t, user2.Filters.DisableFsChecks) } +func TestFolderSaveFromTemplateMock(t *testing.T) { + folder1 := "f1" + folder2 := "f2" + token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass) + assert.NoError(t, err) + csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) + assert.NoError(t, err) + form := make(url.Values) + form.Set("name", "name") + form.Set("mapped_path", filepath.Join(os.TempDir(), "%name%")) + form.Set("description", "desc folder %name%") + form.Add("tpl_foldername", folder1) + form.Add("tpl_foldername", folder2) + form.Set(csrfFormToken, csrfToken) + b, contentType, _ := getMultipartFormData(form, "", "") + req, err := http.NewRequest(http.MethodPost, webTemplateFolder, &b) + assert.NoError(t, err) + setJWTCookieForReq(req, token) + req.Header.Set("Content-Type", contentType) + rr := executeRequest(req) + checkResponseCode(t, http.StatusSeeOther, rr) + + _, _, err = httpdtest.GetFolderByName(folder1, http.StatusOK) + assert.NoError(t, err) + _, _, err = httpdtest.GetFolderByName(folder2, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder1}, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder2}, http.StatusOK) + assert.NoError(t, err) + + err = dataprovider.Close() + assert.NoError(t, err) + + b, contentType, _ = getMultipartFormData(form, "", "") + req, err = http.NewRequest(http.MethodPost, webTemplateFolder, &b) + assert.NoError(t, err) + setJWTCookieForReq(req, token) + req.Header.Set("Content-Type", contentType) + rr = executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, rr) + assert.Contains(t, rr.Body.String(), "Cannot save the defined folders") + + err = config.LoadConfig(configDir, "") + assert.NoError(t, err) + providerConf := config.GetProviderConf() + providerConf.CredentialsPath = credentialsPath + err = os.RemoveAll(credentialsPath) + assert.NoError(t, err) + err = dataprovider.Initialize(providerConf, configDir, true) + assert.NoError(t, err) +} + func TestFolderTemplateMock(t *testing.T) { folderName := "vfolder-template" mappedPath := filepath.Join(os.TempDir(), "%name%mapped%name%path") @@ -13869,6 +13964,7 @@ func TestFolderTemplateMock(t *testing.T) { form.Add("tpl_foldername", "folder3") form.Add("tpl_foldername", "folder1 ") form.Add("tpl_foldername", " ") + form.Set("form_action", "export_from_template") b, contentType, _ := getMultipartFormData(form, "", "") req, _ := http.NewRequest(http.MethodPost, webTemplateFolder, &b) setJWTCookieForReq(req, token) @@ -13971,7 +14067,7 @@ func TestFolderTemplateMock(t *testing.T) { req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr) - assert.Contains(t, rr.Body.String(), "No folders to export") + assert.Contains(t, rr.Body.String(), "No valid folders defined") form.Set("tpl_foldername", "name") form.Set("mapped_path", "relative-path") diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 1b8e3d81..5e003a92 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -508,6 +508,16 @@ func TestInvalidToken(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") + rr = httptest.NewRecorder() + handleWebTemplateFolderPost(rr, req) + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "invalid token claims") + + rr = httptest.NewRecorder() + handleWebTemplateUserPost(rr, req) + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "invalid token claims") + rr = httptest.NewRecorder() updateFolder(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) diff --git a/httpd/webadmin.go b/httpd/webadmin.go index 6bb1b581..c49550d4 100644 --- a/httpd/webadmin.go +++ b/httpd/webadmin.go @@ -1544,7 +1544,7 @@ func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) { updatedAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderAddUpdateAdminPage(w, r, &updatedAdmin, fmt.Sprintf("Invalid token claims: %v", err), false) + renderAddUpdateAdminPage(w, r, &updatedAdmin, "Invalid token claims", false) return } if username == claims.Username { @@ -1610,6 +1610,7 @@ func handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("from") folder, err := dataprovider.GetFolderByName(name) if err == nil { + folder.FsConfig.SetEmptySecrets() renderFolderPage(w, r, folder, folderPageModeTemplate, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { renderNotFoundPage(w, r, err) @@ -1624,8 +1625,13 @@ func handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) { func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + claims, err := getTokenClaims(r) + if err != nil || claims.Username == "" { + renderBadRequestPage(w, r, errors.New("invalid token claims")) + return + } templateFolder := vfs.BaseVirtualFolder{} - err := r.ParseMultipartForm(maxRequestSize) + err = r.ParseMultipartForm(maxRequestSize) if err != nil { renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "") return @@ -1653,19 +1659,30 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { for _, tmpl := range foldersFields { f := getFolderFromTemplate(templateFolder, tmpl) if err := dataprovider.ValidateFolder(&f); err != nil { - renderMessagePage(w, r, fmt.Sprintf("Error validating folder %#v", f.Name), "", http.StatusBadRequest, err, "") + renderMessagePage(w, r, "Folder validation error", fmt.Sprintf("Error validating folder %#v", f.Name), + http.StatusBadRequest, err, "") return } dump.Folders = append(dump.Folders, f) } if len(dump.Folders) == 0 { - renderMessagePage(w, r, "No folders to export", "No valid folders found, export is not possible", http.StatusBadRequest, nil, "") + renderMessagePage(w, r, "No folders defined", "No valid folders defined, unable to complete the requested action", + http.StatusBadRequest, nil, "") return } - - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-folders-from-template.json\"", len(dump.Folders))) - render.JSON(w, r, dump) + if r.Form.Get("form_action") == "export_from_template" { + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-folders-from-template.json\"", + len(dump.Folders))) + render.JSON(w, r, dump) + return + } + if err = RestoreFolders(dump.Folders, "", 1, 0, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { + renderMessagePage(w, r, "Unable to save folders", "Cannot save the defined folders:", + getRespStatus(err), err, "") + return + } + http.Redirect(w, r, webFoldersPath, http.StatusSeeOther) } func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { @@ -1675,6 +1692,7 @@ func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { user, err := dataprovider.UserExists(username) if err == nil { user.SetEmptySecrets() + user.PublicKeys = nil user.Email = "" user.Description = "" renderUserPage(w, r, &user, userPageModeTemplate, "") @@ -1684,13 +1702,23 @@ func handleWebTemplateUserGet(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, userPageModeTemplate, "") } } func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + claims, err := getTokenClaims(r) + if err != nil || claims.Username == "" { + renderBadRequestPage(w, r, errors.New("invalid token claims")) + return + } templateUser, err := getUserFromPostFields(r) if err != nil { renderMessagePage(w, r, "Error parsing user fields", "", http.StatusBadRequest, err, "") @@ -1708,7 +1736,8 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { for _, tmpl := range userTmplFields { u := getUserFromTemplate(templateUser, tmpl) if err := dataprovider.ValidateUser(&u); err != nil { - renderMessagePage(w, r, fmt.Sprintf("Error validating user %#v", u.Username), "", http.StatusBadRequest, err, "") + renderMessagePage(w, r, "User validation error", fmt.Sprintf("Error validating user %#v", u.Username), + http.StatusBadRequest, err, "") return } dump.Users = append(dump.Users, u) @@ -1720,40 +1749,33 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { } if len(dump.Users) == 0 { - renderMessagePage(w, r, "No users to export", "No valid users found, export is not possible", http.StatusBadRequest, nil, "") + renderMessagePage(w, r, "No users defined", "No valid users defined, unable to complete the requested action", + http.StatusBadRequest, nil, "") return } - - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-users-from-template.json\"", len(dump.Users))) - render.JSON(w, r, dump) + if r.Form.Get("form_action") == "export_from_template" { + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-users-from-template.json\"", + len(dump.Users))) + render.JSON(w, r, dump) + return + } + if err = RestoreUsers(dump.Users, "", 1, 0, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { + renderMessagePage(w, r, "Unable to save users", "Cannot save the defined users:", + getRespStatus(err), err, "") + return + } + http.Redirect(w, r, webUsersPath, http.StatusSeeOther) } func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - if r.URL.Query().Get("clone-from") != "" { - username := r.URL.Query().Get("clone-from") - user, err := dataprovider.UserExists(username) - if err == nil { - user.ID = 0 - user.Username = "" - user.Password = "" - user.PublicKeys = nil - user.SetEmptySecrets() - renderUserPage(w, r, &user, userPageModeAdd, "") - } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) - } else { - renderInternalServerErrorPage(w, r, err) - } - } else { - user := dataprovider.User{BaseUser: sdk.BaseUser{ - Status: 1, - Permissions: map[string][]string{ - "/": {dataprovider.PermAny}, - }, - }} - renderUserPage(w, r, &user, userPageModeAdd, "") - } + user := dataprovider.User{BaseUser: sdk.BaseUser{ + Status: 1, + Permissions: map[string][]string{ + "/": {dataprovider.PermAny}, + }, + }} + renderUserPage(w, r, &user, userPageModeAdd, "") } func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) { diff --git a/templates/webadmin/admin.html b/templates/webadmin/admin.html index 6ce1b409..ab2b6ae7 100644 --- a/templates/webadmin/admin.html +++ b/templates/webadmin/admin.html @@ -111,7 +111,7 @@ - + diff --git a/templates/webadmin/changepassword.html b/templates/webadmin/changepassword.html index 72deeee1..fe87f53a 100644 --- a/templates/webadmin/changepassword.html +++ b/templates/webadmin/changepassword.html @@ -37,7 +37,7 @@ - + diff --git a/templates/webadmin/folder.html b/templates/webadmin/folder.html index 6c092991..d4d8d1ea 100644 --- a/templates/webadmin/folder.html +++ b/templates/webadmin/folder.html @@ -16,7 +16,7 @@ {{if eq .Mode 3}}
- Generate a data provider independent JSON file to create new folders or update existing ones. + Create and save one or more new folders or generate a data provider independent JSON file to import.
The following placeholder is supported:

@@ -31,7 +31,7 @@ {{if eq .Mode 3}}
- Folders + Folders
@@ -80,7 +80,12 @@ {{template "fshtml" .FsWrapper}} - +
+ {{if eq .Mode 3}} + + {{end}} + +
diff --git a/templates/webadmin/folders.html b/templates/webadmin/folders.html index ee90a82a..2ce8d992 100644 --- a/templates/webadmin/folders.html +++ b/templates/webadmin/folders.html @@ -149,8 +149,9 @@ function deleteAction() { }; $.fn.dataTable.ext.buttons.template = { - text: 'Template', + text: '', name: 'template', + titleAttr: "Template", action: function (e, dt, node, config) { var selectedRows = table.rows({ selected: true }).count(); if (selectedRows == 1){ @@ -254,14 +255,14 @@ function deleteAction() { table.button().add(0,'quota_scan'); {{end}} - {{if .LoggedAdmin.HasPermission "manage_system"}} - table.button().add(0,'template'); - {{end}} - {{if .LoggedAdmin.HasPermission "del_users"}} table.button().add(0,'delete'); {{end}} + {{if .LoggedAdmin.HasPermission "add_users"}} + table.button().add(0,'template'); + {{end}} + {{if .LoggedAdmin.HasPermission "edit_users"}} table.button().add(0,'edit'); {{end}} diff --git a/templates/webadmin/fsconfig.html b/templates/webadmin/fsconfig.html index a6daa959..66bad834 100644 --- a/templates/webadmin/fsconfig.html +++ b/templates/webadmin/fsconfig.html @@ -62,8 +62,7 @@
+ value="{{if .S3Config.AccessSecret.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.S3Config.AccessSecret.GetPayload}}{{end}}">
@@ -235,8 +234,7 @@
+ value="{{if .AzBlobConfig.AccountKey.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.AccountKey.GetPayload}}{{end}}">
@@ -244,7 +242,7 @@
+ value="{{if .AzBlobConfig.SASURL.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.SASURL.GetPayload}}{{end}}">
@@ -313,8 +311,7 @@
+ value="{{if .CryptConfig.Passphrase.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.CryptConfig.Passphrase.GetPayload}}{{end}}">
@@ -348,8 +345,7 @@
+ value="{{if .SFTPConfig.Password.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.SFTPConfig.Password.GetPayload}}{{end}}">
diff --git a/templates/webadmin/maintenance.html b/templates/webadmin/maintenance.html index ca903d39..90b6b730 100644 --- a/templates/webadmin/maintenance.html +++ b/templates/webadmin/maintenance.html @@ -45,7 +45,7 @@ - + diff --git a/templates/webadmin/profile.html b/templates/webadmin/profile.html index 42b447c4..979193c6 100644 --- a/templates/webadmin/profile.html +++ b/templates/webadmin/profile.html @@ -43,7 +43,7 @@ - + diff --git a/templates/webadmin/user.html b/templates/webadmin/user.html index 725be749..fc5ebe31 100644 --- a/templates/webadmin/user.html +++ b/templates/webadmin/user.html @@ -22,7 +22,7 @@ {{if eq .Mode 3}}
- Generate a data provider independent JSON file to create new users or update existing ones. + Create and save one or more new users or generate a data provider independent JSON file to import.
The following placeholders are supported:

@@ -42,7 +42,7 @@ {{if eq .Mode 3}}
- Users + Users
For each user set the username and at least one of the password and public key
@@ -753,7 +753,12 @@ - +
+ {{if eq .Mode 3}} + + {{end}} + +
diff --git a/templates/webadmin/users.html b/templates/webadmin/users.html index ca4d375f..69796594 100644 --- a/templates/webadmin/users.html +++ b/templates/webadmin/users.html @@ -153,21 +153,10 @@ enabled: false }; - $.fn.dataTable.ext.buttons.clone = { - text: '', - name: 'clone', - titleAttr: "Clone", - action: function (e, dt, node, config) { - var username = dt.row({ selected: true }).data()[1]; - var path = '{{.UserURL}}' + "?clone-from=" + encodeURIComponent(username); - window.location.href = path; - }, - enabled: false - }; - $.fn.dataTable.ext.buttons.template = { - text: 'Template', + text: '', name: 'template', + titleAttr: "Template", action: function (e, dt, node, config) { var selectedRows = table.rows({ selected: true }).count(); if (selectedRows == 1){ @@ -277,16 +266,12 @@ table.button().add(0,'quota_scan'); {{end}} - {{if .LoggedAdmin.HasPermission "manage_system"}} - table.button().add(0,'template'); - {{end}} - {{if .LoggedAdmin.HasPermission "del_users"}} table.button().add(0,'delete'); {{end}} {{if .LoggedAdmin.HasPermission "add_users"}} - table.button().add(0,'clone'); + table.button().add(0,'template'); {{end}} {{if .LoggedAdmin.HasPermission "edit_users"}} @@ -304,9 +289,6 @@ {{if .LoggedAdmin.HasPermission "edit_users"}} table.button('edit:name').enable(selectedRows == 1); {{end}} - {{if .LoggedAdmin.HasPermission "add_users"}} - table.button('clone:name').enable(selectedRows == 1); - {{end}} {{if .LoggedAdmin.HasPermission "del_users"}} table.button('delete:name').enable(selectedRows == 1); {{end}} diff --git a/templates/webclient/changepassword.html b/templates/webclient/changepassword.html index 9f85d306..af35b451 100644 --- a/templates/webclient/changepassword.html +++ b/templates/webclient/changepassword.html @@ -37,7 +37,7 @@
- +
diff --git a/templates/webclient/profile.html b/templates/webclient/profile.html index fde922ba..f2bb3915 100644 --- a/templates/webclient/profile.html +++ b/templates/webclient/profile.html @@ -88,7 +88,7 @@ {{end}} {{if .CanSubmit}} - + {{end}} diff --git a/templates/webclient/share.html b/templates/webclient/share.html index da62482b..c3ec23b9 100644 --- a/templates/webclient/share.html +++ b/templates/webclient/share.html @@ -135,7 +135,7 @@ - + diff --git a/vfs/filesystem.go b/vfs/filesystem.go index d21f9e24..a3fe2434 100644 --- a/vfs/filesystem.go +++ b/vfs/filesystem.go @@ -27,6 +27,17 @@ type Filesystem struct { SFTPConfig SFTPFsConfig `json:"sftpconfig,omitempty"` } +// SetEmptySecrets sets the secrets to empty +func (f *Filesystem) SetEmptySecrets() { + f.S3Config.AccessSecret = kms.NewEmptySecret() + f.GCSConfig.Credentials = kms.NewEmptySecret() + f.AzBlobConfig.AccountKey = kms.NewEmptySecret() + f.AzBlobConfig.SASURL = kms.NewEmptySecret() + f.CryptConfig.Passphrase = kms.NewEmptySecret() + f.SFTPConfig.Password = kms.NewEmptySecret() + f.SFTPConfig.PrivateKey = kms.NewEmptySecret() +} + // SetEmptySecretsIfNil sets the secrets to empty if nil func (f *Filesystem) SetEmptySecretsIfNil() { if f.S3Config.AccessSecret == nil {