- Roles create and update refuse a submitted code that code-declared grants attach to - compileActionExtensions wraps contributed Administrators Runs with guardOutranked - Prove both holes on Postgres; keep the boot smoke callable with a superuser principal Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
7.0 KiB
Go
158 lines
7.0 KiB
Go
package cabana
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
)
|
|
|
|
// actionContributor owns no admin controller; it declares permissions and
|
|
// adds record actions to other controllers.
|
|
type actionContributor struct {
|
|
id string
|
|
permissions []pact.Permission
|
|
extensions []pact.AdminActionExtension
|
|
}
|
|
|
|
func (p actionContributor) ID() string { return p.id }
|
|
func (actionContributor) Requires() []string { return nil }
|
|
func (actionContributor) Register(*backpack.App) error { return nil }
|
|
func (actionContributor) Boot(*backpack.App) error { return nil }
|
|
func (p actionContributor) Permissions() []pact.Permission { return p.permissions }
|
|
func (p actionContributor) AdminActionExtensions() []pact.AdminActionExtension {
|
|
return p.extensions
|
|
}
|
|
|
|
// namedAction is a valid record action named name behind permission.
|
|
func namedAction(name, permission string) pact.AdminRecordAction {
|
|
action := pingAction()
|
|
action.Name = name
|
|
action.Label = "Run " + name
|
|
action.Permissions = []string{permission}
|
|
return action
|
|
}
|
|
|
|
func toUsers(actions ...pact.AdminRecordAction) []pact.AdminActionExtension {
|
|
return []pact.AdminActionExtension{{Controller: AdministratorsControllerID, RecordActions: actions}}
|
|
}
|
|
|
|
// TestActionExtensionBoot (D-14, SC-3): every refusal of the seam stops boot
|
|
// with an error naming the plugin, the action and the controller; valid
|
|
// contributions land after the owner's actions in plugin and declared order.
|
|
func TestActionExtensionBoot(t *testing.T) {
|
|
base := screensExtender{} // owns acme.ext.items (list only) and acme.ext.ping
|
|
contributor := func(id string, ext []pact.AdminActionExtension) actionContributor {
|
|
return actionContributor{id: id, permissions: []pact.Permission{{Code: id + ".run"}}, extensions: ext}
|
|
}
|
|
noRun := namedAction("acme_norun", "acme.one.run")
|
|
noRun.Run = nil
|
|
noLabel := namedAction("acme_nolabel", "acme.one.run")
|
|
noLabel.Label = " "
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
plugins []party.Plugin
|
|
want []string
|
|
}{
|
|
{"unknown controller", []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{
|
|
Controller: "acme.missing.things", RecordActions: []pact.AdminRecordAction{namedAction("acme_x", "acme.one.run")},
|
|
}})}, []string{"acme.one", "acme.missing.things", "unknown admin controller"}},
|
|
{"target without a preview block", []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{
|
|
Controller: "acme.ext.items", RecordActions: []pact.AdminRecordAction{namedAction("acme_x", "acme.one.run")},
|
|
}})}, []string{"acme.one", "acme.ext.items", "no preview block"}},
|
|
{"duplicate of the owner's action", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("deactivate", "acme.one.run")))},
|
|
[]string{"acme.one", `"deactivate"`, AdministratorsControllerID, "plugin summercms.cabana already registers"}},
|
|
{"duplicate between two contributors", []party.Plugin{base,
|
|
contributor("acme.one", toUsers(namedAction("acme_same", "acme.one.run"))),
|
|
contributor("acme.two", toUsers(namedAction("acme_same", "acme.two.run"))),
|
|
}, []string{"plugin acme.two adds", `"acme_same"`, "plugin acme.one already registers"}},
|
|
{"reserved name create", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("create", "acme.one.run")))},
|
|
[]string{"acme.one", `"create"`, "reserved"}},
|
|
{"reserved name delete", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("delete", "acme.one.run")))},
|
|
[]string{"acme.one", `"delete"`, "reserved"}},
|
|
{"name is not an identifier", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("acme-ping", "acme.one.run")))},
|
|
[]string{"acme.one", `"acme-ping"`, "not an identifier"}},
|
|
{"nil Run", []party.Plugin{base, contributor("acme.one", toUsers(noRun))},
|
|
[]string{"acme.one", `"acme_norun"`, "no Run function"}},
|
|
{"empty label", []party.Plugin{base, contributor("acme.one", toUsers(noLabel))},
|
|
[]string{"acme.one", `"acme_nolabel"`, "needs a label"}},
|
|
{"unknown permission", []party.Plugin{base, contributor("acme.one", toUsers(namedAction("acme_x", "acme.nobody.run")))},
|
|
[]string{"acme.nobody.run"}},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := compileWithScreens(t, tc.plugins)
|
|
if err == nil {
|
|
t.Fatalf("compiled; want an error naming %v", tc.want)
|
|
}
|
|
for _, want := range tc.want {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("err = %v, want it to contain %q", err, want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("valid contributions land after the owner's in order", func(t *testing.T) {
|
|
plugins := []party.Plugin{base,
|
|
contributor("acme.one", toUsers(namedAction("acme_a", "acme.one.run"), namedAction("acme_b", "acme.one.run"))),
|
|
contributor("acme.two", toUsers(namedAction("acme_c", "acme.two.run"))),
|
|
}
|
|
reg, err := compileWithScreens(t, plugins)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cc, _ := reg.Get(AdministratorsControllerID)
|
|
if got := fmt.Sprint(cc.Form.recordActions); got != "[activate deactivate restore acme_a acme_b acme_c]" {
|
|
t.Fatalf("record actions = %s", got)
|
|
}
|
|
for name, plugin := range map[string]string{"acme_a": "acme.one", "acme_b": "acme.one", "acme_c": "acme.two"} {
|
|
if cc.contributed[name] != plugin || !recordActionOfName(cc, name) {
|
|
t.Fatalf("%s contributed by %q (want %s), resolvable %v", name, cc.contributed[name], plugin, recordActionOfName(cc, name))
|
|
}
|
|
}
|
|
if _, ok := cc.contributed["activate"]; ok {
|
|
t.Fatal("the owner's action is marked contributed")
|
|
}
|
|
// The roles screen is untouched.
|
|
roles, _ := reg.Get(RolesControllerID)
|
|
if roles.contributed != nil {
|
|
t.Fatalf("roles contributed = %v", roles.contributed)
|
|
}
|
|
})
|
|
|
|
t.Run("an extension without record actions is ignored", func(t *testing.T) {
|
|
plugins := []party.Plugin{base, contributor("acme.one", []pact.AdminActionExtension{{Controller: "acme.missing.things"}})}
|
|
if _, err := compileWithScreens(t, plugins); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
t.Run("a contributed action runs with the owner's record", func(t *testing.T) {
|
|
var got pact.AdminRecordActionInput
|
|
action := namedAction("acme_spy", "acme.one.run")
|
|
action.Run = func(_ context.Context, in pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
|
got = in
|
|
return pact.AdminRecordActionResult{}, nil
|
|
}
|
|
reg, err := compileWithScreens(t, []party.Plugin{base, contributor("acme.one", toUsers(action))})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cc, _ := reg.Get(AdministratorsControllerID)
|
|
resolved, ok := recordActionOf(cc, "acme_spy")
|
|
if !ok {
|
|
t.Fatal("acme_spy not resolvable")
|
|
}
|
|
ctx := bouncer.WithUser(context.Background(), &bouncer.Principal{ID: 1, Backend: true, IsSuperuser: true})
|
|
if _, err := resolved.Run(ctx, pact.AdminRecordActionInput{RecordID: 7, Record: &BackendUser{ID: 7}}); err != nil || got.RecordID != 7 {
|
|
t.Fatalf("run = %+v, %v", got, err)
|
|
}
|
|
})
|
|
}
|