chore: upgrade to oidc v2 release (#5437)

* chore: upgrade to oidc v2 release

* fix tests

* fix build errors after rebase

* pin oidc v2.1.0

* pin oidc v2.1.1 (include bugfix)

* pin oidc v2.1.2 (include bugfix)

* pin oidc v2.2.1 (bugfix)

include fix zitadel/oidc#349

* fix: refresh token handling

* simplify cognitive complexity

* fix: handle error

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann
2023-03-28 14:28:56 +03:00
committed by GitHub
parent 542271b467
commit 25c3c17986
25 changed files with 362 additions and 249 deletions

View File

@@ -30,7 +30,7 @@ func TestSession_FetchUser(t *testing.T) {
options []ProviderOptions
authURL string
code string
tokens *oidc.Tokens
tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
type want struct {
err func(error) bool
@@ -87,7 +87,7 @@ func TestSession_FetchUser(t *testing.T) {
Reply(http.StatusInternalServerError)
},
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -125,7 +125,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -189,7 +189,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,

View File

@@ -27,7 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
httpMock func()
authURL string
code string
tokens *oidc.Tokens
tokens *oidc.Tokens[*oidc.IDTokenClaims]
scopes []string
options []oauth.ProviderOpts
}
@@ -93,7 +93,7 @@ func TestSession_FetchUser(t *testing.T) {
Reply(http.StatusInternalServerError)
},
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -122,7 +122,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,

View File

@@ -27,7 +27,7 @@ func TestProvider_FetchUser(t *testing.T) {
httpMock func()
authURL string
code string
tokens *openid.Tokens
tokens *openid.Tokens[*openid.IDTokenClaims]
options []oidc.ProviderOpts
}
type want struct {
@@ -85,7 +85,7 @@ func TestProvider_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://gitlab.com/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &openid.Tokens{
tokens: &openid.Tokens[*openid.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: openid.BearerToken,
@@ -122,7 +122,7 @@ func TestProvider_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://gitlab.com/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &openid.Tokens{
tokens: &openid.Tokens[*openid.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: openid.BearerToken,
@@ -200,18 +200,26 @@ func TestProvider_FetchUser(t *testing.T) {
}
}
func userinfo() openid.UserInfoSetter {
info := openid.NewUserInfo()
info.SetSubject("sub")
info.SetGivenName("firstname")
info.SetFamilyName("lastname")
info.SetName("firstname lastname")
info.SetNickname("nickname")
info.SetPreferredUsername("username")
info.SetEmail("email", true)
info.SetPhone("phone", true)
info.SetLocale(language.English)
info.SetPicture("picture")
info.SetProfile("profile")
return info
func userinfo() *openid.UserInfo {
return &openid.UserInfo{
Subject: "sub",
UserInfoProfile: openid.UserInfoProfile{
GivenName: "firstname",
FamilyName: "lastname",
Name: "firstname lastname",
Nickname: "nickname",
PreferredUsername: "username",
Locale: openid.NewLocale(language.English),
Picture: "picture",
Profile: "profile",
},
UserInfoEmail: openid.UserInfoEmail{
Email: "email",
EmailVerified: openid.Bool(true),
},
UserInfoPhone: openid.UserInfoPhone{
PhoneNumber: "phone",
PhoneNumberVerified: true,
},
}
}

View File

@@ -30,7 +30,7 @@ func New(clientID, clientSecret, redirectURI string, scopes []string, opts ...oi
}, nil
}
var userMapper = func(info openid.UserInfo) idp.User {
var userMapper = func(info *openid.UserInfo) idp.User {
return &User{oidc.DefaultMapper(info)}
}

View File

@@ -27,7 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
httpMock func()
authURL string
code string
tokens *openid.Tokens
tokens *openid.Tokens[*openid.IDTokenClaims]
}
type want struct {
err error
@@ -85,7 +85,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://accounts.google.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &openid.Tokens{
tokens: &openid.Tokens[*openid.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: openid.BearerToken,
@@ -122,7 +122,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://accounts.google.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &openid.Tokens{
tokens: &openid.Tokens[*openid.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: openid.BearerToken,
@@ -201,15 +201,22 @@ func TestSession_FetchUser(t *testing.T) {
}
}
func userinfo() openid.UserInfoSetter {
info := openid.NewUserInfo()
info.SetSubject("sub")
info.SetGivenName("firstname")
info.SetFamilyName("lastname")
info.SetName("firstname lastname")
info.SetEmail("email", true)
info.SetLocale(language.English)
info.SetPicture("picture")
info.AppendClaims("hd", "hosted domain")
return info
func userinfo() *openid.UserInfo {
return &openid.UserInfo{
Subject: "sub",
UserInfoProfile: openid.UserInfoProfile{
GivenName: "firstname",
FamilyName: "lastname",
Name: "firstname lastname",
Locale: openid.NewLocale(language.English),
Picture: "picture",
},
UserInfoEmail: openid.UserInfoEmail{
Email: "email",
EmailVerified: openid.Bool(true),
},
Claims: map[string]any{
"hd": "hosted domain",
},
}
}

View File

@@ -27,7 +27,7 @@ var (
type Session struct {
*Provider
AuthURL string
Tokens *oidc.Tokens
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
// GetAuthURL implements the [idp.Session] interface
@@ -48,12 +48,12 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
return &User{s.Tokens.IDTokenClaims}, nil
}
func (s *Session) validateToken(ctx context.Context, token string) (oidc.IDTokenClaims, error) {
func (s *Session) validateToken(ctx context.Context, token string) (*oidc.IDTokenClaims, error) {
logging.Debug("begin token validation")
// TODO: be able to specify them in the template: https://github.com/zitadel/zitadel/issues/5322
offset := 3 * time.Second
maxAge := time.Hour
claims := oidc.EmptyIDTokenClaims()
claims := new(oidc.IDTokenClaims)
payload, err := oidc.ParseToken(token, claims)
if err != nil {
return nil, fmt.Errorf("%w: malformed jwt payload: %v", ErrInvalidToken, err)
@@ -84,45 +84,57 @@ func (s *Session) validateToken(ctx context.Context, token string) (oidc.IDToken
}
type User struct {
oidc.IDTokenClaims
*oidc.IDTokenClaims
}
func (u *User) GetID() string {
return u.IDTokenClaims.GetSubject()
return u.Subject
}
func (u *User) GetFirstName() string {
return u.IDTokenClaims.GetGivenName()
return u.GivenName
}
func (u *User) GetLastName() string {
return u.IDTokenClaims.GetFamilyName()
return u.FamilyName
}
func (u *User) GetDisplayName() string {
return u.IDTokenClaims.GetName()
return u.Name
}
func (u *User) GetNickname() string {
return u.IDTokenClaims.GetNickname()
return u.Nickname
}
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.IDTokenClaims.GetPhoneNumber())
}
func (u *User) IsPhoneVerified() bool {
return u.IDTokenClaims.IsPhoneNumberVerified()
}
func (u *User) GetPreferredLanguage() language.Tag {
return u.IDTokenClaims.GetLocale()
}
func (u *User) GetAvatarURL() string {
return u.IDTokenClaims.GetPicture()
func (u *User) GetPreferredUsername() string {
return u.PreferredUsername
}
func (u *User) GetEmail() domain.EmailAddress {
return domain.EmailAddress(u.IDTokenClaims.GetEmail())
return domain.EmailAddress(u.Email)
}
func (u *User) IsEmailVerified() bool {
return bool(u.EmailVerified)
}
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.IDTokenClaims.PhoneNumber)
}
func (u *User) IsPhoneVerified() bool {
return u.PhoneNumberVerified
}
func (u *User) GetPreferredLanguage() language.Tag {
return u.Locale.Tag()
}
func (u *User) GetAvatarURL() string {
return u.Picture
}
func (u *User) GetProfile() string {
return u.Profile
}

View File

@@ -30,7 +30,7 @@ func TestSession_FetchUser(t *testing.T) {
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
httpMock func(issuer string)
authURL string
tokens *oidc.Tokens
tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
type want struct {
err func(error) bool
@@ -93,7 +93,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(keys(t))
},
authURL: "https://auth.com/jwt?authRequestID=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{},
IDToken: "invalidToken",
},
@@ -121,26 +121,32 @@ func TestSession_FetchUser(t *testing.T) {
JSON(keys(t))
},
authURL: "https://auth.com/jwt?authRequestID=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{},
IDToken: idToken(t, "https://jwt.com"),
IDTokenClaims: func() oidc.IDTokenClaims {
claims := oidc.EmptyIDTokenClaims()
userinfo := oidc.NewUserInfo()
userinfo.SetSubject("sub")
userinfo.SetPicture("picture")
userinfo.SetName("firstname lastname")
userinfo.SetEmail("email", true)
userinfo.SetGivenName("firstname")
userinfo.SetFamilyName("lastname")
userinfo.SetNickname("nickname")
userinfo.SetPreferredUsername("username")
userinfo.SetProfile("profile")
userinfo.SetPhone("phone", true)
userinfo.SetLocale(language.English)
claims.SetUserinfo(userinfo)
return claims
}(),
IDTokenClaims: &oidc.IDTokenClaims{
TokenClaims: oidc.TokenClaims{
Subject: "sub",
},
UserInfoProfile: oidc.UserInfoProfile{
Picture: "picture",
Name: "firstname lastname",
GivenName: "firstname",
FamilyName: "lastname",
Nickname: "nickname",
PreferredUsername: "username",
Profile: "profile",
Locale: oidc.NewLocale(language.English),
},
UserInfoEmail: oidc.UserInfoEmail{
Email: "email",
EmailVerified: oidc.Bool(true),
},
UserInfoPhone: oidc.UserInfoPhone{
PhoneNumber: "phone",
PhoneNumberVerified: true,
},
},
},
},
want: want{
@@ -219,19 +225,25 @@ func idToken(t *testing.T, issuer string) string {
"clientID",
0,
)
info := oidc.NewUserInfo()
info.SetSubject("sub")
info.SetGivenName("firstname")
info.SetFamilyName("lastname")
info.SetName("firstname lastname")
info.SetNickname("nickname")
info.SetPreferredUsername("username")
info.SetEmail("email", true)
info.SetPhone("phone", true)
info.SetLocale(language.English)
info.SetPicture("picture")
info.SetProfile("profile")
claims.SetUserinfo(info)
claims.UserInfoProfile = oidc.UserInfoProfile{
GivenName: "firstname",
FamilyName: "lastname",
Name: "firstname lastname",
Nickname: "nickname",
PreferredUsername: "username",
Locale: oidc.NewLocale(language.English),
Picture: "picture",
Profile: "profile",
}
claims.UserInfoEmail = oidc.UserInfoEmail{
Email: "email",
EmailVerified: oidc.Bool(true),
}
claims.UserInfoPhone = oidc.UserInfoPhone{
PhoneNumber: "phone",
PhoneNumberVerified: true,
}
privateKey, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAs38btwb3c7r0tMaQpGvBmY+mPwMU/LpfuPoC0k2t4RsKp0fv
40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuyrFALIj3Ff1UcKIk0hOH5DDsfh7/q

View File

@@ -20,7 +20,7 @@ var _ idp.Session = (*Session)(nil)
type Session struct {
AuthURL string
Code string
Tokens *oidc.Tokens
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
Provider *Provider
}
@@ -55,7 +55,7 @@ func (s *Session) authorize(ctx context.Context) (err error) {
if s.Code == "" {
return ErrCodeMissing
}
s.Tokens, err = rp.CodeExchange(ctx, s.Code, s.Provider.RelyingParty)
s.Tokens, err = rp.CodeExchange[*oidc.IDTokenClaims](ctx, s.Code, s.Provider.RelyingParty)
if err != nil {
return err
}

View File

@@ -26,7 +26,7 @@ func TestProvider_FetchUser(t *testing.T) {
userMapper func() idp.User
authURL string
code string
tokens *oidc.Tokens
tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
type want struct {
err func(error) bool
@@ -97,7 +97,7 @@ func TestProvider_FetchUser(t *testing.T) {
return NewUserMapper("userID")
},
authURL: "https://oauth2.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -137,7 +137,7 @@ func TestProvider_FetchUser(t *testing.T) {
return NewUserMapper("userID")
},
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=user&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,

View File

@@ -21,7 +21,7 @@ type Provider struct {
isAutoCreation bool
isAutoUpdate bool
useIDToken bool
userInfoMapper func(info oidc.UserInfo) idp.User
userInfoMapper func(info *oidc.UserInfo) idp.User
authOptions []rp.AuthURLOpt
}
@@ -77,9 +77,9 @@ func WithSelectAccount() ProviderOpts {
}
}
type UserInfoMapper func(info oidc.UserInfo) idp.User
type UserInfoMapper func(info *oidc.UserInfo) idp.User
var DefaultMapper UserInfoMapper = func(info oidc.UserInfo) idp.User {
var DefaultMapper UserInfoMapper = func(info *oidc.UserInfo) idp.User {
return NewUser(info)
}

View File

@@ -21,7 +21,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientSecret string
redirectURI string
scopes []string
userMapper func(info oidc.UserInfo) idp.User
userMapper func(info *oidc.UserInfo) idp.User
httpMock func(issuer string)
opts []ProviderOpts
}
@@ -82,7 +82,7 @@ func TestProvider_Options(t *testing.T) {
clientSecret string
redirectURI string
scopes []string
userMapper func(info oidc.UserInfo) idp.User
userMapper func(info *oidc.UserInfo) idp.User
opts []ProviderOpts
httpMock func(issuer string)
}

View File

@@ -21,7 +21,7 @@ type Session struct {
Provider *Provider
AuthURL string
Code string
Tokens *oidc.Tokens
Tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
// GetAuthURL implements the [idp.Session] interface.
@@ -48,7 +48,7 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
return nil, err
}
if s.Provider.useIDToken {
info = s.Tokens.IDTokenClaims
info = s.Tokens.IDTokenClaims.GetUserInfo()
}
u := s.Provider.userInfoMapper(info)
return u, nil
@@ -58,50 +58,66 @@ func (s *Session) authorize(ctx context.Context) (err error) {
if s.Code == "" {
return ErrCodeMissing
}
s.Tokens, err = rp.CodeExchange(ctx, s.Code, s.Provider.RelyingParty)
s.Tokens, err = rp.CodeExchange[*oidc.IDTokenClaims](ctx, s.Code, s.Provider.RelyingParty)
return err
}
func NewUser(info oidc.UserInfo) *User {
func NewUser(info *oidc.UserInfo) *User {
return &User{UserInfo: info}
}
type User struct {
oidc.UserInfo
*oidc.UserInfo
}
func (u *User) GetID() string {
return u.GetSubject()
return u.Subject
}
func (u *User) GetFirstName() string {
return u.GetGivenName()
return u.GivenName
}
func (u *User) GetLastName() string {
return u.GetFamilyName()
return u.FamilyName
}
func (u *User) GetDisplayName() string {
return u.GetName()
return u.Name
}
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.GetPhoneNumber())
func (u *User) GetNickname() string {
return u.Nickname
}
func (u *User) IsPhoneVerified() bool {
return u.IsPhoneNumberVerified()
}
func (u *User) GetPreferredLanguage() language.Tag {
return u.GetLocale()
}
func (u *User) GetAvatarURL() string {
return u.GetPicture()
func (u *User) GetPreferredUsername() string {
return u.PreferredUsername
}
func (u *User) GetEmail() domain.EmailAddress {
return domain.EmailAddress(u.UserInfo.GetEmail())
return domain.EmailAddress(u.UserInfo.Email)
}
func (u *User) IsEmailVerified() bool {
return bool(u.EmailVerified)
}
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.PhoneNumber)
}
func (u *User) IsPhoneVerified() bool {
return u.PhoneNumberVerified
}
func (u *User) GetPreferredLanguage() language.Tag {
return u.Locale.Tag()
}
func (u *User) GetAvatarURL() string {
return u.Picture
}
func (u *User) GetProfile() string {
return u.Profile
}

View File

@@ -29,11 +29,11 @@ func TestSession_FetchUser(t *testing.T) {
clientSecret string
redirectURI string
scopes []string
userMapper func(oidc.UserInfo) idp.User
userMapper func(*oidc.UserInfo) idp.User
httpMock func(issuer string)
authURL string
code string
tokens *oidc.Tokens
tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
type want struct {
err error
@@ -114,7 +114,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -163,7 +163,7 @@ func TestSession_FetchUser(t *testing.T) {
JSON(userinfo())
},
authURL: "https://issuer.com/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
tokens: &oidc.Tokens{
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
TokenType: oidc.BearerToken,
@@ -294,20 +294,28 @@ func TestSession_FetchUser(t *testing.T) {
}
}
func userinfo() oidc.UserInfoSetter {
info := oidc.NewUserInfo()
info.SetSubject("sub")
info.SetGivenName("firstname")
info.SetFamilyName("lastname")
info.SetName("firstname lastname")
info.SetNickname("nickname")
info.SetPreferredUsername("username")
info.SetEmail("email", true)
info.SetPhone("phone", true)
info.SetLocale(language.English)
info.SetPicture("picture")
info.SetProfile("profile")
return info
func userinfo() *oidc.UserInfo {
return &oidc.UserInfo{
Subject: "sub",
UserInfoProfile: oidc.UserInfoProfile{
GivenName: "firstname",
FamilyName: "lastname",
Name: "firstname lastname",
Nickname: "nickname",
PreferredUsername: "username",
Locale: oidc.NewLocale(language.English),
Picture: "picture",
Profile: "profile",
},
UserInfoEmail: oidc.UserInfoEmail{
Email: "email",
EmailVerified: oidc.Bool(true),
},
UserInfoPhone: oidc.UserInfoPhone{
PhoneNumber: "phone",
PhoneNumberVerified: true,
},
}
}
func tokenResponse(t *testing.T, issuer string) *oidc.AccessTokenResponse {