mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
dccc583b5d
also gcs credentials are now encrypted, both on disk and inside the provider. Data provider is automatically migrated and load data will accept old format too but you should upgrade to the new format to avoid future issues
760 lines
22 KiB
Go
760 lines
22 KiB
Go
package httpd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi"
|
|
|
|
"github.com/drakkan/sftpgo/common"
|
|
"github.com/drakkan/sftpgo/dataprovider"
|
|
"github.com/drakkan/sftpgo/utils"
|
|
"github.com/drakkan/sftpgo/version"
|
|
"github.com/drakkan/sftpgo/vfs"
|
|
)
|
|
|
|
const (
|
|
templateBase = "base.html"
|
|
templateUsers = "users.html"
|
|
templateUser = "user.html"
|
|
templateConnections = "connections.html"
|
|
templateFolders = "folders.html"
|
|
templateFolder = "folder.html"
|
|
templateMessage = "message.html"
|
|
pageUsersTitle = "Users"
|
|
pageConnectionsTitle = "Connections"
|
|
pageFoldersTitle = "Folders"
|
|
page400Title = "Bad request"
|
|
page404Title = "Not found"
|
|
page404Body = "The page you are looking for does not exist."
|
|
page500Title = "Internal Server Error"
|
|
page500Body = "The server is unable to fulfill your request."
|
|
defaultQueryLimit = 500
|
|
webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
|
|
redactedSecret = "[**redacted**]"
|
|
)
|
|
|
|
var (
|
|
templates = make(map[string]*template.Template)
|
|
)
|
|
|
|
type basePage struct {
|
|
Title string
|
|
CurrentURL string
|
|
UsersURL string
|
|
UserURL string
|
|
APIUserURL string
|
|
APIConnectionsURL string
|
|
APIQuotaScanURL string
|
|
ConnectionsURL string
|
|
FoldersURL string
|
|
FolderURL string
|
|
APIFoldersURL string
|
|
APIFolderQuotaScanURL string
|
|
UsersTitle string
|
|
ConnectionsTitle string
|
|
FoldersTitle string
|
|
Version string
|
|
}
|
|
|
|
type usersPage struct {
|
|
basePage
|
|
Users []dataprovider.User
|
|
}
|
|
|
|
type foldersPage struct {
|
|
basePage
|
|
Folders []vfs.BaseVirtualFolder
|
|
}
|
|
|
|
type connectionsPage struct {
|
|
basePage
|
|
Connections []common.ConnectionStatus
|
|
}
|
|
|
|
type userPage struct {
|
|
basePage
|
|
User dataprovider.User
|
|
RootPerms []string
|
|
Error string
|
|
ValidPerms []string
|
|
ValidSSHLoginMethods []string
|
|
ValidProtocols []string
|
|
RootDirPerms []string
|
|
RedactedSecret string
|
|
IsAdd bool
|
|
IsS3SecretEnc bool
|
|
IsAzSecretEnc bool
|
|
}
|
|
|
|
type folderPage struct {
|
|
basePage
|
|
Folder vfs.BaseVirtualFolder
|
|
Error string
|
|
}
|
|
|
|
type messagePage struct {
|
|
basePage
|
|
Error string
|
|
Success string
|
|
}
|
|
|
|
func loadTemplates(templatesPath string) {
|
|
usersPaths := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateUsers),
|
|
}
|
|
userPaths := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateUser),
|
|
}
|
|
connectionsPaths := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateConnections),
|
|
}
|
|
messagePath := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateMessage),
|
|
}
|
|
foldersPath := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateFolders),
|
|
}
|
|
folderPath := []string{
|
|
filepath.Join(templatesPath, templateBase),
|
|
filepath.Join(templatesPath, templateFolder),
|
|
}
|
|
usersTmpl := utils.LoadTemplate(template.ParseFiles(usersPaths...))
|
|
userTmpl := utils.LoadTemplate(template.ParseFiles(userPaths...))
|
|
connectionsTmpl := utils.LoadTemplate(template.ParseFiles(connectionsPaths...))
|
|
messageTmpl := utils.LoadTemplate(template.ParseFiles(messagePath...))
|
|
foldersTmpl := utils.LoadTemplate(template.ParseFiles(foldersPath...))
|
|
folderTmpl := utils.LoadTemplate(template.ParseFiles(folderPath...))
|
|
|
|
templates[templateUsers] = usersTmpl
|
|
templates[templateUser] = userTmpl
|
|
templates[templateConnections] = connectionsTmpl
|
|
templates[templateMessage] = messageTmpl
|
|
templates[templateFolders] = foldersTmpl
|
|
templates[templateFolder] = folderTmpl
|
|
}
|
|
|
|
func getBasePageData(title, currentURL string) basePage {
|
|
return basePage{
|
|
Title: title,
|
|
CurrentURL: currentURL,
|
|
UsersURL: webUsersPath,
|
|
UserURL: webUserPath,
|
|
FoldersURL: webFoldersPath,
|
|
FolderURL: webFolderPath,
|
|
APIUserURL: userPath,
|
|
APIConnectionsURL: activeConnectionsPath,
|
|
APIQuotaScanURL: quotaScanPath,
|
|
APIFoldersURL: folderPath,
|
|
APIFolderQuotaScanURL: quotaScanVFolderPath,
|
|
ConnectionsURL: webConnectionsPath,
|
|
UsersTitle: pageUsersTitle,
|
|
ConnectionsTitle: pageConnectionsTitle,
|
|
FoldersTitle: pageFoldersTitle,
|
|
Version: version.GetAsString(),
|
|
}
|
|
}
|
|
|
|
func renderTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
|
|
err := templates[tmplName].ExecuteTemplate(w, tmplName, data)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func renderMessagePage(w http.ResponseWriter, title, body string, statusCode int, err error, message string) {
|
|
var errorString string
|
|
if len(body) > 0 {
|
|
errorString = body + " "
|
|
}
|
|
if err != nil {
|
|
errorString += err.Error()
|
|
}
|
|
data := messagePage{
|
|
basePage: getBasePageData(title, ""),
|
|
Error: errorString,
|
|
Success: message,
|
|
}
|
|
w.WriteHeader(statusCode)
|
|
renderTemplate(w, templateMessage, data)
|
|
}
|
|
|
|
func renderInternalServerErrorPage(w http.ResponseWriter, err error) {
|
|
renderMessagePage(w, page500Title, page500Body, http.StatusInternalServerError, err, "")
|
|
}
|
|
|
|
func renderBadRequestPage(w http.ResponseWriter, err error) {
|
|
renderMessagePage(w, page400Title, "", http.StatusBadRequest, err, "")
|
|
}
|
|
|
|
func renderNotFoundPage(w http.ResponseWriter, err error) {
|
|
renderMessagePage(w, page404Title, page404Body, http.StatusNotFound, err, "")
|
|
}
|
|
|
|
func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
|
|
data := userPage{
|
|
basePage: getBasePageData("Add a new user", webUserPath),
|
|
IsAdd: true,
|
|
Error: error,
|
|
User: user,
|
|
ValidPerms: dataprovider.ValidPerms,
|
|
ValidSSHLoginMethods: dataprovider.ValidSSHLoginMethods,
|
|
ValidProtocols: dataprovider.ValidProtocols,
|
|
RootDirPerms: user.GetPermissionsForPath("/"),
|
|
IsS3SecretEnc: user.FsConfig.S3Config.AccessSecret.IsEncrypted(),
|
|
IsAzSecretEnc: user.FsConfig.AzBlobConfig.AccountKey.IsEncrypted(),
|
|
RedactedSecret: redactedSecret,
|
|
}
|
|
renderTemplate(w, templateUser, data)
|
|
}
|
|
|
|
func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
|
|
data := userPage{
|
|
basePage: getBasePageData("Update user", fmt.Sprintf("%v/%v", webUserPath, user.ID)),
|
|
IsAdd: false,
|
|
Error: error,
|
|
User: user,
|
|
ValidPerms: dataprovider.ValidPerms,
|
|
ValidSSHLoginMethods: dataprovider.ValidSSHLoginMethods,
|
|
ValidProtocols: dataprovider.ValidProtocols,
|
|
RootDirPerms: user.GetPermissionsForPath("/"),
|
|
IsS3SecretEnc: user.FsConfig.S3Config.AccessSecret.IsEncrypted(),
|
|
IsAzSecretEnc: user.FsConfig.AzBlobConfig.AccountKey.IsEncrypted(),
|
|
RedactedSecret: redactedSecret,
|
|
}
|
|
renderTemplate(w, templateUser, data)
|
|
}
|
|
|
|
func renderAddFolderPage(w http.ResponseWriter, folder vfs.BaseVirtualFolder, error string) {
|
|
data := folderPage{
|
|
basePage: getBasePageData("Add a new folder", webFolderPath),
|
|
Error: error,
|
|
Folder: folder,
|
|
}
|
|
renderTemplate(w, templateFolder, data)
|
|
}
|
|
|
|
func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
|
|
var virtualFolders []vfs.VirtualFolder
|
|
formValue := r.Form.Get("virtual_folders")
|
|
for _, cleaned := range getSliceFromDelimitedValues(formValue, "\n") {
|
|
if strings.Contains(cleaned, "::") {
|
|
mapping := strings.Split(cleaned, "::")
|
|
if len(mapping) > 1 {
|
|
vfolder := vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
MappedPath: strings.TrimSpace(mapping[1]),
|
|
},
|
|
VirtualPath: strings.TrimSpace(mapping[0]),
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
}
|
|
if len(mapping) > 2 {
|
|
quotaFiles, err := strconv.Atoi(strings.TrimSpace(mapping[2]))
|
|
if err == nil {
|
|
vfolder.QuotaFiles = quotaFiles
|
|
}
|
|
}
|
|
if len(mapping) > 3 {
|
|
quotaSize, err := strconv.ParseInt(strings.TrimSpace(mapping[3]), 10, 64)
|
|
if err == nil {
|
|
vfolder.QuotaSize = quotaSize
|
|
}
|
|
}
|
|
virtualFolders = append(virtualFolders, vfolder)
|
|
}
|
|
}
|
|
}
|
|
return virtualFolders
|
|
}
|
|
|
|
func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
|
permissions := make(map[string][]string)
|
|
permissions["/"] = r.Form["permissions"]
|
|
subDirsPermsValue := r.Form.Get("sub_dirs_permissions")
|
|
for _, cleaned := range getSliceFromDelimitedValues(subDirsPermsValue, "\n") {
|
|
if strings.Contains(cleaned, "::") {
|
|
dirPerms := strings.Split(cleaned, "::")
|
|
if len(dirPerms) > 1 {
|
|
dir := dirPerms[0]
|
|
dir = strings.TrimSpace(dir)
|
|
perms := []string{}
|
|
for _, p := range strings.Split(dirPerms[1], ",") {
|
|
cleanedPerm := strings.TrimSpace(p)
|
|
if len(cleanedPerm) > 0 {
|
|
perms = append(perms, cleanedPerm)
|
|
}
|
|
}
|
|
if len(dir) > 0 {
|
|
permissions[dir] = perms
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
func getSliceFromDelimitedValues(values, delimiter string) []string {
|
|
result := []string{}
|
|
for _, v := range strings.Split(values, delimiter) {
|
|
cleaned := strings.TrimSpace(v)
|
|
if len(cleaned) > 0 {
|
|
result = append(result, cleaned)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getListFromPostFields(value string) map[string][]string {
|
|
result := make(map[string][]string)
|
|
for _, cleaned := range getSliceFromDelimitedValues(value, "\n") {
|
|
if strings.Contains(cleaned, "::") {
|
|
dirExts := strings.Split(cleaned, "::")
|
|
if len(dirExts) > 1 {
|
|
dir := dirExts[0]
|
|
dir = path.Clean(strings.TrimSpace(dir))
|
|
exts := []string{}
|
|
for _, e := range strings.Split(dirExts[1], ",") {
|
|
cleanedExt := strings.TrimSpace(e)
|
|
if cleanedExt != "" {
|
|
exts = append(exts, cleanedExt)
|
|
}
|
|
}
|
|
if dir != "" {
|
|
if _, ok := result[dir]; ok {
|
|
result[dir] = append(result[dir], exts...)
|
|
} else {
|
|
result[dir] = exts
|
|
}
|
|
result[dir] = utils.RemoveDuplicates(result[dir])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getFilePatternsFromPostField(valueAllowed, valuesDenied string) []dataprovider.PatternsFilter {
|
|
var result []dataprovider.PatternsFilter
|
|
allowedPatterns := getListFromPostFields(valueAllowed)
|
|
deniedPatterns := getListFromPostFields(valuesDenied)
|
|
|
|
for dirAllowed, allowPatterns := range allowedPatterns {
|
|
filter := dataprovider.PatternsFilter{
|
|
Path: dirAllowed,
|
|
AllowedPatterns: allowPatterns,
|
|
}
|
|
for dirDenied, denPatterns := range deniedPatterns {
|
|
if dirAllowed == dirDenied {
|
|
filter.DeniedPatterns = denPatterns
|
|
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 {
|
|
result = append(result, dataprovider.PatternsFilter{
|
|
Path: dirDenied,
|
|
DeniedPatterns: denPatterns,
|
|
})
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getFileExtensionsFromPostField(valueAllowed, valuesDenied string) []dataprovider.ExtensionsFilter {
|
|
var result []dataprovider.ExtensionsFilter
|
|
allowedExtensions := getListFromPostFields(valueAllowed)
|
|
deniedExtensions := getListFromPostFields(valuesDenied)
|
|
|
|
for dirAllowed, allowedExts := range allowedExtensions {
|
|
filter := dataprovider.ExtensionsFilter{
|
|
Path: dirAllowed,
|
|
AllowedExtensions: allowedExts,
|
|
}
|
|
for dirDenied, deniedExts := range deniedExtensions {
|
|
if dirAllowed == dirDenied {
|
|
filter.DeniedExtensions = deniedExts
|
|
break
|
|
}
|
|
}
|
|
result = append(result, filter)
|
|
}
|
|
for dirDenied, deniedExts := range deniedExtensions {
|
|
found := false
|
|
for _, res := range result {
|
|
if res.Path == dirDenied {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
result = append(result, dataprovider.ExtensionsFilter{
|
|
Path: dirDenied,
|
|
DeniedExtensions: deniedExts,
|
|
})
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
|
|
var filters dataprovider.UserFilters
|
|
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"]
|
|
filters.FileExtensions = getFileExtensionsFromPostField(r.Form.Get("allowed_extensions"), r.Form.Get("denied_extensions"))
|
|
filters.FilePatterns = getFilePatternsFromPostField(r.Form.Get("allowed_patterns"), r.Form.Get("denied_patterns"))
|
|
return filters
|
|
}
|
|
|
|
func getSecretFromFormField(r *http.Request, field string) vfs.Secret {
|
|
secret := vfs.Secret{
|
|
Payload: r.Form.Get(field),
|
|
Status: vfs.SecretStatusPlain,
|
|
}
|
|
if strings.TrimSpace(secret.Payload) == redactedSecret {
|
|
secret.Status = vfs.SecretStatusRedacted
|
|
}
|
|
if strings.TrimSpace(secret.Payload) == "" {
|
|
secret.Status = ""
|
|
}
|
|
return secret
|
|
}
|
|
|
|
func getFsConfigFromUserPostFields(r *http.Request) (dataprovider.Filesystem, error) {
|
|
var fs dataprovider.Filesystem
|
|
provider, err := strconv.Atoi(r.Form.Get("fs_provider"))
|
|
if err != nil {
|
|
provider = int(dataprovider.LocalFilesystemProvider)
|
|
}
|
|
fs.Provider = dataprovider.FilesystemProvider(provider)
|
|
if fs.Provider == dataprovider.S3FilesystemProvider {
|
|
fs.S3Config.Bucket = r.Form.Get("s3_bucket")
|
|
fs.S3Config.Region = r.Form.Get("s3_region")
|
|
fs.S3Config.AccessKey = r.Form.Get("s3_access_key")
|
|
fs.S3Config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
|
|
fs.S3Config.Endpoint = r.Form.Get("s3_endpoint")
|
|
fs.S3Config.StorageClass = r.Form.Get("s3_storage_class")
|
|
fs.S3Config.KeyPrefix = r.Form.Get("s3_key_prefix")
|
|
fs.S3Config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
|
|
if err != nil {
|
|
return fs, err
|
|
}
|
|
fs.S3Config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
|
|
if err != nil {
|
|
return fs, err
|
|
}
|
|
} else if fs.Provider == dataprovider.GCSFilesystemProvider {
|
|
fs.GCSConfig.Bucket = r.Form.Get("gcs_bucket")
|
|
fs.GCSConfig.StorageClass = r.Form.Get("gcs_storage_class")
|
|
fs.GCSConfig.KeyPrefix = r.Form.Get("gcs_key_prefix")
|
|
autoCredentials := r.Form.Get("gcs_auto_credentials")
|
|
if len(autoCredentials) > 0 {
|
|
fs.GCSConfig.AutomaticCredentials = 1
|
|
} else {
|
|
fs.GCSConfig.AutomaticCredentials = 0
|
|
}
|
|
credentials, _, err := r.FormFile("gcs_credential_file")
|
|
if err == http.ErrMissingFile {
|
|
return fs, nil
|
|
}
|
|
if err != nil {
|
|
return fs, err
|
|
}
|
|
defer credentials.Close()
|
|
fileBytes, err := ioutil.ReadAll(credentials)
|
|
if err != nil || len(fileBytes) == 0 {
|
|
if len(fileBytes) == 0 {
|
|
err = errors.New("credentials file size must be greater than 0")
|
|
}
|
|
return fs, err
|
|
}
|
|
fs.GCSConfig.Credentials = vfs.Secret{
|
|
Status: vfs.SecretStatusPlain,
|
|
Payload: string(fileBytes),
|
|
}
|
|
fs.GCSConfig.AutomaticCredentials = 0
|
|
} else if fs.Provider == dataprovider.AzureBlobFilesystemProvider {
|
|
fs.AzBlobConfig.Container = r.Form.Get("az_container")
|
|
fs.AzBlobConfig.AccountName = r.Form.Get("az_account_name")
|
|
fs.AzBlobConfig.AccountKey = getSecretFromFormField(r, "az_account_key")
|
|
fs.AzBlobConfig.SASURL = r.Form.Get("az_sas_url")
|
|
fs.AzBlobConfig.Endpoint = r.Form.Get("az_endpoint")
|
|
fs.AzBlobConfig.KeyPrefix = r.Form.Get("az_key_prefix")
|
|
fs.AzBlobConfig.AccessTier = r.Form.Get("az_access_tier")
|
|
fs.AzBlobConfig.UseEmulator = len(r.Form.Get("az_use_emulator")) > 0
|
|
fs.AzBlobConfig.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
|
|
if err != nil {
|
|
return fs, err
|
|
}
|
|
fs.AzBlobConfig.UploadConcurrency, err = strconv.Atoi(r.Form.Get("az_upload_concurrency"))
|
|
if err != nil {
|
|
return fs, err
|
|
}
|
|
}
|
|
return fs, nil
|
|
}
|
|
|
|
func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
|
|
var user dataprovider.User
|
|
err := r.ParseMultipartForm(maxRequestSize)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
publicKeysFormValue := r.Form.Get("public_keys")
|
|
publicKeys := getSliceFromDelimitedValues(publicKeysFormValue, "\n")
|
|
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")
|
|
if len(strings.TrimSpace(expirationDateString)) > 0 {
|
|
expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
expirationDateMillis = utils.GetTimeAsMsSinceEpoch(expirationDate)
|
|
}
|
|
fsConfig, err := getFsConfigFromUserPostFields(r)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
user = dataprovider.User{
|
|
Username: r.Form.Get("username"),
|
|
Password: r.Form.Get("password"),
|
|
PublicKeys: publicKeys,
|
|
HomeDir: r.Form.Get("home_dir"),
|
|
VirtualFolders: getVirtualFoldersFromPostFields(r),
|
|
UID: uid,
|
|
GID: gid,
|
|
Permissions: getUserPermissionsFromPostFields(r),
|
|
MaxSessions: maxSessions,
|
|
QuotaSize: quotaSize,
|
|
QuotaFiles: quotaFiles,
|
|
UploadBandwidth: bandwidthUL,
|
|
DownloadBandwidth: bandwidthDL,
|
|
Status: status,
|
|
ExpirationDate: expirationDateMillis,
|
|
Filters: getFiltersFromUserPostFields(r),
|
|
FsConfig: fsConfig,
|
|
}
|
|
maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
|
|
user.Filters.MaxUploadFileSize = maxFileSize
|
|
return user, err
|
|
}
|
|
|
|
func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
|
|
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, err)
|
|
return
|
|
}
|
|
users = append(users, u...)
|
|
if len(u) < limit {
|
|
break
|
|
}
|
|
}
|
|
data := usersPage{
|
|
basePage: getBasePageData(pageUsersTitle, webUsersPath),
|
|
Users: users,
|
|
}
|
|
renderTemplate(w, templateUsers, data)
|
|
}
|
|
|
|
func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
|
|
renderAddUserPage(w, dataprovider.User{Status: 1}, "")
|
|
}
|
|
|
|
func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
|
|
if err != nil {
|
|
renderBadRequestPage(w, err)
|
|
return
|
|
}
|
|
user, err := dataprovider.GetUserByID(id)
|
|
if err == nil {
|
|
renderUpdateUserPage(w, user, "")
|
|
} else if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
|
renderNotFoundPage(w, err)
|
|
} else {
|
|
renderInternalServerErrorPage(w, err)
|
|
}
|
|
}
|
|
|
|
func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
user, err := getUserFromPostFields(r)
|
|
if err != nil {
|
|
renderAddUserPage(w, user, err.Error())
|
|
return
|
|
}
|
|
err = dataprovider.AddUser(user)
|
|
if err == nil {
|
|
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
|
} else {
|
|
renderAddUserPage(w, user, err.Error())
|
|
}
|
|
}
|
|
|
|
func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
id, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
|
|
if err != nil {
|
|
renderBadRequestPage(w, err)
|
|
return
|
|
}
|
|
user, err := dataprovider.GetUserByID(id)
|
|
if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
|
|
renderNotFoundPage(w, err)
|
|
return
|
|
} else if err != nil {
|
|
renderInternalServerErrorPage(w, err)
|
|
return
|
|
}
|
|
updatedUser, err := getUserFromPostFields(r)
|
|
if err != nil {
|
|
renderUpdateUserPage(w, user, err.Error())
|
|
return
|
|
}
|
|
updatedUser.ID = user.ID
|
|
if len(updatedUser.Password) == 0 {
|
|
updatedUser.Password = user.Password
|
|
}
|
|
if !updatedUser.FsConfig.S3Config.AccessSecret.IsPlain() && !updatedUser.FsConfig.S3Config.AccessSecret.IsEmpty() {
|
|
updatedUser.FsConfig.S3Config.AccessSecret = user.FsConfig.S3Config.AccessSecret
|
|
}
|
|
if !updatedUser.FsConfig.AzBlobConfig.AccountKey.IsPlain() && !updatedUser.FsConfig.AzBlobConfig.AccountKey.IsEmpty() {
|
|
updatedUser.FsConfig.AzBlobConfig.AccountKey = user.FsConfig.AzBlobConfig.AccountKey
|
|
}
|
|
err = dataprovider.UpdateUser(updatedUser)
|
|
if err == nil {
|
|
if len(r.Form.Get("disconnect")) > 0 {
|
|
disconnectUser(user.Username)
|
|
}
|
|
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
|
} else {
|
|
renderUpdateUserPage(w, user, err.Error())
|
|
}
|
|
}
|
|
|
|
func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
|
|
connectionStats := common.Connections.GetStats()
|
|
data := connectionsPage{
|
|
basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath),
|
|
Connections: connectionStats,
|
|
}
|
|
renderTemplate(w, templateConnections, data)
|
|
}
|
|
|
|
func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
|
|
renderAddFolderPage(w, vfs.BaseVirtualFolder{}, "")
|
|
}
|
|
|
|
func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
folder := vfs.BaseVirtualFolder{}
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
renderAddFolderPage(w, folder, err.Error())
|
|
return
|
|
}
|
|
folder.MappedPath = r.Form.Get("mapped_path")
|
|
|
|
err = dataprovider.AddFolder(folder)
|
|
if err == nil {
|
|
http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
|
|
} else {
|
|
renderAddFolderPage(w, folder, err.Error())
|
|
}
|
|
}
|
|
|
|
func handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
|
|
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 := make([]vfs.BaseVirtualFolder, 0, limit)
|
|
for {
|
|
f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC, "")
|
|
if err != nil {
|
|
renderInternalServerErrorPage(w, err)
|
|
return
|
|
}
|
|
folders = append(folders, f...)
|
|
if len(f) < limit {
|
|
break
|
|
}
|
|
}
|
|
|
|
data := foldersPage{
|
|
basePage: getBasePageData(pageFoldersTitle, webFoldersPath),
|
|
Folders: folders,
|
|
}
|
|
renderTemplate(w, templateFolders, data)
|
|
}
|