mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
Admin UI: allow to create multiple users/folders from templates
the clone button is not needed anymore, you can select a user and click on template to generate one or more similar users or you can create users/folders from an empty template Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
ef626befb1
commit
467708dc1c
17 changed files with 247 additions and 122 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
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,32 +1749,26 @@ 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)))
|
||||
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{
|
||||
|
@ -1753,7 +1776,6 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
|
|||
},
|
||||
}}
|
||||
renderUserPage(w, r, &user, userPageModeAdd, "")
|
||||
}
|
||||
}
|
||||
|
||||
func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Change my password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
{{if eq .Mode 3}}
|
||||
<div class="card mb-4 border-left-info">
|
||||
<div class="card-body">
|
||||
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.
|
||||
<br>
|
||||
The following placeholder is supported:
|
||||
<br><br>
|
||||
|
@ -31,7 +31,7 @@
|
|||
{{if eq .Mode 3}}
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
Folders
|
||||
<b>Folders</b>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
|
@ -80,7 +80,12 @@
|
|||
{{template "fshtml" .FsWrapper}}
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">{{if eq .Mode 3}}Generate and export folders{{else}}Submit{{end}}</button>
|
||||
<div class="col-sm-12 text-right px-0">
|
||||
{{if eq .Mode 3}}
|
||||
<button type="submit" class="btn btn-secondary mt-3 px-5" name="form_action" value="export_from_template">Generate and export folders</button>
|
||||
{{end}}
|
||||
<button type="submit" class="btn btn-primary mt-3 ml-3 px-5" name="form_action" value="submit">{{if eq .Mode 3}}Generate and save new folders{{else}}Submit{{end}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -149,8 +149,9 @@ function deleteAction() {
|
|||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.template = {
|
||||
text: 'Template',
|
||||
text: '<i class="fas fa-clone"></i>',
|
||||
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}}
|
||||
|
|
|
@ -62,8 +62,7 @@
|
|||
<label for="idS3AccessSecret" class="col-sm-2 col-form-label">Access Secret</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" id="idS3AccessSecret" name="s3_access_secret" placeholder=""
|
||||
value="{{if .S3Config.AccessSecret.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.S3Config.AccessSecret.GetPayload}}{{end}}"
|
||||
maxlength="1000">
|
||||
value="{{if .S3Config.AccessSecret.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.S3Config.AccessSecret.GetPayload}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -235,8 +234,7 @@
|
|||
<label for="idAzAccountKey" class="col-sm-2 col-form-label">Account Key</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idAzAccountKey" name="az_account_key" placeholder=""
|
||||
value="{{if .AzBlobConfig.AccountKey.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.AccountKey.GetPayload}}{{end}}"
|
||||
maxlength="1000">
|
||||
value="{{if .AzBlobConfig.AccountKey.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.AccountKey.GetPayload}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -244,7 +242,7 @@
|
|||
<label for="idAzSASURL" class="col-sm-2 col-form-label">SAS URL</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idAzSASURL" name="az_sas_url" placeholder=""
|
||||
value="{{if .AzBlobConfig.SASURL.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.SASURL.GetPayload}}{{end}}" maxlength="1000">
|
||||
value="{{if .AzBlobConfig.SASURL.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.SASURL.GetPayload}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row fsconfig fsconfig-azblobfs">
|
||||
|
@ -313,8 +311,7 @@
|
|||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idCryptPassphrase" name="crypt_passphrase"
|
||||
placeholder=""
|
||||
value="{{if .CryptConfig.Passphrase.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.CryptConfig.Passphrase.GetPayload}}{{end}}"
|
||||
maxlength="1000">
|
||||
value="{{if .CryptConfig.Passphrase.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.CryptConfig.Passphrase.GetPayload}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -348,8 +345,7 @@
|
|||
<label for="idSFTPPassword" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" id="idSFTPPassword" name="sftp_password" placeholder=""
|
||||
value="{{if .SFTPConfig.Password.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.SFTPConfig.Password.GetPayload}}{{end}}"
|
||||
maxlength="1000">
|
||||
value="{{if .SFTPConfig.Password.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.SFTPConfig.Password.GetPayload}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Import</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Import</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{{if eq .Mode 3}}
|
||||
<div class="card mb-4 border-left-info">
|
||||
<div class="card-body">
|
||||
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.
|
||||
<br>
|
||||
The following placeholders are supported:
|
||||
<br><br>
|
||||
|
@ -42,7 +42,7 @@
|
|||
{{if eq .Mode 3}}
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
Users
|
||||
<b>Users</b>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-4">For each user set the username and at least one of the password and public key</h6>
|
||||
|
@ -753,7 +753,12 @@
|
|||
|
||||
<input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">{{if eq .Mode 3}}Generate and export users{{else}}Submit{{end}}</button>
|
||||
<div class="col-sm-12 text-right px-0">
|
||||
{{if eq .Mode 3}}
|
||||
<button type="submit" class="btn btn-secondary mt-3 px-5" name="form_action" value="export_from_template">Generate and export users</button>
|
||||
{{end}}
|
||||
<button type="submit" class="btn btn-primary mt-3 ml-3 px-5" name="form_action" value="submit">{{if eq .Mode 3}}Generate and save new users{{else}}Submit{{end}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -153,21 +153,10 @@
|
|||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.clone = {
|
||||
text: '<i class="fas fa-clone"></i>',
|
||||
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: '<i class="fas fa-clone"></i>',
|
||||
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}}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Change my password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
{{end}}
|
||||
{{if .CanSubmit}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
|
||||
<input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue