package cabana import ( "context" "io/fs" "strings" "testing" "testing/fstest" "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" ) // screensExtender owns one list controller (so the admin is active) and adds // record actions to other controllers through pact.HasAdminActionExtensions. type screensExtender struct { extensions []pact.AdminActionExtension } type screensExtenderController struct{} func (screensExtenderController) ID() string { return "acme.ext.items" } func (screensExtenderController) ModelName() string { return "Item" } func (screensExtenderController) ConfigDir() string { return "controllers/items" } func (screensExtenderController) RequiredPermissions() []string { return []string{"acme.ext.access"} } func (screensExtender) ID() string { return "acme.ext" } func (screensExtender) Requires() []string { return nil } func (screensExtender) Register(*backpack.App) error { return nil } func (screensExtender) Boot(*backpack.App) error { return nil } func (screensExtender) AdminControllers() []pact.AdminController { return []pact.AdminController{screensExtenderController{}} } func (screensExtender) AdminFS() fs.FS { return fstest.MapFS{ "controllers/items/config_list.yaml": &fstest.MapFile{Data: []byte("list: ~/plugins/acme/ext/models/item/columns.yaml\nmodelClass: Item\nrecordsPerPage: 20\n")}, "models/item/columns.yaml": &fstest.MapFile{Data: []byte("columns:\n name:\n label: Name\n")}, } } func (screensExtender) Permissions() []pact.Permission { return []pact.Permission{ {Code: "acme.ext.access", Roles: []string{"developer"}}, {Code: "acme.ext.ping"}, } } func (p screensExtender) AdminActionExtensions() []pact.AdminActionExtension { return p.extensions } func pingAction() pact.AdminRecordAction { return pact.AdminRecordAction{ Name: "acme_ping", Label: "Ping", Permissions: []string{"acme.ext.ping"}, Run: func(context.Context, pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) { return pact.AdminRecordActionResult{}, nil }, } } // compileWithScreens runs the compile steps of Activate that need no config // or database on plugins plus the admin's own screens. func compileWithScreens(t *testing.T, plugins []party.Plugin) (*Registry, error) { t.Helper() extended, screens := withBackendScreens(nil, plugins) if len(extended) != len(plugins)+1 || extended[len(extended)-1] != party.Plugin(screens) { t.Fatalf("withBackendScreens = %v", extended) } items, err := collectControllers(extended) if err != nil { return nil, err } if err := checkReservedSegments(items); err != nil { return nil, err } types, err := compileFieldTypes(extended) if err != nil { return nil, err } reg, err := compileRegistryWithTypes(items, types) if err != nil { return nil, err } if err := compileActionExtensions(reg, extended); err != nil { return nil, err } if err := compileContributions(reg, extended); err != nil { return nil, err } screens.reg = reg return reg, nil } func TestBackendScreensSmoke(t *testing.T) { extender := screensExtender{extensions: []pact.AdminActionExtension{{ Controller: AdministratorsControllerID, RecordActions: []pact.AdminRecordAction{pingAction()}, }}} plugins := []party.Plugin{extender} reg, err := compileWithScreens(t, plugins) if err != nil { t.Fatal(err) } if len(plugins) != 1 { t.Fatalf("the caller's plugin slice changed: %v", plugins) } cc, ok := reg.Get(AdministratorsControllerID) if !ok || cc.Form == nil || cc.Form.preview == nil { t.Fatalf("administrators controller = %#v", cc) } names := cc.Form.recordActions if len(names) == 0 || names[len(names)-1] != "acme_ping" { t.Fatalf("administrators record actions = %v, want acme_ping last", names) } if _, ok := cc.RecordActions["acme_ping"]; !ok || cc.contributed["acme_ping"] != "acme.ext" { t.Fatalf("RecordActions = %v contributed = %v", cc.RecordActions, cc.contributed) } if got := recordActionOfName(cc, "acme_ping"); !got { t.Fatal("recordActionOf does not resolve the contributed action") } for _, code := range []string{PermissionManageUsers, PermissionManageRoles} { if _, ok := reg.permissions[code]; !ok { t.Fatalf("catalog lacks %s", code) } for _, role := range []string{"developer", "publisher"} { if reg.rolePermissions(role)[code] { t.Fatalf("role %s is granted %s in code", role, code) } } } if order := reg.permissionOrder; len(order) != len(reg.permissions) || order[0] != "acme.ext.access" { t.Fatalf("permissionOrder = %v", order) } ctx := context.Background() _, settings := reg.Metadata(ctx, &bouncer.Principal{ID: 1, Backend: true, IsSuperuser: true}, nil) found := false for _, entry := range settings { if entry.Code == "administrators" { found = entry.Controller == AdministratorsControllerID && entry.Category == "backend::lang.settings.category_system" && entry.Order == 100 } } if !found { t.Fatalf("superuser settings = %+v, want administrators", settings) } _, settings = reg.Metadata(ctx, &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{"acme.ext.access": true}}, nil) for _, entry := range settings { if entry.Code == "administrators" { t.Fatalf("an administrator without %s sees %+v", PermissionManageUsers, entry) } } _, settings = reg.Metadata(ctx, &bouncer.Principal{ID: 3, Backend: true, PermissionGrants: map[string]bool{PermissionManageUsers: true}}, nil) if len(settings) != 1 || settings[0].Code != "administrators" { t.Fatalf("holder of %s sees %+v", PermissionManageUsers, settings) } missing := screensExtender{extensions: []pact.AdminActionExtension{{ Controller: "acme.missing.things", RecordActions: []pact.AdminRecordAction{pingAction()}, }}} if _, err := compileWithScreens(t, []party.Plugin{missing}); err == nil || !strings.Contains(err.Error(), "acme.missing.things") { t.Fatalf("unknown controller error = %v", err) } routes, err := Activate(nil, []party.Plugin{contributionPlugin{id: "acme.plain", permissions: []pact.Permission{{Code: "acme.plain.use"}}}}) if routes != nil || err != nil { t.Fatalf("Activate without admin controllers = %v, %v", routes, err) } } // recordActionOfName reports whether the record action route resolves name. func recordActionOfName(cc *CompiledController, name string) bool { _, ok := recordActionOf(cc, name) return ok } // TestBackendScreensActivation (D-01, D-04): the admin's own screens join // only an application that has an admin; then they bring both controllers, // both settings links (Administrators before Roles), both permission codes // granted to no role, and no navigation entry. func TestBackendScreensActivation(t *testing.T) { // No admin controllers: no admin, and no admin config is read (the app // has no admin.jwt.secret). app := backpack.New(nil) plain := contributionPlugin{id: "acme.plain", permissions: []pact.Permission{{Code: "acme.plain.use"}}} routes, err := Activate(app, []party.Plugin{plain}) if routes != nil || err != nil { t.Fatalf("Activate without admin controllers = %v, %v", routes, err) } // With a controller the same app needs the secret: the admin is active. if _, err := Activate(app, []party.Plugin{screensExtender{}}); err == nil || !strings.Contains(err.Error(), "admin.jwt.secret") { t.Fatalf("Activate with a controller and no secret = %v", err) } reg, err := compileWithScreens(t, []party.Plugin{screensExtender{}}) if err != nil { t.Fatal(err) } for _, id := range []string{AdministratorsControllerID, RolesControllerID} { cc, ok := reg.Get(id) if !ok || cc.PluginID != "summercms.cabana" || cc.List == nil || cc.Form == nil { t.Fatalf("controller %s = %#v", id, cc) } } for _, code := range []string{PermissionManageUsers, PermissionManageRoles} { permission, ok := reg.permissions[code] if !ok || len(permission.Roles) != 0 { t.Fatalf("permission %s = %+v (present %v), want no role grants", code, permission, ok) } for _, role := range []string{"developer", "publisher"} { if reg.rolePermissions(role)[code] { t.Fatalf("role %s holds %s", role, code) } } } ctx := context.Background() navigation, settings := reg.Metadata(ctx, &bouncer.Principal{ID: 1, Backend: true, IsSuperuser: true}, nil) var codes []string for _, entry := range settings { if entry.Controller == AdministratorsControllerID || entry.Controller == RolesControllerID { codes = append(codes, entry.Code) } } if strings.Join(codes, ",") != "administrators,roles" { t.Fatalf("superuser settings for the screens = %v, want administrators then roles", codes) } for _, entry := range navigation { if strings.HasPrefix(entry.Controller, "summercms.cabana.") { t.Fatalf("navigation entry %+v from the screens", entry) } } // A holder of backend.manage_roles alone sees the Roles link only. _, settings = reg.Metadata(ctx, &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{PermissionManageRoles: true}}, nil) if len(settings) != 1 || settings[0].Code != "roles" { t.Fatalf("holder of %s sees %+v", PermissionManageRoles, settings) } }