diff --git a/backend/handler/thirdparty_callback_test.go b/backend/handler/thirdparty_callback_test.go index 556ca5970..cc163468e 100644 --- a/backend/handler/thirdparty_callback_test.go +++ b/backend/handler/thirdparty_callback_test.go @@ -275,7 +275,7 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_Apple() { s.T().Skip("skipping test in short mode.") } - fakeIdToken := s.setUpAppleIdToken("apple_abcde", "fakeClientID", "test-apple-signup@example.com", true) + fakeIdToken := s.setUpAppleIdToken("apple_abcde", "fakeClientID", "test-apple-signup@example.com", true, false) gock.New(thirdparty.AppleTokenEndpoint). Post("/"). Reply(200). @@ -334,7 +334,66 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Apple() { err := s.LoadFixtures("../test/fixtures/thirdparty") s.NoError(err) - fakeIdToken := s.setUpAppleIdToken("apple_abcde", "fakeClientID", "test-with-apple-identity@example.com", true) + fakeIdToken := s.setUpAppleIdToken("apple_abcde", "fakeClientID", "test-with-apple-identity@example.com", true, false) + gock.New(thirdparty.AppleTokenEndpoint). + Post("/"). + Reply(200). + JSON(map[string]string{"access_token": "fakeAccessToken", "id_token": fakeIdToken}) + + fakeJwkSet := s.setUpFakeJwkSet() + gock.New(thirdparty.AppleKeysEndpoint). + Get("/"). + Reply(200). + JSON(fakeJwkSet) + + cfg := s.setUpConfig([]string{"apple"}, []string{"https://example.com"}) + + state, err := thirdparty.GenerateState(cfg, "apple", "https://example.com") + s.NoError(err) + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil) + req.AddCookie(&http.Cookie{ + Name: utils.HankoThirdpartyStateCookie, + Value: string(state), + }) + + c, rec := s.setUpContext(req) + handler := s.setUpHandler(cfg) + + if s.NoError(handler.Callback(c)) { + s.Equal(http.StatusTemporaryRedirect, rec.Code) + + s.assertLocationHeaderHasToken(rec) + s.assertStateCookieRemoved(rec) + + email, err := s.Storage.GetEmailPersister().FindByAddress("test-with-apple-identity@example.com") + s.NoError(err) + s.NotNil(email) + s.True(email.IsPrimary()) + + user, err := s.Storage.GetUserPersister().Get(*email.UserID) + s.NoError(err) + s.NotNil(user) + + identity := email.Identities.GetIdentity("apple", "apple_abcde") + s.NotNil(identity) + + logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), email.Address, "", "") + s.NoError(lerr) + s.Len(logs, 1) + } +} + +func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Apple_WithBooleanEmailVerifiedClaim() { + defer gock.Off() + if testing.Short() { + s.T().Skip("skipping test in short mode.") + } + + err := s.LoadFixtures("../test/fixtures/thirdparty") + s.NoError(err) + + fakeIdToken := s.setUpAppleIdToken("apple_abcde", "fakeClientID", "test-with-apple-identity@example.com", true, true) gock.New(thirdparty.AppleTokenEndpoint). Post("/"). Reply(200). diff --git a/backend/handler/thirdparty_test.go b/backend/handler/thirdparty_test.go index 812d79ad9..409530842 100644 --- a/backend/handler/thirdparty_test.go +++ b/backend/handler/thirdparty_test.go @@ -143,15 +143,19 @@ func (s *thirdPartySuite) setUpFakeJwkSet() jwk2.Set { return keySet } -func (s *thirdPartySuite) setUpAppleIdToken(sub, aud, email string, emailVerified bool) string { +func (s *thirdPartySuite) setUpAppleIdToken(sub, aud, email string, emailVerified bool, emailVerifiedTypeBool bool) string { s.T().Helper() token := jwt.New() _ = token.Set(jwt.SubjectKey, sub) _ = token.Set(jwt.IssuedAtKey, time.Now().UTC()) _ = token.Set(jwt.IssuerKey, "https://appleid.apple.com") _ = token.Set(jwt.AudienceKey, aud) - _ = token.Set("email_verified", strconv.FormatBool(emailVerified)) _ = token.Set("email", email) + if emailVerifiedTypeBool { + _ = token.Set("email_verified", emailVerified) + } else { + _ = token.Set("email_verified", strconv.FormatBool(emailVerified)) + } generator := test.JwkManager{} signingKey, err := generator.GetSigningKey() diff --git a/backend/thirdparty/provider_apple.go b/backend/thirdparty/provider_apple.go index 8cf59a3f0..c83b90c5b 100644 --- a/backend/thirdparty/provider_apple.go +++ b/backend/thirdparty/provider_apple.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" + zeroLogger "github.com/rs/zerolog/log" "github.com/teamhanko/hanko/backend/config" "golang.org/x/oauth2" "net/url" @@ -89,13 +90,19 @@ func (a appleProvider) GetUserData(token *oauth2.Token) (*UserData, error) { return nil, errors.New("email claim expected to be of type string") } - var emailVerified bool - if emailVerifiedRaw, ok := parsedIDToken.PrivateClaims()["email_verified"].(string); !ok { - return nil, errors.New("email_verified claim expected to be of type string") - } else { - emailVerified, err = strconv.ParseBool(emailVerifiedRaw) - if err != nil { - return nil, errors.New("cannot parse email_verified claim as bool") + var emailVerified = false + emailVerifiedRaw, found := parsedIDToken.PrivateClaims()["email_verified"] + if found { + switch v := emailVerifiedRaw.(type) { + case string: + emailVerified, err = strconv.ParseBool(v) + if err != nil { + zeroLogger.Warn().Err(err).Msgf("could not parse 'email_verified' claim as bool") + } + case bool: + emailVerified = v + default: + zeroLogger.Warn().Msgf("'email_verified' claim is neither of type 'string' or 'bool'") } }