mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 23:00:55 +03:00
add groups support
Using groups simplifies the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user. Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -43,40 +43,51 @@ const (
|
||||
folderPageModeTemplate
|
||||
)
|
||||
|
||||
type groupPageMode int
|
||||
|
||||
const (
|
||||
templateAdminDir = "webadmin"
|
||||
templateBase = "base.html"
|
||||
templateBaseLogin = "baselogin.html"
|
||||
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"
|
||||
templateDefender = "defender.html"
|
||||
templateProfile = "profile.html"
|
||||
templateChangePwd = "changepassword.html"
|
||||
templateMaintenance = "maintenance.html"
|
||||
templateMFA = "mfa.html"
|
||||
templateSetup = "adminsetup.html"
|
||||
pageUsersTitle = "Users"
|
||||
pageAdminsTitle = "Admins"
|
||||
pageConnectionsTitle = "Connections"
|
||||
pageStatusTitle = "Status"
|
||||
pageFoldersTitle = "Folders"
|
||||
pageProfileTitle = "My profile"
|
||||
pageChangePwdTitle = "Change password"
|
||||
pageMaintenanceTitle = "Maintenance"
|
||||
pageDefenderTitle = "Defender"
|
||||
pageForgotPwdTitle = "SFTPGo Admin - Forgot password"
|
||||
pageResetPwdTitle = "SFTPGo Admin - Reset password"
|
||||
pageSetupTitle = "Create first admin user"
|
||||
defaultQueryLimit = 500
|
||||
groupPageModeAdd groupPageMode = iota + 1
|
||||
groupPageModeUpdate
|
||||
)
|
||||
|
||||
const (
|
||||
templateAdminDir = "webadmin"
|
||||
templateBase = "base.html"
|
||||
templateBaseLogin = "baselogin.html"
|
||||
templateFsConfig = "fsconfig.html"
|
||||
templateSharedComponents = "sharedcomponents.html"
|
||||
templateUsers = "users.html"
|
||||
templateUser = "user.html"
|
||||
templateAdmins = "admins.html"
|
||||
templateAdmin = "admin.html"
|
||||
templateConnections = "connections.html"
|
||||
templateGroups = "groups.html"
|
||||
templateGroup = "group.html"
|
||||
templateFolders = "folders.html"
|
||||
templateFolder = "folder.html"
|
||||
templateMessage = "message.html"
|
||||
templateStatus = "status.html"
|
||||
templateLogin = "login.html"
|
||||
templateDefender = "defender.html"
|
||||
templateProfile = "profile.html"
|
||||
templateChangePwd = "changepassword.html"
|
||||
templateMaintenance = "maintenance.html"
|
||||
templateMFA = "mfa.html"
|
||||
templateSetup = "adminsetup.html"
|
||||
pageUsersTitle = "Users"
|
||||
pageAdminsTitle = "Admins"
|
||||
pageConnectionsTitle = "Connections"
|
||||
pageStatusTitle = "Status"
|
||||
pageFoldersTitle = "Folders"
|
||||
pageGroupsTitle = "Groups"
|
||||
pageProfileTitle = "My profile"
|
||||
pageChangePwdTitle = "Change password"
|
||||
pageMaintenanceTitle = "Maintenance"
|
||||
pageDefenderTitle = "Defender"
|
||||
pageForgotPwdTitle = "SFTPGo Admin - Forgot password"
|
||||
pageResetPwdTitle = "SFTPGo Admin - Reset password"
|
||||
pageSetupTitle = "Create first admin user"
|
||||
defaultQueryLimit = 500
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,6 +104,8 @@ type basePage struct {
|
||||
AdminURL string
|
||||
QuotaScanURL string
|
||||
ConnectionsURL string
|
||||
GroupsURL string
|
||||
GroupURL string
|
||||
FoldersURL string
|
||||
FolderURL string
|
||||
FolderTemplateURL string
|
||||
@@ -109,6 +122,7 @@ type basePage struct {
|
||||
AdminsTitle string
|
||||
ConnectionsTitle string
|
||||
FoldersTitle string
|
||||
GroupsTitle string
|
||||
StatusTitle string
|
||||
MaintenanceTitle string
|
||||
DefenderTitle string
|
||||
@@ -135,6 +149,11 @@ type foldersPage struct {
|
||||
Folders []vfs.BaseVirtualFolder
|
||||
}
|
||||
|
||||
type groupsPage struct {
|
||||
basePage
|
||||
Groups []dataprovider.Group
|
||||
}
|
||||
|
||||
type connectionsPage struct {
|
||||
basePage
|
||||
Connections []common.ConnectionStatus
|
||||
@@ -148,6 +167,7 @@ type statusPage struct {
|
||||
type fsWrapper struct {
|
||||
vfs.Filesystem
|
||||
IsUserPage bool
|
||||
IsGroupPage bool
|
||||
HasUsersBaseDir bool
|
||||
DirPath string
|
||||
}
|
||||
@@ -166,6 +186,7 @@ type userPage struct {
|
||||
RedactedSecret string
|
||||
Mode userPageMode
|
||||
VirtualFolders []vfs.BaseVirtualFolder
|
||||
Groups []dataprovider.Group
|
||||
CanImpersonate bool
|
||||
FsWrapper fsWrapper
|
||||
}
|
||||
@@ -228,6 +249,20 @@ type folderPage struct {
|
||||
FsWrapper fsWrapper
|
||||
}
|
||||
|
||||
type groupPage struct {
|
||||
basePage
|
||||
Group *dataprovider.Group
|
||||
Error string
|
||||
Mode groupPageMode
|
||||
ValidPerms []string
|
||||
ValidLoginMethods []string
|
||||
ValidProtocols []string
|
||||
TwoFactorProtocols []string
|
||||
WebClientOptions []string
|
||||
VirtualFolders []vfs.BaseVirtualFolder
|
||||
FsWrapper fsWrapper
|
||||
}
|
||||
|
||||
type messagePage struct {
|
||||
basePage
|
||||
Error string
|
||||
@@ -247,6 +282,7 @@ func loadAdminTemplates(templatesPath string) {
|
||||
}
|
||||
userPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateSharedComponents),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateUser),
|
||||
}
|
||||
@@ -283,6 +319,16 @@ func loadAdminTemplates(templatesPath string) {
|
||||
filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateFolder),
|
||||
}
|
||||
groupsPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateGroups),
|
||||
}
|
||||
groupPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateSharedComponents),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateGroup),
|
||||
}
|
||||
statusPaths := []string{
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateStatus),
|
||||
@@ -336,6 +382,8 @@ func loadAdminTemplates(templatesPath string) {
|
||||
adminTmpl := util.LoadTemplate(nil, adminPaths...)
|
||||
connectionsTmpl := util.LoadTemplate(nil, connectionsPaths...)
|
||||
messageTmpl := util.LoadTemplate(nil, messagePaths...)
|
||||
groupsTmpl := util.LoadTemplate(nil, groupsPaths...)
|
||||
groupTmpl := util.LoadTemplate(fsBaseTpl, groupPaths...)
|
||||
foldersTmpl := util.LoadTemplate(nil, foldersPaths...)
|
||||
folderTmpl := util.LoadTemplate(fsBaseTpl, folderPaths...)
|
||||
statusTmpl := util.LoadTemplate(nil, statusPaths...)
|
||||
@@ -357,6 +405,8 @@ func loadAdminTemplates(templatesPath string) {
|
||||
adminTemplates[templateAdmin] = adminTmpl
|
||||
adminTemplates[templateConnections] = connectionsTmpl
|
||||
adminTemplates[templateMessage] = messageTmpl
|
||||
adminTemplates[templateGroups] = groupsTmpl
|
||||
adminTemplates[templateGroup] = groupTmpl
|
||||
adminTemplates[templateFolders] = foldersTmpl
|
||||
adminTemplates[templateFolder] = folderTmpl
|
||||
adminTemplates[templateStatus] = statusTmpl
|
||||
@@ -386,6 +436,8 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request)
|
||||
UserTemplateURL: webTemplateUser,
|
||||
AdminsURL: webAdminsPath,
|
||||
AdminURL: webAdminPath,
|
||||
GroupsURL: webGroupsPath,
|
||||
GroupURL: webGroupPath,
|
||||
FoldersURL: webFoldersPath,
|
||||
FolderURL: webFolderPath,
|
||||
FolderTemplateURL: webTemplateFolder,
|
||||
@@ -404,6 +456,7 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request)
|
||||
AdminsTitle: pageAdminsTitle,
|
||||
ConnectionsTitle: pageConnectionsTitle,
|
||||
FoldersTitle: pageFoldersTitle,
|
||||
GroupsTitle: pageGroupsTitle,
|
||||
StatusTitle: pageStatusTitle,
|
||||
MaintenanceTitle: pageMaintenanceTitle,
|
||||
DefenderTitle: pageDefenderTitle,
|
||||
@@ -595,7 +648,11 @@ func (s *httpdServer) renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Re
|
||||
func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User,
|
||||
mode userPageMode, error string,
|
||||
) {
|
||||
folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit)
|
||||
folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
groups, err := s.getWebGroups(w, r, defaultQueryLimit, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -628,10 +685,12 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
|
||||
WebClientOptions: sdk.WebClientOptions,
|
||||
RootDirPerms: user.GetPermissionsForPath("/"),
|
||||
VirtualFolders: folders,
|
||||
Groups: groups,
|
||||
CanImpersonate: os.Getuid() == 0,
|
||||
FsWrapper: fsWrapper{
|
||||
Filesystem: user.FsConfig,
|
||||
IsUserPage: true,
|
||||
IsGroupPage: false,
|
||||
HasUsersBaseDir: dataprovider.HasUsersBaseDir(),
|
||||
DirPath: user.HomeDir,
|
||||
},
|
||||
@@ -639,7 +698,52 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
|
||||
renderAdminTemplate(w, templateUser, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder, mode folderPageMode, error string) {
|
||||
func (s *httpdServer) renderGroupPage(w http.ResponseWriter, r *http.Request, group dataprovider.Group,
|
||||
mode groupPageMode, error string,
|
||||
) {
|
||||
folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
group.SetEmptySecretsIfNil()
|
||||
group.UserSettings.FsConfig.RedactedSecret = redactedSecret
|
||||
var title, currentURL string
|
||||
switch mode {
|
||||
case groupPageModeAdd:
|
||||
title = "Add a new group"
|
||||
currentURL = webGroupPath
|
||||
case groupPageModeUpdate:
|
||||
title = "Update group"
|
||||
currentURL = fmt.Sprintf("%v/%v", webGroupPath, url.PathEscape(group.Name))
|
||||
}
|
||||
group.UserSettings.FsConfig.RedactedSecret = redactedSecret
|
||||
group.UserSettings.FsConfig.SetEmptySecretsIfNil()
|
||||
|
||||
data := groupPage{
|
||||
basePage: s.getBasePageData(title, currentURL, r),
|
||||
Error: error,
|
||||
Group: &group,
|
||||
Mode: mode,
|
||||
ValidPerms: dataprovider.ValidPerms,
|
||||
ValidLoginMethods: dataprovider.ValidLoginMethods,
|
||||
ValidProtocols: dataprovider.ValidProtocols,
|
||||
TwoFactorProtocols: dataprovider.MFAProtocols,
|
||||
WebClientOptions: sdk.WebClientOptions,
|
||||
VirtualFolders: folders,
|
||||
FsWrapper: fsWrapper{
|
||||
Filesystem: group.UserSettings.FsConfig,
|
||||
IsUserPage: false,
|
||||
IsGroupPage: true,
|
||||
HasUsersBaseDir: false,
|
||||
DirPath: group.UserSettings.HomeDir,
|
||||
},
|
||||
}
|
||||
renderAdminTemplate(w, templateGroup, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder,
|
||||
mode folderPageMode, error string,
|
||||
) {
|
||||
var title, currentURL string
|
||||
switch mode {
|
||||
case folderPageModeAdd:
|
||||
@@ -663,6 +767,7 @@ func (s *httpdServer) renderFolderPage(w http.ResponseWriter, r *http.Request, f
|
||||
FsWrapper: fsWrapper{
|
||||
Filesystem: folder.FsConfig,
|
||||
IsUserPage: false,
|
||||
IsGroupPage: false,
|
||||
HasUsersBaseDir: false,
|
||||
DirPath: folder.MappedPath,
|
||||
},
|
||||
@@ -763,9 +868,8 @@ func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
|
||||
return virtualFolders
|
||||
}
|
||||
|
||||
func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
||||
func getSubDirPermissionsFromPostFields(r *http.Request) map[string][]string {
|
||||
permissions := make(map[string][]string)
|
||||
permissions["/"] = r.Form["permissions"]
|
||||
|
||||
for k := range r.Form {
|
||||
if strings.HasPrefix(k, "sub_perm_path") {
|
||||
@@ -780,6 +884,13 @@ func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
||||
return permissions
|
||||
}
|
||||
|
||||
func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
|
||||
permissions := getSubDirPermissionsFromPostFields(r)
|
||||
permissions["/"] = r.Form["permissions"]
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func getDataTransferLimitsFromPostFields(r *http.Request) ([]sdk.DataTransferLimit, error) {
|
||||
var result []sdk.DataTransferLimit
|
||||
|
||||
@@ -928,6 +1039,27 @@ func getFilePatternsFromPostField(r *http.Request) []sdk.PatternsFilter {
|
||||
return result
|
||||
}
|
||||
|
||||
func getGroupsFromUserPostFields(r *http.Request) []sdk.GroupMapping {
|
||||
var groups []sdk.GroupMapping
|
||||
|
||||
primaryGroup := r.Form.Get("primary_group")
|
||||
if primaryGroup != "" {
|
||||
groups = append(groups, sdk.GroupMapping{
|
||||
Name: primaryGroup,
|
||||
Type: sdk.GroupTypePrimary,
|
||||
})
|
||||
}
|
||||
secondaryGroups := r.Form["secondary_groups"]
|
||||
for _, name := range secondaryGroups {
|
||||
groups = append(groups, sdk.GroupMapping{
|
||||
Name: name,
|
||||
Type: sdk.GroupTypeSecondary,
|
||||
})
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) {
|
||||
var filters sdk.BaseUserFilters
|
||||
bwLimits, err := getBandwidthLimitsFromPostFields(r)
|
||||
@@ -938,6 +1070,10 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error)
|
||||
if err != nil {
|
||||
return filters, err
|
||||
}
|
||||
maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
|
||||
if err != nil {
|
||||
return filters, fmt.Errorf("invalid max upload file size: %w", err)
|
||||
}
|
||||
filters.BandwidthLimits = bwLimits
|
||||
filters.DataTransferLimits = dtLimits
|
||||
filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
|
||||
@@ -960,9 +1096,13 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error)
|
||||
}
|
||||
filters.DisableFsChecks = len(r.Form.Get("disable_fs_checks")) > 0
|
||||
filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
|
||||
filters.ExternalAuthCacheTime, err = strconv.ParseInt(r.Form.Get("external_auth_cache_time"), 10, 64)
|
||||
filters.StartDirectory = r.Form.Get("start_directory")
|
||||
return filters, err
|
||||
filters.MaxUploadFileSize = maxFileSize
|
||||
filters.ExternalAuthCacheTime, err = strconv.ParseInt(r.Form.Get("external_auth_cache_time"), 10, 64)
|
||||
if err != nil {
|
||||
return filters, fmt.Errorf("invalid external auth cache time: %w", err)
|
||||
}
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
|
||||
@@ -990,27 +1130,30 @@ func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
|
||||
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
|
||||
return config, fmt.Errorf("invalid s3 upload part size: %w", err)
|
||||
}
|
||||
config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid s3 upload concurrency: %w", err)
|
||||
}
|
||||
config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("s3_download_part_size"), 10, 64)
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid s3 download part size: %w", err)
|
||||
}
|
||||
config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("s3_download_concurrency"))
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid s3 download concurrency: %w", err)
|
||||
}
|
||||
config.ForcePathStyle = r.Form.Get("s3_force_path_style") != ""
|
||||
config.DownloadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_download_part_max_time"))
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid s3 download part max time: %w", err)
|
||||
}
|
||||
config.UploadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_upload_part_max_time"))
|
||||
return config, err
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("invalid s3 upload part max time: %w", err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
|
||||
@@ -1059,7 +1202,10 @@ func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
|
||||
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
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("invalid SFTP buffer size: %w", err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
||||
@@ -1075,18 +1221,21 @@ func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
||||
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
|
||||
return config, fmt.Errorf("invalid azure upload part size: %w", err)
|
||||
}
|
||||
config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("az_upload_concurrency"))
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid azure upload concurrency: %w", err)
|
||||
}
|
||||
config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("az_download_part_size"), 10, 64)
|
||||
if err != nil {
|
||||
return config, err
|
||||
return config, fmt.Errorf("invalid azure download part size: %w", err)
|
||||
}
|
||||
config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("az_download_concurrency"))
|
||||
return config, err
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("invalid azure download concurrency: %w", err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func getFsConfigFromPostFields(r *http.Request) (vfs.Filesystem, error) {
|
||||
@@ -1287,7 +1436,7 @@ func getQuotaLimits(r *http.Request) (int64, int, error) {
|
||||
}
|
||||
|
||||
func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
|
||||
var user dataprovider.User
|
||||
user := dataprovider.User{}
|
||||
err := r.ParseMultipartForm(maxRequestSize)
|
||||
if err != nil {
|
||||
return user, err
|
||||
@@ -1370,13 +1519,71 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
|
||||
},
|
||||
VirtualFolders: getVirtualFoldersFromPostFields(r),
|
||||
FsConfig: fsConfig,
|
||||
Groups: getGroupsFromUserPostFields(r),
|
||||
}
|
||||
maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func getGroupFromPostFields(r *http.Request) (dataprovider.Group, error) {
|
||||
group := dataprovider.Group{}
|
||||
err := r.ParseMultipartForm(maxRequestSize)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("invalid max upload file size: %w", err)
|
||||
return group, err
|
||||
}
|
||||
user.Filters.MaxUploadFileSize = maxFileSize
|
||||
return user, err
|
||||
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
||||
|
||||
maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
|
||||
if err != nil {
|
||||
return group, fmt.Errorf("invalid max sessions: %w", err)
|
||||
}
|
||||
quotaSize, quotaFiles, err := getQuotaLimits(r)
|
||||
if err != nil {
|
||||
return group, err
|
||||
}
|
||||
bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
|
||||
if err != nil {
|
||||
return group, fmt.Errorf("invalid upload bandwidth: %w", err)
|
||||
}
|
||||
bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
|
||||
if err != nil {
|
||||
return group, fmt.Errorf("invalid download bandwidth: %w", err)
|
||||
}
|
||||
dataTransferUL, dataTransferDL, dataTransferTotal, err := getTransferLimits(r)
|
||||
if err != nil {
|
||||
return group, err
|
||||
}
|
||||
fsConfig, err := getFsConfigFromPostFields(r)
|
||||
if err != nil {
|
||||
return group, err
|
||||
}
|
||||
filters, err := getFiltersFromUserPostFields(r)
|
||||
if err != nil {
|
||||
return group, err
|
||||
}
|
||||
group = dataprovider.Group{
|
||||
BaseGroup: sdk.BaseGroup{
|
||||
Name: r.Form.Get("name"),
|
||||
Description: r.Form.Get("description"),
|
||||
},
|
||||
UserSettings: dataprovider.GroupUserSettings{
|
||||
BaseGroupUserSettings: sdk.BaseGroupUserSettings{
|
||||
HomeDir: r.Form.Get("home_dir"),
|
||||
MaxSessions: maxSessions,
|
||||
QuotaSize: quotaSize,
|
||||
QuotaFiles: quotaFiles,
|
||||
Permissions: getSubDirPermissionsFromPostFields(r),
|
||||
UploadBandwidth: bandwidthUL,
|
||||
DownloadBandwidth: bandwidthDL,
|
||||
UploadDataTransfer: dataTransferUL,
|
||||
DownloadDataTransfer: dataTransferDL,
|
||||
TotalDataTransfer: dataTransferTotal,
|
||||
Filters: filters,
|
||||
},
|
||||
FsConfig: fsConfig,
|
||||
},
|
||||
VirtualFolders: getVirtualFoldersFromPostFields(r),
|
||||
}
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1927,11 +2134,11 @@ func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Reques
|
||||
PublicKeys: user.PublicKeys,
|
||||
})
|
||||
err = dataprovider.AddUser(&user, claims.Username, ipAddr)
|
||||
if err == nil {
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
} else {
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1979,14 +2186,14 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
|
||||
})
|
||||
|
||||
err = dataprovider.UpdateUser(&updatedUser, claims.Username, ipAddr)
|
||||
if err == nil {
|
||||
if r.Form.Get("disconnect") != "" {
|
||||
disconnectUser(user.Username)
|
||||
}
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
} else {
|
||||
s.renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &updatedUser, userPageModeUpdate, err.Error())
|
||||
return
|
||||
}
|
||||
if r.Form.Get("disconnect") != "" {
|
||||
disconnectUser(user.Username)
|
||||
}
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -2108,18 +2315,18 @@ func (s *httpdServer) handleWebUpdateFolderPost(w http.ResponseWriter, r *http.R
|
||||
|
||||
updatedFolder = getFolderFromTemplate(updatedFolder, updatedFolder.Name)
|
||||
|
||||
err = dataprovider.UpdateFolder(&updatedFolder, folder.Users, claims.Username, ipAddr)
|
||||
err = dataprovider.UpdateFolder(&updatedFolder, folder.Users, folder.Groups, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
|
||||
s.renderFolderPage(w, r, updatedFolder, folderPageModeUpdate, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *httpdServer) getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]vfs.BaseVirtualFolder, error) {
|
||||
func (s *httpdServer) getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int, minimal bool) ([]vfs.BaseVirtualFolder, error) {
|
||||
folders := make([]vfs.BaseVirtualFolder, 0, limit)
|
||||
for {
|
||||
f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC)
|
||||
f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC, minimal)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return folders, err
|
||||
@@ -2142,7 +2349,7 @@ func (s *httpdServer) handleWebGetFolders(w http.ResponseWriter, r *http.Request
|
||||
limit = defaultQueryLimit
|
||||
}
|
||||
}
|
||||
folders, err := s.getWebVirtualFolders(w, r, limit)
|
||||
folders, err := s.getWebVirtualFolders(w, r, limit, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -2153,3 +2360,127 @@ func (s *httpdServer) handleWebGetFolders(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
renderAdminTemplate(w, templateFolders, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) getWebGroups(w http.ResponseWriter, r *http.Request, limit int, minimal bool) ([]dataprovider.Group, error) {
|
||||
groups := make([]dataprovider.Group, 0, limit)
|
||||
for {
|
||||
f, err := dataprovider.GetGroups(limit, len(groups), dataprovider.OrderASC, minimal)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return groups, err
|
||||
}
|
||||
groups = append(groups, f...)
|
||||
if len(f) < limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebGetGroups(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
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
|
||||
}
|
||||
}
|
||||
groups, err := s.getWebGroups(w, r, limit, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := groupsPage{
|
||||
basePage: s.getBasePageData(pageGroupsTitle, webGroupsPath, r),
|
||||
Groups: groups,
|
||||
}
|
||||
renderAdminTemplate(w, templateGroups, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAddGroupGet(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
s.renderGroupPage(w, r, dataprovider.Group{}, groupPageModeAdd, "")
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAddGroupPost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
s.renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
||||
return
|
||||
}
|
||||
group, err := getGroupFromPostFields(r)
|
||||
if err != nil {
|
||||
s.renderGroupPage(w, r, group, groupPageModeAdd, err.Error())
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderForbiddenPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
err = dataprovider.AddGroup(&group, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderGroupPage(w, r, group, groupPageModeAdd, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webGroupsPath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebUpdateGroupGet(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
name := getURLParam(r, "name")
|
||||
group, err := dataprovider.GroupExists(name)
|
||||
if err == nil {
|
||||
s.renderGroupPage(w, r, group, groupPageModeUpdate, "")
|
||||
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
||||
s.renderNotFoundPage(w, r, err)
|
||||
} else {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebUpdateGroupPost(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
claims, err := getTokenClaims(r)
|
||||
if err != nil || claims.Username == "" {
|
||||
s.renderBadRequestPage(w, r, errors.New("invalid token claims"))
|
||||
return
|
||||
}
|
||||
name := getURLParam(r, "name")
|
||||
group, err := dataprovider.GroupExists(name)
|
||||
if _, ok := err.(*util.RecordNotFoundError); ok {
|
||||
s.renderNotFoundPage(w, r, err)
|
||||
return
|
||||
} else if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return
|
||||
}
|
||||
updatedGroup, err := getGroupFromPostFields(r)
|
||||
if err != nil {
|
||||
s.renderGroupPage(w, r, group, groupPageModeUpdate, err.Error())
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderForbiddenPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
updatedGroup.ID = group.ID
|
||||
updatedGroup.Name = group.Name
|
||||
updatedGroup.SetEmptySecretsIfNil()
|
||||
|
||||
updateEncryptedSecrets(&updatedGroup.UserSettings.FsConfig, group.UserSettings.FsConfig.S3Config.AccessSecret,
|
||||
group.UserSettings.FsConfig.AzBlobConfig.AccountKey, group.UserSettings.FsConfig.AzBlobConfig.SASURL,
|
||||
group.UserSettings.FsConfig.GCSConfig.Credentials, group.UserSettings.FsConfig.CryptConfig.Passphrase,
|
||||
group.UserSettings.FsConfig.SFTPConfig.Password, group.UserSettings.FsConfig.SFTPConfig.PrivateKey)
|
||||
|
||||
err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderGroupPage(w, r, updatedGroup, groupPageModeUpdate, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webGroupsPath, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user