Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions apis/cmk/cmk-ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,30 @@ paths:
$ref: "#/components/responses/429"
"500":
$ref: "#/components/responses/500"
/systems/filterOptions:
get:
tags:
- Systems
summary: Get System Filter Values
description: Get system possible values for filters
operationId: getFilters
responses:
"200":
description: System retrieved
content:
application/json:
schema:
$ref: "#/components/schemas/SystemFilters"
"400":
$ref: "#/components/responses/400"
"403":
$ref: "#/components/responses/403"
"404":
$ref: "#/components/responses/404"
"429":
$ref: "#/components/responses/429"
"500":
$ref: "#/components/responses/500"
/systems/{systemID}:
get:
tags:
Expand Down Expand Up @@ -1507,6 +1531,25 @@ components:
enum:
- RETRY
- CANCEL
SystemFilters:
description: System Filters
type: object
properties:
region:
type: array
items:
type: string
description: List of existing system regions
type:
type: array
items:
type: string
description: List of existing system types
keyConfigurationName:
type: array
items:
type: string
description: List of existing system key configurations
ClientCertificates:
type: object
properties:
Expand Down
301 changes: 210 additions & 91 deletions internal/api/cmkapi/cmkapi.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions internal/authz/api_endpoint_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ var allRestrictions = []Restricted{
APIResourceTypeName: APIResourceTypeSystem,
APIAction: APIActionRead,
},
{
APIPath: "/systems/filterOptions",
APIMethod: APIMethodGet,
APIResourceTypeName: APIResourceTypeSystem,
APIAction: APIActionRead,
},
{
APIPath: "/systems/{systemID}",
APIMethod: APIMethodGet,
Expand Down
33 changes: 26 additions & 7 deletions internal/authz/repo/authz_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
cmkcontext "github.com/openkcm/cmk/utils/context"
)

var (
ErrUnauthorized = errors.New("action on resource unauthorized")
)
var ErrUnauthorized = errors.New("action on resource unauthorized")

