Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions internal/fs/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package fs

import (
"context"

"github.com/OpenListTeam/OpenList/v4/internal/conf"
"github.com/OpenListTeam/OpenList/v4/internal/errs"
"github.com/OpenListTeam/OpenList/v4/internal/model"
"github.com/OpenListTeam/OpenList/v4/internal/op"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
"github.com/OpenListTeam/OpenList/v4/server/common"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"path"
)

// List files
Expand Down Expand Up @@ -43,7 +44,29 @@ func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error)
om.InitHideReg(meta.Hide)
}
objs := om.Merge(_objs, virtualFiles...)
return objs, nil
objs, err = filterReadableObjs(objs, user, path, meta)
return objs, err
}

func filterReadableObjs(objs []model.Obj, user *model.User, reqPath string, parentMeta *model.Meta) ([]model.Obj, error) {
var result []model.Obj
for _, obj := range objs {
var meta *model.Meta
objPath := path.Join(reqPath, obj.GetName())
if obj.IsDir() {
var err error
meta, err = op.GetNearestMeta(objPath)
if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return result, err
}
} else {
meta = parentMeta
}
if common.CanRead(user, meta, objPath) {
result = append(result, obj)
}
}
return result, nil
}

func whetherHide(user *model.User, meta *model.Meta, path string) bool {
Expand All @@ -60,7 +83,7 @@ func whetherHide(user *model.User, meta *model.Meta, path string) bool {
return false
}
// if meta doesn't apply to sub_folder, don't hide
if !utils.PathEqual(meta.Path, path) && !meta.HSub {
if !common.MetaCoversPath(meta.Path, path, meta.HSub) {
return false
}
// if is guest, hide
Expand Down
151 changes: 151 additions & 0 deletions internal/fs/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package fs

import (
"testing"

"github.com/OpenListTeam/OpenList/v4/internal/model"
)

func TestWhetherHide(t *testing.T) {
tests := []struct {
name string
user *model.User
meta *model.Meta
path string
want bool
reason string
}{
{
name: "nil user",
user: nil,
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: true,
},
path: "/folder",
want: false,
reason: "nil user (treated as admin) should not hide",
},
{
name: "user with can_see_hides permission",
user: &model.User{
Role: model.GENERAL,
Permission: 1, // bit 0 set = can see hides
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: true,
},
path: "/folder",
want: false,
reason: "user with can_see_hides permission should not hide",
},
{
name: "nil meta",
user: &model.User{
Role: model.GUEST,
},
meta: nil,
path: "/folder",
want: false,
reason: "nil meta should not hide",
},
{
name: "empty hide string",
user: &model.User{
Role: model.GUEST,
},
meta: &model.Meta{
Path: "/folder",
Hide: "",
HSub: true,
},
path: "/folder",
want: false,
reason: "empty hide string should not hide",
},
{
name: "exact path match with HSub=false",
user: &model.User{
Role: model.GUEST,
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: false,
},
path: "/folder",
want: true,
reason: "exact path match should hide for guest",
},
{
name: "sub path with HSub=true",
user: &model.User{
Role: model.GUEST,
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: true,
},
path: "/folder/subfolder",
want: true,
reason: "sub path with HSub=true should hide for guest",
},
{
name: "sub path with HSub=false",
user: &model.User{
Role: model.GUEST,
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: false,
},
path: "/folder/subfolder",
want: false,
reason: "sub path with HSub=false should not hide",
},
{
name: "non-sub path with HSub=true",
user: &model.User{
Role: model.GUEST,
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: true,
},
path: "/other",
want: false,
reason: "non-sub path should not hide even with HSub=true",
},
{
name: "user without can_see_hides permission",
user: &model.User{
Role: model.GENERAL,
Permission: 0, // bit 0 not set = cannot see hides
},
meta: &model.Meta{
Path: "/folder",
Hide: "secret",
HSub: true,
},
path: "/folder",
want: true,
reason: "user without can_see_hides permission should hide",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := whetherHide(tt.user, tt.meta, tt.path)
if got != tt.want {
t.Errorf("whetherHide() = %v, want %v\nReason: %s",
got, tt.want, tt.reason)
}
})
}
}
28 changes: 16 additions & 12 deletions internal/model/meta.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package model

