diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 951966d0..6332de0a 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -1278,11 +1278,11 @@ func validatePublicKeys(user *User) error { } _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k)) if err != nil { - return &ValidationError{err: fmt.Sprintf("could not parse key nr. %d: %s", i, err)} + return &ValidationError{err: fmt.Sprintf("could not parse key nr. %d: %s", i+1, err)} } validatedKeys = append(validatedKeys, k) } - user.PublicKeys = validatedKeys + user.PublicKeys = utils.RemoveDuplicates(validatedKeys) return nil } @@ -1330,49 +1330,6 @@ func validateFiltersPatternExtensions(user *User) error { return nil } -func validateFiltersFileExtensions(user *User) error { - if len(user.Filters.FileExtensions) == 0 { - user.Filters.FileExtensions = []ExtensionsFilter{} - return nil - } - filteredPaths := []string{} - var filters []ExtensionsFilter - for _, f := range user.Filters.FileExtensions { - cleanedPath := filepath.ToSlash(path.Clean(f.Path)) - if !path.IsAbs(cleanedPath) { - return &ValidationError{err: fmt.Sprintf("invalid path %#v for file extensions filter", f.Path)} - } - if utils.IsStringInSlice(cleanedPath, filteredPaths) { - return &ValidationError{err: fmt.Sprintf("duplicate file extensions filter for path %#v", f.Path)} - } - if len(f.AllowedExtensions) == 0 && len(f.DeniedExtensions) == 0 { - return &ValidationError{err: fmt.Sprintf("empty file extensions filter for path %#v", f.Path)} - } - f.Path = cleanedPath - allowed := make([]string, 0, len(f.AllowedExtensions)) - denied := make([]string, 0, len(f.DeniedExtensions)) - for _, ext := range f.AllowedExtensions { - allowed = append(allowed, strings.ToLower(ext)) - } - for _, ext := range f.DeniedExtensions { - denied = append(denied, strings.ToLower(ext)) - } - f.AllowedExtensions = allowed - f.DeniedExtensions = denied - filters = append(filters, f) - filteredPaths = append(filteredPaths, cleanedPath) - } - user.Filters.FileExtensions = filters - return nil -} - -func validateFileFilters(user *User) error { - if err := validateFiltersFileExtensions(user); err != nil { - return err - } - return validateFiltersPatternExtensions(user) -} - func checkEmptyFiltersStruct(user *User) { if len(user.Filters.AllowedIP) == 0 { user.Filters.AllowedIP = []string{} @@ -1428,7 +1385,7 @@ func validateFilters(user *User) error { return &ValidationError{err: fmt.Sprintf("invalid web client options %#v", opts)} } } - return validateFileFilters(user) + return validateFiltersPatternExtensions(user) } func saveGCSCredentials(fsConfig *vfs.Filesystem, helper fsValidatorHelper) error { diff --git a/dataprovider/user.go b/dataprovider/user.go index e6e3ee3c..92293a5a 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -85,28 +85,6 @@ var ( errNoMatchingVirtualFolder = errors.New("no matching virtual folder found") ) -// ExtensionsFilter defines filters based on file extensions. -// These restrictions do not apply to files listing for performance reasons, so -// a denied file cannot be downloaded/overwritten/renamed but will still be -// in the list of files. -// System commands such as Git and rsync interacts with the filesystem directly -// and they are not aware about these restrictions so they are not allowed -// inside paths with extensions filters -type ExtensionsFilter struct { - // Virtual path, if no other specific filter is defined, the filter apply for - // sub directories too. - // For example if filters are defined for the paths "/" and "/sub" then the - // filters for "/" are applied for any file outside the "/sub" directory - Path string `json:"path"` - // only files with these, case insensitive, extensions are allowed. - // Shell like expansion is not supported so you have to specify ".jpg" and - // not "*.jpg". If you want shell like patterns use pattern filters - AllowedExtensions []string `json:"allowed_extensions,omitempty"` - // files with these, case insensitive, extensions are not allowed. - // Denied file extensions are evaluated before the allowed ones - DeniedExtensions []string `json:"denied_extensions,omitempty"` -} - // PatternsFilter defines filters based on shell like patterns. // These restrictions do not apply to files listing for performance reasons, so // a denied file cannot be downloaded/overwritten/renamed but will still be @@ -151,10 +129,8 @@ type UserFilters struct { // these protocols are not allowed. // If null or empty any available protocol is allowed DeniedProtocols []string `json:"denied_protocols,omitempty"` - // filters based on file extensions. + // filter based on shell patterns. // Please note that these restrictions can be easily bypassed. - FileExtensions []ExtensionsFilter `json:"file_extensions,omitempty"` - // filter based on shell patterns FilePatterns []PatternsFilter `json:"file_patterns,omitempty"` // max size allowed for a single upload, 0 means unlimited MaxUploadFileSize int64 `json:"max_upload_file_size,omitempty"` @@ -780,41 +756,7 @@ func (u *User) GetAllowedLoginMethods() []string { // IsFileAllowed returns true if the specified file is allowed by the file restrictions filters func (u *User) IsFileAllowed(virtualPath string) bool { - return u.isFilePatternAllowed(virtualPath) && u.isFileExtensionAllowed(virtualPath) -} - -func (u *User) isFileExtensionAllowed(virtualPath string) bool { - if len(u.Filters.FileExtensions) == 0 { - return true - } - dirsForPath := utils.GetDirsForVirtualPath(path.Dir(virtualPath)) - var filter ExtensionsFilter - for _, dir := range dirsForPath { - for _, f := range u.Filters.FileExtensions { - if f.Path == dir { - filter = f - break - } - } - if filter.Path != "" { - break - } - } - if filter.Path != "" { - toMatch := strings.ToLower(virtualPath) - for _, denied := range filter.DeniedExtensions { - if strings.HasSuffix(toMatch, denied) { - return false - } - } - for _, allowed := range filter.AllowedExtensions { - if strings.HasSuffix(toMatch, allowed) { - return true - } - } - return len(filter.AllowedExtensions) == 0 - } - return true + return u.isFilePatternAllowed(virtualPath) } func (u *User) isFilePatternAllowed(virtualPath string) bool { @@ -1113,8 +1055,6 @@ func (u *User) getACopy() User { copy(filters.DeniedIP, u.Filters.DeniedIP) filters.DeniedLoginMethods = make([]string, len(u.Filters.DeniedLoginMethods)) copy(filters.DeniedLoginMethods, u.Filters.DeniedLoginMethods) - filters.FileExtensions = make([]ExtensionsFilter, len(u.Filters.FileExtensions)) - copy(filters.FileExtensions, u.Filters.FileExtensions) filters.FilePatterns = make([]PatternsFilter, len(u.Filters.FilePatterns)) copy(filters.FilePatterns, u.Filters.FilePatterns) filters.DeniedProtocols = make([]string, len(u.Filters.DeniedProtocols)) diff --git a/ftpd/ftpd_test.go b/ftpd/ftpd_test.go index ac78a059..fa88a21d 100644 --- a/ftpd/ftpd_test.go +++ b/ftpd/ftpd_test.go @@ -983,18 +983,11 @@ func TestDownloadErrors(t *testing.T) { u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems} u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermDownload} - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ - { - Path: "/sub2", - AllowedExtensions: []string{}, - DeniedExtensions: []string{".zip"}, - }, - } u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { Path: "/sub2", AllowedPatterns: []string{}, - DeniedPatterns: []string{"*.jpg"}, + DeniedPatterns: []string{"*.jpg", "*.zip"}, }, } user, _, err := httpdtest.AddUser(u, http.StatusCreated) @@ -1042,11 +1035,11 @@ func TestUploadErrors(t *testing.T) { u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems} u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete} - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/sub2", - AllowedExtensions: []string{}, - DeniedExtensions: []string{".zip"}, + Path: "/sub2", + AllowedPatterns: []string{}, + DeniedPatterns: []string{"*.zip"}, }, } user, _, err := httpdtest.AddUser(u, http.StatusCreated) diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index 358f7465..2693b6a4 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -645,39 +645,38 @@ func TestAddUserInvalidFilters(t *testing.T) { _, _, err = httpdtest.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) u.Filters.DeniedLoginMethods = []string{} - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "relative", - AllowedExtensions: []string{}, - DeniedExtensions: []string{}, + Path: "relative", + AllowedPatterns: []string{}, + DeniedPatterns: []string{}, }, } _, _, err = httpdtest.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/", - AllowedExtensions: []string{}, - DeniedExtensions: []string{}, + Path: "/", + AllowedPatterns: []string{}, + DeniedPatterns: []string{}, }, } _, _, err = httpdtest.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/subdir", - AllowedExtensions: []string{".zip"}, - DeniedExtensions: []string{}, + Path: "/subdir", + AllowedPatterns: []string{"*.zip"}, + DeniedPatterns: []string{}, }, { - Path: "/subdir", - AllowedExtensions: []string{".rar"}, - DeniedExtensions: []string{".jpg"}, + Path: "/subdir", + AllowedPatterns: []string{"*.rar"}, + DeniedPatterns: []string{"*.jpg"}, }, } _, _, err = httpdtest.AddUser(u, http.StatusBadRequest) assert.NoError(t, err) - u.Filters.FileExtensions = nil u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { Path: "relative", @@ -1140,11 +1139,6 @@ func TestUpdateUser(t *testing.T) { user.Filters.Hooks.PreLoginDisabled = true user.Filters.Hooks.CheckPasswordDisabled = false user.Filters.DisableFsChecks = true - user.Filters.FileExtensions = append(user.Filters.FileExtensions, dataprovider.ExtensionsFilter{ - Path: "/subdir", - AllowedExtensions: []string{".zip", ".rar"}, - DeniedExtensions: []string{".jpg", ".png"}, - }) user.Filters.FilePatterns = append(user.Filters.FilePatterns, dataprovider.PatternsFilter{ Path: "/subdir", AllowedPatterns: []string{"*.zip", "*.rar"}, @@ -5907,8 +5901,6 @@ func TestWebUserAddMock(t *testing.T) { form.Set("permissions", "*") form.Set("sub_dirs_permissions", " /subdir::list ,download ") form.Set("virtual_folders", fmt.Sprintf(" /vdir:: %v :: 2 :: 1024", folderName)) - form.Set("allowed_extensions", "/dir2::.jpg,.png\n/dir2::.ico\n/dir1::.rar") - form.Set("denied_extensions", "/dir2::.webp,.webp\n/dir2::.tiff\n/dir1::.zip") form.Set("allowed_patterns", "/dir2::*.jpg,*.png\n/dir1::*.png") form.Set("denied_patterns", "/dir1::*.zip\n/dir3::*.rar\n/dir2::*.mkv") form.Set("additional_info", user.AdditionalInfo) @@ -6093,24 +6085,6 @@ func TestWebUserAddMock(t *testing.T) { assert.Equal(t, v.QuotaFiles, 2) assert.Equal(t, v.QuotaSize, int64(1024)) } - assert.Len(t, newUser.Filters.FileExtensions, 2) - for _, filter := range newUser.Filters.FileExtensions { - if filter.Path == "/dir1" { - assert.Len(t, filter.DeniedExtensions, 1) - assert.Len(t, filter.AllowedExtensions, 1) - assert.True(t, utils.IsStringInSlice(".zip", filter.DeniedExtensions)) - assert.True(t, utils.IsStringInSlice(".rar", filter.AllowedExtensions)) - } - if filter.Path == "/dir2" { - assert.Len(t, filter.DeniedExtensions, 2) - assert.Len(t, filter.AllowedExtensions, 3) - assert.True(t, utils.IsStringInSlice(".jpg", filter.AllowedExtensions)) - assert.True(t, utils.IsStringInSlice(".png", filter.AllowedExtensions)) - assert.True(t, utils.IsStringInSlice(".ico", filter.AllowedExtensions)) - assert.True(t, utils.IsStringInSlice(".webp", filter.DeniedExtensions)) - assert.True(t, utils.IsStringInSlice(".tiff", filter.DeniedExtensions)) - } - } assert.Len(t, newUser.Filters.FilePatterns, 3) for _, filter := range newUser.Filters.FilePatterns { if filter.Path == "/dir1" { @@ -6185,7 +6159,7 @@ func TestWebUserUpdateMock(t *testing.T) { form.Set("expiration_date", "2020-01-01 00:00:00") form.Set("allowed_ip", " 192.168.1.3/32, 192.168.2.0/24 ") form.Set("denied_ip", " 10.0.0.2/32 ") - form.Set("denied_extensions", "/dir1::.zip") + form.Set("denied_patterns", "/dir1::*.zip") form.Set("ssh_login_methods", dataprovider.SSHLoginMethodKeyboardInteractive) form.Set("denied_protocols", common.ProtocolFTP) form.Set("max_upload_file_size", "100") @@ -6267,7 +6241,7 @@ func TestWebUserUpdateMock(t *testing.T) { assert.True(t, utils.IsStringInSlice("10.0.0.2/32", updateUser.Filters.DeniedIP)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, updateUser.Filters.DeniedLoginMethods)) assert.True(t, utils.IsStringInSlice(common.ProtocolFTP, updateUser.Filters.DeniedProtocols)) - assert.True(t, utils.IsStringInSlice(".zip", updateUser.Filters.FileExtensions[0].DeniedExtensions)) + assert.True(t, utils.IsStringInSlice("*.zip", updateUser.Filters.FilePatterns[0].DeniedPatterns)) req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil) assert.NoError(t, err) setBearerForReq(req, apiToken) @@ -6737,8 +6711,8 @@ func TestWebUserS3Mock(t *testing.T) { form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass) form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint) form.Set("s3_key_prefix", user.FsConfig.S3Config.KeyPrefix) - form.Set("allowed_extensions", "/dir1::.jpg,.png") - form.Set("denied_extensions", "/dir2::.zip") + form.Set("allowed_patterns", "/dir1::*.jpg,*.png") + form.Set("denied_patterns", "/dir2::*.zip") form.Set("max_upload_file_size", "0") form.Set("description", user.Description) form.Add("hooks", "pre_login_disabled") @@ -6783,7 +6757,7 @@ func TestWebUserS3Mock(t *testing.T) { assert.Equal(t, updateUser.FsConfig.S3Config.KeyPrefix, user.FsConfig.S3Config.KeyPrefix) assert.Equal(t, updateUser.FsConfig.S3Config.UploadPartSize, user.FsConfig.S3Config.UploadPartSize) assert.Equal(t, updateUser.FsConfig.S3Config.UploadConcurrency, user.FsConfig.S3Config.UploadConcurrency) - assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) + assert.Equal(t, 2, len(updateUser.Filters.FilePatterns)) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.S3Config.AccessSecret.GetStatus()) assert.NotEmpty(t, updateUser.FsConfig.S3Config.AccessSecret.GetPayload()) assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetKey()) @@ -6882,7 +6856,7 @@ func TestWebUserGCSMock(t *testing.T) { form.Set("gcs_bucket", user.FsConfig.GCSConfig.Bucket) form.Set("gcs_storage_class", user.FsConfig.GCSConfig.StorageClass) form.Set("gcs_key_prefix", user.FsConfig.GCSConfig.KeyPrefix) - form.Set("allowed_extensions", "/dir1::.jpg,.png") + form.Set("allowed_patterns", "/dir1::*.jpg,*.png") form.Set("max_upload_file_size", "0") b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -6916,7 +6890,7 @@ func TestWebUserGCSMock(t *testing.T) { assert.Equal(t, user.FsConfig.GCSConfig.Bucket, updateUser.FsConfig.GCSConfig.Bucket) assert.Equal(t, user.FsConfig.GCSConfig.StorageClass, updateUser.FsConfig.GCSConfig.StorageClass) assert.Equal(t, user.FsConfig.GCSConfig.KeyPrefix, updateUser.FsConfig.GCSConfig.KeyPrefix) - assert.Equal(t, "/dir1", updateUser.Filters.FileExtensions[0].Path) + assert.Equal(t, "/dir1", updateUser.Filters.FilePatterns[0].Path) form.Set("gcs_auto_credentials", "on") b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b) @@ -6989,8 +6963,8 @@ func TestWebUserAzureBlobMock(t *testing.T) { form.Set("az_endpoint", user.FsConfig.AzBlobConfig.Endpoint) form.Set("az_key_prefix", user.FsConfig.AzBlobConfig.KeyPrefix) form.Set("az_use_emulator", "checked") - form.Set("allowed_extensions", "/dir1::.jpg,.png") - form.Set("denied_extensions", "/dir2::.zip") + form.Set("allowed_patterns", "/dir1::*.jpg,*.png") + form.Set("denied_patterns", "/dir2::*.zip") form.Set("max_upload_file_size", "0") // test invalid az_upload_part_size form.Set("az_upload_part_size", "a") @@ -7032,7 +7006,7 @@ func TestWebUserAzureBlobMock(t *testing.T) { assert.Equal(t, updateUser.FsConfig.AzBlobConfig.KeyPrefix, user.FsConfig.AzBlobConfig.KeyPrefix) assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadPartSize, user.FsConfig.AzBlobConfig.UploadPartSize) assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadConcurrency, user.FsConfig.AzBlobConfig.UploadConcurrency) - assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) + assert.Equal(t, 2, len(updateUser.Filters.FilePatterns)) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.AzBlobConfig.AccountKey.GetStatus()) assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload()) assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetKey()) @@ -7099,8 +7073,8 @@ func TestWebUserCryptMock(t *testing.T) { form.Set("denied_ip", "") form.Set("fs_provider", "4") form.Set("crypt_passphrase", "") - form.Set("allowed_extensions", "/dir1::.jpg,.png") - form.Set("denied_extensions", "/dir2::.zip") + form.Set("allowed_patterns", "/dir1::*.jpg,*.png") + form.Set("denied_patterns", "/dir2::*.zip") form.Set("max_upload_file_size", "0") // passphrase cannot be empty b, contentType, _ := getMultipartFormData(form, "", "") @@ -7124,7 +7098,7 @@ func TestWebUserCryptMock(t *testing.T) { err = render.DecodeJSON(rr.Body, &updateUser) assert.NoError(t, err) assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate) - assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) + assert.Equal(t, 2, len(updateUser.Filters.FilePatterns)) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.CryptConfig.Passphrase.GetStatus()) assert.NotEmpty(t, updateUser.FsConfig.CryptConfig.Passphrase.GetPayload()) assert.Empty(t, updateUser.FsConfig.CryptConfig.Passphrase.GetKey()) @@ -7198,8 +7172,8 @@ func TestWebUserSFTPFsMock(t *testing.T) { form.Set("denied_ip", "") form.Set("fs_provider", "5") form.Set("crypt_passphrase", "") - form.Set("allowed_extensions", "/dir1::.jpg,.png") - form.Set("denied_extensions", "/dir2::.zip") + form.Set("allowed_patterns", "/dir1::*.jpg,*.png") + form.Set("denied_patterns", "/dir2::*.zip") form.Set("max_upload_file_size", "0") // empty sftpconfig b, contentType, _ := getMultipartFormData(form, "", "") @@ -7230,7 +7204,7 @@ func TestWebUserSFTPFsMock(t *testing.T) { err = render.DecodeJSON(rr.Body, &updateUser) assert.NoError(t, err) assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate) - assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) + assert.Equal(t, 2, len(updateUser.Filters.FilePatterns)) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.SFTPConfig.Password.GetStatus()) assert.NotEmpty(t, updateUser.FsConfig.SFTPConfig.Password.GetPayload()) assert.Empty(t, updateUser.FsConfig.SFTPConfig.Password.GetKey()) diff --git a/httpd/schema/openapi.yaml b/httpd/schema/openapi.yaml index 1ca609d2..1bacbb20 100644 --- a/httpd/schema/openapi.yaml +++ b/httpd/schema/openapi.yaml @@ -1414,27 +1414,6 @@ components: description: 'list of, case insensitive, denied shell like file patterns. Denied patterns are evaluated before the allowed ones' example: - '*.zip' - ExtensionsFilter: - type: object - properties: - path: - type: string - description: 'exposed virtual path, if no other specific filter is defined, the filter apply for sub directories too. For example if filters are defined for the paths "/" and "/sub" then the filters for "/" are applied for any file outside the "/sub" directory' - allowed_extensions: - type: array - items: - type: string - description: 'list of, case insensitive, allowed files extension. Shell like expansion is not supported so you have to specify `.jpg` and not `*.jpg`' - example: - - .jpg - - .png - denied_extensions: - type: array - items: - type: string - description: 'list of, case insensitive, denied files extension. Denied file extensions are evaluated before the allowed ones' - example: - - .zip HooksFilter: type: object properties: @@ -1484,11 +1463,6 @@ components: items: $ref: '#/components/schemas/PatternsFilter' description: 'filters based on shell like file patterns. These restrictions do not apply to files listing for performance reasons, so a denied file cannot be downloaded/overwritten/renamed but it will still be in the list of files. Please note that these restrictions can be easily bypassed' - file_extensions: - type: array - items: - $ref: '#/components/schemas/ExtensionsFilter' - description: 'filters based on shell like patterns. Deprecated, use file_patterns. These restrictions do not apply to files listing for performance reasons, so a denied file cannot be downloaded/overwritten/renamed but it will still be in the list of files. Please note that these restrictions can be easily bypassed' max_upload_file_size: type: integer format: int64 diff --git a/httpd/webadmin.go b/httpd/webadmin.go index 7c234238..697e4dc7 100644 --- a/httpd/webadmin.go +++ b/httpd/webadmin.go @@ -587,49 +587,12 @@ func getFilePatternsFromPostField(valueAllowed, valuesDenied string) []dataprovi 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")) filters.TLSUsername = dataprovider.TLSUsername(r.Form.Get("tls_username")) filters.WebClient = r.Form["web_client_options"] diff --git a/httpdtest/httpdtest.go b/httpdtest/httpdtest.go index 0f3c241f..aa999146 100644 --- a/httpdtest/httpdtest.go +++ b/httpdtest/httpdtest.go @@ -1213,9 +1213,6 @@ func compareUserFilters(expected *dataprovider.User, actual *dataprovider.User) if err := compareUserFilterSubStructs(expected, actual); err != nil { return err } - if err := compareUserFileExtensionsFilters(expected, actual); err != nil { - return err - } return compareUserFilePatternsFilters(expected, actual) } @@ -1253,28 +1250,6 @@ func compareUserFilePatternsFilters(expected *dataprovider.User, actual *datapro return nil } -func compareUserFileExtensionsFilters(expected *dataprovider.User, actual *dataprovider.User) error { - if len(expected.Filters.FileExtensions) != len(actual.Filters.FileExtensions) { - return errors.New("file extensions mismatch") - } - for _, f := range expected.Filters.FileExtensions { - found := false - for _, f1 := range actual.Filters.FileExtensions { - if path.Clean(f.Path) == path.Clean(f1.Path) { - if !checkFilterMatch(f.AllowedExtensions, f1.AllowedExtensions) || - !checkFilterMatch(f.DeniedExtensions, f1.DeniedExtensions) { - return errors.New("file extensions contents mismatch") - } - found = true - } - } - if !found { - return errors.New("file extensions contents mismatch") - } - } - return nil -} - func compareEqualsUserFields(expected *dataprovider.User, actual *dataprovider.User) error { if expected.Username != actual.Username { return errors.New("username mismatch") diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index 5e4a9e62..15c4b659 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -592,11 +592,11 @@ func TestCommandsWithExtensionsFilter(t *testing.T) { HomeDir: os.TempDir(), Status: 1, } - user.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + user.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/subdir", - AllowedExtensions: []string{".jpg"}, - DeniedExtensions: []string{}, + Path: "/subdir", + AllowedPatterns: []string{".jpg"}, + DeniedPatterns: []string{}, }, } diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 2a341f40..fffd70e1 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -3534,13 +3534,6 @@ func TestExtensionsFilters(t *testing.T) { err = sftpUploadFile(testFilePath, testFileName+".jpg", testFileSize, client) assert.NoError(t, err) } - user.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ - { - Path: "/", - AllowedExtensions: []string{".zIp", ".jPg"}, - DeniedExtensions: []string{}, - }, - } user.Filters.FilePatterns = []dataprovider.PatternsFilter{ { Path: "/", @@ -6760,7 +6753,6 @@ func TestUserPerms(t *testing.T) { assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/1/test/file.dat")) } -//nolint:dupl func TestFilterFilePatterns(t *testing.T) { user := getTestUser(true) pattern := dataprovider.PatternsFilter{ @@ -6797,43 +6789,6 @@ func TestFilterFilePatterns(t *testing.T) { assert.False(t, user.IsFileAllowed("/test/test.zip")) } -//nolint:dupl -func TestFilterFileExtensions(t *testing.T) { - user := getTestUser(true) - extension := dataprovider.ExtensionsFilter{ - Path: "/test", - AllowedExtensions: []string{".jpg", ".png"}, - DeniedExtensions: []string{".pdf"}, - } - filters := dataprovider.UserFilters{ - FileExtensions: []dataprovider.ExtensionsFilter{extension}, - } - user.Filters = filters - assert.True(t, user.IsFileAllowed("/test/test.jPg")) - assert.False(t, user.IsFileAllowed("/test/test.pdf")) - assert.True(t, user.IsFileAllowed("/test.pDf")) - - filters.FileExtensions = append(filters.FileExtensions, dataprovider.ExtensionsFilter{ - Path: "/", - AllowedExtensions: []string{".zip", ".rar", ".pdf"}, - DeniedExtensions: []string{".gz"}, - }) - user.Filters = filters - assert.False(t, user.IsFileAllowed("/test1/test.gz")) - assert.True(t, user.IsFileAllowed("/test1/test.zip")) - assert.False(t, user.IsFileAllowed("/test/sub/test.pdf")) - assert.False(t, user.IsFileAllowed("/test1/test.png")) - - filters.FileExtensions = append(filters.FileExtensions, dataprovider.ExtensionsFilter{ - Path: "/test/sub", - DeniedExtensions: []string{".tar"}, - }) - user.Filters = filters - assert.False(t, user.IsFileAllowed("/test/sub/sub/test.tar")) - assert.True(t, user.IsFileAllowed("/test/sub/test.gz")) - assert.False(t, user.IsFileAllowed("/test/test.zip")) -} - func TestUserAllowedLoginMethods(t *testing.T) { user := getTestUser(true) user.Filters.DeniedLoginMethods = dataprovider.ValidLoginMethods @@ -7286,10 +7241,10 @@ func TestSSHCopy(t *testing.T) { QuotaFiles: 100, QuotaSize: 0, }) - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/", - DeniedExtensions: []string{".denied"}, + Path: "/", + DeniedPatterns: []string{"*.denied"}, }, } err := os.MkdirAll(mappedPath1, os.ModePerm) @@ -7564,10 +7519,10 @@ func TestSSHCopyQuotaLimits(t *testing.T) { QuotaFiles: 3, QuotaSize: testFileSize + testFileSize1 + 1, }) - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/", - DeniedExtensions: []string{".denied"}, + Path: "/", + DeniedPatterns: []string{"*.denied"}, }, } err := os.MkdirAll(mappedPath1, os.ModePerm) @@ -8327,7 +8282,7 @@ func TestSCPRecursive(t *testing.T) { assert.NoError(t, err) } -func TestSCPExtensionsFilter(t *testing.T) { +func TestSCPPatternsFilter(t *testing.T) { if len(scpPath) == 0 { t.Skip("scp command not found, unable to execute this test") } @@ -8344,11 +8299,11 @@ func TestSCPExtensionsFilter(t *testing.T) { assert.NoError(t, err) err = scpUpload(testFilePath, remoteUpPath, false, false) assert.NoError(t, err) - user.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + user.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/", - AllowedExtensions: []string{".zip"}, - DeniedExtensions: []string{}, + Path: "/", + AllowedPatterns: []string{"*.zip"}, + DeniedPatterns: []string{}, }, } _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") diff --git a/sftpd/ssh_cmd.go b/sftpd/ssh_cmd.go index 4dedbb5b..65a5b595 100644 --- a/sftpd/ssh_cmd.go +++ b/sftpd/ssh_cmd.go @@ -415,17 +415,17 @@ func (c *sshCommand) isSystemCommandAllowed() error { c.command, sshDestPath, c.connection.User.Username) return errUnsupportedConfig } - for _, f := range c.connection.User.Filters.FileExtensions { + for _, f := range c.connection.User.Filters.FilePatterns { if f.Path == sshDestPath { c.connection.Log(logger.LevelDebug, - "command %#v is not allowed inside folders with files extensions filters %#v user %#v", + "command %#v is not allowed inside folders with files patterns filters %#v user %#v", c.command, sshDestPath, c.connection.User.Username) return errUnsupportedConfig } if len(sshDestPath) > len(f.Path) { if strings.HasPrefix(sshDestPath, f.Path+"/") || f.Path == "/" { c.connection.Log(logger.LevelDebug, - "command %#v is not allowed it includes folders with files extensions filters %#v user %#v", + "command %#v is not allowed it includes folders with files patterns filters %#v user %#v", c.command, sshDestPath, c.connection.User.Username) return errUnsupportedConfig } diff --git a/templates/webadmin/user.html b/templates/webadmin/user.html index e4354029..41f9ab33 100644 --- a/templates/webadmin/user.html +++ b/templates/webadmin/user.html @@ -339,38 +339,6 @@ -
- -
- - - One exposed virtual directory per line as /dir::extension1,extension2, for example - /subdir::.zip,.rar. Deprecated, use file patterns - -
-
- -
- -
- - - One exposed virtual directory per line as /dir::extension1,extension2, for example - /somedir::.jpg,.png. Deprecated, use file patterns - -
-
-
diff --git a/webdavd/webdavd_test.go b/webdavd/webdavd_test.go index 5274fba4..c9b9f19c 100644 --- a/webdavd/webdavd_test.go +++ b/webdavd/webdavd_test.go @@ -1022,18 +1022,11 @@ func TestDownloadErrors(t *testing.T) { u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermDownload} // use an unknown mime to trigger content type detection - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ - { - Path: "/sub2", - AllowedExtensions: []string{}, - DeniedExtensions: []string{".zipp"}, - }, - } u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { Path: "/sub2", AllowedPatterns: []string{}, - DeniedPatterns: []string{"*.jpg"}, + DeniedPatterns: []string{"*.jpg", "*.zipp"}, }, } user, _, err := httpdtest.AddUser(u, http.StatusCreated) @@ -1079,11 +1072,11 @@ func TestUploadErrors(t *testing.T) { u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems, dataprovider.PermDownload} u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermDownload} - u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ + u.Filters.FilePatterns = []dataprovider.PatternsFilter{ { - Path: "/sub2", - AllowedExtensions: []string{}, - DeniedExtensions: []string{".zip"}, + Path: "/sub2", + AllowedPatterns: []string{}, + DeniedPatterns: []string{"*.zip"}, }, } user, _, err := httpdtest.AddUser(u, http.StatusCreated)