mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-01 11:52:15 +00:00
fix: add email query to github idp if email empty (#10705)
# Which Problems Are Solved In the integration with Github, private emails are not returned with the userinfo. # How the Problems Are Solved If the scope `user:email` is set in the Github IDP and the email is not included in the userinfo, a request to Github's API is executed to query the email of the user. # Additional Changes Additional tests. # Additional Context Closes #10098 --------- Co-authored-by: Marco A. <[email protected]> Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
co-authored by
Marco A.
Livio Spring
parent
695db96745
commit
a7e1bfb4a3
@@ -21,15 +21,16 @@ import (
|
||||
|
||||
func TestSession_FetchUser(t *testing.T) {
|
||||
type fields struct {
|
||||
clientID string
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
httpMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
scopes []string
|
||||
options []oauth.ProviderOpts
|
||||
clientID string
|
||||
clientSecret string
|
||||
redirectURI string
|
||||
httpMock func()
|
||||
httpEmailMock func()
|
||||
authURL string
|
||||
code string
|
||||
tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
||||
scopes []string
|
||||
options []oauth.ProviderOpts
|
||||
}
|
||||
type args struct {
|
||||
session idp.Session
|
||||
@@ -73,7 +74,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
tokens: nil,
|
||||
},
|
||||
args: args{
|
||||
&oauth.Session{},
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
err: func(err error) bool {
|
||||
@@ -101,7 +102,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
&oauth.Session{},
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
err: func(err error) bool {
|
||||
@@ -130,7 +131,7 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
&oauth.Session{},
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
@@ -159,44 +160,277 @@ func TestSession_FetchUser(t *testing.T) {
|
||||
profile: "htmlURL",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful fetch, no verified email",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user").
|
||||
Reply(200).
|
||||
JSON(userinfoWithoutEmail())
|
||||
},
|
||||
httpEmailMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user/emails").
|
||||
Reply(200).
|
||||
JSON(emailList())
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
Login: "login",
|
||||
ID: 1,
|
||||
AvatarUrl: "avatarURL",
|
||||
GravatarId: "gravatarID",
|
||||
Name: "name",
|
||||
Email: "",
|
||||
HtmlUrl: "htmlURL",
|
||||
CreatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
},
|
||||
id: "1",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "name",
|
||||
nickName: "login",
|
||||
preferredUsername: "login",
|
||||
email: "",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "avatarURL",
|
||||
profile: "htmlURL",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful fetch, no user scope provided",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user").
|
||||
Reply(200).
|
||||
JSON(userinfoWithoutEmail())
|
||||
},
|
||||
httpEmailMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user/email").
|
||||
Reply(200).
|
||||
JSON(emailListVerified())
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
Login: "login",
|
||||
ID: 1,
|
||||
AvatarUrl: "avatarURL",
|
||||
GravatarId: "gravatarID",
|
||||
Name: "name",
|
||||
Email: "",
|
||||
HtmlUrl: "htmlURL",
|
||||
CreatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
},
|
||||
id: "1",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "name",
|
||||
nickName: "login",
|
||||
preferredUsername: "login",
|
||||
email: "",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "avatarURL",
|
||||
profile: "htmlURL",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful fetch, private email",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user").
|
||||
Reply(200).
|
||||
JSON(userinfoWithoutEmail())
|
||||
},
|
||||
httpEmailMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user/email").
|
||||
Reply(200).
|
||||
JSON(emailListVerified())
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
},
|
||||
scopes: []string{"user:email"},
|
||||
},
|
||||
args: args{
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
Login: "login",
|
||||
ID: 1,
|
||||
AvatarUrl: "avatarURL",
|
||||
GravatarId: "gravatarID",
|
||||
Name: "name",
|
||||
Email: "[email protected]",
|
||||
HtmlUrl: "htmlURL",
|
||||
CreatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
},
|
||||
id: "1",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "name",
|
||||
nickName: "login",
|
||||
preferredUsername: "login",
|
||||
email: "[email protected]",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "avatarURL",
|
||||
profile: "htmlURL",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful fetch, unsuccessful email",
|
||||
fields: fields{
|
||||
clientID: "clientID",
|
||||
clientSecret: "clientSecret",
|
||||
redirectURI: "redirectURI",
|
||||
httpMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user").
|
||||
Reply(200).
|
||||
JSON(userinfoWithoutEmail())
|
||||
},
|
||||
httpEmailMock: func() {
|
||||
gock.New("https://api.github.com").
|
||||
Get("/user/email").
|
||||
Reply(400)
|
||||
},
|
||||
authURL: "https://github.com/login/oauth/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&state=testState",
|
||||
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
TokenType: oidc.BearerToken,
|
||||
},
|
||||
},
|
||||
scopes: []string{"user"},
|
||||
},
|
||||
args: args{
|
||||
&Session{},
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
Login: "login",
|
||||
ID: 1,
|
||||
AvatarUrl: "avatarURL",
|
||||
GravatarId: "gravatarID",
|
||||
Name: "name",
|
||||
Email: "",
|
||||
HtmlUrl: "htmlURL",
|
||||
CreatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
},
|
||||
id: "1",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "name",
|
||||
nickName: "login",
|
||||
preferredUsername: "login",
|
||||
email: "",
|
||||
isEmailVerified: true,
|
||||
phone: "",
|
||||
isPhoneVerified: false,
|
||||
preferredLanguage: language.Und,
|
||||
avatarURL: "avatarURL",
|
||||
profile: "htmlURL",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer gock.Off()
|
||||
tt.fields.httpMock()
|
||||
if tt.fields.httpEmailMock != nil {
|
||||
tt.fields.httpEmailMock()
|
||||
}
|
||||
a := assert.New(t)
|
||||
|
||||
provider, err := New(tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
|
||||
require.NoError(t, err)
|
||||
|
||||
session := &oauth.Session{
|
||||
AuthURL: tt.fields.authURL,
|
||||
session := &Session{
|
||||
OAuthSession: &oauth.Session{
|
||||
AuthURL: tt.fields.authURL,
|
||||
Tokens: tt.fields.tokens,
|
||||
Provider: provider.Provider,
|
||||
Code: tt.fields.code,
|
||||
},
|
||||
Code: tt.fields.code,
|
||||
Tokens: tt.fields.tokens,
|
||||
Provider: provider.Provider,
|
||||
Provider: provider,
|
||||
}
|
||||
|
||||
user, err := session.FetchUser(context.Background())
|
||||
if tt.want.err != nil && !tt.want.err(err) {
|
||||
a.Fail("invalid error", err)
|
||||
}
|
||||
if tt.want.err == nil {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.want.user, user)
|
||||
a.Equal(tt.want.id, user.GetID())
|
||||
a.Equal(tt.want.firstName, user.GetFirstName())
|
||||
a.Equal(tt.want.lastName, user.GetLastName())
|
||||
a.Equal(tt.want.displayName, user.GetDisplayName())
|
||||
a.Equal(tt.want.nickName, user.GetNickname())
|
||||
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
|
||||
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
|
||||
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
|
||||
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
|
||||
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
|
||||
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
|
||||
a.Equal(tt.want.avatarURL, user.GetAvatarURL())
|
||||
a.Equal(tt.want.profile, user.GetProfile())
|
||||
if tt.want.err != nil {
|
||||
a.True(tt.want.err(err), "invalid err")
|
||||
return
|
||||
}
|
||||
|
||||
a.NoError(err)
|
||||
a.Equal(tt.want.user, user)
|
||||
a.Equal(tt.want.id, user.GetID())
|
||||
a.Equal(tt.want.firstName, user.GetFirstName())
|
||||
a.Equal(tt.want.lastName, user.GetLastName())
|
||||
a.Equal(tt.want.displayName, user.GetDisplayName())
|
||||
a.Equal(tt.want.nickName, user.GetNickname())
|
||||
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
|
||||
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
|
||||
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
|
||||
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
|
||||
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
|
||||
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
|
||||
a.Equal(tt.want.avatarURL, user.GetAvatarURL())
|
||||
a.Equal(tt.want.profile, user.GetProfile())
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -214,3 +448,38 @@ func userinfo() *User {
|
||||
UpdatedAt: time.Date(2023, 01, 10, 11, 10, 35, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
func userinfoWithoutEmail() *User {
|
||||
userinfo := userinfo()
|
||||
userinfo.Email = ""
|
||||
return userinfo
|
||||
}
|
||||
|
||||
func emailListVerified() []Email {
|
||||
return append(emailList(),
|
||||
Email{
|
||||
Email: "[email protected]",
|
||||
Primary: true,
|
||||
Verified: true,
|
||||
})
|
||||
}
|
||||
|
||||
func emailList() []Email {
|
||||
return []Email{
|
||||
{
|
||||
Email: "[email protected]",
|
||||
Primary: false,
|
||||
Verified: false,
|
||||
},
|
||||
{
|
||||
Email: "[email protected]",
|
||||
Primary: false,
|
||||
Verified: true,
|
||||
},
|
||||
{
|
||||
Email: "[email protected]",
|
||||
Primary: true,
|
||||
Verified: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user