feat: extend start/retrieve idp intent for Zitadel provider (#12424)

# Which Problems Are Solved

This PR extends `StartIdentityProviderIntent` and
`RetrieveIdentityProviderIntent` for the new Zitadel provider.

# How the Problems Are Solved
- Added `domain.IDPTypeZitadel` cases to both the instance and org
switches in `NewAllIDPWriteModel` to return Zitadel provider when
`GetProvider` is called
- Added Zitadel provider to RetrieveIdentityProviderIntent's provider
type-switch
- Added new `zitadel.go` provider, an OIDC wrapper with forced PKCE
- Added unit/integration tests

# Additional Changes


# Additional Context
- Closes  https://github.com/zitadel/zitadel/issues/12050

---------

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
This commit is contained in:
gayathri
2026-07-17 13:24:18 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent 53d7920349
commit b91221143b
8 changed files with 755 additions and 0 deletions
@@ -324,6 +324,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
oauthIdpID := Instance.AddGenericOAuthProvider(IamCTX, integration.IDPName()).GetId()
azureIdpID := Instance.AddAzureADProvider(IamCTX, integration.IDPName()).GetId()
oidcIdpID := Instance.AddGenericOIDCProvider(IamCTX, integration.IDPName()).GetId()
zitadelIdpID := Instance.AddZitadelProvider(IamCTX, integration.IDPName()).GetId()
samlIdpID := Instance.AddSAMLPostProvider(IamCTX)
ldapIdpID := Instance.AddLDAPProvider(IamCTX)
jwtIdPID := Instance.AddJWTProvider(IamCTX)
@@ -360,6 +361,12 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user", expiry)
require.NoError(t, err)
// the ZITADEL provider reuses the OIDC user mapper
zitadelSuccessful, zitadelToken, zitadelChangeDate, zitadelSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), zitadelIdpID, "id", "", expiry)
require.NoError(t, err)
zitadelSuccessfulWithUserID, zitadelWithUserIDToken, zitadelWithUserIDChangeDate, zitadelWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), zitadelIdpID, "id", "user", expiry)
require.NoError(t, err)
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "")
require.NoError(t, err)
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
@@ -841,6 +848,134 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
},
wantErr: false,
},
{
name: "retrieve successful zitadel intent",
args: args{
OrgCTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: zitadelSuccessful,
IdpIntentToken: zitadelToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(zitadelChangeDate),
ResourceOwner: Instance.ID(),
Sequence: zitadelSequence,
},
UserId: "",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
RefreshToken: gu.Ptr("refreshToken"),
IdToken: gu.Ptr("idToken"),
},
},
IdpId: zitadelIdpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
AddHumanUser: &user.AddHumanUserRequest{
Username: gu.Ptr("username"),
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
IdpLinks: []*user.IDPLink{
{IdpId: zitadelIdpID, UserId: "id", UserName: "username"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_CreateUser{
CreateUser: &user.CreateUserRequest{
Username: gu.Ptr("username"),
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
IdpLinks: []*user.IDPLink{
{IdpId: zitadelIdpID, UserId: "id", UserName: "username"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
{
name: "retrieve successful zitadel intent with linked user",
args: args{
OrgCTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: zitadelSuccessfulWithUserID,
IdpIntentToken: zitadelWithUserIDToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(zitadelWithUserIDChangeDate),
ResourceOwner: Instance.ID(),
Sequence: zitadelWithUserIDSequence,
},
UserId: "user",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
RefreshToken: gu.Ptr("refreshToken"),
IdToken: gu.Ptr("idToken"),
},
},
IdpId: zitadelIdpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
UpdateHumanUser: &user.UpdateHumanUserRequest{
Username: gu.Ptr("username"),
UserId: "user",
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_UpdateUser{
UpdateUser: &user.UpdateUserRequest{
Username: gu.Ptr("username"),
UserId: "user",
UserType: &user.UpdateUserRequest_Human_{
Human: &user.UpdateUserRequest_Human{
Profile: &user.UpdateUserRequest_Human_Profile{
PreferredLanguage: gu.Ptr("und"),
},
},
},
},
},
},
wantErr: false,
},
{
name: "retrieve successful ldap intent",
args: args{
+3
View File
@@ -27,6 +27,7 @@ import (
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/idp/providers/saml"
"github.com/zitadel/zitadel/internal/idp/providers/zitadel"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
@@ -181,6 +182,8 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *connec
idpUser, err = unmarshalRawIdpUser(intent.IDPUser, p.User())
case *oidc.Provider:
idpUser, err = unmarshalIdpUser(intent.IDPUser, oidc.InitUser())
case *zitadel.Provider:
idpUser, err = unmarshalIdpUser(intent.IDPUser, oidc.InitUser())
case *jwt.Provider:
idpUser, err = unmarshalIdpUser(intent.IDPUser, jwt.InitUser())
case *azuread.Provider:
@@ -2213,6 +2213,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
oauthIdpID := Instance.AddGenericOAuthProvider(IamCTX, integration.IDPName()).GetId()
oidcIdpID := Instance.AddGenericOIDCProvider(IamCTX, integration.IDPName()).GetId()
zitadelIdpID := Instance.AddZitadelProvider(IamCTX, integration.IDPName()).GetId()
samlIdpID := Instance.AddSAMLPostProvider(IamCTX)
ldapIdpID := Instance.AddLDAPProvider(IamCTX)
authURL, err := url.Parse(Instance.CreateIntent(CTX, oauthIdpID).GetAuthUrl())
@@ -2241,6 +2242,11 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
require.NoError(t, err)
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user", expiry)
require.NoError(t, err)
// the ZITADEL provider reuses the OIDC user mapper
zitadelSuccessful, zitadelToken, zitadelChangeDate, zitadelSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), zitadelIdpID, "id", "", expiry)
require.NoError(t, err)
zitadelSuccessfulWithUserID, zitadelWithUserIDToken, zitadelWithUserIDChangeDate, zitadelWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), zitadelIdpID, "id", "user", expiry)
require.NoError(t, err)
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "")
require.NoError(t, err)
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
@@ -2462,6 +2468,84 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
},
wantErr: false,
},
{
name: "retrieve successful zitadel intent",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: zitadelSuccessful,
IdpIntentToken: zitadelToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(zitadelChangeDate),
ResourceOwner: Instance.ID(),
Sequence: zitadelSequence,
},
UserId: "",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
RefreshToken: gu.Ptr("refreshToken"),
IdToken: gu.Ptr("idToken"),
},
},
IdpId: zitadelIdpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful zitadel intent with linked user",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: zitadelSuccessfulWithUserID,
IdpIntentToken: zitadelWithUserIDToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(zitadelWithUserIDChangeDate),
ResourceOwner: Instance.ID(),
Sequence: zitadelWithUserIDSequence,
},
UserId: "user",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
RefreshToken: gu.Ptr("refreshToken"),
IdToken: gu.Ptr("idToken"),
},
},
IdpId: zitadelIdpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful ldap intent",
args: args{