ソースを参照

remove deprecated file extensions filters

these filters were deprecated a long time ago, everyone should use
patterns filters now
Nicola Murino 4 年 前
コミット
02bb09ec01

+ 3 - 46
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 {

+ 2 - 62
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))

+ 5 - 12
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)

+ 31 - 57
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())

+ 0 - 26
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

+ 0 - 37
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"]

+ 0 - 25
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")

+ 4 - 4
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{},
 		},
 	}
 

+ 11 - 56
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, "")

+ 3 - 3
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
 			}

+ 0 - 32
templates/webadmin/user.html

@@ -339,38 +339,6 @@
                 </div>
             </div>
 
-            <div class="form-group row">
-                <label for="idFilesExtensionsDenied" class="col-sm-2 col-form-label">Denied file extensions</label>
-                <div class="col-sm-10">
-                    <textarea class="form-control" id="idFilesExtensionsDenied" name="denied_extensions" rows="3"
-                        aria-describedby="deniedExtensionsHelpBlock">{{range $index, $filter := .User.Filters.FileExtensions -}}
-                        {{if $filter.DeniedExtensions -}}
-                        {{$filter.Path}}::{{range $idx, $p := $filter.DeniedExtensions}}{{if $idx}},{{end}}{{$p}}{{end}}&#10;
-                        {{- end}}
-                        {{- end}}</textarea>
-                    <small id="deniedExtensionsHelpBlock" class="form-text text-muted">
-                        One exposed virtual directory per line as /dir::extension1,extension2, for example
-                        /subdir::.zip,.rar. Deprecated, use file patterns
-                    </small>
-                </div>
-            </div>
-
-            <div class="form-group row">
-                <label for="idFilesExtensionsAllowed" class="col-sm-2 col-form-label">Allowed file extensions</label>
-                <div class="col-sm-10">
-                    <textarea class="form-control" id="idFilesExtensionsAllowed" name="allowed_extensions" rows="3"
-                        aria-describedby="allowedExtensionsHelpBlock">{{range $index, $filter := .User.Filters.FileExtensions -}}
-                        {{if $filter.AllowedExtensions -}}
-                        {{$filter.Path}}::{{range $idx, $p := $filter.AllowedExtensions}}{{if $idx}},{{end}}{{$p}}{{end}}&#10;
-                        {{- end}}
-                        {{- end}}</textarea>
-                    <small id="allowedExtensionsHelpBlock" class="form-text text-muted">
-                        One exposed virtual directory per line as /dir::extension1,extension2, for example
-                        /somedir::.jpg,.png. Deprecated, use file patterns
-                    </small>
-                </div>
-            </div>
-
             <div class="form-group row">
                 <label for="idHooks" class="col-sm-2 col-form-label">Hooks</label>
                 <div class="col-sm-10">

+ 5 - 12
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)