type AuthzRepo struct {
repo repo.Repo
Expand All @@ -21,15 +19,17 @@ type AuthzRepo struct {

func NewAuthzRepo(
repo repo.Repo, authzLoader *authz_loader.AuthzLoader[authz.RepoResourceTypeName,
authz.RepoAction]) *AuthzRepo {
authz.RepoAction],
) *AuthzRepo {
return &AuthzRepo{
repo: repo,
authzLoader: authzLoader,
}
}

func (r *AuthzRepo) Create(
ctx context.Context, resource repo.Resource) error {
ctx context.Context, resource repo.Resource,
) error {
err := r.checkResourceAuthZ(ctx, resource, authz.RepoActionCreate)
if err != nil {
return err
Expand Down Expand Up @@ -136,8 +136,26 @@ func (r *AuthzRepo) Transaction(ctx context.Context, txFunc repo.TransactionFunc
return r.repo.Transaction(ctx, txFunc)
}

func (r *AuthzRepo) GetFilterOptions(
ctx context.Context,
resource repo.Resource,
columns []repo.Filter,
query repo.Query,
) error {
err := r.checkResourceAuthZ(ctx, resource, authz.RepoActionList)
if err != nil {
return err
}
err = r.checkQueryAuthZ(ctx, query, authz.RepoActionList)
if err != nil {
return err
}
return r.repo.GetFilterOptions(ctx, resource, columns, query)
}

func (r *AuthzRepo) checkResourceAuthZ(
ctx context.Context, resource repo.Resource, action authz.RepoAction) error {
ctx context.Context, resource repo.Resource, action authz.RepoAction,
) error {
tenantID, err := cmkcontext.ExtractTenantID(ctx)
if err != nil {
return err
Expand All @@ -159,7 +177,8 @@ func (r *AuthzRepo) checkResourceAuthZ(
}

func (r *AuthzRepo) checkQueryAuthZ(
ctx context.Context, query repo.Query, action authz.RepoAction) error {
ctx context.Context, query repo.Query, action authz.RepoAction,
) error {
tenantID, err := cmkcontext.ExtractTenantID(ctx)
if err != nil {
return err
Expand Down
12 changes: 12 additions & 0 deletions internal/controllers/cmk/system_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,15 @@ func (c *APIController) UnlinkSystemAction(

return cmkapi.UnlinkSystemAction204Response(struct{}{}), nil
}

func (c *APIController) GetFilters(
ctx context.Context,
_ cmkapi.GetFiltersRequestObject,
) (cmkapi.GetFiltersResponseObject, error) {
filters, err := c.Manager.System.GetFilters(ctx)
if err != nil {
return nil, err
}

return cmkapi.GetFilters200JSONResponse(filters), nil
}
Comment thread
jmpTeixeira02 marked this conversation as resolved.
41 changes: 41 additions & 0 deletions internal/controllers/cmk/system_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -961,3 +961,44 @@ func TestUnlinkSystemAction(t *testing.T) {
})
}
}

func TestGetFilters(t *testing.T) {
db, sv, tenant := startAPISystems(t, testutils.TestAPIServerConfig{})
ctx := cmkcontext.CreateTenantContext(t.Context(), tenant)
r := sql.NewRepository(db)

authClient := testutils.NewAuthClient(ctx, t, r, testutils.WithKeyAdminRole())

keyConfig1 := testutils.NewKeyConfig(func(k *model.KeyConfiguration) {
k.Name = "kcName1"
}, testutils.WithAuthClientDataKC(authClient))
keyConfig2 := testutils.NewKeyConfig(func(k *model.KeyConfiguration) {
k.Name = "kcName2"
}, testutils.WithAuthClientDataKC(authClient))

system1 := testutils.NewSystem(func(s *model.System) {
s.Type = "type1"
s.Region = "region1"
s.KeyConfigurationID = &keyConfig1.ID
})

system2 := testutils.NewSystem(func(s *model.System) {
s.Type = "type2"
s.Region = "region2"
s.KeyConfigurationID = &keyConfig2.ID
})

testutils.CreateTestEntities(ctx, t, r, keyConfig1, keyConfig2, system1, system2)

w := testutils.MakeHTTPRequest(t, sv, testutils.RequestOptions{
Method: http.MethodGet,
Endpoint: "/systems/filterOptions",
Tenant: tenant,
AdditionalContext: authClient.GetClientMap(),
})

assert.Equal(t, http.StatusOK, w.Code)

response := testutils.GetJSONBody[cmkapi.SystemFilters](t, w)
assert.NotNil(t, response)
}
Comment thread
jmpTeixeira02 marked this conversation as resolved.
59 changes: 59 additions & 0 deletions internal/manager/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package manager

import (
"context"
"fmt"
"log/slog"
"strings"

Expand Down Expand Up @@ -43,6 +44,7 @@ type System interface {
systemID uuid.UUID,
action cmkapi.SystemRecoveryActionBodyAction,
) error
GetFilters(ctx context.Context) (cmkapi.SystemFilters, error)
}

type SystemManager struct {
Expand Down Expand Up @@ -429,6 +431,63 @@ func (m *SystemManager) UnlinkSystemAction(ctx context.Context, systemID uuid.UU
return nil
}

func (m *SystemManager) GetFilters(ctx context.Context) (cmkapi.SystemFilters, error) {
var types []string
var regions []string
var keyConfigNames []string

query := repo.NewQuery().Join(repo.LeftJoin, repo.JoinCondition{
JoinTable: &model.KeyConfiguration{},
JoinField: repo.IDField,
Table: &model.System{},
Field: repo.KeyConfigIDField,
})
filters := []repo.Filter{
{Values: &types, Column: repo.TypeField},
{Values: &regions, Column: repo.RegionField},
{Values: &keyConfigNames, Column: fmt.Sprintf("%s.%s", model.KeyConfiguration{}.TableName(), repo.NameField)},
}
isGroupFiltered, err := m.user.NeedsGroupFiltering(ctx, authz.APIActionRead, authz.APIResourceTypeWorkFlow)
if err != nil {
return cmkapi.SystemFilters{}, err
}

if isGroupFiltered {
// Only show systems linked to key configurations where the user has admin access
iamIdentifiers, err := cmkcontext.ExtractClientDataGroupsString(ctx)
if err != nil {
return cmkapi.SystemFilters{}, err
}

// If IAM identifiers list is empty, user has no access to any key configurations
if len(iamIdentifiers) == 0 {
return cmkapi.SystemFilters{
KeyConfigurationName: &keyConfigNames,
Region: &regions,
Type: &types,
}, nil
}

query = query.Join(repo.LeftJoin, repo.JoinCondition{
Table: &model.KeyConfiguration{},
Field: repo.AdminGroupIDField,
JoinField: repo.IDField,
JoinTable: &model.Group{},
})

ck := repo.NewCompositeKey().
Where(fmt.Sprintf(`"%s".%s`, model.Group{}.TableName(), repo.IAMIdField), iamIdentifiers)
query = query.Where(repo.NewCompositeKeyGroup(ck))
}
err = m.repo.GetFilterOptions(ctx, model.System{}, filters, *query)

return cmkapi.SystemFilters{
KeyConfigurationName: &keyConfigNames,
Region: &regions,
Type: &types,
}, err
}

func (m *SystemManager) UnmapSystemFromRegistry(ctx context.Context, system *model.System) error {
tenant, err := cmkcontext.ExtractTenantID(ctx)
if err != nil {
Expand Down
69 changes: 63 additions & 6 deletions internal/manager/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,17 @@ func TestNewSystemManager(t *testing.T) {
}

func TestGetAllSystems(t *testing.T) {
groupID := uuid.NewString()

m, db, tenant := SetupSystemManager(t, nil)
ctx := testutils.CreateCtxWithTenant(tenant)
ctx = testutils.InjectClientDataIntoContext(ctx, "test-user", []string{"test-group1"})
ctx = testutils.InjectClientDataIntoContext(ctx, "test-user", []string{groupID})
r := sql.NewRepository(db)

testGroup := testutils.NewGroup(
func(g *model.Group) {
g.IAMIdentifier = "test-group1"
},
)
testGroup := testutils.NewGroup(func(g *model.Group) {
g.IAMIdentifier = groupID
})

keyConfig := testutils.NewKeyConfig(
func(k *model.KeyConfiguration) {
k.AdminGroupID = testGroup.ID
Expand Down Expand Up @@ -1508,3 +1509,59 @@ func TestRefreshSystems(t *testing.T) {
},
)
}

func TestGetFilters(t *testing.T) {
groupID := uuid.NewString()
m, db, tenant := SetupSystemManager(t, nil)
ctx := testutils.CreateCtxWithTenant(tenant)
ctx = testutils.InjectClientDataIntoContext(ctx, "test-user", []string{groupID})
r := sql.NewRepository(db)

testGroup := testutils.NewGroup(func(g *model.Group) {
g.IAMIdentifier = groupID
})

keyConfig1 := testutils.NewKeyConfig(func(k *model.KeyConfiguration) {
k.Name = "kcName1"
k.AdminGroup = *testGroup
})
keyConfig2 := testutils.NewKeyConfig(func(k *model.KeyConfiguration) {
k.Name = "kcName2"
k.AdminGroup = *testGroup
})

keyConfig3 := testutils.NewKeyConfig(func(k *model.KeyConfiguration) {
k.Name = "kcName3"
})

system1 := testutils.NewSystem(func(s *model.System) {
s.Type = "type1"
s.Region = "region1"
s.KeyConfigurationID = &keyConfig1.ID
})

system2 := testutils.NewSystem(func(s *model.System) {
s.Type = "type2"
s.Region = "region2"
s.KeyConfigurationID = &keyConfig2.ID
})

testutils.CreateTestEntities(ctx, t, r, keyConfig1, keyConfig2, system1, system2, keyConfig3)

t.Run("Should get filters for system for only user key configs", func(t *testing.T) {
filters, err := m.GetFilters(ctx)
assert.NoError(t, err)

assert.Len(t, *filters.Type, 2)
assert.Contains(t, *filters.Type, system1.Type)
assert.Contains(t, *filters.Type, system2.Type)

assert.Len(t, *filters.Region, 2)
assert.Contains(t, *filters.Region, system1.Region)
assert.Contains(t, *filters.Region, system2.Region)

assert.Len(t, *filters.KeyConfigurationName, 2)
assert.Contains(t, *filters.KeyConfigurationName, keyConfig1.Name)
assert.Contains(t, *filters.KeyConfigurationName, keyConfig2.Name)
})
}
7 changes: 7 additions & 0 deletions internal/manager/tenant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ func (s *mockSystemManager) UnlinkSystemAction(context.Context, uuid.UUID, strin
func (s *mockSystemManager) GetAllSystems(context.Context, repo.QueryMapper) ([]*model.System, int, error) {
panic("not implemented")
}

func (s *mockSystemManager) GetSystemByID(context.Context, uuid.UUID) (*model.System, error) {
panic("not implemented")
}
Expand All @@ -400,9 +401,11 @@ func (s *mockSystemManager) RefreshSystemsData(context.Context) bool { return tr
func (s *mockSystemManager) LinkSystemAction(context.Context, uuid.UUID, cmkapi.SystemPatch) (*model.System, error) {
panic("not implemented")
}

func (s *mockSystemManager) GetRecoveryActions(context.Context, uuid.UUID) (cmkapi.SystemRecoveryAction, error) {
panic("not implemented")
}

func (s *mockSystemManager) SendRecoveryActions(
context.Context,
uuid.UUID,
Expand All @@ -411,6 +414,10 @@ func (s *mockSystemManager) SendRecoveryActions(
panic("not implemented")
}

func (s *mockSystemManager) GetFilters(context.Context) (cmkapi.SystemFilters, error) {
panic("not implemented")
}

func disconnectAllExistingSystems(t *testing.T, ctx context.Context, r repo.Repo) {
t.Helper()

Expand Down
Loading
Loading