Merge pull request #286 from ericchiang/v3-cherry-picks
Various cherry picks from v2 onto v3
diff --git a/oidc/oidc.go b/oidc/oidc.go
index c9e18e4..9726f13 100644
--- a/oidc/oidc.go
+++ b/oidc/oidc.go
@@ -203,6 +203,16 @@
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 {
@@ -241,7 +251,9 @@
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)
@@ -249,12 +261,17 @@
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
@@ -376,6 +393,20 @@
AccessToken string `json:"access_token"`
}
+type stringAsBool bool
+
+func (sb *stringAsBool) UnmarshalJSON(b []byte) error {
+ switch string(b) {
+ case "true", `"true"`:
+ *sb = stringAsBool(true)
+ case "false", `"false"`:
+ *sb = stringAsBool(false)
+ default:
+ return errors.New("invalid value for boolean")
+ }
+ 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 3e4b2a3..554f3da 100644
--- a/oidc/oidc_test.go
+++ b/oidc/oidc_test.go
@@ -331,7 +331,7 @@
"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 @@
"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 @@
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",
@@ -412,6 +419,50 @@
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.ejzc2IOLtvYp-2n5w3w4SW3rHNG9pOahnwpQCwuIaj7DvO4SxDIzeJmFPMKTJUc-1zi5T42mS4Gs2r18KWhSkk8kqYermRX0VcGEEsH0r2BG5boeza_EjCoJ5-jBPX5ODWGhu2sZIkZl29IbaVSC8jk8qKnqacchiHNmuv_xXjRsAgUsqYftrEQOxqhpfL5KN2qtgeVTczg3ABqs2-SFeEzcgA1TnA9H3AynCPCVUMFgh7xyS8jxx7DN-1vRHBySz5gNbf8z8MNx_XBLfRxxxMF24rDIE8Z2gf1DEAPr4tT38hD8ugKSE84gC3xHJWFWsRLg-Ll6OQqshs82axS00Q",
+ },
+ wantUserInfo: UserInfo{
+ Subject: "1234567890",
+ Profile: "Joe Doe",
+ Email: "joe@doe.com",
+ EmailVerified: true,
+ 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 {
@@ -434,6 +485,10 @@
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)
+ }
})
}
diff --git a/oidc/verify.go b/oidc/verify.go
index d43f066..5c4d658 100644
--- a/oidc/verify.go
+++ b/oidc/verify.go
@@ -185,7 +185,7 @@
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.