From 2d9279f298035f967975a787c6f4af455da18f01 Mon Sep 17 00:00:00 2001 From: Konrad Holowinski Date: Fri, 26 Jun 2020 15:22:52 +0200 Subject: [PATCH 1/5] userinfo - handling charset in Content-Type header Using mime.ParseMediaType --- oidc/oidc.go | 4 +++- oidc/oidc_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/oidc/oidc.go b/oidc/oidc.go index c9e18e4e..05066574 100644 --- a/oidc/oidc.go +++ b/oidc/oidc.go @@ -241,7 +241,9 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) return nil, fmt.Errorf("%s: %s", resp.Status, body) } - if strings.EqualFold(resp.Header.Get("Content-Type"), "application/jwt") { + ct := resp.Header.Get("Content-Type") + mediaType, _, parseErr := mime.ParseMediaType(ct) + if parseErr == nil && mediaType == "application/jwt" { payload, err := p.remoteKeySet.VerifySignature(ctx, string(body)) if err != nil { return nil, fmt.Errorf("oidc: invalid userinfo jwt signature %v", err) diff --git a/oidc/oidc_test.go b/oidc/oidc_test.go index 3e4b2a32..37bfde9b 100644 --- a/oidc/oidc_test.go +++ b/oidc/oidc_test.go @@ -412,6 +412,21 @@ func TestUserInfoEndpoint(t *testing.T) { claims: []byte(userInfoJSON), }, }, + { + name: "signed jwt userinfo, content-type with charset", + server: testServer{ + contentType: "application/jwt; charset=ISO-8859-1", + // generated with jwt.io based on the private/public key pair + userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzX2FkbWluIjp0cnVlfQ.AP9Y8Md1rjPfuFPTw7hI6kREQe1J0Wb2P5SeVnu_dmAFAyYbG8nbu2Xveb4HOY9wMZbU7UAuSrlvvF_duImlIWei_Ym0ZVrFDATYoMI_MNKwmt4-vM_pm-97zghuPfpXTLYenHgeyPTkHv_SEwhiKzg0Ap7kC3PlAOGeElMO1L1thDZdMd1MqClOEzie00fZwbUGXwkUdDV0_vd173GBACniEQF_9qtgDyxNzh9IMYPNVdRk0bqzBCdQuhTE1AQmWebTrri962uHdWex25KEk_sxOsSW5HIDc0vEF8uBBPUJjaHDPTvwzMh0RuqwT_SqwJvyOHhG0jSz-LYEa5eugQ", + }, + wantUserInfo: UserInfo{ + Subject: "1234567890", + Profile: "Joe Doe", + Email: "joe@doe.com", + EmailVerified: true, + claims: []byte(userInfoJson), + }, + }, } for _, test := range tests { From f598eb511944c628544e7afe9774ebbcc675ad20 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Mon, 20 Jul 2020 09:16:57 -0700 Subject: [PATCH 2/5] oidc: fix Json/JSON spelling --- oidc/oidc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc/oidc_test.go b/oidc/oidc_test.go index 37bfde9b..5ba864be 100644 --- a/oidc/oidc_test.go +++ b/oidc/oidc_test.go @@ -424,7 +424,7 @@ func TestUserInfoEndpoint(t *testing.T) { Profile: "Joe Doe", Email: "joe@doe.com", EmailVerified: true, - claims: []byte(userInfoJson), + claims: []byte(userInfoJSON), }, }, } From fa69e948499f8f510f307c37fd7060d2fcb2d8ac Mon Sep 17 00:00:00 2001 From: Dallan Quass Date: Sat, 22 Aug 2020 20:07:30 -0600 Subject: [PATCH 3/5] add support for AWS Cognito, which returns email_verified as a string instead of a bool --- oidc/oidc.go | 44 +++++++++++++++++++++++++++++++++++++++++--- oidc/oidc_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/oidc/oidc.go b/oidc/oidc.go index 05066574..c4c3a830 100644 --- a/oidc/oidc.go +++ b/oidc/oidc.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "mime" "net/http" + "strconv" "strings" "time" @@ -203,6 +204,16 @@ type UserInfo struct { claims []byte } +type userInfoRaw struct { + Subject string `json:"sub"` + Profile string `json:"profile"` + Email string `json:"email"` + // Handle providers that return email_verified as a string + // https://forums.aws.amazon.com/thread.jspa?messageID=949441󧳁 and + // https://discuss.elastic.co/t/openid-error-after-authenticating-against-aws-cognito/206018/11 + EmailVerified stringAsBool `json:"email_verified"` +} + // Claims unmarshals the raw JSON object claims into the provided object. func (u *UserInfo) Claims(v interface{}) error { if u.claims == nil { @@ -251,12 +262,17 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) body = payload } - var userInfo UserInfo + var userInfo userInfoRaw if err := json.Unmarshal(body, &userInfo); err != nil { return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err) } - userInfo.claims = body - return &userInfo, nil + return &UserInfo{ + Subject: userInfo.Subject, + Profile: userInfo.Profile, + Email: userInfo.Email, + EmailVerified: bool(userInfo.EmailVerified), + claims: body, + }, nil } // IDToken is an OpenID Connect extension that provides a predictable representation @@ -378,6 +394,28 @@ type claimSource struct { AccessToken string `json:"access_token"` } +type stringAsBool bool + +func (sb *stringAsBool) UnmarshalJSON(b []byte) error { + var result bool + err := json.Unmarshal(b, &result) + if err == nil { + *sb = stringAsBool(result) + return nil + } + var s string + err = json.Unmarshal(b, &s) + if err != nil { + return err + } + result, err = strconv.ParseBool(s) + if err != nil { + return err + } + *sb = stringAsBool(result) + return nil +} + type audience []string func (a *audience) UnmarshalJSON(b []byte) error { diff --git a/oidc/oidc_test.go b/oidc/oidc_test.go index 5ba864be..554f3da8 100644 --- a/oidc/oidc_test.go +++ b/oidc/oidc_test.go @@ -331,7 +331,7 @@ func (ts *testServer) run(t *testing.T) string { "use": "sig", "kid": "test", "alg": "RS256", - "n": "luTpO0eGNYC36udr3gvoBxTjF1RxHXBMRcEdY13E_IocCM5GuqFNLbScH3q69O6WSq8a43cVmsdnayw3oHu8GDTZuggnsPG28Ln4FFWehdV306YBPBgS_6C8x6mX9PipoNnIpG2PAGhqw1iL_V0WmmNqdJPl9EirgbbHJh7GIkMxyj9UZiwi19YSFHhDdyJvux1L6hieqjrsFFJdwxk1QOlp9NkkCcVNZarUqUltb5JH82IiMSXYsDeOjjE7DlrFLqdo-zg8QlOtY8pow6gueweMWyY4iVv5IAziOh7128aid0-48-mNLTdZtAG758rtuKHJg9dq0nfOm64qROCNUQ" + "n": "ilhCmTGFjjIPVN7Lfdn_fvpXOlzxa3eWnQGZ_eRa2ibFB1mnqoWxZJ8fkWIVFOQpsn66bIfWjBo_OI3sE6LhhRF8xhsMxlSeRKhpsWg0klYnMBeTWYET69YEAX_rGxy0MCZlFZ5tpr56EVZ-3QLfNiR4hcviqj9F2qE6jopfywsnlulJgyMi3N3kugit_JCNBJ0yz4ndZrMozVOtGqt35HhggUgYROzX6SWHUJdPXSmbAZU-SVLlesQhPfHS8LLq0sACb9OmdcwrpEFdbGCSTUPlHGkN5h6Zy8CS4s_bCdXKkjD20jv37M3GjRQkjE8vyMxFlo_qT8F8VZlSgXYTFw" } ] }` @@ -377,6 +377,13 @@ func TestUserInfoEndpoint(t *testing.T) { "email_verified": true, "is_admin": true }` + userInfoJSONCognitoVariant := `{ + "sub": "1234567890", + "profile": "Joe Doe", + "email": "joe@doe.com", + "email_verified": "true", + "is_admin": true + }` tests := []struct { name string @@ -402,7 +409,7 @@ func TestUserInfoEndpoint(t *testing.T) { server: testServer{ contentType: "application/jwt", // generated with jwt.io based on the private/public key pair - userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzX2FkbWluIjp0cnVlfQ.AP9Y8Md1rjPfuFPTw7hI6kREQe1J0Wb2P5SeVnu_dmAFAyYbG8nbu2Xveb4HOY9wMZbU7UAuSrlvvF_duImlIWei_Ym0ZVrFDATYoMI_MNKwmt4-vM_pm-97zghuPfpXTLYenHgeyPTkHv_SEwhiKzg0Ap7kC3PlAOGeElMO1L1thDZdMd1MqClOEzie00fZwbUGXwkUdDV0_vd173GBACniEQF_9qtgDyxNzh9IMYPNVdRk0bqzBCdQuhTE1AQmWebTrri962uHdWex25KEk_sxOsSW5HIDc0vEF8uBBPUJjaHDPTvwzMh0RuqwT_SqwJvyOHhG0jSz-LYEa5eugQ", + userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzX2FkbWluIjp0cnVlfQ.ejzc2IOLtvYp-2n5w3w4SW3rHNG9pOahnwpQCwuIaj7DvO4SxDIzeJmFPMKTJUc-1zi5T42mS4Gs2r18KWhSkk8kqYermRX0VcGEEsH0r2BG5boeza_EjCoJ5-jBPX5ODWGhu2sZIkZl29IbaVSC8jk8qKnqacchiHNmuv_xXjRsAgUsqYftrEQOxqhpfL5KN2qtgeVTczg3ABqs2-SFeEzcgA1TnA9H3AynCPCVUMFgh7xyS8jxx7DN-1vRHBySz5gNbf8z8MNx_XBLfRxxxMF24rDIE8Z2gf1DEAPr4tT38hD8ugKSE84gC3xHJWFWsRLg-Ll6OQqshs82axS00Q", }, wantUserInfo: UserInfo{ Subject: "1234567890", @@ -417,7 +424,7 @@ func TestUserInfoEndpoint(t *testing.T) { server: testServer{ contentType: "application/jwt; charset=ISO-8859-1", // generated with jwt.io based on the private/public key pair - userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzX2FkbWluIjp0cnVlfQ.AP9Y8Md1rjPfuFPTw7hI6kREQe1J0Wb2P5SeVnu_dmAFAyYbG8nbu2Xveb4HOY9wMZbU7UAuSrlvvF_duImlIWei_Ym0ZVrFDATYoMI_MNKwmt4-vM_pm-97zghuPfpXTLYenHgeyPTkHv_SEwhiKzg0Ap7kC3PlAOGeElMO1L1thDZdMd1MqClOEzie00fZwbUGXwkUdDV0_vd173GBACniEQF_9qtgDyxNzh9IMYPNVdRk0bqzBCdQuhTE1AQmWebTrri962uHdWex25KEk_sxOsSW5HIDc0vEF8uBBPUJjaHDPTvwzMh0RuqwT_SqwJvyOHhG0jSz-LYEa5eugQ", + userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzX2FkbWluIjp0cnVlfQ.ejzc2IOLtvYp-2n5w3w4SW3rHNG9pOahnwpQCwuIaj7DvO4SxDIzeJmFPMKTJUc-1zi5T42mS4Gs2r18KWhSkk8kqYermRX0VcGEEsH0r2BG5boeza_EjCoJ5-jBPX5ODWGhu2sZIkZl29IbaVSC8jk8qKnqacchiHNmuv_xXjRsAgUsqYftrEQOxqhpfL5KN2qtgeVTczg3ABqs2-SFeEzcgA1TnA9H3AynCPCVUMFgh7xyS8jxx7DN-1vRHBySz5gNbf8z8MNx_XBLfRxxxMF24rDIE8Z2gf1DEAPr4tT38hD8ugKSE84gC3xHJWFWsRLg-Ll6OQqshs82axS00Q", }, wantUserInfo: UserInfo{ Subject: "1234567890", @@ -427,6 +434,35 @@ func TestUserInfoEndpoint(t *testing.T) { claims: []byte(userInfoJSON), }, }, + { + name: "basic json userinfo - cognito variant", + server: testServer{ + contentType: "application/json", + userInfo: userInfoJSONCognitoVariant, + }, + wantUserInfo: UserInfo{ + Subject: "1234567890", + Profile: "Joe Doe", + Email: "joe@doe.com", + EmailVerified: true, + claims: []byte(userInfoJSONCognitoVariant), + }, + }, + { + name: "signed jwt userinfo - cognito variant", + server: testServer{ + contentType: "application/jwt", + // generated with jwt.io based on the private/public key pair + userInfo: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicHJvZmlsZSI6IkpvZSBEb2UiLCJlbWFpbCI6ImpvZUBkb2UuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiaXNfYWRtaW4iOnRydWV9.V9j6Q208fnj7E5dhCHnAktqndvelyz6PYxmd2fLzA4ze8N770Tq9KFEE3QSM400GTxiP7tMyvBqnTj2q5Hr6DeRoy0WtLmYlnDfOJCr2qKbrPN0k94Ts9_sXAKEiJSKsTFUBHkrH4NhyWsaBaPamI8ghuqPKJ1LniNuskHUlzBmDDW4mTy15ArsaIno8S4XVn19OoqODIO30axJJxKfxEbsDR3-YW4OD9qn80Wzw0zOsGJ04NJRfO56VSprX0PhqvduOSUuHvm4cxtJIHHvj3AitrQriKZebZpXSs9PXPSPCysiQHyDz0A8y7R-sDgEhJlxe93nVbTU0itBehrbugQ", + }, + wantUserInfo: UserInfo{ + Subject: "1234567890", + Profile: "Joe Doe", + Email: "joe@doe.com", + EmailVerified: true, + claims: []byte(userInfoJSONCognitoVariant), + }, + }, } for _, test := range tests { @@ -449,6 +485,10 @@ func TestUserInfoEndpoint(t *testing.T) { if info.Email != test.wantUserInfo.Email { t.Errorf("expected UserInfo to be %v , got %v", test.wantUserInfo, info) } + + if info.EmailVerified != test.wantUserInfo.EmailVerified { + t.Errorf("expected UserInfo.EmailVerified to be %v , got %v", test.wantUserInfo.EmailVerified, info.EmailVerified) + } }) } From 06e1265e47296bb8a141563b7815d95085288b6b Mon Sep 17 00:00:00 2001 From: dickynovanto1103 Date: Wed, 5 Aug 2020 23:22:55 +0800 Subject: [PATCH 4/5] IDTokenVerifier: fix typo word: `preforms` to `performs` this fix one word typo of the IDTokenVerifier.Verify function's comment Fixes #265 --- oidc/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc/verify.go b/oidc/verify.go index d43f0662..5c4d6582 100644 --- a/oidc/verify.go +++ b/oidc/verify.go @@ -185,7 +185,7 @@ func parseClaim(raw []byte, name string, v interface{}) error { return json.Unmarshal([]byte(val), v) } -// Verify parses a raw ID Token, verifies it's been signed by the provider, preforms +// Verify parses a raw ID Token, verifies it's been signed by the provider, performs // any additional checks depending on the Config, and returns the payload. // // Verify does NOT do nonce validation, which is the callers responsibility. From ea120f20efae593c3155acf416c20406d7a28e98 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Mon, 11 Jan 2021 09:48:06 -0800 Subject: [PATCH 5/5] oidc: simplify JSON parsing logic for boolean --- oidc/oidc.go | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/oidc/oidc.go b/oidc/oidc.go index c4c3a830..9726f13b 100644 --- a/oidc/oidc.go +++ b/oidc/oidc.go @@ -13,7 +13,6 @@ import ( "io/ioutil" "mime" "net/http" - "strconv" "strings" "time" @@ -397,22 +396,14 @@ type claimSource struct { type stringAsBool bool func (sb *stringAsBool) UnmarshalJSON(b []byte) error { - var result bool - err := json.Unmarshal(b, &result) - if err == nil { - *sb = stringAsBool(result) - return nil - } - var s string - err = json.Unmarshal(b, &s) - if err != nil { - return err - } - result, err = strconv.ParseBool(s) - if err != nil { - return err + switch string(b) { + case "true", `"true"`: + *sb = stringAsBool(true) + case "false", `"false"`: + *sb = stringAsBool(false) + default: + return errors.New("invalid value for boolean") } - *sb = stringAsBool(result) return nil }