fix(09): WR-02 drop a denied main menu item and never link it to a controller the admin cannot open

This commit is contained in:
Jakub Zych
2026-10-01 20:59:26 +02:00
parent b4b8b5df64
commit 28aa073de0
3 changed files with 93 additions and 5 deletions

View File

@@ -1,9 +1,11 @@
package cabana
import (
"context"
"testing"
"git.golem15.com/golem15/summercms/modules/bouncer"
"git.golem15.com/golem15/summercms/modules/pact"
)
// TestAllowsFollowsWinterHasAnyAccess pins the permission check to Winter's
@@ -49,3 +51,57 @@ func TestAllowsFollowsWinterHasAnyAccess(t *testing.T) {
})
}
}
type navController struct {
id string
required []string
}
func (c navController) ID() string { return c.id }
func (navController) ModelName() string { return "Metadata" }
func (navController) ConfigDir() string { return "controllers/metadata" }
func (c navController) RequiredPermissions() []string { return c.required }
// TestNavigationDropsDeniedParentAndRepointsTarget covers the WR-02 rules: a
// main item the principal may not open is dropped whatever its children allow,
// and an allowed parent never links to a controller the principal cannot open.
func TestNavigationDropsDeniedParentAndRepointsTarget(t *testing.T) {
reg := &Registry{
byID: map[string]*CompiledController{
"acme.shop.albums": {Controller: navController{"acme.shop.albums", []string{"acme.shop.access_albums"}}},
"acme.shop.genres": {Controller: navController{"acme.shop.genres", []string{"acme.shop.access_genres"}}},
},
navigation: []pact.NavigationItem{
{
Code: "shop", Label: "Shop", Controller: "acme.shop.albums", Permissions: []string{"acme.shop.*"},
SideMenu: []pact.NavigationItem{
{Code: "albums", Label: "Albums", Controller: "acme.shop.albums", Permissions: []string{"acme.shop.access_albums"}},
{Code: "genres", Label: "Genres", Controller: "acme.shop.genres", Permissions: []string{"acme.shop.access_genres"}},
},
},
{
Code: "locked", Label: "Locked", Controller: "acme.shop.albums", Permissions: []string{"acme.locked.access"},
SideMenu: []pact.NavigationItem{
{Code: "genres", Label: "Genres", Controller: "acme.shop.genres", Permissions: []string{"acme.shop.access_genres"}},
},
},
},
}
genresOnly := &bouncer.Principal{ID: 1, Backend: true, PermissionGrants: map[string]bool{"acme.shop.access_genres": true}}
nav, _ := reg.Metadata(context.Background(), genresOnly, nil)
if len(nav) != 1 || nav[0].Code != "shop" {
t.Fatalf("navigation = %#v, want only the shop item (the locked parent must be dropped)", nav)
}
if nav[0].Controller != "acme.shop.genres" {
t.Fatalf("parent controller = %q, want the first openable child", nav[0].Controller)
}
if len(nav[0].SideMenu) != 1 || nav[0].SideMenu[0].Code != "genres" {
t.Fatalf("side menu = %#v", nav[0].SideMenu)
}
both := &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{"acme.shop.access_albums": true}}
nav, _ = reg.Metadata(context.Background(), both, nil)
if len(nav) != 1 || nav[0].Controller != "acme.shop.albums" {
t.Fatalf("navigation = %#v, want the parent to keep its own controller", nav)
}
}