From 7a767d8568772197503dca7f90b5a767f6f23572 Mon Sep 17 00:00:00 2001 From: BitMasher <61257372+BitMasher@users.noreply.github.com> Date: Wed, 12 Mar 2025 07:00:29 -0500 Subject: [PATCH 1/2] feat: add CanGetPrivateClaimsFromRequest interface (#717) --- pkg/op/storage.go | 6 ++++++ pkg/op/token.go | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 8488b28..a579810 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -144,6 +144,12 @@ type CanSetUserinfoFromRequest interface { SetUserinfoFromRequest(ctx context.Context, userinfo *oidc.UserInfo, request IDTokenRequest, scopes []string) error } +// CanGetPrivateClaimsFromRequest is an optional additional interface that may be implemented by +// implementors of Storage. It allows setting the jwt token claims based on the request. +type CanGetPrivateClaimsFromRequest interface { + GetPrivateClaimsFromRequest(ctx context.Context, request TokenRequest, restrictedScopes []string) (map[string]any, error) +} + // Storage is a required parameter for NewOpenIDProvider(). In addition to the // embedded interfaces below, if the passed Storage implements ClientCredentialsStorage // then the grant type "client_credentials" will be supported. In that case, the access diff --git a/pkg/op/token.go b/pkg/op/token.go index 04cd3cc..1df9cc2 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -147,7 +147,11 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex tokenExchangeRequest, ) } else { - privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) + if fromRequest, ok := storage.(CanGetPrivateClaimsFromRequest); ok { + privateClaims, err = fromRequest.GetPrivateClaimsFromRequest(ctx, tokenRequest, removeUserinfoScopes(restrictedScopes)) + } else { + privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) + } } if err != nil { From efd6fdad7aa2382879821fde4d016236bb5c243a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 14 Mar 2025 12:30:08 +0200 Subject: [PATCH 2/2] fix: ignore empty json strings for locale (#678) * Revert "fix: ignore all unmarshal errors from locale (#673)" This reverts commit fbf009fe75dac732dde39e0eb6fe324b337675e0. * fix: ignore empty json strings for locale --- pkg/oidc/types.go | 22 +++++++++++++----- pkg/oidc/types_test.go | 51 ++++++++++++++++++++++++++++++------------ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/pkg/oidc/types.go b/pkg/oidc/types.go index 7063426..9b307bc 100644 --- a/pkg/oidc/types.go +++ b/pkg/oidc/types.go @@ -3,6 +3,7 @@ package oidc import ( "database/sql/driver" "encoding/json" + "errors" "fmt" "reflect" "strings" @@ -77,16 +78,25 @@ func (l *Locale) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements json.Unmarshaler. -// All unmarshal errors for are ignored. -// When an error is encountered, the containing tag will be set +// When [language.ValueError] is encountered, the containing tag will be set // to an empty value (language "und") and no error will be returned. // This state can be checked with the `l.Tag().IsRoot()` method. func (l *Locale) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &l.tag) - if err != nil { - l.tag = language.Tag{} + if len(data) == 0 || string(data) == "\"\"" { + return nil } - return nil + err := json.Unmarshal(data, &l.tag) + if err == nil { + return nil + } + + // catch "well-formed but unknown" errors + var target language.ValueError + if errors.As(err, &target) { + l.tag = language.Tag{} + return nil + } + return err } type Locales []language.Tag diff --git a/pkg/oidc/types_test.go b/pkg/oidc/types_test.go index c7ce0ee..53a9779 100644 --- a/pkg/oidc/types_test.go +++ b/pkg/oidc/types_test.go @@ -217,6 +217,30 @@ func TestLocale_UnmarshalJSON(t *testing.T) { want dst wantErr bool }{ + { + name: "value not present", + input: `{}`, + wantErr: false, + want: dst{ + Locale: nil, + }, + }, + { + name: "null", + input: `{"locale": null}`, + wantErr: false, + want: dst{ + Locale: nil, + }, + }, + { + name: "empty, ignored", + input: `{"locale": ""}`, + wantErr: false, + want: dst{ + Locale: &Locale{}, + }, + }, { name: "afrikaans, ok", input: `{"locale": "af"}`, @@ -232,23 +256,22 @@ func TestLocale_UnmarshalJSON(t *testing.T) { }, }, { - name: "bad form, error", - input: `{"locale": "g!!!!!"}`, - want: dst{ - Locale: &Locale{}, - }, + name: "bad form, error", + input: `{"locale": "g!!!!!"}`, + wantErr: true, }, } - for _, tt := range tests { - var got dst - err := json.Unmarshal([]byte(tt.input), &got) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.want, got) + t.Run(tt.name, func(t *testing.T) { + var got dst + err := json.Unmarshal([]byte(tt.input), &got) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) } }