mirror of
https://github.com/zitadel/zitadel.git
synced 2025-11-02 03:38:46 +00:00
# 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. <marco@zitadel.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
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
|
|
}
|