mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-01 20:02: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
@@ -0,0 +1,113 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
)
|
||||
|
||||
var _ idp.Session = (*Session)(nil)
|
||||
|
||||
// Session extends the [oauth.Session] to be able to handle private email addresses.
|
||||
type Session struct {
|
||||
*Provider
|
||||
Code string
|
||||
IDPArguments map[string]any
|
||||
|
||||
OAuthSession *oauth.Session
|
||||
}
|
||||
|
||||
func NewSession(provider *Provider, code string, idpArguments map[string]any) *Session {
|
||||
return &Session{Provider: provider, Code: code, IDPArguments: idpArguments}
|
||||
}
|
||||
|
||||
// GetAuth implements the [idp.Provider] interface by calling the wrapped [oauth.Session].
|
||||
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||
return s.oauth().GetAuth(ctx)
|
||||
}
|
||||
|
||||
// PersistentParameters implements the [idp.Session] interface by calling the wrapped [oauth.Session].
|
||||
func (s *Session) PersistentParameters() map[string]any {
|
||||
return s.oauth().PersistentParameters()
|
||||
}
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
// It will execute an OAuth 2.0 code exchange if needed to retrieve the access token,
|
||||
// call the specified userEndpoint and map the received information into an [idp.User].
|
||||
// In case of a specific TenantID as [TenantType] it will additionally extract the id_token and validate it.
|
||||
func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
user, err = s.oauth().FetchUser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.GetEmail() == "" {
|
||||
userInternal, ok := user.(*User)
|
||||
if !ok {
|
||||
return user, nil
|
||||
}
|
||||
for _, scope := range s.Provider.Provider.OAuthConfig().Scopes {
|
||||
if strings.TrimSpace(scope) == "user:email" {
|
||||
email, err := s.getPrivateMail()
|
||||
if err != nil {
|
||||
return user, nil
|
||||
}
|
||||
userInternal.Email = domain.EmailAddress(email)
|
||||
return userInternal, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.OAuthSession == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.OAuthSession.ExpiresAt()
|
||||
}
|
||||
|
||||
// Tokens returns the [oidc.Tokens] of the underlying [oauth.Session].
|
||||
func (s *Session) Tokens() *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
return s.oauth().Tokens
|
||||
}
|
||||
|
||||
func (s *Session) oauth() *oauth.Session {
|
||||
if s.OAuthSession != nil {
|
||||
return s.OAuthSession
|
||||
}
|
||||
s.OAuthSession = oauth.NewSession(s.Provider.Provider, s.Code, s.IDPArguments)
|
||||
return s.OAuthSession
|
||||
}
|
||||
|
||||
type Email struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
Verified bool `json:"verified"`
|
||||
}
|
||||
|
||||
func (s *Session) getPrivateMail() (email string, err error) {
|
||||
req, err := http.NewRequest("GET", s.Provider.emailURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("authorization", s.oauth().Tokens.TokenType+" "+s.oauth().Tokens.AccessToken)
|
||||
|
||||
emailList := make([]Email, 0)
|
||||
if err := httphelper.HttpRequest(s.Provider.HttpClient(), req, &emailList); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, v := range emailList {
|
||||
if v.Primary && v.Verified {
|
||||
return v.Email, nil
|
||||
}
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
Reference in New Issue
Block a user