diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go
index dd5fd32d..c7472221 100644
--- a/internal/common/connection_test.go
+++ b/internal/common/connection_test.go
@@ -28,6 +28,7 @@ import (
"github.com/rs/xid"
"github.com/sftpgo/sdk"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
"github.com/drakkan/sftpgo/v2/internal/kms"
@@ -647,7 +648,7 @@ func TestFsFileCopier(t *testing.T) {
assert.True(t, ok)
}
-func TestFilterListDirs(t *testing.T) {
+func TestFilePatterns(t *testing.T) {
filters := dataprovider.UserFilters{
BaseUserFilters: sdk.BaseUserFilters{
FilePatterns: []sdk.PatternsFilter{
@@ -661,6 +662,16 @@ func TestFilterListDirs(t *testing.T) {
DenyPolicy: sdk.DenyPolicyHide,
AllowedPatterns: []string{"*.jpg"},
},
+ {
+ Path: "/dir3",
+ DenyPolicy: sdk.DenyPolicyDefault,
+ DeniedPatterns: []string{"*.jpg"},
+ },
+ {
+ Path: "/dir4",
+ DenyPolicy: sdk.DenyPolicyHide,
+ DeniedPatterns: []string{"*"},
+ },
},
},
}
@@ -693,13 +704,90 @@ func TestFilterListDirs(t *testing.T) {
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
}
-
+ // dirContents are modified in place, we need to redefine them each time
filtered := user.FilterListDir(dirContents, "/dir1")
assert.Len(t, filtered, 5)
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir1/vdir1")
+ assert.Len(t, filtered, 2)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir2/vdir2")
+ require.Len(t, filtered, 1)
+ assert.Equal(t, "file1.jpg", filtered[0].Name())
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir2/vdir2/sub")
+ require.Len(t, filtered, 1)
+ assert.Equal(t, "file1.jpg", filtered[0].Name())
+
+ res, _ := user.IsFileAllowed("/dir1/vdir1/file.txt")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir1/vdir1/sub/file.txt")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir1/vdir1/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir1/vdir1/sub/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/dir1/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/dir1/sub/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir4/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir4/dir1/sub/file.jpg")
+ assert.False(t, res)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir4")
+ require.Len(t, filtered, 0)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir4/vdir2/sub")
+ require.Len(t, filtered, 0)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+
filtered = user.FilterListDir(dirContents, "/dir2")
assert.Len(t, filtered, 2)
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+
+ filtered = user.FilterListDir(dirContents, "/dir4")
+ assert.Len(t, filtered, 0)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ }
+
+ filtered = user.FilterListDir(dirContents, "/dir4/sub")
+ assert.Len(t, filtered, 0)
+
dirContents = []os.FileInfo{
vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
vfs.NewFileInfo("vdir3.jpg", false, 123, time.Now(), false),
@@ -708,11 +796,6 @@ func TestFilterListDirs(t *testing.T) {
filtered = user.FilterListDir(dirContents, "/dir1")
assert.Len(t, filtered, 5)
- dirContents = []os.FileInfo{
- vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
- vfs.NewFileInfo("vdir3.jpg", false, 123, time.Now(), false),
- }
-
filtered = user.FilterListDir(dirContents, "/dir2")
if assert.Len(t, filtered, 1) {
assert.True(t, filtered[0].IsDir())
@@ -799,7 +882,155 @@ func TestFilterListDirs(t *testing.T) {
dirContents = append(dirContents, vfs.NewFileInfo("file.jpg", false, 123, time.Now(), false))
filtered = user.FilterListDir(dirContents, "/dir3")
- if assert.Len(t, filtered, 1) {
- assert.Equal(t, "ic35", filtered[0].Name())
+ require.Len(t, filtered, 1)
+ assert.Equal(t, "ic35", filtered[0].Name())
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
}
+ filtered = user.FilterListDir(dirContents, "/dir3/ic36")
+ require.Len(t, filtered, 0)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35")
+ require.Len(t, filtered, 3)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub")
+ require.Len(t, filtered, 3)
+
+ res, _ = user.IsFileAllowed("/dir3/file.txt")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35a")
+ assert.False(t, res)
+ res, policy := user.IsFileAllowed("/dir3/ic35a/file")
+ assert.False(t, res)
+ assert.Equal(t, sdk.DenyPolicyHide, policy)
+ res, _ = user.IsFileAllowed("/dir3/ic35")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub/file.txt")
+ assert.True(t, res)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub")
+ require.Len(t, filtered, 3)
+
+ user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
+ Path: "/dir3/ic35/sub1",
+ AllowedPatterns: []string{"*.jpg"},
+ DenyPolicy: sdk.DenyPolicyDefault,
+ })
+ user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
+ Path: "/dir3/ic35/sub2",
+ DeniedPatterns: []string{"*.jpg"},
+ DenyPolicy: sdk.DenyPolicyHide,
+ })
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub1")
+ require.Len(t, filtered, 3)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub2")
+ require.Len(t, filtered, 2)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/sub2/sub1")
+ require.Len(t, filtered, 2)
+
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub/dir/file.txt")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub/dir/file.jpg")
+ assert.True(t, res)
+
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub1/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub1/file.txt")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub1/sub/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub1/sub2/file.txt")
+ assert.False(t, res)
+
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.txt")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub1/file.txt")
+ assert.True(t, res)
+
+ user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
+ Path: "/dir3/ic35",
+ DeniedPatterns: []string{"*.txt"},
+ DenyPolicy: sdk.DenyPolicyHide,
+ })
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/file.txt")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/adir/sub/file.jpg")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/adir/file.txt")
+ assert.False(t, res)
+
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/file.txt")
+ assert.True(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub/file.jpg")
+ assert.False(t, res)
+ res, _ = user.IsFileAllowed("/dir3/ic35/sub2/sub1/file.txt")
+ assert.True(t, res)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35")
+ require.Len(t, filtered, 1)
+
+ dirContents = []os.FileInfo{
+ vfs.NewFileInfo("file1.jpg", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file1.txt", false, 123, time.Now(), false),
+ vfs.NewFileInfo("file2.txt", false, 123, time.Now(), false),
+ }
+ filtered = user.FilterListDir(dirContents, "/dir3/ic35/abc")
+ require.Len(t, filtered, 1)
}
diff --git a/internal/dataprovider/user.go b/internal/dataprovider/user.go
index c8e1141b..07fd2ced 100644
--- a/internal/dataprovider/user.go
+++ b/internal/dataprovider/user.go
@@ -981,9 +981,14 @@ func (u *User) getPatternsFilterForPath(virtualPath string) sdk.PatternsFilter {
return filter
}
dirsForPath := util.GetDirsForVirtualPath(virtualPath)
- for _, dir := range dirsForPath {
+ for idx, dir := range dirsForPath {
for _, f := range u.Filters.FilePatterns {
if f.Path == dir {
+ if idx > 0 && len(f.AllowedPatterns) > 0 && len(f.DeniedPatterns) > 0 && f.DeniedPatterns[0] == "*" {
+ if f.CheckAllowed(path.Base(dirsForPath[idx-1])) {
+ return filter
+ }
+ }
filter = f
break
}
@@ -1004,7 +1009,7 @@ func (u *User) isDirHidden(virtualPath string) bool {
return false
}
filter := u.getPatternsFilterForPath(dirPath)
- if filter.DenyPolicy == sdk.DenyPolicyHide {
+ if filter.DenyPolicy == sdk.DenyPolicyHide && filter.Path != dirPath {
if !filter.CheckAllowed(path.Base(dirPath)) {
return true
}
diff --git a/templates/webadmin/group.html b/templates/webadmin/group.html
index 2a5e6cbb..08d7b812 100644
--- a/templates/webadmin/group.html
+++ b/templates/webadmin/group.html
@@ -222,7 +222,7 @@ along with this program. If not, see
Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories
+Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories. Setting a denied pattern as "*" and allowed pattern/s for the same directory you can create denied except rules, but note that if you allow a directory, everything in it will be allowed unless more specific patterns/permissions are defined.
Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories
+Match is case insensitive, set you patterns as lowercase. Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories. Setting a denied pattern as "*" and allowed pattern/s for the same directory you can create denied except rules, but note that if you allow a directory, everything in it will be allowed unless more specific patterns/permissions are defined.