From e4a0e888b5097a87e47422dc3ebd9468e84bdd75 Mon Sep 17 00:00:00 2001 From: Darren Yu Date: Mon, 3 Aug 2026 01:29:48 +0800 Subject: [PATCH] Update share_navigator.go Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/filemanager/fs/dbfs/share_navigator.go | 21 ++++ .../fs/dbfs/share_navigator_test.go | 105 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 pkg/filemanager/fs/dbfs/share_navigator_test.go diff --git a/pkg/filemanager/fs/dbfs/share_navigator.go b/pkg/filemanager/fs/dbfs/share_navigator.go index 11823e88..f0e8c933 100644 --- a/pkg/filemanager/fs/dbfs/share_navigator.go +++ b/pkg/filemanager/fs/dbfs/share_navigator.go @@ -202,8 +202,29 @@ func (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) { n.share = share n.owner = share.Edges.User + + // Root() is skipped when using restored state, so re-check requester permissions + // to ensure group permission changes take effect immediately. + if n.user.ID != n.owner.ID && !n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareDownload)) { + if inventory.IsAnonymousUser(n.user) { + return nil, serializer.NewError( + serializer.CodeAnonymouseAccessDenied, + fmt.Sprintf("You don't have permission to access share links"), + nil, + ) + } + + return nil, serializer.NewError( + serializer.CodeNoPermissionErr, + fmt.Sprintf("You don't have permission to access share links"), + nil, + ) + } + if n.shareRoot != nil { n.shareRoot.OwnerModel = n.owner + n.shareRoot.disableView = (share.Props == nil || !share.Props.ShareView) && n.user.ID != n.owner.ID + n.shareRoot.CapabilitiesBs = n.Capabilities(false).Capability } if n.ownerRoot != nil { n.ownerRoot.OwnerModel = n.owner diff --git a/pkg/filemanager/fs/dbfs/share_navigator_test.go b/pkg/filemanager/fs/dbfs/share_navigator_test.go new file mode 100644 index 00000000..b696a9c7 --- /dev/null +++ b/pkg/filemanager/fs/dbfs/share_navigator_test.go @@ -0,0 +1,105 @@ +package dbfs + +import ( + "context" + "testing" + + "github.com/cloudreve/Cloudreve/v4/ent" + entuser "github.com/cloudreve/Cloudreve/v4/ent/user" + "github.com/cloudreve/Cloudreve/v4/inventory" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" + "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" + "github.com/cloudreve/Cloudreve/v4/pkg/hashid" + "github.com/cloudreve/Cloudreve/v4/pkg/setting" +) + +type restoredStateShareClient struct { + inventory.ShareClient + share *ent.Share +} + +func (c *restoredStateShareClient) GetByHashID(context.Context, string) (*ent.Share, error) { + return c.share, nil +} + +func TestShareNavigatorRestoredStateRevalidatesRequester(t *testing.T) { + ownerPermissions := &boolset.BooleanSet{} + boolset.Set(types.GroupPermissionShare, true, ownerPermissions) + owner := &ent.User{ID: 1, Status: entuser.StatusActive} + owner.SetGroup(&ent.Group{Permissions: ownerPermissions}) + + file := &ent.File{OwnerID: owner.ID, FileChildren: 1} + share := &ent.Share{ID: 1, Props: &types.ShareProps{ShareView: true}} + share.SetUser(owner) + share.SetFile(file) + + hasher, err := hashid.New("restored-state-test") + if err != nil { + t.Fatalf("hashid.New() error = %v", err) + } + path, err := fs.NewUriFromString(fs.NewShareUri(hashid.EncodeShareID(hasher, share.ID), "")) + if err != nil { + t.Fatalf("fs.NewUriFromString() error = %v", err) + } + + tests := []struct { + name string + canDownload bool + wantErr bool + }{ + {name: "permission revoked", wantErr: true}, + {name: "permission retained", canDownload: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + requesterPermissions := &boolset.BooleanSet{} + boolset.Set(types.GroupPermissionShareDownload, tt.canDownload, requesterPermissions) + requester := &ent.User{ID: 2, Status: entuser.StatusActive} + requester.SetGroup(&ent.Group{Permissions: requesterPermissions}) + + cachedOwner := &ent.User{ID: owner.ID, Status: entuser.StatusActive} + root := newFile(nil, file) + t.Cleanup(root.Recycle) + root.OwnerModel = cachedOwner + root.disableView = true + root.CapabilitiesBs = &boolset.BooleanSet{0xff} + + navigator := NewShareNavigator( + requester, + nil, + &restoredStateShareClient{share: share}, + nil, + &setting.DBFS{}, + hasher, + ).(*shareNavigator) + if err := navigator.RestoreState(shareNavigatorState{ + ShareRoot: root, + OwnerRoot: root, + Share: share, + Owner: cachedOwner, + }); err != nil { + t.Fatalf("RestoreState() error = %v", err) + } + + _, err := navigator.To(context.Background(), path) + if (err != nil) != tt.wantErr { + t.Fatalf("To() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { + return + } + + if root.OwnerModel != owner { + t.Fatal("To() did not refresh the cached owner") + } + if root.disableView { + t.Fatal("To() did not refresh the cached share view setting") + } + if root.CapabilitiesBs != shareNavigatorCapability { + t.Fatal("To() did not refresh the cached navigator capabilities") + } + }) + } +}