fix: use of generic oauth provider (#5345)

Adds a id_attribute to the GenericOAuthProvider, which is used to map the external User. Further mapping can be done in actions by using the `rawInfo` of the new `ctx.v1.providerInfo` field.
This commit is contained in:
Livio Spring
2023-03-03 11:38:49 +01:00
committed by GitHub
parent cfe00ef0d0
commit 2efa305e10
28 changed files with 456 additions and 98 deletions

View File

@@ -13,6 +13,7 @@ import (
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
func (l *Login) runPostExternalAuthenticationActions(
@@ -20,6 +21,7 @@ func (l *Login) runPostExternalAuthenticationActions(
tokens *oidc.Tokens,
authRequest *domain.AuthRequest,
httpRequest *http.Request,
idpUser idp.User,
authenticationError error,
) (*domain.ExternalUser, error) {
ctx := httpRequest.Context()
@@ -86,6 +88,9 @@ func (l *Login) runPostExternalAuthenticationActions(
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
return object.UserFromExternalUser(c, user)
}),
actions.SetFields("providerInfo", func(c *actions.FieldConfig) interface{} {
return c.Runtime.ToValue(idpUser)
}),
actions.SetFields("authRequest", object.AuthRequestField(authRequest)),
actions.SetFields("httpRequest", object.HTTPRequestField(httpRequest)),
actions.SetFields("authError", authErrStr),
@@ -337,18 +342,39 @@ func (l *Login) runPostCreationActions(
}
func tokenCtxFields(tokens *oidc.Tokens) []actions.FieldOption {
return []actions.FieldOption{
actions.SetFields("accessToken", tokens.AccessToken),
actions.SetFields("idToken", tokens.IDToken),
actions.SetFields("getClaim", func(claim string) interface{} {
var accessToken, idToken string
getClaim := func(claim string) interface{} {
return nil
}
claimsJSON := func() (string, error) {
return "", nil
}
if tokens == nil {
return []actions.FieldOption{
actions.SetFields("accessToken", accessToken),
actions.SetFields("idToken", idToken),
actions.SetFields("getClaim", getClaim),
actions.SetFields("claimsJSON", claimsJSON),
}
}
accessToken = tokens.AccessToken
idToken = tokens.IDToken
if tokens.IDTokenClaims != nil {
getClaim = func(claim string) interface{} {
return tokens.IDTokenClaims.GetClaim(claim)
}),
actions.SetFields("claimsJSON", func() (string, error) {
}
claimsJSON = func() (string, error) {
c, err := json.Marshal(tokens.IDTokenClaims)
if err != nil {
return "", err
}
return string(c), nil
}),
}
}
return []actions.FieldOption{
actions.SetFields("accessToken", accessToken),
actions.SetFields("idToken", idToken),
actions.SetFields("getClaim", getClaim),
actions.SetFields("claimsJSON", claimsJSON),
}
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/google"
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/query"
)
@@ -134,14 +135,15 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
}
var provider idp.Provider
switch identityProvider.Type {
case domain.IDPTypeOAuth:
provider, err = l.oauthProvider(r.Context(), identityProvider)
case domain.IDPTypeOIDC:
provider, err = l.oidcProvider(r.Context(), identityProvider)
case domain.IDPTypeJWT:
provider, err = l.jwtProvider(identityProvider)
case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeOAuth,
domain.IDPTypeLDAP,
case domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeGitHub,
domain.IDPTypeGitHubEE,
@@ -177,33 +179,39 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, err)
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
identityProvider, err := l.getIDPByID(r, authReq.SelectedIDPConfigID)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, err)
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
var provider idp.Provider
var session idp.Session
switch identityProvider.Type {
case domain.IDPTypeOAuth:
provider, err = l.oauthProvider(r.Context(), identityProvider)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
session = &oauth.Session{Provider: provider.(*oauth.Provider), Code: data.Code}
case domain.IDPTypeOIDC:
provider, err = l.oidcProvider(r.Context(), identityProvider)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, err)
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
session = &openid.Session{Provider: provider.(*openid.Provider), Code: data.Code}
case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, err)
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
case domain.IDPTypeJWT,
domain.IDPTypeOAuth,
domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeGitHub,
@@ -219,7 +227,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
user, err := session.FetchUser(r.Context())
if err != nil {
l.externalAuthFailed(w, r, authReq, tokens(session), err)
l.externalAuthFailed(w, r, authReq, tokens(session), user, err)
return
}
l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep)
@@ -236,7 +244,7 @@ func (l *Login) handleExternalUserAuthenticated(
callback func(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest),
) {
externalUser := mapIDPUserToExternalUser(user, provider.ID)
externalUser, err := l.runPostExternalAuthenticationActions(externalUser, tokens(session), authReq, r, nil)
externalUser, err := l.runPostExternalAuthenticationActions(externalUser, tokens(session), authReq, r, user, nil)
if err != nil {
l.renderError(w, r, authReq, err)
return
@@ -600,6 +608,31 @@ func (l *Login) jwtProvider(identityProvider *query.IDPTemplate) (*jwt.Provider,
)
}
func (l *Login) oauthProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*oauth.Provider, error) {
secret, err := crypto.DecryptString(identityProvider.OAuthIDPTemplate.ClientSecret, l.idpConfigAlg)
if err != nil {
return nil, err
}
config := &oauth2.Config{
ClientID: identityProvider.OAuthIDPTemplate.ClientID,
ClientSecret: secret,
Endpoint: oauth2.Endpoint{
AuthURL: identityProvider.OAuthIDPTemplate.AuthorizationEndpoint,
TokenURL: identityProvider.OAuthIDPTemplate.TokenEndpoint,
},
RedirectURL: l.baseURL(ctx) + EndpointExternalLoginCallback,
Scopes: identityProvider.OAuthIDPTemplate.Scopes,
}
return oauth.New(
config,
identityProvider.Name,
identityProvider.OAuthIDPTemplate.UserEndpoint,
func() idp.User {
return oauth.NewUserMapper(identityProvider.OAuthIDPTemplate.IDAttribute)
},
)
}
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
if len(userGrants) == 0 {
return nil
@@ -613,11 +646,8 @@ func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserG
return nil
}
func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens, err error) {
if tokens == nil {
tokens = &oidc.Tokens{Token: &oauth2.Token{}}
}
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, err); actionErr != nil {
func (l *Login) externalAuthFailed(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, tokens *oidc.Tokens, user idp.User, err error) {
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens, authReq, r, user, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
l.renderLogin(w, r, authReq, err)

View File

@@ -66,8 +66,7 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, identityProvider *query.IDPTemplate) {
token, err := getToken(r, identityProvider.JWTIDPTemplate.HeaderName)
if err != nil {
emptyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emptyTokens, authReq, r, err); actionErr != nil {
if _, actionErr := l.runPostExternalAuthenticationActions(new(domain.ExternalUser), nil, authReq, r, nil, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
@@ -76,8 +75,7 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
}
provider, err := l.jwtProvider(identityProvider)
if err != nil {
emptyTokens := &oidc.Tokens{Token: &oauth2.Token{}}
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, emptyTokens, authReq, r, err); actionErr != nil {
if _, actionErr := l.runPostExternalAuthenticationActions(new(domain.ExternalUser), nil, authReq, r, nil, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
l.renderError(w, r, authReq, err)
@@ -86,7 +84,7 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
session := &jwt.Session{Provider: provider, Tokens: &oidc.Tokens{IDToken: token, Token: &oauth2.Token{}}}
user, err := session.FetchUser(r.Context())
if err != nil {
if _, actionErr := l.runPostExternalAuthenticationActions(&domain.ExternalUser{}, tokens(session), authReq, r, err); actionErr != nil {
if _, actionErr := l.runPostExternalAuthenticationActions(new(domain.ExternalUser), tokens(session), authReq, r, user, err); actionErr != nil {
logging.WithError(err).Error("both external user authentication and action post authentication failed")
}
l.renderError(w, r, authReq, err)