From 534a2d529ce9ceebe2278868428050b2f333f99c Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 19 Apr 2026 18:47:15 +0530 Subject: [PATCH 1/2] fix: enhance search functionality with user access control for namespaces --- pkg/handlers/resources/generic_resource_handler_list.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/handlers/resources/generic_resource_handler_list.go b/pkg/handlers/resources/generic_resource_handler_list.go index ad05f76e..1ad6f6d8 100644 --- a/pkg/handlers/resources/generic_resource_handler_list.go +++ b/pkg/handlers/resources/generic_resource_handler_list.go @@ -138,6 +138,7 @@ func (h *GenericResourceHandler[T, V]) Search(c *gin.Context, q string, limit in return nil, nil } cs := c.MustGet("cluster").(*cluster.ClientSet) + user := c.MustGet("user").(model.User) ctx := c.Request.Context() objectList := reflect.New(h.listType).Interface().(V) var listOpts []client.ListOption @@ -172,6 +173,12 @@ func (h *GenericResourceHandler[T, V]) Search(c *gin.Context, q string, limit in if !isLabelSearch && !strings.Contains(strings.ToLower(obj.GetName()), strings.ToLower(q)) { continue } + if h.Name() == string(common.Namespaces) && !rbac.CanAccessNamespace(user, cs.Name, obj.GetName()) { + continue + } + if obj.GetNamespace() != "" && !rbac.CanAccessNamespace(user, cs.Name, obj.GetNamespace()) { + continue + } result := common.SearchResult{ ID: string(obj.GetUID()), Name: obj.GetName(), From 419112ad3d0c0fb73dd514440c5d520502e6ab0d Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 19 Apr 2026 20:13:50 +0530 Subject: [PATCH 2/2] fix: include user key in search cache to isolate results per user Previously the cache key only included cluster+query+limit, so two users with different RBAC permissions could receive each other's cached search results. Adding user.Key() scopes the cache per user. Co-Authored-By: Claude Sonnet 4.6 --- pkg/handlers/search_handler.go | 11 +++++++---- pkg/handlers/search_handler_test.go | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/handlers/search_handler.go b/pkg/handlers/search_handler.go index 4498f5ab..5a854100 100644 --- a/pkg/handlers/search_handler.go +++ b/pkg/handlers/search_handler.go @@ -15,6 +15,7 @@ import ( "github.com/zxh326/kite/pkg/common" "github.com/zxh326/kite/pkg/handlers/resources" "github.com/zxh326/kite/pkg/middleware" + "github.com/zxh326/kite/pkg/model" "github.com/zxh326/kite/pkg/utils" "golang.org/x/sync/errgroup" "k8s.io/klog/v2" @@ -52,8 +53,8 @@ func NewSearchHandler() *SearchHandler { } } -func (h *SearchHandler) createCacheKey(clusterName, query string, limit int) string { - return fmt.Sprintf("search:%s:%d:%s", clusterName, limit, normalizeSearchQuery(query)) +func (h *SearchHandler) createCacheKey(clusterName, userKey, query string, limit int) string { + return fmt.Sprintf("search:%s:%s:%d:%s", clusterName, userKey, limit, normalizeSearchQuery(query)) } func (h *SearchHandler) Search(c *gin.Context, query string, limit int) ([]common.SearchResult, error) { @@ -119,7 +120,8 @@ func (h *SearchHandler) Search(c *gin.Context, query string, limit int) ([]commo // Only cache results when no failure (panic or error) occurred — avoids // caching incomplete results that would be served as valid 200 OK for the TTL. if !hadFailure.Load() { - h.cache.Add(h.createCacheKey(getSearchClusterName(c), query, limit), allResults) + user := c.MustGet("user").(model.User) + h.cache.Add(h.createCacheKey(getSearchClusterName(c), user.Key(), query, limit), allResults) } return allResults, nil } @@ -140,7 +142,8 @@ func (h *SearchHandler) GlobalSearch(c *gin.Context) { } limit = normalizeSearchLimit(limit) - cacheKey := h.createCacheKey(getSearchClusterName(c), query, limit) + user := c.MustGet("user").(model.User) + cacheKey := h.createCacheKey(getSearchClusterName(c), user.Key(), query, limit) if cachedResults, found := h.cache.Get(cacheKey); found { response := SearchResponse{ diff --git a/pkg/handlers/search_handler_test.go b/pkg/handlers/search_handler_test.go index a3e46653..8d9637e5 100644 --- a/pkg/handlers/search_handler_test.go +++ b/pkg/handlers/search_handler_test.go @@ -13,6 +13,7 @@ import ( "github.com/zxh326/kite/pkg/common" "github.com/zxh326/kite/pkg/handlers/resources" "github.com/zxh326/kite/pkg/middleware" + "github.com/zxh326/kite/pkg/model" ) func TestNormalizeSearchQuery(t *testing.T) { @@ -115,6 +116,7 @@ func TestGlobalSearchNegativeLimitDoesNotPanic(t *testing.T) { rec := httptest.NewRecorder() ctx, _ := gin.CreateTestContext(rec) ctx.Request = httptest.NewRequest(http.MethodGet, "/search?q=po&limit=-1", nil) + ctx.Set("user", model.AnonymousUser) handler := NewSearchHandler() @@ -188,6 +190,7 @@ func newSearchContext(t *testing.T, clusterName string) *gin.Context { if clusterName != "" { ctx.Set(middleware.ClusterNameKey, clusterName) } + ctx.Set("user", model.AnonymousUser) return ctx } @@ -212,6 +215,7 @@ func performGlobalSearch(t *testing.T, handler *SearchHandler, clusterName, targ if clusterName != "" { ctx.Set(middleware.ClusterNameKey, clusterName) } + ctx.Set("user", model.AnonymousUser) handler.GlobalSearch(ctx)