2021-05-06 19:35:43 +00:00
|
|
|
package httpd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2022-01-10 18:44:16 +00:00
|
|
|
"os"
|
2021-05-06 19:35:43 +00:00
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-chi/render"
|
2022-01-06 10:54:43 +00:00
|
|
|
"github.com/sftpgo/sdk"
|
|
|
|
sdkkms "github.com/sftpgo/sdk/kms"
|
2021-05-06 19:35:43 +00:00
|
|
|
|
2021-06-26 05:31:41 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/common"
|
|
|
|
"github.com/drakkan/sftpgo/v2/dataprovider"
|
2022-01-06 09:11:47 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/kms"
|
2021-09-04 10:11:04 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/mfa"
|
2021-11-13 12:25:43 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/smtp"
|
2021-07-11 13:26:51 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/util"
|
2021-06-26 05:31:41 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/version"
|
|
|
|
"github.com/drakkan/sftpgo/v2/vfs"
|
2021-05-06 19:35:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type userPageMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
userPageModeAdd userPageMode = iota + 1
|
|
|
|
userPageModeUpdate
|
|
|
|
userPageModeTemplate
|
|
|
|
)
|
|
|
|
|
|
|
|
type folderPageMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
folderPageModeAdd folderPageMode = iota + 1
|
|
|
|
folderPageModeUpdate
|
|
|
|
folderPageModeTemplate
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
templateAdminDir = "webadmin"
|
|
|
|
templateBase = "base.html"
|
2021-09-04 10:11:04 +00:00
|
|
|
templateBaseLogin = "baselogin.html"
|
2021-05-06 19:35:43 +00:00
|
|
|
templateFsConfig = "fsconfig.html"
|
|
|
|
templateUsers = "users.html"
|
|
|
|
templateUser = "user.html"
|
|
|
|
templateAdmins = "admins.html"
|
|
|
|
templateAdmin = "admin.html"
|
|
|
|
templateConnections = "connections.html"
|
|
|
|
templateFolders = "folders.html"
|
|
|
|
templateFolder = "folder.html"
|
|
|
|
templateMessage = "message.html"
|
|
|
|
templateStatus = "status.html"
|
|
|
|
templateLogin = "login.html"
|
2021-06-08 11:24:28 +00:00
|
|
|
templateDefender = "defender.html"
|
2021-09-29 16:46:15 +00:00
|
|
|
templateProfile = "profile.html"
|
|
|
|
templateChangePwd = "changepassword.html"
|
2021-05-06 19:35:43 +00:00
|
|
|
templateMaintenance = "maintenance.html"
|
2021-09-04 10:11:04 +00:00
|
|
|
templateMFA = "mfa.html"
|
2021-05-14 17:21:15 +00:00
|
|
|
templateSetup = "adminsetup.html"
|
2021-05-06 19:35:43 +00:00
|
|
|
pageUsersTitle = "Users"
|
|
|
|
pageAdminsTitle = "Admins"
|
|
|
|
pageConnectionsTitle = "Connections"
|
|
|
|
pageStatusTitle = "Status"
|
|
|
|
pageFoldersTitle = "Folders"
|
2021-09-29 16:46:15 +00:00
|
|
|
pageProfileTitle = "My profile"
|
|
|
|
pageChangePwdTitle = "Change password"
|
2021-05-06 19:35:43 +00:00
|
|
|
pageMaintenanceTitle = "Maintenance"
|
2021-06-08 11:24:28 +00:00
|
|
|
pageDefenderTitle = "Defender"
|
2021-11-13 12:25:43 +00:00
|
|
|
pageForgotPwdTitle = "SFTPGo Admin - Forgot password"
|
|
|
|
pageResetPwdTitle = "SFTPGo Admin - Reset password"
|
2021-05-14 17:21:15 +00:00
|
|
|
pageSetupTitle = "Create first admin user"
|
2021-05-06 19:35:43 +00:00
|
|
|
defaultQueryLimit = 500
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
adminTemplates = make(map[string]*template.Template)
|
|
|
|
)
|
|
|
|
|
|
|
|
type basePage struct {
|
|
|
|
Title string
|
|
|
|
CurrentURL string
|
|
|
|
UsersURL string
|
|
|
|
UserURL string
|
|
|
|
UserTemplateURL string
|
|
|
|
AdminsURL string
|
|
|
|
AdminURL string
|
|
|
|
QuotaScanURL string
|
|
|
|
ConnectionsURL string
|
|
|
|
FoldersURL string
|
|
|
|
FolderURL string
|
|
|
|
FolderTemplateURL string
|
2021-06-08 11:24:28 +00:00
|
|
|
DefenderURL string
|
2021-05-06 19:35:43 +00:00
|
|
|
LogoutURL string
|
2021-09-29 16:46:15 +00:00
|
|
|
ProfileURL string
|
|
|
|
ChangePwdURL string
|
2021-09-04 10:11:04 +00:00
|
|
|
MFAURL string
|
2021-05-06 19:35:43 +00:00
|
|
|
FolderQuotaScanURL string
|
|
|
|
StatusURL string
|
|
|
|
MaintenanceURL string
|
|
|
|
StaticURL string
|
|
|
|
UsersTitle string
|
|
|
|
AdminsTitle string
|
|
|
|
ConnectionsTitle string
|
|
|
|
FoldersTitle string
|
|
|
|
StatusTitle string
|
|
|
|
MaintenanceTitle string
|
2021-06-08 11:24:28 +00:00
|
|
|
DefenderTitle string
|
2021-05-06 19:35:43 +00:00
|
|
|
Version string
|
|
|
|
CSRFToken string
|
2021-06-08 11:24:28 +00:00
|
|
|
HasDefender bool
|
2021-05-06 19:35:43 +00:00
|
|
|
LoggedAdmin *dataprovider.Admin
|
|
|
|
}
|
|
|
|
|
|
|
|
type usersPage struct {
|
|
|
|
basePage
|
|
|
|
Users []dataprovider.User
|
|
|
|
}
|
|
|
|
|
|
|
|
type adminsPage struct {
|
|
|
|
basePage
|
|
|
|
Admins []dataprovider.Admin
|
|
|
|
}
|
|
|
|
|
|
|
|
type foldersPage struct {
|
|
|
|
basePage
|
|
|
|
Folders []vfs.BaseVirtualFolder
|
|
|
|
}
|
|
|
|
|
|
|
|
type connectionsPage struct {
|
|
|
|
basePage
|
|
|
|
Connections []*common.ConnectionStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
type statusPage struct {
|
|
|
|
basePage
|
|
|
|
Status ServicesStatus
|
|
|
|
}
|
|
|
|
|
2022-01-10 18:44:16 +00:00
|
|
|
type fsWrapper struct {
|
|
|
|
vfs.Filesystem
|
|
|
|
IsUserPage bool
|
|
|
|
HasUsersBaseDir bool
|
|
|
|
DirPath string
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
type userPage struct {
|
|
|
|
basePage
|
|
|
|
User *dataprovider.User
|
|
|
|
RootPerms []string
|
|
|
|
Error string
|
|
|
|
ValidPerms []string
|
|
|
|
ValidLoginMethods []string
|
|
|
|
ValidProtocols []string
|
|
|
|
WebClientOptions []string
|
|
|
|
RootDirPerms []string
|
|
|
|
RedactedSecret string
|
|
|
|
Mode userPageMode
|
2021-05-23 20:02:01 +00:00
|
|
|
VirtualFolders []vfs.BaseVirtualFolder
|
2022-01-10 18:44:16 +00:00
|
|
|
CanImpersonate bool
|
|
|
|
FsWrapper fsWrapper
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type adminPage struct {
|
|
|
|
basePage
|
|
|
|
Admin *dataprovider.Admin
|
|
|
|
Error string
|
|
|
|
IsAdd bool
|
|
|
|
}
|
|
|
|
|
2021-09-29 16:46:15 +00:00
|
|
|
type profilePage struct {
|
2021-05-06 19:35:43 +00:00
|
|
|
basePage
|
2021-08-17 16:08:32 +00:00
|
|
|
Error string
|
|
|
|
AllowAPIKeyAuth bool
|
2021-09-29 16:46:15 +00:00
|
|
|
Email string
|
|
|
|
Description string
|
|
|
|
}
|
|
|
|
|
|
|
|
type changePasswordPage struct {
|
|
|
|
basePage
|
|
|
|
Error string
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 10:11:04 +00:00
|
|
|
type mfaPage struct {
|
|
|
|
basePage
|
|
|
|
TOTPConfigs []string
|
2022-01-06 09:11:47 +00:00
|
|
|
TOTPConfig dataprovider.AdminTOTPConfig
|
2021-09-04 10:11:04 +00:00
|
|
|
GenerateTOTPURL string
|
|
|
|
ValidateTOTPURL string
|
|
|
|
SaveTOTPURL string
|
|
|
|
RecCodesURL string
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
type maintenancePage struct {
|
|
|
|
basePage
|
|
|
|
BackupPath string
|
|
|
|
RestorePath string
|
|
|
|
Error string
|
|
|
|
}
|
|
|
|
|
2021-06-08 11:24:28 +00:00
|
|
|
type defenderHostsPage struct {
|
|
|
|
basePage
|
|
|
|
DefenderHostsURL string
|
|
|
|
}
|
|
|
|
|
2021-05-14 17:21:15 +00:00
|
|
|
type setupPage struct {
|
|
|
|
basePage
|
|
|
|
Username string
|
|
|
|
Error string
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
type folderPage struct {
|
|
|
|
basePage
|
2022-01-10 18:44:16 +00:00
|
|
|
Folder vfs.BaseVirtualFolder
|
|
|
|
Error string
|
|
|
|
Mode folderPageMode
|
|
|
|
FsWrapper fsWrapper
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type messagePage struct {
|
|
|
|
basePage
|
|
|
|
Error string
|
|
|
|
Success string
|
|
|
|
}
|
|
|
|
|
|
|
|
type userTemplateFields struct {
|
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
PublicKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadAdminTemplates(templatesPath string) {
|
|
|
|
usersPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateUsers),
|
|
|
|
}
|
|
|
|
userPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateUser),
|
|
|
|
}
|
|
|
|
adminsPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateAdmins),
|
|
|
|
}
|
|
|
|
adminPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateAdmin),
|
|
|
|
}
|
2021-09-29 16:46:15 +00:00
|
|
|
profilePaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
2021-09-29 16:46:15 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateProfile),
|
|
|
|
}
|
|
|
|
changePwdPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateChangePwd),
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
connectionsPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateConnections),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
messagePaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateMessage),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
foldersPaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateFolders),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
folderPaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateFolder),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
statusPaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateStatus),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
loginPaths := []string{
|
2021-09-04 10:11:04 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateLogin),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
maintenancePaths := []string{
|
2021-05-06 19:35:43 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateMaintenance),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
defenderPaths := []string{
|
2021-06-08 11:24:28 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateDefender),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
mfaPaths := []string{
|
2021-09-04 10:11:04 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateMFA),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
twoFactorPaths := []string{
|
2021-09-04 10:11:04 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateTwoFactor),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
twoFactorRecoveryPaths := []string{
|
2021-09-04 10:11:04 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateTwoFactorRecovery),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
setupPaths := []string{
|
2021-09-04 10:11:04 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
2021-05-14 17:21:15 +00:00
|
|
|
filepath.Join(templatesPath, templateAdminDir, templateSetup),
|
|
|
|
}
|
2021-11-13 12:25:43 +00:00
|
|
|
forgotPwdPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateCommonDir, templateForgotPassword),
|
|
|
|
}
|
|
|
|
resetPwdPaths := []string{
|
|
|
|
filepath.Join(templatesPath, templateCommonDir, templateResetPassword),
|
|
|
|
}
|
2021-06-05 16:06:53 +00:00
|
|
|
|
2021-10-02 20:25:41 +00:00
|
|
|
fsBaseTpl := template.New("fsBaseTemplate").Funcs(template.FuncMap{
|
2022-01-10 18:44:16 +00:00
|
|
|
"ListFSProviders": func() []sdk.FilesystemProvider {
|
|
|
|
return []sdk.FilesystemProvider{sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider,
|
|
|
|
sdk.S3FilesystemProvider, sdk.GCSFilesystemProvider,
|
|
|
|
sdk.AzureBlobFilesystemProvider, sdk.SFTPFilesystemProvider,
|
|
|
|
}
|
|
|
|
},
|
2021-06-19 12:06:56 +00:00
|
|
|
})
|
2021-09-30 08:23:25 +00:00
|
|
|
usersTmpl := util.LoadTemplate(nil, usersPaths...)
|
|
|
|
userTmpl := util.LoadTemplate(fsBaseTpl, userPaths...)
|
|
|
|
adminsTmpl := util.LoadTemplate(nil, adminsPaths...)
|
|
|
|
adminTmpl := util.LoadTemplate(nil, adminPaths...)
|
|
|
|
connectionsTmpl := util.LoadTemplate(nil, connectionsPaths...)
|
2021-11-13 12:25:43 +00:00
|
|
|
messageTmpl := util.LoadTemplate(nil, messagePaths...)
|
|
|
|
foldersTmpl := util.LoadTemplate(nil, foldersPaths...)
|
|
|
|
folderTmpl := util.LoadTemplate(fsBaseTpl, folderPaths...)
|
|
|
|
statusTmpl := util.LoadTemplate(nil, statusPaths...)
|
|
|
|
loginTmpl := util.LoadTemplate(nil, loginPaths...)
|
2021-09-30 08:23:25 +00:00
|
|
|
profileTmpl := util.LoadTemplate(nil, profilePaths...)
|
|
|
|
changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
|
2021-11-13 12:25:43 +00:00
|
|
|
maintenanceTmpl := util.LoadTemplate(nil, maintenancePaths...)
|
|
|
|
defenderTmpl := util.LoadTemplate(nil, defenderPaths...)
|
|
|
|
mfaTmpl := util.LoadTemplate(nil, mfaPaths...)
|
|
|
|
twoFactorTmpl := util.LoadTemplate(nil, twoFactorPaths...)
|
|
|
|
twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPaths...)
|
|
|
|
setupTmpl := util.LoadTemplate(nil, setupPaths...)
|
|
|
|
forgotPwdTmpl := util.LoadTemplate(nil, forgotPwdPaths...)
|
|
|
|
resetPwdTmpl := util.LoadTemplate(nil, resetPwdPaths...)
|
2021-05-06 19:35:43 +00:00
|
|
|
|
|
|
|
adminTemplates[templateUsers] = usersTmpl
|
|
|
|
adminTemplates[templateUser] = userTmpl
|
|
|
|
adminTemplates[templateAdmins] = adminsTmpl
|
|
|
|
adminTemplates[templateAdmin] = adminTmpl
|
|
|
|
adminTemplates[templateConnections] = connectionsTmpl
|
|
|
|
adminTemplates[templateMessage] = messageTmpl
|
|
|
|
adminTemplates[templateFolders] = foldersTmpl
|
|
|
|
adminTemplates[templateFolder] = folderTmpl
|
|
|
|
adminTemplates[templateStatus] = statusTmpl
|
|
|
|
adminTemplates[templateLogin] = loginTmpl
|
2021-09-29 16:46:15 +00:00
|
|
|
adminTemplates[templateProfile] = profileTmpl
|
|
|
|
adminTemplates[templateChangePwd] = changePwdTmpl
|
2021-05-06 19:35:43 +00:00
|
|
|
adminTemplates[templateMaintenance] = maintenanceTmpl
|
2021-06-08 11:24:28 +00:00
|
|
|
adminTemplates[templateDefender] = defenderTmpl
|
2021-09-04 10:11:04 +00:00
|
|
|
adminTemplates[templateMFA] = mfaTmpl
|
|
|
|
adminTemplates[templateTwoFactor] = twoFactorTmpl
|
|
|
|
adminTemplates[templateTwoFactorRecovery] = twoFactorRecoveryTmpl
|
2021-05-14 17:21:15 +00:00
|
|
|
adminTemplates[templateSetup] = setupTmpl
|
2021-11-13 12:25:43 +00:00
|
|
|
adminTemplates[templateForgotPassword] = forgotPwdTmpl
|
|
|
|
adminTemplates[templateResetPassword] = resetPwdTmpl
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getBasePageData(title, currentURL string, r *http.Request) basePage {
|
|
|
|
var csrfToken string
|
|
|
|
if currentURL != "" {
|
|
|
|
csrfToken = createCSRFToken()
|
|
|
|
}
|
|
|
|
return basePage{
|
|
|
|
Title: title,
|
|
|
|
CurrentURL: currentURL,
|
|
|
|
UsersURL: webUsersPath,
|
|
|
|
UserURL: webUserPath,
|
|
|
|
UserTemplateURL: webTemplateUser,
|
|
|
|
AdminsURL: webAdminsPath,
|
|
|
|
AdminURL: webAdminPath,
|
|
|
|
FoldersURL: webFoldersPath,
|
|
|
|
FolderURL: webFolderPath,
|
|
|
|
FolderTemplateURL: webTemplateFolder,
|
2021-06-08 11:24:28 +00:00
|
|
|
DefenderURL: webDefenderPath,
|
2021-05-06 19:35:43 +00:00
|
|
|
LogoutURL: webLogoutPath,
|
2021-09-29 16:46:15 +00:00
|
|
|
ProfileURL: webAdminProfilePath,
|
|
|
|
ChangePwdURL: webChangeAdminPwdPath,
|
2021-09-04 10:11:04 +00:00
|
|
|
MFAURL: webAdminMFAPath,
|
2021-05-06 19:35:43 +00:00
|
|
|
QuotaScanURL: webQuotaScanPath,
|
|
|
|
ConnectionsURL: webConnectionsPath,
|
|
|
|
StatusURL: webStatusPath,
|
|
|
|
FolderQuotaScanURL: webScanVFolderPath,
|
|
|
|
MaintenanceURL: webMaintenancePath,
|
|
|
|
StaticURL: webStaticFilesPath,
|
|
|
|
UsersTitle: pageUsersTitle,
|
|
|
|
AdminsTitle: pageAdminsTitle,
|
|
|
|
ConnectionsTitle: pageConnectionsTitle,
|
|
|
|
FoldersTitle: pageFoldersTitle,
|
|
|
|
StatusTitle: pageStatusTitle,
|
|
|
|
MaintenanceTitle: pageMaintenanceTitle,
|
2021-06-08 11:24:28 +00:00
|
|
|
DefenderTitle: pageDefenderTitle,
|
2021-05-06 19:35:43 +00:00
|
|
|
Version: version.GetAsString(),
|
|
|
|
LoggedAdmin: getAdminFromToken(r),
|
2021-06-08 11:24:28 +00:00
|
|
|
HasDefender: common.Config.DefenderConfig.Enabled,
|
2021-05-06 19:35:43 +00:00
|
|
|
CSRFToken: csrfToken,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderAdminTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
|
|
|
|
err := adminTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
|
|
|
|
var errorString string
|
|
|
|
if body != "" {
|
|
|
|
errorString = body + " "
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
errorString += err.Error()
|
|
|
|
}
|
|
|
|
data := messagePage{
|
|
|
|
basePage: getBasePageData(title, "", r),
|
|
|
|
Error: errorString,
|
|
|
|
Success: message,
|
|
|
|
}
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
renderAdminTemplate(w, templateMessage, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
|
|
|
|
renderMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
|
|
|
|
renderMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderForbiddenPage(w http.ResponseWriter, r *http.Request, body string) {
|
|
|
|
renderMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
|
|
|
|
renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
|
|
|
|
}
|
|
|
|
|
2021-11-13 12:25:43 +00:00
|
|
|
func renderForgotPwdPage(w http.ResponseWriter, error string) {
|
|
|
|
data := forgotPwdPage{
|
|
|
|
CurrentURL: webAdminForgotPwdPath,
|
|
|
|
Error: error,
|
|
|
|
CSRFToken: createCSRFToken(),
|
|
|
|
StaticURL: webStaticFilesPath,
|
|
|
|
Title: pageForgotPwdTitle,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateForgotPassword, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderResetPwdPage(w http.ResponseWriter, error string) {
|
|
|
|
data := resetPwdPage{
|
|
|
|
CurrentURL: webAdminResetPwdPath,
|
|
|
|
Error: error,
|
|
|
|
CSRFToken: createCSRFToken(),
|
|
|
|
StaticURL: webStaticFilesPath,
|
|
|
|
Title: pageResetPwdTitle,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateResetPassword, data)
|
|
|
|
}
|
|
|
|
|
2021-09-04 10:11:04 +00:00
|
|
|
func renderTwoFactorPage(w http.ResponseWriter, error string) {
|
|
|
|
data := twoFactorPage{
|
|
|
|
CurrentURL: webAdminTwoFactorPath,
|
|
|
|
Version: version.Get().Version,
|
|
|
|
Error: error,
|
|
|
|
CSRFToken: createCSRFToken(),
|
|
|
|
StaticURL: webStaticFilesPath,
|
|
|
|
RecoveryURL: webAdminTwoFactorRecoveryPath,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateTwoFactor, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderTwoFactorRecoveryPage(w http.ResponseWriter, error string) {
|
|
|
|
data := twoFactorPage{
|
|
|
|
CurrentURL: webAdminTwoFactorRecoveryPath,
|
|
|
|
Version: version.Get().Version,
|
|
|
|
Error: error,
|
|
|
|
CSRFToken: createCSRFToken(),
|
|
|
|
StaticURL: webStaticFilesPath,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateTwoFactorRecovery, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderMFAPage(w http.ResponseWriter, r *http.Request) {
|
|
|
|
data := mfaPage{
|
|
|
|
basePage: getBasePageData(pageMFATitle, webAdminMFAPath, r),
|
|
|
|
TOTPConfigs: mfa.GetAvailableTOTPConfigNames(),
|
|
|
|
GenerateTOTPURL: webAdminTOTPGeneratePath,
|
|
|
|
ValidateTOTPURL: webAdminTOTPValidatePath,
|
|
|
|
SaveTOTPURL: webAdminTOTPSavePath,
|
|
|
|
RecCodesURL: webAdminRecoveryCodesPath,
|
|
|
|
}
|
|
|
|
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
|
|
|
|
if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
data.TOTPConfig = admin.Filters.TOTPConfig
|
|
|
|
renderAdminTemplate(w, templateMFA, data)
|
|
|
|
}
|
|
|
|
|
2021-09-29 16:46:15 +00:00
|
|
|
func renderProfilePage(w http.ResponseWriter, r *http.Request, error string) {
|
|
|
|
data := profilePage{
|
|
|
|
basePage: getBasePageData(pageProfileTitle, webAdminProfilePath, r),
|
|
|
|
Error: error,
|
2021-08-17 16:08:32 +00:00
|
|
|
}
|
|
|
|
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
|
|
|
|
if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
2021-08-17 16:08:32 +00:00
|
|
|
data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
|
2021-09-29 16:46:15 +00:00
|
|
|
data.Email = admin.Email
|
|
|
|
data.Description = admin.Description
|
2021-05-06 19:35:43 +00:00
|
|
|
|
2021-09-29 16:46:15 +00:00
|
|
|
renderAdminTemplate(w, templateProfile, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
|
|
|
|
data := changePasswordPage{
|
|
|
|
basePage: getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r),
|
|
|
|
Error: error,
|
|
|
|
}
|
|
|
|
|
|
|
|
renderAdminTemplate(w, templateChangePwd, data)
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) {
|
|
|
|
data := maintenancePage{
|
|
|
|
basePage: getBasePageData(pageMaintenanceTitle, webMaintenancePath, r),
|
|
|
|
BackupPath: webBackupPath,
|
|
|
|
RestorePath: webRestorePath,
|
|
|
|
Error: error,
|
|
|
|
}
|
|
|
|
|
|
|
|
renderAdminTemplate(w, templateMaintenance, data)
|
|
|
|
}
|
|
|
|
|
2021-05-14 17:21:15 +00:00
|
|
|
func renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) {
|
|
|
|
data := setupPage{
|
|
|
|
basePage: getBasePageData(pageSetupTitle, webAdminSetupPath, r),
|
|
|
|
Username: username,
|
|
|
|
Error: error,
|
|
|
|
}
|
|
|
|
|
|
|
|
renderAdminTemplate(w, templateSetup, data)
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
func renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
|
|
|
|
error string, isAdd bool) {
|
|
|
|
currentURL := webAdminPath
|
2021-11-06 13:13:20 +00:00
|
|
|
title := "Add a new admin"
|
2021-05-06 19:35:43 +00:00
|
|
|
if !isAdd {
|
|
|
|
currentURL = fmt.Sprintf("%v/%v", webAdminPath, url.PathEscape(admin.Username))
|
2021-11-06 13:13:20 +00:00
|
|
|
title = "Update admin"
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
data := adminPage{
|
2021-11-06 13:13:20 +00:00
|
|
|
basePage: getBasePageData(title, currentURL, r),
|
2021-05-06 19:35:43 +00:00
|
|
|
Admin: admin,
|
|
|
|
Error: error,
|
|
|
|
IsAdd: isAdd,
|
|
|
|
}
|
|
|
|
|
|
|
|
renderAdminTemplate(w, templateAdmin, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User, mode userPageMode, error string) {
|
2021-05-23 20:02:01 +00:00
|
|
|
folders, err := getWebVirtualFolders(w, r, defaultQueryLimit)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
user.SetEmptySecretsIfNil()
|
|
|
|
var title, currentURL string
|
|
|
|
switch mode {
|
|
|
|
case userPageModeAdd:
|
|
|
|
title = "Add a new user"
|
|
|
|
currentURL = webUserPath
|
|
|
|
case userPageModeUpdate:
|
|
|
|
title = "Update user"
|
|
|
|
currentURL = fmt.Sprintf("%v/%v", webUserPath, url.PathEscape(user.Username))
|
|
|
|
case userPageModeTemplate:
|
|
|
|
title = "User template"
|
|
|
|
currentURL = webTemplateUser
|
|
|
|
}
|
|
|
|
if user.Password != "" && user.IsPasswordHashed() && mode == userPageModeUpdate {
|
|
|
|
user.Password = redactedSecret
|
|
|
|
}
|
|
|
|
user.FsConfig.RedactedSecret = redactedSecret
|
|
|
|
data := userPage{
|
|
|
|
basePage: getBasePageData(title, currentURL, r),
|
|
|
|
Mode: mode,
|
|
|
|
Error: error,
|
|
|
|
User: user,
|
|
|
|
ValidPerms: dataprovider.ValidPerms,
|
|
|
|
ValidLoginMethods: dataprovider.ValidLoginMethods,
|
|
|
|
ValidProtocols: dataprovider.ValidProtocols,
|
2021-07-11 13:26:51 +00:00
|
|
|
WebClientOptions: sdk.WebClientOptions,
|
2021-05-06 19:35:43 +00:00
|
|
|
RootDirPerms: user.GetPermissionsForPath("/"),
|
2021-05-23 20:02:01 +00:00
|
|
|
VirtualFolders: folders,
|
2022-01-10 18:44:16 +00:00
|
|
|
CanImpersonate: os.Getuid() == 0,
|
|
|
|
FsWrapper: fsWrapper{
|
|
|
|
Filesystem: user.FsConfig,
|
|
|
|
IsUserPage: true,
|
|
|
|
HasUsersBaseDir: dataprovider.HasUsersBaseDir(),
|
|
|
|
DirPath: user.HomeDir,
|
|
|
|
},
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateUser, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder, mode folderPageMode, error string) {
|
|
|
|
var title, currentURL string
|
|
|
|
switch mode {
|
|
|
|
case folderPageModeAdd:
|
|
|
|
title = "Add a new folder"
|
|
|
|
currentURL = webFolderPath
|
|
|
|
case folderPageModeUpdate:
|
|
|
|
title = "Update folder"
|
|
|
|
currentURL = fmt.Sprintf("%v/%v", webFolderPath, url.PathEscape(folder.Name))
|
|
|
|
case folderPageModeTemplate:
|
|
|
|
title = "Folder template"
|
|
|
|
currentURL = webTemplateFolder
|
|
|
|
}
|
|
|
|
folder.FsConfig.RedactedSecret = redactedSecret
|
|
|
|
folder.FsConfig.SetEmptySecretsIfNil()
|
|
|
|
|
|
|
|
data := folderPage{
|
|
|
|
basePage: getBasePageData(title, currentURL, r),
|
|
|
|
Error: error,
|
|
|
|
Folder: folder,
|
|
|
|
Mode: mode,
|
2022-01-10 18:44:16 +00:00
|
|
|
FsWrapper: fsWrapper{
|
|
|
|
Filesystem: folder.FsConfig,
|
|
|
|
IsUserPage: false,
|
|
|
|
HasUsersBaseDir: false,
|
|
|
|
DirPath: folder.MappedPath,
|
|
|
|
},
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateFolder, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFoldersForTemplate(r *http.Request) []string {
|
|
|
|
var res []string
|
2021-05-23 20:02:01 +00:00
|
|
|
folderNames := r.Form["tpl_foldername"]
|
2021-05-06 19:35:43 +00:00
|
|
|
folders := make(map[string]bool)
|
2021-05-23 20:02:01 +00:00
|
|
|
for _, name := range folderNames {
|
|
|
|
name = strings.TrimSpace(name)
|
|
|
|
if name == "" {
|
|
|
|
continue
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
if _, ok := folders[name]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
folders[name] = true
|
|
|
|
res = append(res, name)
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUsersForTemplate(r *http.Request) []userTemplateFields {
|
|
|
|
var res []userTemplateFields
|
2021-05-23 20:02:01 +00:00
|
|
|
tplUsernames := r.Form["tpl_username"]
|
|
|
|
tplPasswords := r.Form["tpl_password"]
|
|
|
|
tplPublicKeys := r.Form["tpl_public_keys"]
|
2021-05-06 19:35:43 +00:00
|
|
|
|
2021-05-23 20:02:01 +00:00
|
|
|
users := make(map[string]bool)
|
|
|
|
for idx, username := range tplUsernames {
|
|
|
|
username = strings.TrimSpace(username)
|
|
|
|
password := ""
|
|
|
|
publicKey := ""
|
|
|
|
if len(tplPasswords) > idx {
|
|
|
|
password = strings.TrimSpace(tplPasswords[idx])
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
2021-05-23 20:02:01 +00:00
|
|
|
if len(tplPublicKeys) > idx {
|
|
|
|
publicKey = strings.TrimSpace(tplPublicKeys[idx])
|
|
|
|
}
|
|
|
|
if username == "" || (password == "" && publicKey == "") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, ok := users[username]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
users[username] = true
|
|
|
|
res = append(res, userTemplateFields{
|
|
|
|
Username: username,
|
|
|
|
Password: password,
|
|
|
|
PublicKey: publicKey,
|
|
|
|
})
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
2021-05-23 20:02:01 +00:00
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
|
|
|
|
var virtualFolders []vfs.VirtualFolder
|
2021-05-23 20:02:01 +00:00
|
|
|
folderPaths := r.Form["vfolder_path"]
|
|
|
|
folderNames := r.Form["vfolder_name"]
|
|
|
|
folderQuotaSizes := r.Form["vfolder_quota_size"]
|
|
|
|
folderQuotaFiles := r.Form["vfolder_quota_files"]
|
|
|
|
for idx, p := range folderPaths {
|
|
|
|
p = strings.TrimSpace(p)
|
|
|
|
name := ""
|
|
|
|
if len(folderNames) > idx {
|
|
|
|
name = folderNames[idx]
|
|
|
|
}
|
|
|
|
if p != "" && name != "" {
|
|
|
|
vfolder := vfs.VirtualFolder{
|
|
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
VirtualPath: p,
|
|
|
|
QuotaFiles: -1,
|
|
|
|
QuotaSize: -1,
|
|
|
|
}
|
|
|
|
if len(folderQuotaSizes) > idx {
|
|
|
|
quotaSize, err := strconv.ParseInt(strings.TrimSpace(folderQuotaSizes[idx]), 10, 64)
|
|
|
|
if err == nil {
|
|
|
|
vfolder.QuotaSize = quotaSize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(folderQuotaFiles) > idx {
|
|
|
|
quotaFiles, err := strconv.Atoi(strings.TrimSpace(folderQuotaFiles[idx]))
|
|
|
|
if err == nil {
|
|
|
|
vfolder.QuotaFiles = quotaFiles
|
|
|
|
}
|
|
|
|
}
|
|
|
|
virtualFolders = append(virtualFolders, vfolder)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
return virtualFolders
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
|
|
|
permissions := make(map[string][]string)
|
|
|
|
permissions["/"] = r.Form["permissions"]
|
2021-05-23 20:02:01 +00:00
|
|
|
|
|
|
|
for k := range r.Form {
|
|
|
|
if strings.HasPrefix(k, "sub_perm_path") {
|
|
|
|
p := strings.TrimSpace(r.Form.Get(k))
|
|
|
|
if p != "" {
|
|
|
|
idx := strings.TrimPrefix(k, "sub_perm_path")
|
|
|
|
permissions[p] = r.Form[fmt.Sprintf("sub_perm_permissions%v", idx)]
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-23 20:02:01 +00:00
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
return permissions
|
|
|
|
}
|
|
|
|
|
2021-12-10 17:43:26 +00:00
|
|
|
func getBandwidthLimitsFromPostFields(r *http.Request) ([]sdk.BandwidthLimit, error) {
|
|
|
|
var result []sdk.BandwidthLimit
|
|
|
|
|
|
|
|
for k := range r.Form {
|
|
|
|
if strings.HasPrefix(k, "bandwidth_limit_sources") {
|
|
|
|
sources := getSliceFromDelimitedValues(r.Form.Get(k), ",")
|
|
|
|
if len(sources) > 0 {
|
|
|
|
bwLimit := sdk.BandwidthLimit{
|
|
|
|
Sources: sources,
|
|
|
|
}
|
|
|
|
idx := strings.TrimPrefix(k, "bandwidth_limit_sources")
|
|
|
|
ul := r.Form.Get(fmt.Sprintf("upload_bandwidth_source%v", idx))
|
|
|
|
dl := r.Form.Get(fmt.Sprintf("download_bandwidth_source%v", idx))
|
|
|
|
if ul != "" {
|
|
|
|
bandwidthUL, err := strconv.ParseInt(ul, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("invalid upload_bandwidth_source%v %#v: %w", idx, ul, err)
|
|
|
|
}
|
|
|
|
bwLimit.UploadBandwidth = bandwidthUL
|
|
|
|
}
|
|
|
|
if dl != "" {
|
|
|
|
bandwidthDL, err := strconv.ParseInt(dl, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return result, fmt.Errorf("invalid download_bandwidth_source%v %#v: %w", idx, ul, err)
|
|
|
|
}
|
|
|
|
bwLimit.DownloadBandwidth = bandwidthDL
|
|
|
|
}
|
|
|
|
result = append(result, bwLimit)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2021-07-11 13:26:51 +00:00
|
|
|
func getFilePatternsFromPostField(r *http.Request) []sdk.PatternsFilter {
|
|
|
|
var result []sdk.PatternsFilter
|
2021-05-23 20:02:01 +00:00
|
|
|
|
|
|
|
allowedPatterns := make(map[string][]string)
|
|
|
|
deniedPatterns := make(map[string][]string)
|
|
|
|
|
|
|
|
for k := range r.Form {
|
|
|
|
if strings.HasPrefix(k, "pattern_path") {
|
|
|
|
p := strings.TrimSpace(r.Form.Get(k))
|
|
|
|
idx := strings.TrimPrefix(k, "pattern_path")
|
|
|
|
filters := strings.TrimSpace(r.Form.Get(fmt.Sprintf("patterns%v", idx)))
|
|
|
|
filters = strings.ReplaceAll(filters, " ", "")
|
|
|
|
patternType := r.Form.Get(fmt.Sprintf("pattern_type%v", idx))
|
|
|
|
if p != "" && filters != "" {
|
|
|
|
if patternType == "allowed" {
|
|
|
|
allowedPatterns[p] = append(allowedPatterns[p], strings.Split(filters, ",")...)
|
|
|
|
} else {
|
|
|
|
deniedPatterns[p] = append(deniedPatterns[p], strings.Split(filters, ",")...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
|
|
|
|
for dirAllowed, allowPatterns := range allowedPatterns {
|
2021-07-11 13:26:51 +00:00
|
|
|
filter := sdk.PatternsFilter{
|
2021-05-06 19:35:43 +00:00
|
|
|
Path: dirAllowed,
|
2021-07-11 13:26:51 +00:00
|
|
|
AllowedPatterns: util.RemoveDuplicates(allowPatterns),
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
for dirDenied, denPatterns := range deniedPatterns {
|
|
|
|
if dirAllowed == dirDenied {
|
2021-07-11 13:26:51 +00:00
|
|
|
filter.DeniedPatterns = util.RemoveDuplicates(denPatterns)
|
2021-05-06 19:35:43 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = append(result, filter)
|
|
|
|
}
|
|
|
|
for dirDenied, denPatterns := range deniedPatterns {
|
|
|
|
found := false
|
|
|
|
for _, res := range result {
|
|
|
|
if res.Path == dirDenied {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
2021-07-11 13:26:51 +00:00
|
|
|
result = append(result, sdk.PatternsFilter{
|
2021-05-06 19:35:43 +00:00
|
|
|
Path: dirDenied,
|
|
|
|
DeniedPatterns: denPatterns,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-01-06 09:11:47 +00:00
|
|
|
func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) {
|
|
|
|
var filters sdk.BaseUserFilters
|
2021-12-10 17:43:26 +00:00
|
|
|
bwLimits, err := getBandwidthLimitsFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
return filters, err
|
|
|
|
}
|
|
|
|
filters.BandwidthLimits = bwLimits
|
2021-05-06 19:35:43 +00:00
|
|
|
filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
|
|
|
|
filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
|
|
|
|
filters.DeniedLoginMethods = r.Form["ssh_login_methods"]
|
|
|
|
filters.DeniedProtocols = r.Form["denied_protocols"]
|
2021-05-23 20:02:01 +00:00
|
|
|
filters.FilePatterns = getFilePatternsFromPostField(r)
|
2021-07-11 13:26:51 +00:00
|
|
|
filters.TLSUsername = sdk.TLSUsername(r.Form.Get("tls_username"))
|
2021-05-06 19:35:43 +00:00
|
|
|
filters.WebClient = r.Form["web_client_options"]
|
|
|
|
hooks := r.Form["hooks"]
|
2021-07-11 13:26:51 +00:00
|
|
|
if util.IsStringInSlice("external_auth_disabled", hooks) {
|
2021-05-06 19:35:43 +00:00
|
|
|
filters.Hooks.ExternalAuthDisabled = true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
if util.IsStringInSlice("pre_login_disabled", hooks) {
|
2021-05-06 19:35:43 +00:00
|
|
|
filters.Hooks.PreLoginDisabled = true
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
if util.IsStringInSlice("check_password_disabled", hooks) {
|
2021-05-06 19:35:43 +00:00
|
|
|
filters.Hooks.CheckPasswordDisabled = true
|
|
|
|
}
|
|
|
|
filters.DisableFsChecks = len(r.Form.Get("disable_fs_checks")) > 0
|
2021-08-17 16:08:32 +00:00
|
|
|
filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
2021-12-10 17:43:26 +00:00
|
|
|
return filters, nil
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
|
|
|
|
secret := kms.NewPlainSecret(r.Form.Get(field))
|
|
|
|
if strings.TrimSpace(secret.GetPayload()) == redactedSecret {
|
2022-01-06 09:11:47 +00:00
|
|
|
secret.SetStatus(sdkkms.SecretStatusRedacted)
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
if strings.TrimSpace(secret.GetPayload()) == "" {
|
|
|
|
secret.SetStatus("")
|
|
|
|
}
|
|
|
|
return secret
|
|
|
|
}
|
|
|
|
|
|
|
|
func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
|
|
|
|
var err error
|
|
|
|
config := vfs.S3FsConfig{}
|
|
|
|
config.Bucket = r.Form.Get("s3_bucket")
|
|
|
|
config.Region = r.Form.Get("s3_region")
|
|
|
|
config.AccessKey = r.Form.Get("s3_access_key")
|
|
|
|
config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
|
|
|
|
config.Endpoint = r.Form.Get("s3_endpoint")
|
|
|
|
config.StorageClass = r.Form.Get("s3_storage_class")
|
2021-11-13 15:05:40 +00:00
|
|
|
config.ACL = r.Form.Get("s3_acl")
|
2021-05-06 19:35:43 +00:00
|
|
|
config.KeyPrefix = r.Form.Get("s3_key_prefix")
|
|
|
|
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
2021-07-23 14:56:48 +00:00
|
|
|
config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
|
2021-07-11 16:39:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
2021-07-23 14:56:48 +00:00
|
|
|
config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("s3_download_part_size"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("s3_download_concurrency"))
|
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
config.ForcePathStyle = r.Form.Get("s3_force_path_style") != ""
|
|
|
|
config.DownloadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_download_part_max_time"))
|
2021-05-06 19:35:43 +00:00
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
|
|
|
|
var err error
|
|
|
|
config := vfs.GCSFsConfig{}
|
|
|
|
|
|
|
|
config.Bucket = r.Form.Get("gcs_bucket")
|
|
|
|
config.StorageClass = r.Form.Get("gcs_storage_class")
|
2021-11-15 20:57:41 +00:00
|
|
|
config.ACL = r.Form.Get("gcs_acl")
|
2021-05-06 19:35:43 +00:00
|
|
|
config.KeyPrefix = r.Form.Get("gcs_key_prefix")
|
|
|
|
autoCredentials := r.Form.Get("gcs_auto_credentials")
|
|
|
|
if autoCredentials != "" {
|
|
|
|
config.AutomaticCredentials = 1
|
|
|
|
} else {
|
|
|
|
config.AutomaticCredentials = 0
|
|
|
|
}
|
|
|
|
credentials, _, err := r.FormFile("gcs_credential_file")
|
|
|
|
if err == http.ErrMissingFile {
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
defer credentials.Close()
|
|
|
|
fileBytes, err := io.ReadAll(credentials)
|
|
|
|
if err != nil || len(fileBytes) == 0 {
|
|
|
|
if len(fileBytes) == 0 {
|
|
|
|
err = errors.New("credentials file size must be greater than 0")
|
|
|
|
}
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
config.Credentials = kms.NewPlainSecret(string(fileBytes))
|
|
|
|
config.AutomaticCredentials = 0
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
|
|
|
|
var err error
|
|
|
|
config := vfs.SFTPFsConfig{}
|
|
|
|
config.Endpoint = r.Form.Get("sftp_endpoint")
|
|
|
|
config.Username = r.Form.Get("sftp_username")
|
|
|
|
config.Password = getSecretFromFormField(r, "sftp_password")
|
|
|
|
config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
|
|
|
|
fingerprintsFormValue := r.Form.Get("sftp_fingerprints")
|
|
|
|
config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n")
|
|
|
|
config.Prefix = r.Form.Get("sftp_prefix")
|
|
|
|
config.DisableCouncurrentReads = len(r.Form.Get("sftp_disable_concurrent_reads")) > 0
|
|
|
|
config.BufferSize, err = strconv.ParseInt(r.Form.Get("sftp_buffer_size"), 10, 64)
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
|
|
|
var err error
|
|
|
|
config := vfs.AzBlobFsConfig{}
|
|
|
|
config.Container = r.Form.Get("az_container")
|
|
|
|
config.AccountName = r.Form.Get("az_account_name")
|
|
|
|
config.AccountKey = getSecretFromFormField(r, "az_account_key")
|
2021-06-11 20:27:36 +00:00
|
|
|
config.SASURL = getSecretFromFormField(r, "az_sas_url")
|
2021-05-06 19:35:43 +00:00
|
|
|
config.Endpoint = r.Form.Get("az_endpoint")
|
|
|
|
config.KeyPrefix = r.Form.Get("az_key_prefix")
|
|
|
|
config.AccessTier = r.Form.Get("az_access_tier")
|
|
|
|
config.UseEmulator = len(r.Form.Get("az_use_emulator")) > 0
|
|
|
|
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("az_upload_concurrency"))
|
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFsConfigFromPostFields(r *http.Request) (vfs.Filesystem, error) {
|
|
|
|
var fs vfs.Filesystem
|
2021-07-11 13:26:51 +00:00
|
|
|
fs.Provider = sdk.GetProviderByName(r.Form.Get("fs_provider"))
|
2021-05-06 19:35:43 +00:00
|
|
|
switch fs.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
config, err := getS3Config(r)
|
|
|
|
if err != nil {
|
|
|
|
return fs, err
|
|
|
|
}
|
|
|
|
fs.S3Config = config
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
config, err := getAzureConfig(r)
|
|
|
|
if err != nil {
|
|
|
|
return fs, err
|
|
|
|
}
|
|
|
|
fs.AzBlobConfig = config
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
config, err := getGCSConfig(r)
|
|
|
|
if err != nil {
|
|
|
|
return fs, err
|
|
|
|
}
|
|
|
|
fs.GCSConfig = config
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
fs.CryptConfig.Passphrase = getSecretFromFormField(r, "crypt_passphrase")
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
config, err := getSFTPConfig(r)
|
|
|
|
if err != nil {
|
|
|
|
return fs, err
|
|
|
|
}
|
|
|
|
fs.SFTPConfig = config
|
|
|
|
}
|
|
|
|
return fs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) {
|
|
|
|
var admin dataprovider.Admin
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
return admin, err
|
|
|
|
}
|
|
|
|
status, err := strconv.Atoi(r.Form.Get("status"))
|
|
|
|
if err != nil {
|
|
|
|
return admin, err
|
|
|
|
}
|
|
|
|
admin.Username = r.Form.Get("username")
|
|
|
|
admin.Password = r.Form.Get("password")
|
|
|
|
admin.Permissions = r.Form["permissions"]
|
|
|
|
admin.Email = r.Form.Get("email")
|
|
|
|
admin.Status = status
|
|
|
|
admin.Filters.AllowList = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
|
2021-08-17 16:08:32 +00:00
|
|
|
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
2021-05-06 19:35:43 +00:00
|
|
|
admin.AdditionalInfo = r.Form.Get("additional_info")
|
|
|
|
admin.Description = r.Form.Get("description")
|
|
|
|
return admin, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func replacePlaceholders(field string, replacements map[string]string) string {
|
|
|
|
for k, v := range replacements {
|
|
|
|
field = strings.ReplaceAll(field, k, v)
|
|
|
|
}
|
|
|
|
return field
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFolderFromTemplate(folder vfs.BaseVirtualFolder, name string) vfs.BaseVirtualFolder {
|
|
|
|
folder.Name = name
|
|
|
|
replacements := make(map[string]string)
|
|
|
|
replacements["%name%"] = folder.Name
|
|
|
|
|
|
|
|
folder.MappedPath = replacePlaceholders(folder.MappedPath, replacements)
|
|
|
|
folder.Description = replacePlaceholders(folder.Description, replacements)
|
|
|
|
switch folder.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
folder.FsConfig.CryptConfig = getCryptFsFromTemplate(folder.FsConfig.CryptConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
folder.FsConfig.S3Config = getS3FsFromTemplate(folder.FsConfig.S3Config, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
folder.FsConfig.GCSConfig = getGCSFsFromTemplate(folder.FsConfig.GCSConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
folder.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(folder.FsConfig.AzBlobConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
folder.FsConfig.SFTPConfig = getSFTPFsFromTemplate(folder.FsConfig.SFTPConfig, replacements)
|
|
|
|
}
|
|
|
|
|
|
|
|
return folder
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCryptFsFromTemplate(fsConfig vfs.CryptFsConfig, replacements map[string]string) vfs.CryptFsConfig {
|
|
|
|
if fsConfig.Passphrase != nil {
|
|
|
|
if fsConfig.Passphrase.IsPlain() {
|
|
|
|
payload := replacePlaceholders(fsConfig.Passphrase.GetPayload(), replacements)
|
|
|
|
fsConfig.Passphrase = kms.NewPlainSecret(payload)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func getS3FsFromTemplate(fsConfig vfs.S3FsConfig, replacements map[string]string) vfs.S3FsConfig {
|
|
|
|
fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
|
|
|
|
fsConfig.AccessKey = replacePlaceholders(fsConfig.AccessKey, replacements)
|
|
|
|
if fsConfig.AccessSecret != nil && fsConfig.AccessSecret.IsPlain() {
|
|
|
|
payload := replacePlaceholders(fsConfig.AccessSecret.GetPayload(), replacements)
|
|
|
|
fsConfig.AccessSecret = kms.NewPlainSecret(payload)
|
|
|
|
}
|
|
|
|
return fsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func getGCSFsFromTemplate(fsConfig vfs.GCSFsConfig, replacements map[string]string) vfs.GCSFsConfig {
|
|
|
|
fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
|
|
|
|
return fsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAzBlobFsFromTemplate(fsConfig vfs.AzBlobFsConfig, replacements map[string]string) vfs.AzBlobFsConfig {
|
|
|
|
fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
|
|
|
|
fsConfig.AccountName = replacePlaceholders(fsConfig.AccountName, replacements)
|
|
|
|
if fsConfig.AccountKey != nil && fsConfig.AccountKey.IsPlain() {
|
|
|
|
payload := replacePlaceholders(fsConfig.AccountKey.GetPayload(), replacements)
|
|
|
|
fsConfig.AccountKey = kms.NewPlainSecret(payload)
|
|
|
|
}
|
|
|
|
return fsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSFTPFsFromTemplate(fsConfig vfs.SFTPFsConfig, replacements map[string]string) vfs.SFTPFsConfig {
|
|
|
|
fsConfig.Prefix = replacePlaceholders(fsConfig.Prefix, replacements)
|
|
|
|
fsConfig.Username = replacePlaceholders(fsConfig.Username, replacements)
|
|
|
|
if fsConfig.Password != nil && fsConfig.Password.IsPlain() {
|
|
|
|
payload := replacePlaceholders(fsConfig.Password.GetPayload(), replacements)
|
|
|
|
fsConfig.Password = kms.NewPlainSecret(payload)
|
|
|
|
}
|
|
|
|
return fsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUserFromTemplate(user dataprovider.User, template userTemplateFields) dataprovider.User {
|
|
|
|
user.Username = template.Username
|
|
|
|
user.Password = template.Password
|
|
|
|
user.PublicKeys = nil
|
|
|
|
if template.PublicKey != "" {
|
|
|
|
user.PublicKeys = append(user.PublicKeys, template.PublicKey)
|
|
|
|
}
|
|
|
|
replacements := make(map[string]string)
|
|
|
|
replacements["%username%"] = user.Username
|
|
|
|
user.Password = replacePlaceholders(user.Password, replacements)
|
|
|
|
replacements["%password%"] = user.Password
|
|
|
|
|
|
|
|
user.HomeDir = replacePlaceholders(user.HomeDir, replacements)
|
|
|
|
var vfolders []vfs.VirtualFolder
|
|
|
|
for _, vfolder := range user.VirtualFolders {
|
|
|
|
vfolder.Name = replacePlaceholders(vfolder.Name, replacements)
|
|
|
|
vfolder.VirtualPath = replacePlaceholders(vfolder.VirtualPath, replacements)
|
|
|
|
vfolders = append(vfolders, vfolder)
|
|
|
|
}
|
|
|
|
user.VirtualFolders = vfolders
|
|
|
|
user.Description = replacePlaceholders(user.Description, replacements)
|
|
|
|
user.AdditionalInfo = replacePlaceholders(user.AdditionalInfo, replacements)
|
|
|
|
|
|
|
|
switch user.FsConfig.Provider {
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.CryptedFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
user.FsConfig.CryptConfig = getCryptFsFromTemplate(user.FsConfig.CryptConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.S3FilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
user.FsConfig.S3Config = getS3FsFromTemplate(user.FsConfig.S3Config, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.GCSFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
user.FsConfig.GCSConfig = getGCSFsFromTemplate(user.FsConfig.GCSConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.AzureBlobFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
user.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(user.FsConfig.AzBlobConfig, replacements)
|
2021-07-11 13:26:51 +00:00
|
|
|
case sdk.SFTPFilesystemProvider:
|
2021-05-06 19:35:43 +00:00
|
|
|
user.FsConfig.SFTPConfig = getSFTPFsFromTemplate(user.FsConfig.SFTPConfig, replacements)
|
|
|
|
}
|
|
|
|
|
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
|
|
|
|
var user dataprovider.User
|
|
|
|
err := r.ParseMultipartForm(maxRequestSize)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
2021-10-13 07:15:04 +00:00
|
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
2021-05-06 19:35:43 +00:00
|
|
|
uid, err := strconv.Atoi(r.Form.Get("uid"))
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
gid, err := strconv.Atoi(r.Form.Get("gid"))
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
quotaSize, err := strconv.ParseInt(r.Form.Get("quota_size"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
status, err := strconv.Atoi(r.Form.Get("status"))
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
expirationDateMillis := int64(0)
|
|
|
|
expirationDateString := r.Form.Get("expiration_date")
|
2021-11-06 13:13:20 +00:00
|
|
|
if strings.TrimSpace(expirationDateString) != "" {
|
2021-05-06 19:35:43 +00:00
|
|
|
expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
2021-07-11 13:26:51 +00:00
|
|
|
expirationDateMillis = util.GetTimeAsMsSinceEpoch(expirationDate)
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
fsConfig, err := getFsConfigFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
2021-12-10 17:43:26 +00:00
|
|
|
filters, err := getFiltersFromUserPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
return user, err
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
user = dataprovider.User{
|
2021-07-11 13:26:51 +00:00
|
|
|
BaseUser: sdk.BaseUser{
|
|
|
|
Username: r.Form.Get("username"),
|
2021-09-25 17:06:13 +00:00
|
|
|
Email: r.Form.Get("email"),
|
2021-07-11 13:26:51 +00:00
|
|
|
Password: r.Form.Get("password"),
|
|
|
|
PublicKeys: r.Form["public_keys"],
|
|
|
|
HomeDir: r.Form.Get("home_dir"),
|
|
|
|
UID: uid,
|
|
|
|
GID: gid,
|
|
|
|
Permissions: getUserPermissionsFromPostFields(r),
|
|
|
|
MaxSessions: maxSessions,
|
|
|
|
QuotaSize: quotaSize,
|
|
|
|
QuotaFiles: quotaFiles,
|
|
|
|
UploadBandwidth: bandwidthUL,
|
|
|
|
DownloadBandwidth: bandwidthDL,
|
|
|
|
Status: status,
|
|
|
|
ExpirationDate: expirationDateMillis,
|
|
|
|
AdditionalInfo: r.Form.Get("additional_info"),
|
|
|
|
Description: r.Form.Get("description"),
|
|
|
|
},
|
2022-01-06 09:11:47 +00:00
|
|
|
Filters: dataprovider.UserFilters{
|
|
|
|
BaseUserFilters: filters,
|
|
|
|
},
|
2021-07-11 13:26:51 +00:00
|
|
|
VirtualFolders: getVirtualFoldersFromPostFields(r),
|
|
|
|
FsConfig: fsConfig,
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
|
|
|
|
user.Filters.MaxUploadFileSize = maxFileSize
|
|
|
|
return user, err
|
|
|
|
}
|
|
|
|
|
2021-11-13 12:25:43 +00:00
|
|
|
func handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
if !smtp.IsEnabled() {
|
|
|
|
renderNotFoundPage(w, r, errors.New("this page does not exist"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderForgotPwdPage(w, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
renderForgotPwdPage(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
username := r.Form.Get("username")
|
|
|
|
err = handleForgotPassword(r, username, true)
|
|
|
|
if err != nil {
|
|
|
|
if e, ok := err.(*util.ValidationError); ok {
|
|
|
|
renderForgotPwdPage(w, e.GetErrorString())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderForgotPwdPage(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminPasswordReset(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
|
|
|
|
if !smtp.IsEnabled() {
|
|
|
|
renderNotFoundPage(w, r, errors.New("this page does not exist"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderResetPwdPage(w, "")
|
|
|
|
}
|
|
|
|
|
2021-09-04 10:11:04 +00:00
|
|
|
func handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
renderTwoFactorPage(w, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
renderTwoFactorRecoveryPage(w, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminMFA(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
renderMFAPage(w, r)
|
|
|
|
}
|
|
|
|
|
2021-09-29 16:46:15 +00:00
|
|
|
func handleWebAdminProfile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
renderProfilePage(w, r, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-09-29 16:46:15 +00:00
|
|
|
renderChangePasswordPage(w, r, "")
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderChangePasswordPage(w, r, err.Error())
|
2021-05-06 19:35:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
|
|
|
|
r.Form.Get("new_password2"))
|
|
|
|
if err != nil {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderChangePasswordPage(w, r, err.Error())
|
2021-05-06 19:35:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
handleWebLogout(w, r)
|
|
|
|
}
|
|
|
|
|
2021-09-29 16:46:15 +00:00
|
|
|
func handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderProfilePage(w, r, err.Error())
|
2021-08-17 16:08:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderProfilePage(w, r, "Invalid token claims")
|
2021-08-17 16:08:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
admin, err := dataprovider.AdminExists(claims.Username)
|
|
|
|
if err != nil {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderProfilePage(w, r, err.Error())
|
2021-08-17 16:08:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
2021-09-29 16:46:15 +00:00
|
|
|
admin.Email = r.Form.Get("email")
|
|
|
|
admin.Description = r.Form.Get("description")
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
2021-08-17 16:08:32 +00:00
|
|
|
if err != nil {
|
2021-09-29 16:46:15 +00:00
|
|
|
renderProfilePage(w, r, err.Error())
|
2021-08-17 16:08:32 +00:00
|
|
|
return
|
|
|
|
}
|
2021-09-29 16:46:15 +00:00
|
|
|
renderMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
|
|
|
|
"Your profile has been successfully updated")
|
2021-08-17 16:08:32 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
func handleWebLogout(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
c := jwtTokenClaims{}
|
2021-05-16 07:13:00 +00:00
|
|
|
c.removeCookie(w, r, webBaseAdminPath)
|
2021-05-06 19:35:43 +00:00
|
|
|
|
|
|
|
http.Redirect(w, r, webLoginPath, http.StatusFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
renderMaintenancePage(w, r, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebRestore(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, MaxRestoreSize)
|
2021-10-10 11:08:05 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = r.ParseMultipartForm(MaxRestoreSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
if err != nil {
|
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-10-13 07:15:04 +00:00
|
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
2021-05-06 19:35:43 +00:00
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
restoreMode, err := strconv.Atoi(r.Form.Get("mode"))
|
|
|
|
if err != nil {
|
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
scanQuota, err := strconv.Atoi(r.Form.Get("quota"))
|
|
|
|
if err != nil {
|
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
backupFile, _, err := r.FormFile("backup_file")
|
|
|
|
if err != nil {
|
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer backupFile.Close()
|
|
|
|
|
|
|
|
backupContent, err := io.ReadAll(backupFile)
|
|
|
|
if err != nil || len(backupContent) == 0 {
|
|
|
|
if len(backupContent) == 0 {
|
|
|
|
err = errors.New("backup file size must be greater than 0")
|
|
|
|
}
|
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-10-10 11:08:05 +00:00
|
|
|
if err := restoreBackup(backupContent, "", scanQuota, restoreMode, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderMaintenancePage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
renderMessagePage(w, r, "Data restored", "", http.StatusOK, nil, "Your backup was successfully restored")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
limit := defaultQueryLimit
|
|
|
|
if _, ok := r.URL.Query()["qlimit"]; ok {
|
|
|
|
var err error
|
|
|
|
limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
|
|
|
|
if err != nil {
|
|
|
|
limit = defaultQueryLimit
|
|
|
|
}
|
|
|
|
}
|
|
|
|
admins := make([]dataprovider.Admin, 0, limit)
|
|
|
|
for {
|
|
|
|
a, err := dataprovider.GetAdmins(limit, len(admins), dataprovider.OrderASC)
|
|
|
|
if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
admins = append(admins, a...)
|
|
|
|
if len(a) < limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data := adminsPage{
|
|
|
|
basePage: getBasePageData(pageAdminsTitle, webAdminsPath, r),
|
|
|
|
Admins: admins,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateAdmins, data)
|
|
|
|
}
|
|
|
|
|
2021-05-14 17:21:15 +00:00
|
|
|
func handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
|
2021-05-14 17:21:15 +00:00
|
|
|
if dataprovider.HasAdmin() {
|
|
|
|
http.Redirect(w, r, webLoginPath, http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderAdminSetupPage(w, r, "", "")
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
func handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
admin := &dataprovider.Admin{Status: 1}
|
|
|
|
renderAddUpdateAdminPage(w, r, admin, "", true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateAdminGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
username := getURLParam(r, "username")
|
|
|
|
admin, err := dataprovider.AdminExists(username)
|
|
|
|
if err == nil {
|
|
|
|
renderAddUpdateAdminPage(w, r, &admin, "", false)
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAddAdminPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-10-10 11:08:05 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
admin, err := getAdminFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderAddUpdateAdminPage(w, r, &admin, err.Error(), true)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.AddAdmin(&admin, claims.Username, util.GetIPFromRemoteAddress(r.Method))
|
2021-05-06 19:35:43 +00:00
|
|
|
if err != nil {
|
|
|
|
renderAddUpdateAdminPage(w, r, &admin, err.Error(), true)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
|
|
|
|
username := getURLParam(r, "username")
|
|
|
|
admin, err := dataprovider.AdminExists(username)
|
2021-07-11 13:26:51 +00:00
|
|
|
if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
updatedAdmin, err := getAdminFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderAddUpdateAdminPage(w, r, &updatedAdmin, err.Error(), false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
updatedAdmin.ID = admin.ID
|
|
|
|
updatedAdmin.Username = admin.Username
|
|
|
|
if updatedAdmin.Password == "" {
|
|
|
|
updatedAdmin.Password = admin.Password
|
|
|
|
}
|
2021-09-04 10:11:04 +00:00
|
|
|
updatedAdmin.Filters.TOTPConfig = admin.Filters.TOTPConfig
|
|
|
|
updatedAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes
|
2021-05-06 19:35:43 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderAddUpdateAdminPage(w, r, &updatedAdmin, fmt.Sprintf("Invalid token claims: %v", err), false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if username == claims.Username {
|
|
|
|
if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
|
|
|
|
renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot remove these permissions to yourself", false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if updatedAdmin.Status == 0 {
|
|
|
|
renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot disable yourself", false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.UpdateAdmin(&updatedAdmin, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
2021-05-06 19:35:43 +00:00
|
|
|
if err != nil {
|
|
|
|
renderAddUpdateAdminPage(w, r, &admin, err.Error(), false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2021-06-08 11:24:28 +00:00
|
|
|
func handleWebDefenderPage(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-06-08 11:24:28 +00:00
|
|
|
data := defenderHostsPage{
|
|
|
|
basePage: getBasePageData(pageDefenderTitle, webDefenderPath, r),
|
|
|
|
DefenderHostsURL: webDefenderHostsPath,
|
|
|
|
}
|
|
|
|
|
|
|
|
renderAdminTemplate(w, templateDefender, data)
|
|
|
|
}
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
limit := defaultQueryLimit
|
|
|
|
if _, ok := r.URL.Query()["qlimit"]; ok {
|
|
|
|
var err error
|
|
|
|
limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
|
|
|
|
if err != nil {
|
|
|
|
limit = defaultQueryLimit
|
|
|
|
}
|
|
|
|
}
|
|
|
|
users := make([]dataprovider.User, 0, limit)
|
|
|
|
for {
|
|
|
|
u, err := dataprovider.GetUsers(limit, len(users), dataprovider.OrderASC)
|
|
|
|
if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
users = append(users, u...)
|
|
|
|
if len(u) < limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data := usersPage{
|
|
|
|
basePage: getBasePageData(pageUsersTitle, webUsersPath, r),
|
|
|
|
Users: users,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateUsers, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
if r.URL.Query().Get("from") != "" {
|
|
|
|
name := r.URL.Query().Get("from")
|
|
|
|
folder, err := dataprovider.GetFolderByName(name)
|
|
|
|
if err == nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeTemplate, "")
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
folder := vfs.BaseVirtualFolder{}
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeTemplate, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
templateFolder := vfs.BaseVirtualFolder{}
|
2021-05-23 20:02:01 +00:00
|
|
|
err := r.ParseMultipartForm(maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
if err != nil {
|
|
|
|
renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "")
|
|
|
|
return
|
|
|
|
}
|
2021-10-13 07:15:04 +00:00
|
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
2021-05-06 19:35:43 +00:00
|
|
|
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
templateFolder.MappedPath = r.Form.Get("mapped_path")
|
|
|
|
templateFolder.Description = r.Form.Get("description")
|
|
|
|
fsConfig, err := getFsConfigFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
templateFolder.FsConfig = fsConfig
|
|
|
|
|
|
|
|
var dump dataprovider.BackupData
|
|
|
|
dump.Version = dataprovider.DumpVersion
|
|
|
|
|
|
|
|
foldersFields := getFoldersForTemplate(r)
|
|
|
|
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, "")
|
|
|
|
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, "")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-folders-from-template.json\"", len(dump.Folders)))
|
|
|
|
render.JSON(w, r, dump)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
if r.URL.Query().Get("from") != "" {
|
|
|
|
username := r.URL.Query().Get("from")
|
|
|
|
user, err := dataprovider.UserExists(username)
|
|
|
|
if err == nil {
|
|
|
|
user.SetEmptySecrets()
|
2021-09-25 17:06:13 +00:00
|
|
|
user.Email = ""
|
|
|
|
user.Description = ""
|
2021-05-06 19:35:43 +00:00
|
|
|
renderUserPage(w, r, &user, userPageModeTemplate, "")
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
} else {
|
2021-07-11 13:26:51 +00:00
|
|
|
user := dataprovider.User{BaseUser: sdk.BaseUser{Status: 1}}
|
2021-05-06 19:35:43 +00:00
|
|
|
renderUserPage(w, r, &user, userPageModeTemplate, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
templateUser, err := getUserFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderMessagePage(w, r, "Error parsing user fields", "", http.StatusBadRequest, err, "")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var dump dataprovider.BackupData
|
|
|
|
dump.Version = dataprovider.DumpVersion
|
|
|
|
|
|
|
|
userTmplFields := getUsersForTemplate(r)
|
|
|
|
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, "")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dump.Users = append(dump.Users, u)
|
|
|
|
for _, folder := range u.VirtualFolders {
|
|
|
|
if !dump.HasFolder(folder.Name) {
|
|
|
|
dump.Folders = append(dump.Folders, folder.BaseVirtualFolder)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dump.Users) == 0 {
|
|
|
|
renderMessagePage(w, r, "No users to export", "No valid users found, export is not possible", 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
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 = ""
|
2022-01-10 18:44:16 +00:00
|
|
|
user.PublicKeys = nil
|
2021-05-06 19:35:43 +00:00
|
|
|
user.SetEmptySecrets()
|
|
|
|
renderUserPage(w, r, &user, userPageModeAdd, "")
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
} else {
|
2022-01-10 18:44:16 +00:00
|
|
|
user := dataprovider.User{BaseUser: sdk.BaseUser{
|
|
|
|
Status: 1,
|
|
|
|
Permissions: map[string][]string{
|
|
|
|
"/": {dataprovider.PermAny},
|
|
|
|
},
|
|
|
|
}}
|
2021-05-06 19:35:43 +00:00
|
|
|
renderUserPage(w, r, &user, userPageModeAdd, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
username := getURLParam(r, "username")
|
|
|
|
user, err := dataprovider.UserExists(username)
|
|
|
|
if err == nil {
|
|
|
|
renderUserPage(w, r, &user, userPageModeUpdate, "")
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-10-10 11:08:05 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
user, err := getUserFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.AddUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
2021-05-06 19:35:43 +00:00
|
|
|
if err == nil {
|
|
|
|
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-10-10 11:08:05 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
username := getURLParam(r, "username")
|
|
|
|
user, err := dataprovider.UserExists(username)
|
2021-07-11 13:26:51 +00:00
|
|
|
if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
updatedUser, err := getUserFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
updatedUser.ID = user.ID
|
|
|
|
updatedUser.Username = user.Username
|
2021-09-04 10:11:04 +00:00
|
|
|
updatedUser.Filters.RecoveryCodes = user.Filters.RecoveryCodes
|
|
|
|
updatedUser.Filters.TOTPConfig = user.Filters.TOTPConfig
|
2021-05-06 19:35:43 +00:00
|
|
|
updatedUser.SetEmptySecretsIfNil()
|
|
|
|
if updatedUser.Password == redactedSecret {
|
|
|
|
updatedUser.Password = user.Password
|
|
|
|
}
|
|
|
|
updateEncryptedSecrets(&updatedUser.FsConfig, user.FsConfig.S3Config.AccessSecret, user.FsConfig.AzBlobConfig.AccountKey,
|
2021-06-11 20:27:36 +00:00
|
|
|
user.FsConfig.AzBlobConfig.SASURL, user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase,
|
|
|
|
user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey)
|
2021-05-06 19:35:43 +00:00
|
|
|
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.UpdateUser(&updatedUser, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
2021-05-06 19:35:43 +00:00
|
|
|
if err == nil {
|
|
|
|
if len(r.Form.Get("disconnect")) > 0 {
|
|
|
|
disconnectUser(user.Username)
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
data := statusPage{
|
|
|
|
basePage: getBasePageData(pageStatusTitle, webStatusPath, r),
|
|
|
|
Status: getServicesStatus(),
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateStatus, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
connectionStats := common.Connections.GetStats()
|
|
|
|
data := connectionsPage{
|
|
|
|
basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath, r),
|
|
|
|
Connections: connectionStats,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateConnections, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
|
|
folder := vfs.BaseVirtualFolder{}
|
|
|
|
err := r.ParseMultipartForm(maxRequestSize)
|
|
|
|
if err != nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-10-13 07:15:04 +00:00
|
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
folder.MappedPath = r.Form.Get("mapped_path")
|
|
|
|
folder.Name = r.Form.Get("name")
|
|
|
|
folder.Description = r.Form.Get("description")
|
|
|
|
fsConfig, err := getFsConfigFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
folder.FsConfig = fsConfig
|
|
|
|
|
|
|
|
err = dataprovider.AddFolder(&folder)
|
|
|
|
if err == nil {
|
|
|
|
http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateFolderGet(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-06 19:35:43 +00:00
|
|
|
name := getURLParam(r, "name")
|
|
|
|
folder, err := dataprovider.GetFolderByName(name)
|
|
|
|
if err == nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeUpdate, "")
|
2021-07-11 13:26:51 +00:00
|
|
|
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
} else {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-10-10 11:08:05 +00:00
|
|
|
claims, err := getTokenClaims(r)
|
|
|
|
if err != nil || claims.Username == "" {
|
|
|
|
renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
name := getURLParam(r, "name")
|
|
|
|
folder, err := dataprovider.GetFolderByName(name)
|
2021-07-11 13:26:51 +00:00
|
|
|
if _, ok := err.(*util.RecordNotFoundError); ok {
|
2021-05-06 19:35:43 +00:00
|
|
|
renderNotFoundPage(w, r, err)
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = r.ParseMultipartForm(maxRequestSize)
|
|
|
|
if err != nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-10-13 07:15:04 +00:00
|
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
|
|
|
|
2021-05-06 19:35:43 +00:00
|
|
|
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
|
|
|
|
renderForbiddenPage(w, r, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fsConfig, err := getFsConfigFromPostFields(r)
|
|
|
|
if err != nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
updatedFolder := &vfs.BaseVirtualFolder{
|
|
|
|
MappedPath: r.Form.Get("mapped_path"),
|
|
|
|
Description: r.Form.Get("description"),
|
|
|
|
}
|
|
|
|
updatedFolder.ID = folder.ID
|
|
|
|
updatedFolder.Name = folder.Name
|
|
|
|
updatedFolder.FsConfig = fsConfig
|
|
|
|
updatedFolder.FsConfig.SetEmptySecretsIfNil()
|
|
|
|
updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey,
|
2021-06-11 20:27:36 +00:00
|
|
|
folder.FsConfig.AzBlobConfig.SASURL, folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase,
|
|
|
|
folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey)
|
2021-05-06 19:35:43 +00:00
|
|
|
|
2021-10-10 11:08:05 +00:00
|
|
|
err = dataprovider.UpdateFolder(updatedFolder, folder.Users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
2021-05-06 19:35:43 +00:00
|
|
|
if err != nil {
|
|
|
|
renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
|
|
|
|
}
|
|
|
|
|
2021-05-23 20:02:01 +00:00
|
|
|
func getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]vfs.BaseVirtualFolder, error) {
|
2021-05-06 19:35:43 +00:00
|
|
|
folders := make([]vfs.BaseVirtualFolder, 0, limit)
|
|
|
|
for {
|
|
|
|
f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC)
|
|
|
|
if err != nil {
|
|
|
|
renderInternalServerErrorPage(w, r, err)
|
2021-05-23 20:02:01 +00:00
|
|
|
return folders, err
|
2021-05-06 19:35:43 +00:00
|
|
|
}
|
|
|
|
folders = append(folders, f...)
|
|
|
|
if len(f) < limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2021-05-23 20:02:01 +00:00
|
|
|
return folders, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
|
2021-08-17 16:08:32 +00:00
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
2021-05-23 20:02:01 +00:00
|
|
|
limit := defaultQueryLimit
|
|
|
|
if _, ok := r.URL.Query()["qlimit"]; ok {
|
|
|
|
var err error
|
|
|
|
limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
|
|
|
|
if err != nil {
|
|
|
|
limit = defaultQueryLimit
|
|
|
|
}
|
|
|
|
}
|
|
|
|
folders, err := getWebVirtualFolders(w, r, limit)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-05-06 19:35:43 +00:00
|
|
|
|
|
|
|
data := foldersPage{
|
|
|
|
basePage: getBasePageData(pageFoldersTitle, webFoldersPath, r),
|
|
|
|
Folders: folders,
|
|
|
|
}
|
|
|
|
renderAdminTemplate(w, templateFolders, data)
|
|
|
|
}
|