From 91525c05c8f30ad417eb2f2710bb3e2111c9be26 Mon Sep 17 00:00:00 2001 From: Mihaly Gyongyosi Date: Tue, 13 Jan 2026 16:53:21 +0100 Subject: [PATCH] wip --- pkg/registry/apis/iam/authorizer.go | 2 + .../apis/iam/externalgroupmapping/models.go | 10 +- .../iam/externalgroupmapping/search_noop.go | 147 ++++++++++++++++++ pkg/registry/apis/iam/models.go | 11 +- pkg/registry/apis/iam/register.go | 58 +++---- pkg/registry/apis/wireset.go | 3 + pkg/server/wire_gen.go | 6 +- 7 files changed, 203 insertions(+), 34 deletions(-) create mode 100644 pkg/registry/apis/iam/externalgroupmapping/search_noop.go diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index efcf6fa5b39..8f46d969d31 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -57,6 +57,8 @@ func newIAMAuthorizer( resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer resourceAuthorizer["searchUsers"] = serviceAuthorizer resourceAuthorizer["searchTeams"] = serviceAuthorizer + // TODO: Implement fine-grained authorization for external group mapping search on the search level + resourceAuthorizer["searchExternalGroupMappings"] = allowAuthorizer return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer} } diff --git a/pkg/registry/apis/iam/externalgroupmapping/models.go b/pkg/registry/apis/iam/externalgroupmapping/models.go index 4a8f2e5df08..0360e89bb31 100644 --- a/pkg/registry/apis/iam/externalgroupmapping/models.go +++ b/pkg/registry/apis/iam/externalgroupmapping/models.go @@ -1,6 +1,10 @@ package externalgroupmapping -import "k8s.io/apiserver/pkg/registry/rest" +import ( + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kube-openapi/pkg/common" +) type TeamGroupsHandler interface { rest.Storage @@ -8,3 +12,7 @@ type TeamGroupsHandler interface { rest.StorageMetadata rest.Connecter } + +type SearchHandler interface { + GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes +} diff --git a/pkg/registry/apis/iam/externalgroupmapping/search_noop.go b/pkg/registry/apis/iam/externalgroupmapping/search_noop.go new file mode 100644 index 00000000000..c52ba75ab40 --- /dev/null +++ b/pkg/registry/apis/iam/externalgroupmapping/search_noop.go @@ -0,0 +1,147 @@ +package externalgroupmapping + +import ( + "fmt" + "net/http" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/util/errhttp" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +var _ SearchHandler = (*NoopSearchREST)(nil) + +type NoopSearchREST struct{} + +func ProvideNoopSearchREST() *NoopSearchREST { + return &NoopSearchREST{} +} + +func (n *NoopSearchREST) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes { + searchResults := defs["github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList"].Schema + return &builder.APIRoutes{ + Namespace: []builder.APIRouteHandler{ + { + Path: "searchExternalGroupMappings", + Spec: &spec3.PathProps{ + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + Description: "External Group Mapping search", + Tags: []string{"Search"}, + OperationId: "searchExternalGroupMappings", + Parameters: []*spec3.Parameter{ + { + ParameterProps: spec3.ParameterProps{ + Name: "namespace", + In: "path", + Required: true, + Example: "default", + Description: "workspace", + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "externalGroup", + In: "query", + Required: false, + Description: "External group name", + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "teamName", + In: "query", + Required: false, + Description: "Team name", + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "limit", + In: "query", + Description: "number of results to return", + Example: 30, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "page", + In: "query", + Description: "page number (starting from 1)", + Example: 1, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "offset", + In: "query", + Description: "number of results to skip", + Example: 0, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "sort", + In: "query", + Description: "sortable field", + Example: "", + Examples: map[string]*spec3.Example{ + "externalGroup": { + ExampleProps: spec3.ExampleProps{ + Summary: "externalGroup ascending", + Value: "externalGroup", + }, + }, + "-externalGroup": { + ExampleProps: spec3.ExampleProps{ + Summary: "externalGroup descending", + Value: "-externalGroup", + }, + }, + }, + Required: false, + Schema: spec.StringProperty(), + }, + }, + }, + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &searchResults, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Handler: n.doSearch, + }, + }, + } +} + +func (n *NoopSearchREST) doSearch(w http.ResponseWriter, r *http.Request) { + errhttp.Write(r.Context(), errors.NewForbidden(iamv0.ExternalGroupMappingResourceInfo.GroupResource(), "", fmt.Errorf("functionality not available")), w) +} diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index 9e4d8e484e4..b2bcbac4dbe 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -82,11 +82,12 @@ type IdentityAccessManagementAPIBuilder struct { reg prometheus.Registerer logger log.Logger - dual dualwrite.Service - unified resource.ResourceClient - userSearchClient resourcepb.ResourceIndexClient - userSearchHandler *user.SearchHandler - teamSearch *TeamSearchHandler + dual dualwrite.Service + unified resource.ResourceClient + userSearchClient resourcepb.ResourceIndexClient + userSearchHandler *user.SearchHandler + teamSearch *TeamSearchHandler + externalGroupMappingSearchHandler externalgroupmapping.SearchHandler teamGroupsHandler externalgroupmapping.TeamGroupsHandler diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 7f42d620987..2de6b5a9a03 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -79,6 +79,7 @@ func RegisterAPIService( roleBindingsStorage RoleBindingStorageBackend, externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend, teamGroupsHandlerImpl externalgroupmapping.TeamGroupsHandler, + externalGroupMappingSearchHandler externalgroupmapping.SearchHandler, dual dualwrite.Service, unified resource.ResourceClient, orgService org.Service, @@ -101,31 +102,32 @@ func RegisterAPIService( ) builder := &IdentityAccessManagementAPIBuilder{ - store: store, - userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), - saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), - legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), - teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing), - ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing), - coreRolesStorage: coreRolesStorage, - roleApiInstaller: roleApiInstaller, - resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), - roleBindingsStorage: roleBindingsStorage, - externalGroupMappingStorage: externalGroupMappingStorageBackend, - teamGroupsHandler: teamGroupsHandlerImpl, - sso: ssoService, - resourceParentProvider: resourceParentProvider, - authorizer: authorizer, - legacyAccessClient: legacyAccessClient, - accessClient: accessClient, - zClient: zClient, - zTickets: make(chan bool, MaxConcurrentZanzanaWrites), - display: user.NewLegacyDisplayREST(store), - reg: reg, - logger: log.New("iam.apis"), - features: features, - dual: dual, - unified: unified, + store: store, + userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), + saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), + legacyTeamStore: team.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), + teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing), + ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing), + coreRolesStorage: coreRolesStorage, + roleApiInstaller: roleApiInstaller, + resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), + roleBindingsStorage: roleBindingsStorage, + externalGroupMappingStorage: externalGroupMappingStorageBackend, + teamGroupsHandler: teamGroupsHandlerImpl, + externalGroupMappingSearchHandler: externalGroupMappingSearchHandler, + sso: ssoService, + resourceParentProvider: resourceParentProvider, + authorizer: authorizer, + legacyAccessClient: legacyAccessClient, + accessClient: accessClient, + zClient: zClient, + zTickets: make(chan bool, MaxConcurrentZanzanaWrites), + display: user.NewLegacyDisplayREST(store), + reg: reg, + logger: log.New("iam.apis"), + features: features, + dual: dual, + unified: unified, userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), unified, user.NewUserLegacySearchClient(orgService, tracing, cfg), features), teamSearch: NewTeamSearchHandler(tracing, dual, team.NewLegacyTeamSearchClient(teamService), unified, features), @@ -617,7 +619,7 @@ func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenA func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes { defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) - searchRoutes := make([]*builder.APIRoutes, 0, 2) + searchRoutes := make([]*builder.APIRoutes, 0, 3) if b.userSearchHandler != nil { searchRoutes = append(searchRoutes, b.userSearchHandler.GetAPIRoutes(defs)) } @@ -626,6 +628,10 @@ func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion searchRoutes = append(searchRoutes, b.teamSearch.GetAPIRoutes(defs)) } + if b.externalGroupMappingSearchHandler != nil { + searchRoutes = append(searchRoutes, b.externalGroupMappingSearchHandler.GetAPIRoutes(defs)) + } + routes := []*builder.APIRoutes{b.display.GetAPIRoutes(defs)} routes = append(routes, searchRoutes...) return mergeAPIRoutes(routes...) diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 296b7d25b59..a8fc46a4a13 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -35,6 +35,9 @@ var WireSetExts = wire.NewSet( externalgroupmapping.ProvideNoopTeamGroupsREST, wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)), + externalgroupmapping.ProvideNoopSearchREST, + wire.Bind(new(externalgroupmapping.SearchHandler), new(*externalgroupmapping.NoopSearchREST)), + // Auditing Options auditing.ProvideNoopBackend, auditing.ProvideNoopPolicyRuleProvider, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 218e9fabc36..030ede1f14d 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -883,7 +883,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api storageBackendImpl := noopstorage.ProvideStorageBackend() roleApiInstaller := iam.ProvideNoopRoleApiInstaller() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) + noopSearchREST := externalgroupmapping.ProvideNoopSearchREST() + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1551,7 +1552,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac storageBackendImpl := noopstorage.ProvideStorageBackend() roleApiInstaller := iam.ProvideNoopRoleApiInstaller() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) + noopSearchREST := externalgroupmapping.ProvideNoopSearchREST() + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, noopSearchREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider) if err != nil { return nil, err }