security: Actions that people didnt have permission to view were being returned (#921)
This commit is contained in:
parent
5ff6b5d080
commit
71bb999950
|
|
@ -432,6 +432,73 @@ func TestViewPermissionAllowedSeesAction(t *testing.T) {
|
||||||
assert.Equal(t, "secret_action", resp.Action.BindingId)
|
assert.Equal(t, "secret_action", resp.Action.BindingId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestViewPermissionExcludedFromCustomDashboard (issue #921) asserts that when a custom dashboard
|
||||||
|
// lists an action by title, users without view permission do not see that action (title or icon).
|
||||||
|
func TestViewPermissionExcludedFromCustomDashboard(t *testing.T) {
|
||||||
|
cfg, lowUser, _ := buildViewPermissionTestConfig(t)
|
||||||
|
cfg.Dashboards = []*config.DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "Custom",
|
||||||
|
Contents: []*config.DashboardComponent{
|
||||||
|
{Title: "Secret Action"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ex := executor.DefaultExecutor(cfg)
|
||||||
|
ex.RebuildActionMap()
|
||||||
|
|
||||||
|
rr := &DashboardRenderRequest{
|
||||||
|
AuthenticatedUser: lowUser,
|
||||||
|
cfg: cfg,
|
||||||
|
ex: ex,
|
||||||
|
}
|
||||||
|
dashboard := findDashboardByTitle(rr, "Custom")
|
||||||
|
require.NotNil(t, dashboard)
|
||||||
|
db := buildDashboardFromConfig(dashboard, rr)
|
||||||
|
require.NotNil(t, db)
|
||||||
|
|
||||||
|
bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
|
||||||
|
assert.NotContains(t, bindingIdsInDashboard, "secret_action",
|
||||||
|
"user with view:false must not see action on custom dashboard; got bindingIds: %v", bindingIdsInDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestViewPermissionExcludedFromEntityDashboard (GHSA: view permission) asserts that when a dashboard
|
||||||
|
// has an entity fieldset listing an action, users without view permission do not see that action.
|
||||||
|
func TestViewPermissionExcludedFromEntityDashboard(t *testing.T) {
|
||||||
|
entities.ClearEntitiesOfType("vp_entity_test")
|
||||||
|
defer entities.ClearEntitiesOfType("vp_entity_test")
|
||||||
|
entities.AddEntity("vp_entity_test", "1", map[string]any{"title": "Test Entity"})
|
||||||
|
|
||||||
|
cfg, lowUser, _ := buildViewPermissionTestConfig(t)
|
||||||
|
cfg.Dashboards = []*config.DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "WithEntity",
|
||||||
|
Contents: []*config.DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "Servers", Type: "fieldset", Entity: "vp_entity_test",
|
||||||
|
Contents: []*config.DashboardComponent{{Title: "Secret Action"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ex := executor.DefaultExecutor(cfg)
|
||||||
|
ex.RebuildActionMap()
|
||||||
|
|
||||||
|
rr := &DashboardRenderRequest{
|
||||||
|
AuthenticatedUser: lowUser,
|
||||||
|
cfg: cfg,
|
||||||
|
ex: ex,
|
||||||
|
}
|
||||||
|
dashboard := findDashboardByTitle(rr, "WithEntity")
|
||||||
|
require.NotNil(t, dashboard)
|
||||||
|
db := buildDashboardFromConfig(dashboard, rr)
|
||||||
|
require.NotNil(t, db)
|
||||||
|
|
||||||
|
bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
|
||||||
|
assert.NotContains(t, bindingIdsInDashboard, "secret_action",
|
||||||
|
"user with view:false must not see action in entity fieldset; got bindingIds: %v", bindingIdsInDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
func bindingIdsInDashboardContents(contents []*apiv1.DashboardComponent) []string {
|
func bindingIdsInDashboardContents(contents []*apiv1.DashboardComponent) []string {
|
||||||
var ids []string
|
var ids []string
|
||||||
for _, c := range contents {
|
for _, c := range contents {
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,6 @@ func isLinkType(itemType string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clone *apiv1.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clone *apiv1.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
||||||
clone.Type = "link"
|
|
||||||
clone.Title = tpl.ParseTemplateOfActionBeforeExec(subitem.Title, ent)
|
|
||||||
// Prefer an entity-specific action when available, but fall back to a
|
// Prefer an entity-specific action when available, but fall back to a
|
||||||
// non-entity-scoped action with the same title. This allows inline actions
|
// non-entity-scoped action with the same title. This allows inline actions
|
||||||
// defined inside entity dashboards to work without requiring an explicit
|
// defined inside entity dashboards to work without requiring an explicit
|
||||||
|
|
@ -92,7 +90,11 @@ func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clo
|
||||||
if action == nil {
|
if action == nil {
|
||||||
action = rr.findAction(subitem.Title)
|
action = rr.findAction(subitem.Title)
|
||||||
}
|
}
|
||||||
|
if action == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
clone.Type = "link"
|
||||||
|
clone.Title = tpl.ParseTemplateOfActionBeforeExec(subitem.Title, ent)
|
||||||
clone.Action = action
|
clone.Action = action
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,16 +277,31 @@ func createRootFieldset() *apiv1.DashboardComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendComponentIfNotNil(components *[]*apiv1.DashboardComponent, comp *apiv1.DashboardComponent) {
|
||||||
|
if comp != nil {
|
||||||
|
*components = append(*components, comp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDashboardComponentOrNil(subitem *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.DashboardComponent {
|
||||||
|
if len(subitem.Contents) == 0 && rr.findActionForEntity(subitem.Title, entity) == nil {
|
||||||
|
if !isAllowedType(subitem.Type) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildDashboardComponentSimpleWithEntity(subitem, rr, entity)
|
||||||
|
}
|
||||||
|
|
||||||
func processDashboardSubitemWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent, entity *entities.Entity) {
|
func processDashboardSubitemWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent, entity *entities.Entity) {
|
||||||
if subitem.Type != "fieldset" {
|
if subitem.Type != "fieldset" {
|
||||||
rootFieldset.Contents = append(rootFieldset.Contents, buildDashboardComponentSimpleWithEntity(subitem, rr, entity))
|
appendComponentIfNotNil(&rootFieldset.Contents, getDashboardComponentOrNil(subitem, rr, entity))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if subitem.Entity != "" {
|
if subitem.Entity != "" {
|
||||||
*ret = append(*ret, buildEntityFieldsets(subitem.Entity, subitem, rr)...)
|
*ret = append(*ret, buildEntityFieldsets(subitem.Entity, subitem, rr)...)
|
||||||
} else {
|
} else {
|
||||||
*ret = append(*ret, buildDashboardComponentSimpleWithEntity(subitem, rr, entity))
|
appendComponentIfNotNil(ret, getDashboardComponentOrNil(subitem, rr, entity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue