Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various cherry picks from v2 onto v3 #286

Merged
merged 5 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,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&#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 {
Expand Down Expand Up @@ -241,20 +251,27 @@ 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)
}
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
Expand Down Expand Up @@ -376,6 +393,20 @@ type claimSource struct {
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 {
Expand Down
59 changes: 57 additions & 2 deletions oidc/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}`
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -412,6 +419,50 @@ 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.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 {
Expand All @@ -434,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)
}
})
}

Expand Down
2 changes: 1 addition & 1 deletion oidc/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down