forked from baron/baron-sso
fix(auth): separate pkce and headless trusted rp config
This commit is contained in:
@@ -1699,14 +1699,14 @@ func containsHeadlessAudience(expected []string, actual headlessAssertionAud) bo
|
||||
func (h *AuthHandler) loadHeadlessJWKS(ctx context.Context, client domain.HydraClient) (*jose.JSONWebKeySet, error) {
|
||||
var raw []byte
|
||||
switch {
|
||||
case client.JWKS != nil:
|
||||
data, err := json.Marshal(client.JWKS)
|
||||
case client.HeadlessJWKS() != nil:
|
||||
data, err := json.Marshal(client.HeadlessJWKS())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode jwks: %w", err)
|
||||
}
|
||||
raw = data
|
||||
case strings.TrimSpace(client.JWKSUri) != "":
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimSpace(client.JWKSUri), nil)
|
||||
case client.HeadlessJWKSURI() != "":
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, client.HeadlessJWKSURI(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build jwks request: %w", err)
|
||||
}
|
||||
|
||||
@@ -171,11 +171,12 @@ func TestHeadlessLinkInit_TrustedClientSuccess(t *testing.T) {
|
||||
Challenge: "challenge-123",
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "trusted-rp",
|
||||
TokenEndpointAuthMethod: "private_key_jwt",
|
||||
JWKS: jwks,
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
"headless_jwks": jwks,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -232,11 +233,12 @@ func TestHeadlessLinkPoll_AfterApprovalReturnsRedirect(t *testing.T) {
|
||||
Challenge: "challenge-123",
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "trusted-rp",
|
||||
TokenEndpointAuthMethod: "private_key_jwt",
|
||||
JWKS: jwks,
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
"headless_jwks": jwks,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -306,11 +306,12 @@ func TestHeadlessPasswordLogin_TrustedClientSuccess(t *testing.T) {
|
||||
Challenge: "challenge-123",
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "trusted-rp",
|
||||
TokenEndpointAuthMethod: "private_key_jwt",
|
||||
JWKSUri: jwksServer.URL + "/.well-known/jwks.json",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
"headless_jwks_uri": jwksServer.URL + "/.well-known/jwks.json",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -524,10 +525,11 @@ func TestHeadlessPasswordLogin_HeadlessDisabledRejected(t *testing.T) {
|
||||
Challenge: "challenge-123",
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "trusted-rp",
|
||||
TokenEndpointAuthMethod: "private_key_jwt",
|
||||
JWKSUri: "https://rp.example.com/.well-known/jwks.json",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
"status": "active",
|
||||
"status": "active",
|
||||
"headless_jwks_uri": "https://rp.example.com/.well-known/jwks.json",
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -576,11 +578,12 @@ func TestHeadlessPasswordLogin_ClientIDMismatchRejected(t *testing.T) {
|
||||
Challenge: "challenge-123",
|
||||
Client: domain.HydraClient{
|
||||
ClientID: "other-rp",
|
||||
TokenEndpointAuthMethod: "private_key_jwt",
|
||||
JWKSUri: "https://rp.example.com/.well-known/jwks.json",
|
||||
TokenEndpointAuthMethod: "none",
|
||||
Metadata: map[string]interface{}{
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"status": "active",
|
||||
"headless_login_enabled": true,
|
||||
"headless_token_endpoint_auth_method": "private_key_jwt",
|
||||
"headless_jwks_uri": "https://rp.example.com/.well-known/jwks.json",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -891,6 +891,13 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
||||
tokenAuthMethod = "client_secret_basic"
|
||||
}
|
||||
}
|
||||
tokenAuthMethod, jwksURI, jwks, metadata := normalizeHeadlessClientConfig(
|
||||
clientType,
|
||||
tokenAuthMethod,
|
||||
valueOr(req.JwksUri, ""),
|
||||
req.Jwks,
|
||||
metadata,
|
||||
)
|
||||
|
||||
clientReq := domain.HydraClient{
|
||||
ClientID: clientID,
|
||||
@@ -900,8 +907,8 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
||||
ResponseTypes: responseTypes,
|
||||
Scope: strings.Join(scopes, " "),
|
||||
TokenEndpointAuthMethod: tokenAuthMethod,
|
||||
JWKSUri: valueOr(req.JwksUri, ""),
|
||||
JWKS: req.Jwks,
|
||||
JWKSUri: jwksURI,
|
||||
JWKS: jwks,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
@@ -1044,6 +1051,23 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
||||
}
|
||||
metadata["status"] = status
|
||||
}
|
||||
resolvedClientType := currentSummary.Type
|
||||
if clientType != "" {
|
||||
resolvedClientType = clientType
|
||||
}
|
||||
resolvedTokenAuthMethod := resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod)
|
||||
resolvedJWKSURI := valueOr(req.JwksUri, current.JWKSUri)
|
||||
resolvedJWKS := req.Jwks
|
||||
if req.Jwks == nil {
|
||||
resolvedJWKS = current.JWKS
|
||||
}
|
||||
resolvedTokenAuthMethod, resolvedJWKSURI, resolvedJWKS, metadata = normalizeHeadlessClientConfig(
|
||||
resolvedClientType,
|
||||
resolvedTokenAuthMethod,
|
||||
resolvedJWKSURI,
|
||||
resolvedJWKS,
|
||||
metadata,
|
||||
)
|
||||
|
||||
updated := domain.HydraClient{
|
||||
ClientID: current.ClientID,
|
||||
@@ -1052,14 +1076,11 @@ func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
||||
GrantTypes: derefSlice(req.GrantTypes, current.GrantTypes),
|
||||
ResponseTypes: derefSlice(req.ResponseTypes, current.ResponseTypes),
|
||||
Scope: buildScope(valueOrSlice(req.Scopes, strings.Fields(current.Scope))),
|
||||
TokenEndpointAuthMethod: resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod),
|
||||
JWKSUri: valueOr(req.JwksUri, current.JWKSUri),
|
||||
JWKS: req.Jwks,
|
||||
TokenEndpointAuthMethod: resolvedTokenAuthMethod,
|
||||
JWKSUri: resolvedJWKSURI,
|
||||
JWKS: resolvedJWKS,
|
||||
Metadata: metadata,
|
||||
}
|
||||
if req.Jwks == nil {
|
||||
updated.JWKS = current.JWKS
|
||||
}
|
||||
if err := validateReservedSystemClientName(updated.ClientID, updated.ClientName); err != nil {
|
||||
return errorJSON(c, fiber.StatusForbidden, err.Error())
|
||||
}
|
||||
@@ -1676,6 +1697,70 @@ func (h *DevHandler) mapClientSummary(client domain.HydraClient) clientSummary {
|
||||
}
|
||||
}
|
||||
|
||||
func readMetadataStringValue(metadata map[string]interface{}, key string) string {
|
||||
if metadata == nil {
|
||||
return ""
|
||||
}
|
||||
raw, _ := metadata[key].(string)
|
||||
return strings.TrimSpace(raw)
|
||||
}
|
||||
|
||||
func readMetadataBoolValue(metadata map[string]interface{}, key string) bool {
|
||||
if metadata == nil {
|
||||
return false
|
||||
}
|
||||
value, _ := metadata[key].(bool)
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeHeadlessClientConfig(
|
||||
clientType string,
|
||||
tokenAuthMethod string,
|
||||
jwksURI string,
|
||||
jwks interface{},
|
||||
metadata map[string]interface{},
|
||||
) (string, string, interface{}, map[string]interface{}) {
|
||||
if metadata == nil {
|
||||
metadata = map[string]interface{}{}
|
||||
}
|
||||
|
||||
headlessEnabled := readMetadataBoolValue(metadata, domain.MetadataHeadlessLoginEnabled)
|
||||
if clientType == "pkce" && headlessEnabled {
|
||||
headlessTokenAuthMethod := readMetadataStringValue(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
||||
if headlessTokenAuthMethod == "" && !strings.EqualFold(strings.TrimSpace(tokenAuthMethod), "none") {
|
||||
headlessTokenAuthMethod = strings.TrimSpace(tokenAuthMethod)
|
||||
}
|
||||
if headlessTokenAuthMethod == "" {
|
||||
headlessTokenAuthMethod = "private_key_jwt"
|
||||
}
|
||||
metadata[domain.MetadataHeadlessTokenEndpointAuthMethod] = headlessTokenAuthMethod
|
||||
|
||||
headlessJWKSURI := readMetadataStringValue(metadata, domain.MetadataHeadlessJWKSURI)
|
||||
if headlessJWKSURI == "" && strings.TrimSpace(jwksURI) != "" {
|
||||
headlessJWKSURI = strings.TrimSpace(jwksURI)
|
||||
}
|
||||
if headlessJWKSURI != "" {
|
||||
metadata[domain.MetadataHeadlessJWKSURI] = headlessJWKSURI
|
||||
} else {
|
||||
delete(metadata, domain.MetadataHeadlessJWKSURI)
|
||||
}
|
||||
|
||||
if _, ok := metadata[domain.MetadataHeadlessJWKS]; !ok && jwks != nil {
|
||||
metadata[domain.MetadataHeadlessJWKS] = jwks
|
||||
}
|
||||
if metadata[domain.MetadataHeadlessJWKS] == nil {
|
||||
delete(metadata, domain.MetadataHeadlessJWKS)
|
||||
}
|
||||
|
||||
return "none", "", nil, metadata
|
||||
}
|
||||
|
||||
delete(metadata, domain.MetadataHeadlessTokenEndpointAuthMethod)
|
||||
delete(metadata, domain.MetadataHeadlessJWKSURI)
|
||||
delete(metadata, domain.MetadataHeadlessJWKS)
|
||||
return tokenAuthMethod, jwksURI, jwks, metadata
|
||||
}
|
||||
|
||||
func defaultClientScopes() []string {
|
||||
return []string{"openid", "profile", "email"}
|
||||
}
|
||||
|
||||
@@ -676,9 +676,10 @@ func TestCreateClient_TrustedRPPayloadMapping(t *testing.T) {
|
||||
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||
assert.NotNil(t, captured.JWKS)
|
||||
assert.True(t, captured.IsTrustedRP())
|
||||
assert.Equal(t, "none", captured.TokenEndpointAuthMethod)
|
||||
assert.Nil(t, captured.JWKS)
|
||||
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
||||
assert.NotNil(t, captured.Metadata["headless_jwks"])
|
||||
assert.True(t, captured.IsHeadlessLoginEnabled())
|
||||
assert.Equal(t, true, captured.Metadata["headless_login_enabled"])
|
||||
assert.Equal(t, "RS256", captured.Metadata["request_object_signing_alg"])
|
||||
@@ -754,9 +755,10 @@ func TestUpdateClient_TrustedRPPayloadMapping(t *testing.T) {
|
||||
|
||||
resp, _ := app.Test(req, -1)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, "private_key_jwt", captured.TokenEndpointAuthMethod)
|
||||
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.JWKSUri)
|
||||
assert.True(t, captured.IsTrustedRP())
|
||||
assert.Equal(t, "none", captured.TokenEndpointAuthMethod)
|
||||
assert.Equal(t, "", captured.JWKSUri)
|
||||
assert.Equal(t, "private_key_jwt", captured.Metadata["headless_token_endpoint_auth_method"])
|
||||
assert.Equal(t, "https://rp.example.com/.well-known/jwks.json", captured.Metadata["headless_jwks_uri"])
|
||||
assert.True(t, captured.IsHeadlessLoginEnabled())
|
||||
assert.Equal(t, true, captured.Metadata["headless_login_enabled"])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user