package identity import ( "net/http" "net/http/httptest" "testing" "github.com/golang-jwt/jwt/v5" ) func TestStripIncoming_RemovesSpoofedHeaders(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) r.Header.Set(HeaderUser, "evil-spoof") r.Header.Set(HeaderRoles, "admin:*") r.Header.Set(HeaderActingService, "fake-service") r.Header.Set(HeaderAuthVerified, "true") StripIncoming(r) for _, h := range managed { if got := r.Header.Get(h); got != "" { t.Errorf("header %s should be stripped, got %q", h, got) } } } func TestStripIncoming_PreservesOtherHeaders(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) r.Header.Set("Authorization", "Bearer token") r.Header.Set("Content-Type", "application/json") r.Header.Set(HeaderUser, "spoof") StripIncoming(r) if got := r.Header.Get("Authorization"); got != "Bearer token" { t.Errorf("Authorization should be preserved, got %q", got) } if got := r.Header.Get("Content-Type"); got != "application/json" { t.Errorf("Content-Type should be preserved, got %q", got) } } func TestInject_ServiceAccount(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "abc123-hashed-id", "azp": "portfolio-agent", "roles": []interface{}{"llm:inference", "memory:read"}, } Inject(r, claims) assertHeader(t, r, HeaderAuthVerified, "true") assertHeader(t, r, HeaderUser, "abc123-hashed-id") assertHeader(t, r, HeaderRoles, "llm:inference,memory:read") assertHeader(t, r, HeaderActingService, "portfolio-agent") } func TestInject_HumanUser(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "user-hash-456", "azp": "api-gw", "permissions": []interface{}{"*"}, } Inject(r, claims) assertHeader(t, r, HeaderAuthVerified, "true") assertHeader(t, r, HeaderUser, "user-hash-456") assertHeader(t, r, HeaderRoles, "*") // azp != sub, so acting-service is set assertHeader(t, r, HeaderActingService, "api-gw") } func TestInject_SameSubAndAzp_NoActingService(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "portfolio-agent", "azp": "portfolio-agent", "roles": []interface{}{"llm:inference"}, } Inject(r, claims) assertHeader(t, r, HeaderActingService, "") } func TestInject_RolesOverPermissions(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "user-1", "roles": []interface{}{"llm:inference"}, "permissions": []interface{}{"admin:*"}, } Inject(r, claims) // roles takes precedence over permissions assertHeader(t, r, HeaderRoles, "llm:inference") } func TestInject_PermissionsFallback(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "user-1", "permissions": []interface{}{"grafana:read", "grafana:write"}, } Inject(r, claims) assertHeader(t, r, HeaderRoles, "grafana:read,grafana:write") } func TestInject_EmptyClaims(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{} Inject(r, claims) assertHeader(t, r, HeaderAuthVerified, "true") assertHeader(t, r, HeaderUser, "") assertHeader(t, r, HeaderRoles, "") assertHeader(t, r, HeaderActingService, "") } func TestInject_NilValuesInClaims(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": nil, "azp": nil, "roles": nil, } Inject(r, claims) assertHeader(t, r, HeaderAuthVerified, "true") assertHeader(t, r, HeaderUser, "") assertHeader(t, r, HeaderRoles, "") } func TestInject_WildcardPermission(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "admin-user", "permissions": []interface{}{"*"}, } Inject(r, claims) // Wildcard passed as literal, never expanded assertHeader(t, r, HeaderRoles, "*") } func TestInject_MixedTypeRolesArray(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "user-1", "roles": []interface{}{"llm:inference", 42, nil, "", "memory:read"}, } Inject(r, claims) // Non-string and empty elements skipped assertHeader(t, r, HeaderRoles, "llm:inference,memory:read") } func TestInject_EmptyRolesArray(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) claims := jwt.MapClaims{ "sub": "user-1", "roles": []interface{}{}, "permissions": []interface{}{"backup:read"}, } Inject(r, claims) // Empty roles falls through to permissions assertHeader(t, r, HeaderRoles, "backup:read") } func TestStripThenInject_OverwritesSpoof(t *testing.T) { r := httptest.NewRequest("GET", "/", nil) r.Header.Set(HeaderUser, "evil-spoof") r.Header.Set(HeaderAuthVerified, "true") StripIncoming(r) claims := jwt.MapClaims{ "sub": "real-user", "roles": []interface{}{"llm:inference"}, } Inject(r, claims) assertHeader(t, r, HeaderUser, "real-user") assertHeader(t, r, HeaderAuthVerified, "true") } func assertHeader(t *testing.T, r *http.Request, key, want string) { t.Helper() got := r.Header.Get(key) if got != want { t.Errorf("header %s = %q, want %q", key, got, want) } }