type Meta struct {
ID uint `json:"id" gorm:"primaryKey"`
Path string `json:"path" gorm:"unique" binding:"required"`
Password string `json:"password"`
PSub bool `json:"p_sub"`
Write bool `json:"write"`
WSub bool `json:"w_sub"`
Hide string `json:"hide"`
HSub bool `json:"h_sub"`
Readme string `json:"readme"`
RSub bool `json:"r_sub"`
Header string `json:"header"`
HeaderSub bool `json:"header_sub"`
ID uint `json:"id" gorm:"primaryKey"`
Path string `json:"path" gorm:"unique" binding:"required"`
ReadUsers []uint `json:"read_users" gorm:"serializer:json"`
ReadUsersSub bool `json:"read_users_sub"`
WriteUsers []uint `json:"write_users" gorm:"serializer:json"`
WriteUsersSub bool `json:"write_users_sub"`
Password string `json:"password"`
PSub bool `json:"p_sub"`
Write bool `json:"write"`
WSub bool `json:"w_sub"`
Hide string `json:"hide"`
HSub bool `json:"h_sub"`
Readme string `json:"readme"`
RSub bool `json:"r_sub"`
Header string `json:"header"`
HeaderSub bool `json:"header_sub"`
}
6 changes: 3 additions & 3 deletions internal/model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ func (u *User) CanAddOfflineDownloadTasks() bool {
return CanAddOfflineDownloadTasks(u.Permission)
}

func CanWrite(permission int32) bool {
func CanWriteContent(permission int32) bool {
return (permission>>3)&1 == 1
}

func (u *User) CanWrite() bool {
return CanWrite(u.Permission)
func (u *User) CanWriteContent() bool {
return CanWriteContent(u.Permission)
}

func CanRename(permission int32) bool {
Expand Down
42 changes: 33 additions & 9 deletions server/common/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"path"
"slices"
"strings"

"github.com/OpenListTeam/OpenList/v4/internal/conf"
Expand All @@ -17,31 +18,47 @@ func IsStorageSignEnabled(rawPath string) bool {
return storage != nil && storage.GetStorage().EnableSign
}

func CanWrite(meta *model.Meta, path string) bool {
if meta == nil || !meta.Write {
func CanRead(user *model.User, meta *model.Meta, path string) bool {
if user == nil {
return false
}
if meta != nil && len(meta.ReadUsers) > 0 && !slices.Contains(meta.ReadUsers, user.ID) && (meta.ReadUsersSub || meta.Path == path) {
return false
}
return meta.WSub || meta.Path == path
return true
}

func IsApply(metaPath, reqPath string, applySub bool) bool {
if utils.PathEqual(metaPath, reqPath) {
return true
func CanWrite(user *model.User, meta *model.Meta, path string) bool {
if user == nil {
return false
}
if meta != nil && len(meta.WriteUsers) > 0 && !slices.Contains(meta.WriteUsers, user.ID) && (meta.WriteUsersSub || meta.Path == path) {
return false
}
return true
}

func CanWriteContentBypassUserPerms(meta *model.Meta, path string) bool {
if meta == nil || !meta.Write {
return false
}
return utils.IsSubPath(metaPath, reqPath) && applySub
return MetaCoversPath(meta.Path, path, meta.WSub)
}

func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
// if the reqPath is in hide (only can check the nearest meta) and user can't see hides, can't access
if meta != nil && !user.CanSeeHides() && meta.Hide != "" &&
IsApply(meta.Path, path.Dir(reqPath), meta.HSub) { // the meta should apply to the parent of current path
MetaCoversPath(meta.Path, path.Dir(reqPath), meta.HSub) { // the meta should apply to the parent of current path
for _, hide := range strings.Split(meta.Hide, "\n") {
re := regexp2.MustCompile(hide, regexp2.None)
if isMatch, _ := re.MatchString(path.Base(reqPath)); isMatch {
return false
}
}
}
if !CanRead(user, meta, reqPath) {
return false
}
// if is not guest and can access without password
if user.CanAccessWithoutPassword() {
return true
Expand All @@ -51,13 +68,20 @@ func CanAccess(user *model.User, meta *model.Meta, reqPath string, password stri
return true
}
// if meta doesn't apply to sub_folder, can access
if !utils.PathEqual(meta.Path, reqPath) && !meta.PSub {
if !MetaCoversPath(meta.Path, reqPath, meta.PSub) {
return true
}
// validate password
return meta.Password == password
}

func MetaCoversPath(metaPath, reqPath string, applyToSubFolder bool) bool {
if utils.PathEqual(metaPath, reqPath) {
return true
}
return utils.IsSubPath(metaPath, reqPath) && applyToSubFolder
}

// ShouldProxy TODO need optimize
// when should be proxy?
// 1. config.MustProxy()
Expand Down
Loading