1
0
forked from baron/baron-sso

offline 스코프 제거, rp_claims 값 표준화

This commit is contained in:
2026-06-11 14:50:26 +09:00
parent f60b15a17b
commit c495e9119b
26 changed files with 1034 additions and 300 deletions

View File

@@ -15,6 +15,7 @@ import (
"io"
"log/slog"
"maps"
"math"
"net"
"net/http"
"net/url"
@@ -2099,7 +2100,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
return errorJSON(c, fiber.StatusBadRequest, "redirectUris is required")
}
scopes := derefSlice(req.Scopes, defaultClientScopes())
scopes := normalizeClientScopes(derefSlice(req.Scopes, defaultClientScopes()))
grantTypes := derefSlice(req.GrantTypes, defaultGrantTypes())
responseTypes := derefSlice(req.ResponseTypes, defaultResponseTypes())
@@ -2186,7 +2187,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
RedirectURIs: redirectURIs,
GrantTypes: grantTypes,
ResponseTypes: responseTypes,
Scope: strings.Join(scopes, " "),
Scope: buildScope(scopes),
TokenEndpointAuthMethod: tokenAuthMethod,
SkipConsent: new(valueOrBool(req.SkipConsent, true)),
JWKSUri: jwksURI,
@@ -3593,7 +3594,7 @@ func normalizeIDTokenClaimsWithOptions(rawClaims any, allowTopLevel bool) ([]nor
return nil, fmt.Errorf("metadata.id_token_claims valueType is invalid: %s", valueType)
}
value := strings.TrimSpace(readInterfaceString(record["value"], ""))
value := strings.TrimSpace(readClaimValueString(record["value"], ""))
nullable, _ := record["nullable"].(bool)
if !(nullable && value == "") {
if _, err := parseConfiguredClaimValue(value, valueType); err != nil {
@@ -3631,6 +3632,35 @@ func readInterfaceString(value any, fallback string) string {
return fallback
}
func readClaimValueString(value any, fallback string) string {
if value == nil {
return fallback
}
switch typed := value.(type) {
case string:
return typed
case float64:
if typed == math.Trunc(typed) {
return strconv.FormatInt(int64(typed), 10)
}
return strconv.FormatFloat(typed, 'f', -1, 64)
case float32:
floatValue := float64(typed)
if floatValue == math.Trunc(floatValue) {
return strconv.FormatInt(int64(floatValue), 10)
}
return strconv.FormatFloat(floatValue, 'f', -1, 64)
case int:
return strconv.Itoa(typed)
case int64:
return strconv.FormatInt(typed, 10)
case json.Number:
return typed.String()
default:
return fallback
}
}
func parseConfiguredClaimValue(rawValue string, valueType string) (any, error) {
trimmed := strings.TrimSpace(rawValue)
@@ -3703,21 +3733,36 @@ func parseConfiguredClaimValue(rawValue string, valueType string) (any, error) {
if trimmed == "" {
return nil, errors.New("date value is required")
}
if _, err := time.Parse("2006-01-02", trimmed); err != nil {
return nil, errors.New("date value must use YYYY-MM-DD")
if isIntegerClaimLiteral(trimmed) {
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return nil, errors.New("date value must use unix seconds or YYYY-MM-DD")
}
return parsed, nil
}
return trimmed, nil
parsed, err := time.Parse("2006-01-02", trimmed)
if err != nil {
return nil, errors.New("date value must use unix seconds or YYYY-MM-DD")
}
return parsed.Unix(), nil
case "datetime":
if trimmed == "" {
return nil, errors.New("datetime value is required")
}
if _, err := time.Parse(time.RFC3339, trimmed); err == nil {
return trimmed, nil
if isIntegerClaimLiteral(trimmed) {
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return nil, errors.New("datetime value must use unix seconds, RFC3339, or YYYY-MM-DDTHH:mm")
}
return parsed, nil
}
if _, err := time.Parse("2006-01-02T15:04", trimmed); err == nil {
return trimmed, nil
if parsed, err := time.Parse(time.RFC3339, trimmed); err == nil {
return parsed.Unix(), nil
}
return nil, errors.New("datetime value must use RFC3339 or YYYY-MM-DDTHH:mm")
if parsed, err := time.ParseInLocation("2006-01-02T15:04", trimmed, time.UTC); err == nil {
return parsed.Unix(), nil
}
return nil, errors.New("datetime value must use unix seconds, RFC3339, or YYYY-MM-DDTHH:mm")
default:
return nil, fmt.Errorf("unsupported claim value type: %s", valueType)
}
@@ -3795,7 +3840,33 @@ func defaultResponseTypes() []string {
}
func buildScope(scopes []string) string {
return strings.Join(scopes, " ")
return strings.Join(normalizeClientScopes(scopes), " ")
}
func normalizeClientScopes(scopes []string) []string {
normalized := make([]string, 0, len(scopes))
seen := make(map[string]struct{}, len(scopes))
for _, scope := range scopes {
scope = strings.TrimSpace(scope)
if scope == "" || isRefreshTokenScopeAlias(scope) {
continue
}
if _, ok := seen[scope]; ok {
continue
}
seen[scope] = struct{}{}
normalized = append(normalized, scope)
}
return normalized
}
func isRefreshTokenScopeAlias(scope string) bool {
switch strings.ToLower(strings.TrimSpace(scope)) {
case "offline", "offline_access":
return true
default:
return false
}
}
func valueOr(ptr *string, fallback string) string {