@@ -2,42 +2,18 @@ package providers
22
33import (
44 "context"
5− "crypto/rand"
6− "crypto/rsa"
7− "encoding/base64"
85 "encoding/json"
96 "errors"
10− "fmt"
117 "net/http"
128 "net/http/httptest"
139 "net/url"
14− "strings"
1510 "testing"
16− "time"
1711
1812 "github.com/coreos/go-oidc"
19− "github.com/dgrijalva/jwt-go"
20− "github.com/stretchr/testify/assert"
21− "golang.org/x/oauth2"
22−
2313 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14+ "github.com/stretchr/testify/assert"
2415)
2516
26−const accessToken = "access_token"
27−const refreshToken = "refresh_token"
28−const clientID = "https://test.myapp.com"
29−const secret = "secret"
30−
31−type idTokenClaims struct {
32− Name string `json:"name,omitempty"`
33− Email string `json:"email,omitempty"`
34− Phone string `json:"phone_number,omitempty"`
35− Picture string `json:"picture,omitempty"`
36− Groups interface{} `json:"groups,omitempty"`
37− OtherGroups interface{} `json:"other_groups,omitempty"`
38− jwt.StandardClaims
39−}
40−
4117type redeemTokenResponse struct {
4218 AccessToken string `json:"access_token"`
4319 RefreshToken string `json:"refresh_token"`
@@ -46,88 +22,12 @@ type redeemTokenResponse struct {
4622 IDToken string `json:"id_token,omitempty"`
4723}
4824
49−var defaultIDToken idTokenClaims = idTokenClaims{
50− "Jane Dobbs",
51− "janed@me.com",
52− "+4798765432",
53− "http://mugbook.com/janed/me.jpg",
54− []string{"test:a", "test:b"},
55− []string{"test:c", "test:d"},
56− jwt.StandardClaims{
57− Audience: "https://test.myapp.com",
58− ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
59− Id: "id-some-id",
60− IssuedAt: time.Now().Unix(),
61− Issuer: "https://issuer.example.com",
62− NotBefore: 0,
63− Subject: "123456789",
64− },
65−}
66−
67−var customGroupClaimIDToken idTokenClaims = idTokenClaims{
68− "Jane Dobbs",
69− "janed@me.com",
70− "+4798765432",
71− "http://mugbook.com/janed/me.jpg",
72− []map[string]interface{}{
73− {
74− "groupId": "Admin Group Id",
75− "roles": []string{"Admin"},
76− },
77− },
78− []string{"test:c", "test:d"},
79− jwt.StandardClaims{
80− Audience: "https://test.myapp.com",
81− ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
82− Id: "id-some-id",
83− IssuedAt: time.Now().Unix(),
84− Issuer: "https://issuer.example.com",
85− NotBefore: 0,
86− Subject: "123456789",
87− },
88−}
89−
90−var minimalIDToken idTokenClaims = idTokenClaims{
91− "",
92− "",
93− "",
94− "",
95− []string{},
96− []string{},
97− jwt.StandardClaims{
98− Audience: "https://test.myapp.com",
99− ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
100− Id: "id-some-id",
101− IssuedAt: time.Now().Unix(),
102− Issuer: "https://issuer.example.com",
103− NotBefore: 0,
104− Subject: "minimal",
105− },
106−}
107−
108−type fakeKeySetStub struct{}
109−
110−func (fakeKeySetStub) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
111− decodeString, err := base64.RawURLEncoding.DecodeString(strings.Split(jwt, ".")[1])
112− if err != nil {
113− return nil, err
114− }
115− tokenClaims := &idTokenClaims{}
116− err = json.Unmarshal(decodeString, tokenClaims)
117−
118− if err != nil || tokenClaims.Id == "this-id-fails-validation" {
119− return nil, fmt.Errorf("the validation failed for subject [%v]", tokenClaims.Subject)
120− }
121−
122− return decodeString, err
123−}
124−
12525func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
12626
12727 providerData := &ProviderData{
12828 ProviderName: "oidc",
129− ClientID: clientID,
130− ClientSecret: secret,
29+ ClientID: oidcClientID,
30+ ClientSecret: oidcSecret,
13131 LoginURL: &url.URL{
13232 Scheme: serverURL.Scheme,
13333 Host: serverURL.Host,
@@ -144,18 +44,17 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
14444 Scheme: serverURL.Scheme,
14545 Host: serverURL.Host,
14646 Path: "/api"},
147− Scope: "openid profile offline_access",
47+ Scope: "openid profile offline_access",
48+ EmailClaim: "email",
49+ GroupsClaim: "groups",
14850 Verifier: oidc.NewVerifier(
149− "https://issuer.example.com",
150− fakeKeySetStub{},
151− &oidc.Config{ClientID: clientID},
51+ oidcIssuer,
52+ mockJWKS{},
53+ &oidc.Config{ClientID: oidcClientID},
15254 ),
15355 }
15456
155− p := &OIDCProvider{
156− ProviderData: providerData,
157− UserIDClaim: "email",
158− }
57+ p := &OIDCProvider{ProviderData: providerData}
15958
16059 return p
16160}
@@ -169,22 +68,7 @@ func newOIDCServer(body []byte) (*url.URL, *httptest.Server) {
16968 return u, s
17069}
17170
172−func newSignedTestIDToken(tokenClaims idTokenClaims) (string, error) {
173− key, _ := rsa.GenerateKey(rand.Reader, 2048)
174− standardClaims := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
175− return standardClaims.SignedString(key)
176−}
177−
178−func newOauth2Token() *oauth2.Token {
179− return &oauth2.Token{
180− AccessToken: accessToken,
181− TokenType: "Bearer",
182− RefreshToken: refreshToken,
183− Expiry: time.Time{}.Add(time.Duration(5) * time.Second),
184− }
185−}
186−
187−func newTestSetup(body []byte) (*httptest.Server, *OIDCProvider) {
71+func newTestOIDCSetup(body []byte) (*httptest.Server, *OIDCProvider) {
18872 redeemURL, server := newOIDCServer(body)
18973 provider := newOIDCProvider(redeemURL)
19074 return server, provider
@@ -201,7 +85,7 @@ func TestOIDCProviderRedeem(t *testing.T) {
20185 IDToken: idToken,
20286 })
20387
204− server, provider := newTestSetup(body)
88+ server, provider := newTestOIDCSetup(body)
20589 defer server.Close()
20690
20791 session, err := provider.Redeem(context.Background(), provider.RedeemURL.String(), "code1234")
@@ -224,8 +108,8 @@ func TestOIDCProviderRedeem_custom_userid(t *testing.T) {
224108 IDToken: idToken,
225109 })
226110
227− server, provider := newTestSetup(body)
228− provider.UserIDClaim = "phone_number"
111+ server, provider := newTestOIDCSetup(body)
112+ provider.EmailClaim = "phone_number"
229113 defer server.Close()
230114
231115 session, err := provider.Redeem(context.Background(), provider.RedeemURL.String(), "code1234")
@@ -233,6 +117,333 @@ func TestOIDCProviderRedeem_custom_userid(t *testing.T) {
233117 assert.Equal(t, defaultIDToken.Phone, session.Email)
234118}
235119
120+func TestOIDCProvider_EnrichSession(t *testing.T) {
121+ testCases := map[string]struct {
122+ ExistingSession *sessions.SessionState
123+ EmailClaim string
124+ GroupsClaim string
125+ ProfileJSON map[string]interface{}
126+ ExpectedError error
127+ ExpectedSession *sessions.SessionState
128+ }{
129+ "Already Populated": {
130+ ExistingSession: &sessions.SessionState{
131+ User: "already",
132+ Email: "already@populated.com",
133+ Groups: []string{"already", "populated"},
134+ IDToken: idToken,
135+ AccessToken: accessToken,
136+ RefreshToken: refreshToken,
137+ },
138+ EmailClaim: "email",
139+ GroupsClaim: "groups",
140+ ProfileJSON: map[string]interface{}{
141+ "email": "new@thing.com",
142+ "groups": []string{"new", "thing"},
143+ },
144+ ExpectedError: nil,
145+ ExpectedSession: &sessions.SessionState{
146+ User: "already",
147+ Email: "already@populated.com",
148+ Groups: []string{"already", "populated"},
149+ IDToken: idToken,
150+ AccessToken: accessToken,
151+ RefreshToken: refreshToken,
152+ },
153+ },
154+ "Missing Email": {
155+ ExistingSession: &sessions.SessionState{
156+ User: "missing.email",
157+ Groups: []string{"already", "populated"},
158+ IDToken: idToken,
159+ AccessToken: accessToken,
160+ RefreshToken: refreshToken,
161+ },
162+ EmailClaim: "email",
163+ GroupsClaim: "groups",
164+ ProfileJSON: map[string]interface{}{
165+ "email": "found@email.com",
166+ "groups": []string{"new", "thing"},
167+ },
168+ ExpectedError: nil,
169+ ExpectedSession: &sessions.SessionState{
170+ User: "missing.email",
171+ Email: "found@email.com",
172+ Groups: []string{"already", "populated"},
173+ IDToken: idToken,
174+ AccessToken: accessToken,
175+ RefreshToken: refreshToken,
176+ },
177+ },
178+
179+ "Missing Email Only in Profile URL": {
180+ ExistingSession: &sessions.SessionState{
181+ User: "missing.email",
182+ IDToken: idToken,
183+ AccessToken: accessToken,
184+ RefreshToken: refreshToken,
185+ },
186+ EmailClaim: "email",
187+ GroupsClaim: "groups",
188+ ProfileJSON: map[string]interface{}{
189+ "email": "found@email.com",
190+ },
191+ ExpectedError: nil,
192+ ExpectedSession: &sessions.SessionState{
193+ User: "missing.email",
194+ Email: "found@email.com",
195+ IDToken: idToken,
196+ AccessToken: accessToken,
197+ RefreshToken: refreshToken,
198+ },
199+ },
200+ "Missing Email with Custom Claim": {
201+ ExistingSession: &sessions.SessionState{
202+ User: "missing.email",
203+ Groups: []string{"already", "populated"},
204+ IDToken: idToken,
205+ AccessToken: accessToken,
206+ RefreshToken: refreshToken,
207+ },
208+ EmailClaim: "weird",
209+ GroupsClaim: "groups",
210+ ProfileJSON: map[string]interface{}{
211+ "weird": "weird@claim.com",
212+ "groups": []string{"new", "thing"},
213+ },
214+ ExpectedError: nil,
215+ ExpectedSession: &sessions.SessionState{
216+ User: "missing.email",
217+ Email: "weird@claim.com",
218+ Groups: []string{"already", "populated"},
219+ IDToken: idToken,
220+ AccessToken: accessToken,
221+ RefreshToken: refreshToken,
222+ },
223+ },
224+ "Missing Email not in Profile URL": {
225+ ExistingSession: &sessions.SessionState{
226+ User: "missing.email",
227+ Groups: []string{"already", "populated"},
228+ IDToken: idToken,
229+ AccessToken: accessToken,
230+ RefreshToken: refreshToken,
231+ },
232+ EmailClaim: "email",
233+ GroupsClaim: "groups",
234+ ProfileJSON: map[string]interface{}{
235+ "groups": []string{"new", "thing"},
236+ },
237+ ExpectedError: errors.New("neither the id_token nor the profileURL set an email"),
238+ ExpectedSession: &sessions.SessionState{
239+ User: "missing.email",
240+ Groups: []string{"already", "populated"},
241+ IDToken: idToken,
242+ AccessToken: accessToken,
243+ RefreshToken: refreshToken,
244+ },
245+ },
246+ "Missing Groups": {
247+ ExistingSession: &sessions.SessionState{
248+ User: "already",
249+ Email: "already@populated.com",
250+ Groups: nil,
251+ IDToken: idToken,
252+ AccessToken: accessToken,
253+ RefreshToken: refreshToken,
254+ },
255+ EmailClaim: "email",
256+ GroupsClaim: "groups",
257+ ProfileJSON: map[string]interface{}{
258+ "email": "new@thing.com",
259+ "groups": []string{"new", "thing"},
260+ },
261+ ExpectedError: nil,
262+ ExpectedSession: &sessions.SessionState{
263+ User: "already",
264+ Email: "already@populated.com",
265+ Groups: []string{"new", "thing"},
266+ IDToken: idToken,
267+ AccessToken: accessToken,
268+ RefreshToken: refreshToken,
269+ },
270+ },
271+ "Missing Groups with Complex Groups in Profile URL": {
272+ ExistingSession: &sessions.SessionState{
273+ User: "already",
274+ Email: "already@populated.com",
275+ Groups: nil,
276+ IDToken: idToken,
277+ AccessToken: accessToken,
278+ RefreshToken: refreshToken,
279+ },
280+ EmailClaim: "email",
281+ GroupsClaim: "groups",
282+ ProfileJSON: map[string]interface{}{
283+ "email": "new@thing.com",
284+ "groups": []map[string]interface{}{
285+ {
286+ "groupId": "Admin Group Id",
287+ "roles": []string{"Admin"},
288+ },
289+ },
290+ },
291+ ExpectedError: nil,
292+ ExpectedSession: &sessions.SessionState{
293+ User: "already",
294+ Email: "already@populated.com",
295+ Groups: []string{"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}"},
296+ IDToken: idToken,
297+ AccessToken: accessToken,
298+ RefreshToken: refreshToken,
299+ },
300+ },
301+ "Missing Groups with Singleton Complex Group in Profile URL": {
302+ ExistingSession: &sessions.SessionState{
303+ User: "already",
304+ Email: "already@populated.com",
305+ Groups: nil,
306+ IDToken: idToken,
307+ AccessToken: accessToken,
308+ RefreshToken: refreshToken,
309+ },
310+ EmailClaim: "email",
311+ GroupsClaim: "groups",
312+ ProfileJSON: map[string]interface{}{
313+ "email": "new@thing.com",
314+ "groups": map[string]interface{}{
315+ "groupId": "Admin Group Id",
316+ "roles": []string{"Admin"},
317+ },
318+ },
319+ ExpectedError: nil,
320+ ExpectedSession: &sessions.SessionState{
321+ User: "already",
322+ Email: "already@populated.com",
323+ Groups: []string{"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}"},
324+ IDToken: idToken,
325+ AccessToken: accessToken,
326+ RefreshToken: refreshToken,
327+ },
328+ },
329+ "Empty Groups Claims": {
330+ ExistingSession: &sessions.SessionState{
331+ User: "already",
332+ Email: "already@populated.com",
333+ Groups: []string{},
334+ IDToken: idToken,
335+ AccessToken: accessToken,
336+ RefreshToken: refreshToken,
337+ },
338+ EmailClaim: "email",
339+ GroupsClaim: "groups",
340+ ProfileJSON: map[string]interface{}{
341+ "email": "new@thing.com",
342+ "groups": []string{"new", "thing"},
343+ },
344+ ExpectedError: nil,
345+ ExpectedSession: &sessions.SessionState{
346+ User: "already",
347+ Email: "already@populated.com",
348+ Groups: []string{},
349+ IDToken: idToken,
350+ AccessToken: accessToken,
351+ RefreshToken: refreshToken,
352+ },
353+ },
354+ "Missing Groups with Custom Claim": {
355+ ExistingSession: &sessions.SessionState{
356+ User: "already",
357+ Email: "already@populated.com",
358+ Groups: nil,
359+ IDToken: idToken,
360+ AccessToken: accessToken,
361+ RefreshToken: refreshToken,
362+ },
363+ EmailClaim: "email",
364+ GroupsClaim: "roles",
365+ ProfileJSON: map[string]interface{}{
366+ "email": "new@thing.com",
367+ "roles": []string{"new", "thing", "roles"},
368+ },
369+ ExpectedError: nil,
370+ ExpectedSession: &sessions.SessionState{
371+ User: "already",
372+ Email: "already@populated.com",
373+ Groups: []string{"new", "thing", "roles"},
374+ IDToken: idToken,
375+ AccessToken: accessToken,
376+ RefreshToken: refreshToken,
377+ },
378+ },
379+ "Missing Groups String Profile URL Response": {
380+ ExistingSession: &sessions.SessionState{
381+ User: "already",
382+ Email: "already@populated.com",
383+ Groups: nil,
384+ IDToken: idToken,
385+ AccessToken: accessToken,
386+ RefreshToken: refreshToken,
387+ },
388+ EmailClaim: "email",
389+ GroupsClaim: "groups",
390+ ProfileJSON: map[string]interface{}{
391+ "email": "new@thing.com",
392+ "groups": "singleton",
393+ },
394+ ExpectedError: nil,
395+ ExpectedSession: &sessions.SessionState{
396+ User: "already",
397+ Email: "already@populated.com",
398+ Groups: []string{"singleton"},
399+ IDToken: idToken,
400+ AccessToken: accessToken,
401+ RefreshToken: refreshToken,
402+ },
403+ },
404+ "Missing Groups in both Claims and Profile URL": {
405+ ExistingSession: &sessions.SessionState{
406+ User: "already",
407+ Email: "already@populated.com",
408+ IDToken: idToken,
409+ AccessToken: accessToken,
410+ RefreshToken: refreshToken,
411+ },
412+ EmailClaim: "email",
413+ GroupsClaim: "groups",
414+ ProfileJSON: map[string]interface{}{
415+ "email": "new@thing.com",
416+ },
417+ ExpectedError: nil,
418+ ExpectedSession: &sessions.SessionState{
419+ User: "already",
420+ Email: "already@populated.com",
421+ IDToken: idToken,
422+ AccessToken: accessToken,
423+ RefreshToken: refreshToken,
424+ },
425+ },
426+ }
427+ for testName, tc := range testCases {
428+ t.Run(testName, func(t *testing.T) {
429+ jsonResp, err := json.Marshal(tc.ProfileJSON)
430+ assert.NoError(t, err)
431+
432+ server, provider := newTestOIDCSetup(jsonResp)
433+ provider.ProfileURL, err = url.Parse(server.URL)
434+ assert.NoError(t, err)
435+
436+ provider.EmailClaim = tc.EmailClaim
437+ provider.GroupsClaim = tc.GroupsClaim
438+ defer server.Close()
439+
440+ err = provider.EnrichSession(context.Background(), tc.ExistingSession)
441+ assert.Equal(t, tc.ExpectedError, err)
442+ assert.Equal(t, *tc.ExpectedSession, *tc.ExistingSession)
443+ })
444+ }
445+}
446+
236447func TestOIDCProviderRefreshSessionIfNeededWithoutIdToken(t *testing.T) {
237448
238449 idToken, _ := newSignedTestIDToken(defaultIDToken)
@@ -243,7 +454,7 @@ func TestOIDCProviderRefreshSessionIfNeededWithoutIdToken(t *testing.T) {
243454 RefreshToken: refreshToken,
244455 })
245456
246− server, provider := newTestSetup(body)
457+ server, provider := newTestOIDCSetup(body)
247458 defer server.Close()
248459
249460 existingSession := &sessions.SessionState{
@@ -277,7 +488,7 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
277488 IDToken: idToken,
278489 })
279490
280− server, provider := newTestSetup(body)
491+ server, provider := newTestOIDCSetup(body)
281492 defer server.Close()
282493
283494 existingSession := &sessions.SessionState{
@@ -300,48 +511,45 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
300511}
301512
302513func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
303− const profileURLEmail = "janed@me.com"
304−
305514 testCases := map[string]struct {
306515 IDToken idTokenClaims
307516 GroupsClaim string
308517 ExpectedUser string
309518 ExpectedEmail string
310− ExpectedGroups interface{}
519+ ExpectedGroups []string
311520 }{
312521 "Default IDToken": {
313522 IDToken: defaultIDToken,
314523 GroupsClaim: "groups",
315− ExpectedUser: defaultIDToken.Subject,
316− ExpectedEmail: defaultIDToken.Email,
524+ ExpectedUser: "123456789",
525+ ExpectedEmail: "janed@me.com",
317526 ExpectedGroups: []string{"test:a", "test:b"},
318527 },
319528 "Minimal IDToken with no email claim": {
320529 IDToken: minimalIDToken,
321530 GroupsClaim: "groups",
322− ExpectedUser: minimalIDToken.Subject,
323− ExpectedEmail: minimalIDToken.Subject,
324− ExpectedGroups: []string{},
531+ ExpectedUser: "123456789",
532+ ExpectedEmail: "123456789",
533+ ExpectedGroups: nil,
325534 },
326535 "Custom Groups Claim": {
327536 IDToken: defaultIDToken,
328− GroupsClaim: "other_groups",
329− ExpectedUser: defaultIDToken.Subject,
330− ExpectedEmail: defaultIDToken.Email,
537+ GroupsClaim: "roles",
538+ ExpectedUser: "123456789",
539+ ExpectedEmail: "janed@me.com",
331540 ExpectedGroups: []string{"test:c", "test:d"},
332541 },
333− "Custom Groups Claim2": {
334− IDToken: customGroupClaimIDToken,
542+ "Complex Groups Claim": {
543+ IDToken: complexGroupsIDToken,
335544 GroupsClaim: "groups",
336− ExpectedUser: customGroupClaimIDToken.Subject,
337− ExpectedEmail: customGroupClaimIDToken.Email,
545+ ExpectedUser: "123456789",
546+ ExpectedEmail: "complex@claims.com",
338547 ExpectedGroups: []string{"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}"},
339548 },
340549 }
341550 for testName, tc := range testCases {
342551 t.Run(testName, func(t *testing.T) {
343− jsonResp := []byte(fmt.Sprintf(`{"email":"%s"}`, profileURLEmail))
344− server, provider := newTestSetup(jsonResp)
552+ server, provider := newTestOIDCSetup([]byte(`{}`))
345553 provider.GroupsClaim = tc.GroupsClaim
346554 defer server.Close()
347555
@@ -353,75 +561,10 @@ func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
353561
354562 assert.Equal(t, tc.ExpectedUser, ss.User)
355563 assert.Equal(t, tc.ExpectedEmail, ss.Email)
564+ assert.Equal(t, tc.ExpectedGroups, ss.Groups)
356565 assert.Equal(t, rawIDToken, ss.IDToken)
357566 assert.Equal(t, rawIDToken, ss.AccessToken)
358− assert.Equal(t, tc.ExpectedGroups, ss.Groups)
359567 assert.Equal(t, "", ss.RefreshToken)
360568 })
361569 }
362570}
363−
364−func TestOIDCProvider_findVerifiedIdToken(t *testing.T) {
365−
366− server, provider := newTestSetup([]byte(""))
367−
368− defer server.Close()
369−
370− token := newOauth2Token()
371− signedIDToken, _ := newSignedTestIDToken(defaultIDToken)
372− tokenWithIDToken := token.WithExtra(map[string]interface{}{
373− "id_token": signedIDToken,
374− })
375−
376− verifiedIDToken, err := provider.findVerifiedIDToken(context.Background(), tokenWithIDToken)
377− assert.Equal(t, true, err == nil)
378− if verifiedIDToken == nil {
379− t.Fatal("verifiedIDToken is nil")
380− }
381− assert.Equal(t, defaultIDToken.Issuer, verifiedIDToken.Issuer)
382− assert.Equal(t, defaultIDToken.Subject, verifiedIDToken.Subject)
383−
384− // When the validation fails the response should be nil
385− defaultIDToken.Id = "this-id-fails-validation"
386− signedIDToken, _ = newSignedTestIDToken(defaultIDToken)
387− tokenWithIDToken = token.WithExtra(map[string]interface{}{
388− "id_token": signedIDToken,
389− })
390−
391− verifiedIDToken, err = provider.findVerifiedIDToken(context.Background(), tokenWithIDToken)
392− assert.Equal(t, errors.New("failed to verify signature: the validation failed for subject [123456789]"), err)
393− assert.Equal(t, true, verifiedIDToken == nil)
394−
395− // When there is no id token in the oauth token
396− verifiedIDToken, err = provider.findVerifiedIDToken(context.Background(), newOauth2Token())
397− assert.Equal(t, nil, err)
398− assert.Equal(t, true, verifiedIDToken == nil)
399−}
400−
401−func Test_formatGroup(t *testing.T) {
402− testCases := map[string]struct {
403− RawGroup interface{}
404− ExpectedFormattedGroupValue string
405− }{
406− "String Group": {
407− RawGroup: "group",
408− ExpectedFormattedGroupValue: "group",
409− },
410− "Map Group": {
411− RawGroup: map[string]string{"id": "1", "name": "Test"},
412− ExpectedFormattedGroupValue: "{\"id\":\"1\",\"name\":\"Test\"}",
413− },
414− "List Group": {
415− RawGroup: []string{"First", "Second"},
416− ExpectedFormattedGroupValue: "[\"First\",\"Second\"]",
417− },
418− }
419−
420− for testName, tc := range testCases {
421− t.Run(testName, func(t *testing.T) {
422− formattedGroup, err := formatGroup(tc.RawGroup)
423− assert.Nil(t, err)
424− assert.Equal(t, tc.ExpectedFormattedGroupValue, formattedGroup)
425− })
426− }
427−}