| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- package api
- import (
- "context"
- "testing"
- "connectrpc.com/connect"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
- authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
- config "github.com/OliveTin/OliveTin/internal/config"
- "github.com/OliveTin/OliveTin/internal/entities"
- "github.com/OliveTin/OliveTin/internal/executor"
- )
- func buildEntityAclTestConfig() *config.Config {
- cfg := config.DefaultConfig()
- cfg.DefaultPermissions.View = false
- cfg.DefaultPermissions.Exec = false
- cfg.AccessControlLists = []*config.AccessControlList{
- {
- Name: "ops",
- MatchUsernames: []string{"admin"},
- Permissions: config.PermissionsList{View: true, Exec: true},
- },
- {
- Name: "everyone",
- MatchUsernames: []string{"guest", "admin"},
- Permissions: config.PermissionsList{View: true, Exec: true},
- AddToEveryAction: true,
- },
- }
- cfg.Entities = []*config.EntityFile{
- {Name: "printers", File: "printers.yaml"},
- {Name: "servers", File: "servers.yaml", Acls: []string{"ops"}},
- }
- cfg.Actions = []*config.Action{
- {
- ID: "restart-server",
- Title: "Restart {{ servers.name }}",
- Shell: "echo restart",
- Entity: "servers",
- },
- {
- ID: "ping-printer",
- Title: "Ping printer",
- Shell: "echo ping",
- },
- }
- cfg.Dashboards = []*config.DashboardComponent{
- {
- Title: "Infra",
- Contents: []*config.DashboardComponent{
- {
- Title: "{{ servers.name }}",
- Type: "fieldset",
- Entity: "servers",
- Contents: []*config.DashboardComponent{
- {Title: "Restart {{ servers.name }}"},
- },
- },
- },
- },
- }
- cfg.Sanitize()
- return cfg
- }
- func seedEntityAclTestEntities(t *testing.T) {
- t.Helper()
- entities.ClearEntitiesOfType("printers")
- entities.ClearEntitiesOfType("servers")
- t.Cleanup(func() {
- entities.ClearEntitiesOfType("printers")
- entities.ClearEntitiesOfType("servers")
- })
- entities.AddEntity("printers", "p1", map[string]any{"name": "lobby"})
- entities.AddEntity("servers", "0", map[string]any{"name": "web01"})
- }
- func TestGetEntitiesOmitsRestrictedEntityTypes(t *testing.T) {
- seedEntityAclTestEntities(t)
- cfg := buildEntityAclTestConfig()
- ex := executor.DefaultExecutor(cfg)
- ex.RebuildActionMap()
- api := newServer(ex)
- guest := &authpublic.AuthenticatedUser{Username: "guest", Provider: "system"}
- guest.BuildUserAcls(cfg)
- defs := api.buildEntityDefinitionsResponse(guest, &apiv1.GetEntitiesRequest{}, entities.GetEntities())
- titles := make([]string, 0, len(defs))
- for _, def := range defs {
- titles = append(titles, def.Title)
- }
- assert.Contains(t, titles, "printers")
- assert.NotContains(t, titles, "servers")
- }
- func TestGetEntityNotFoundForRestrictedType(t *testing.T) {
- seedEntityAclTestEntities(t)
- cfg := buildEntityAclTestConfig()
- ex := executor.DefaultExecutor(cfg)
- ex.RebuildActionMap()
- ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
- defer ts.Close()
- // Guest Init/API without login uses guest user with default ACLs from UserFromApiCall.
- _, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
- Type: "servers",
- UniqueKey: "0",
- }))
- require.Error(t, err)
- assert.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
- resp, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
- Type: "printers",
- UniqueKey: "p1",
- }))
- require.NoError(t, err)
- assert.Equal(t, "lobby", resp.Msg.Title)
- }
- func TestSearchHintsOmitRestrictedEntitiesAndEntityBoundActions(t *testing.T) {
- seedEntityAclTestEntities(t)
- cfg := buildEntityAclTestConfig()
- ex := executor.DefaultExecutor(cfg)
- ex.RebuildActionMap()
- api := newServer(ex)
- guest := &authpublic.AuthenticatedUser{Username: "guest", Provider: "system"}
- guest.BuildUserAcls(cfg)
- admin := &authpublic.AuthenticatedUser{Username: "admin"}
- admin.BuildUserAcls(cfg)
- guestHints := api.buildSearchHints(guest)
- require.NotNil(t, guestHints)
- guestEntityKeys := make([]string, 0)
- for _, hint := range guestHints.Entities {
- guestEntityKeys = append(guestEntityKeys, hint.Type+":"+hint.UniqueKey)
- }
- assert.Contains(t, guestEntityKeys, "printers:p1")
- assert.NotContains(t, guestEntityKeys, "servers:0")
- guestActionIDs := actionHintBindingIDs(guestHints.Actions)
- require.NotEmpty(t, guestActionIDs)
- assert.Contains(t, guestActionIDs, "ping-printer")
- assert.NotContains(t, guestActionIDs, "restart")
- adminHints := api.buildSearchHints(admin)
- require.NotNil(t, adminHints)
- adminEntityKeys := make([]string, 0)
- for _, hint := range adminHints.Entities {
- adminEntityKeys = append(adminEntityKeys, hint.Type+":"+hint.UniqueKey)
- }
- assert.Contains(t, adminEntityKeys, "servers:0")
- assert.NotEmpty(t, adminHints.Actions)
- }
- func TestEntityFieldsetOmitsRestrictedEntityType(t *testing.T) {
- seedEntityAclTestEntities(t)
- cfg := buildEntityAclTestConfig()
- ex := executor.DefaultExecutor(cfg)
- ex.RebuildActionMap()
- api := newServer(ex)
- guest := &authpublic.AuthenticatedUser{Username: "guest", Provider: "system"}
- guest.BuildUserAcls(cfg)
- admin := &authpublic.AuthenticatedUser{Username: "admin"}
- admin.BuildUserAcls(cfg)
- guestRR := api.createDashboardRenderRequest(guest, "", "")
- guestDB := renderDashboard(guestRR, "Infra")
- require.NotNil(t, guestDB)
- assert.Empty(t, guestDB.Contents, "restricted entity fieldsets must not leak instances")
- adminRR := api.createDashboardRenderRequest(admin, "", "")
- adminDB := renderDashboard(adminRR, "Infra")
- require.NotNil(t, adminDB)
- require.NotEmpty(t, adminDB.Contents)
- }
- func TestBuildChoicesEntityRespectsEntityACL(t *testing.T) {
- seedEntityAclTestEntities(t)
- cfg := buildEntityAclTestConfig()
- ex := executor.DefaultExecutor(cfg)
- ex.RebuildActionMap()
- guest := &authpublic.AuthenticatedUser{Username: "guest", Provider: "system"}
- guest.BuildUserAcls(cfg)
- admin := &authpublic.AuthenticatedUser{Username: "admin"}
- admin.BuildUserAcls(cfg)
- arg := config.ActionArgument{
- Entity: "servers",
- Choices: []config.ActionArgumentChoice{
- {Title: "{{ servers.name }}", Value: "{{ servers.name }}"},
- },
- }
- guestRR := &DashboardRenderRequest{AuthenticatedUser: guest, cfg: cfg, ex: ex}
- assert.Empty(t, buildChoices(arg, guestRR))
- adminRR := &DashboardRenderRequest{AuthenticatedUser: admin, cfg: cfg, ex: ex}
- choices := buildChoices(arg, adminRR)
- require.Len(t, choices, 1)
- assert.Equal(t, "web01", choices[0].Value)
- }
|