1
0
forked from baron/baron-sso

devfront ID Token Claims 백엔드 반영

This commit is contained in:
2026-04-29 13:45:23 +09:00
parent e484d8c100
commit 0844befb35
5 changed files with 492 additions and 8 deletions

View File

@@ -1449,6 +1449,144 @@ func TestCreateClient_HeadlessLoginRejectsInlineJWKS(t *testing.T) {
assert.False(t, hydraCalled)
}
func TestCreateClient_NormalizesIDTokenClaimsMetadata(t *testing.T) {
var captured domain.HydraClient
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
if r.Method == http.MethodPost && r.URL.Path == "/clients" {
body, err := io.ReadAll(r.Body)
assert.NoError(t, err)
assert.NoError(t, json.Unmarshal(body, &captured))
return httpJSONAny(r, http.StatusCreated, map[string]any{
"client_id": captured.ClientID,
"client_name": captured.ClientName,
"redirect_uris": captured.RedirectURIs,
"grant_types": captured.GrantTypes,
"response_types": captured.ResponseTypes,
"scope": captured.Scope,
"token_endpoint_auth_method": captured.TokenEndpointAuthMethod,
"metadata": captured.Metadata,
}), nil
}
return httpJSONAny(r, http.StatusNotFound, nil), nil
})
h := &DevHandler{
Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test",
PublicURL: "http://hydra.public",
HTTPClient: &http.Client{Transport: transport},
},
Keto: new(devMockKetoService),
}
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
c.Locals("user_profile", &domain.UserProfileResponse{ID: "test-user", Role: domain.RoleSuperAdmin})
return c.Next()
})
app.Post("/api/v1/dev/clients", h.CreateClient)
body, _ := json.Marshal(map[string]any{
"name": "Claims App",
"type": "pkce",
"redirectUris": []string{"https://rp.example.com/callback"},
"metadata": map[string]any{
"id_token_claims": []map[string]any{
{
"id": "claim-1",
"namespace": "top_level",
"key": "locale",
"value": " ko-KR ",
"valueType": "text",
},
{
"id": "claim-2",
"namespace": "rp_claims",
"key": "tier",
"value": "2",
"valueType": "number",
},
},
},
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
claims, ok := captured.Metadata[domain.MetadataIDTokenClaims].([]interface{})
if assert.True(t, ok) && assert.Len(t, claims, 2) {
first, ok := claims[0].(map[string]interface{})
if assert.True(t, ok) {
assert.Equal(t, "top_level", first["namespace"])
assert.Equal(t, "locale", first["key"])
assert.Equal(t, "ko-KR", first["value"])
assert.Equal(t, "text", first["valueType"])
_, hasID := first["id"]
assert.False(t, hasID)
}
second, ok := claims[1].(map[string]interface{})
if assert.True(t, ok) {
assert.Equal(t, "rp_claims", second["namespace"])
assert.Equal(t, "tier", second["key"])
assert.Equal(t, "2", second["value"])
assert.Equal(t, "number", second["valueType"])
}
}
}
func TestCreateClient_RejectsInvalidIDTokenClaimsMetadata(t *testing.T) {
hydraCalled := false
h := &DevHandler{
Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test",
PublicURL: "http://hydra.public",
HTTPClient: &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
hydraCalled = true
return httpJSONAny(r, http.StatusCreated, map[string]any{}), nil
})},
},
Keto: new(devMockKetoService),
}
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
c.Locals("user_profile", &domain.UserProfileResponse{ID: "test-user", Role: domain.RoleSuperAdmin})
return c.Next()
})
app.Post("/api/v1/dev/clients", h.CreateClient)
body, _ := json.Marshal(map[string]any{
"name": "Claims App",
"type": "pkce",
"redirectUris": []string{"https://rp.example.com/callback"},
"metadata": map[string]any{
"id_token_claims": []map[string]any{
{
"namespace": "top_level",
"key": "rp_claims",
"value": "forbidden",
"valueType": "text",
},
},
},
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
assert.Contains(t, string(bodyBytes), "top-level key rp_claims is reserved")
assert.False(t, hydraCalled)
}
func TestUpdateClient_HeadlessLoginPayloadMapping(t *testing.T) {
var captured domain.HydraClient