feat: supporting metadata updates in actions v2 via RetrieveIdentityProviderIntent API (#11810)

# Which Problems Are Solved

Adds support for returning CreateUser/UpdateUser action payloads from
RetrieveIdentityProviderIntent so Actions v2 can update user metadata
(and other fields) using the v2 user APIs, ensuring parity with user
creation using Actions v2.

# How the Problems Are Solved

By:
- deprecating `AddHumanUser` and `UpdateHumanUser` fields in
`RetrieveIdentityProviderIntentResponse`
- adding a oneof field called `UserAction` with `CreateUser` and
`UpdateUser` fields to support user creation/update
- setting `UserAction` in the `RetrieveIdentityProviderIntentResponse`
for user creation/update

# Additional Changes
N/A

# Additional Context
- Related to https://github.com/zitadel/zitadel/issues/11369
- Follow-up for PRs https://github.com/zitadel/zitadel/pull/11719,
https://github.com/zitadel/zitadel/pull/11747
- Actions V2 example docs will be updated in a follow-up PR

---------

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Gayathri Vijayan
2026-03-12 16:01:58 +00:00
committed by GitHub
co-authored by Copilot
parent 238d98bb30
commit d08779e279
4 changed files with 340 additions and 7 deletions
@@ -455,6 +455,23 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_CreateUser{
CreateUser: &user.CreateUserRequest{
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
IdpLinks: []*user.IDPLink{
{IdpId: oauthIdpID, UserId: "id"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -502,6 +519,18 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
PreferredLanguage: gu.Ptr("und"),
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_UpdateUser{
UpdateUser: &user.UpdateUserRequest{
UserId: "user",
UserType: &user.UpdateUserRequest_Human_{
Human: &user.UpdateUserRequest_Human{
Profile: &user.UpdateUserRequest_Human_Profile{
PreferredLanguage: gu.Ptr("und"),
},
},
},
},
},
},
wantErr: false,
},
@@ -582,6 +611,28 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
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"),
GivenName: "firstname",
FamilyName: "lastname",
DisplayName: gu.Ptr("displayname"),
},
IdpLinks: []*user.IDPLink{
{IdpId: azureIdpID, UserId: "id", UserName: "username"},
},
Email: &user.SetHumanEmail{
Email: "[email protected]",
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -639,6 +690,26 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
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"),
GivenName: gu.Ptr("firstname"),
FamilyName: gu.Ptr("lastname"),
DisplayName: gu.Ptr("displayname"),
},
Email: &user.SetHumanEmail{
Email: "[email protected]",
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -690,6 +761,24 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
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: oidcIdpID, UserId: "id", UserName: "username"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -736,6 +825,19 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
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,
},
@@ -793,6 +895,24 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
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("en"),
},
IdpLinks: []*user.IDPLink{
{IdpId: ldapIdpID, UserId: "id", UserName: "username"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -846,6 +966,19 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
PreferredLanguage: gu.Ptr("en"),
},
},
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("en"),
},
},
},
},
},
},
wantErr: false,
},
@@ -895,6 +1028,23 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_CreateUser{
CreateUser: &user.CreateUserRequest{
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
IdpLinks: []*user.IDPLink{
{IdpId: samlIdpID, UserId: "id"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -940,6 +1090,18 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
PreferredLanguage: gu.Ptr("und"),
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_UpdateUser{
UpdateUser: &user.UpdateUserRequest{
UserId: "user",
UserType: &user.UpdateUserRequest_Human_{
Human: &user.UpdateUserRequest_Human{
Profile: &user.UpdateUserRequest_Human_Profile{
PreferredLanguage: gu.Ptr("und"),
},
},
},
},
},
},
wantErr: false,
},
@@ -986,6 +1148,23 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_CreateUser{
CreateUser: &user.CreateUserRequest{
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
PreferredLanguage: gu.Ptr("und"),
},
IdpLinks: []*user.IDPLink{
{IdpId: jwtIdPID, UserId: "id"},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
},
},
},
},
},
},
wantErr: false,
},
@@ -1028,6 +1207,18 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
PreferredLanguage: gu.Ptr("und"),
},
},
UserAction: &user.RetrieveIdentityProviderIntentResponse_UpdateUser{
UpdateUser: &user.UpdateUserRequest{
UserId: "user",
UserType: &user.UpdateUserRequest_Human_{
Human: &user.UpdateUserRequest_Human{
Profile: &user.UpdateUserRequest_Human_Profile{
PreferredLanguage: gu.Ptr("und"),
},
},
},
},
},
},
wantErr: false,
},
+106 -2
View File
@@ -202,9 +202,11 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *connec
return nil, err
}
if idpIntent.UserId == "" {
idpIntent.AddHumanUser = idpUserToAddHumanUser(idpUser, idpIntent.IdpInformation.IdpId)
idpIntent.AddHumanUser = idpUserToAddHumanUser(idpUser, idpIntent.IdpInformation.IdpId) //nolint:staticcheck
idpIntent.UserAction = idpUserToCreateUser(idpUser, idpIntent.IdpInformation.IdpId)
} else {
idpIntent.UpdateHumanUser = idpUserToUpdateHumanUser(intent.UserID, idpUser)
idpIntent.UpdateHumanUser = idpUserToUpdateHumanUser(intent.UserID, idpUser) //nolint:staticcheck
idpIntent.UserAction = idpUserToUpdateUser(intent.UserID, idpUser)
}
return connect.NewResponse(idpIntent), nil
}
@@ -429,3 +431,105 @@ func idpUserToUpdateHumanUser(userID string, idpUser idp.User) *user.UpdateHuman
}
return updateHumanUser
}
func idpUserToCreateUser(idpUser idp.User, idpID string) *user.RetrieveIdentityProviderIntentResponse_CreateUser {
createUser := &user.CreateUserRequest{
UserType: &user.CreateUserRequest_Human_{
Human: &user.CreateUserRequest_Human{
Profile: &user.SetHumanProfile{
GivenName: idpUser.GetFirstName(),
FamilyName: idpUser.GetLastName(),
},
Email: &user.SetHumanEmail{
Email: string(idpUser.GetEmail()),
Verification: &user.SetHumanEmail_SendCode{},
},
IdpLinks: []*user.IDPLink{
{
IdpId: idpID,
UserId: idpUser.GetID(),
UserName: idpUser.GetPreferredUsername(),
},
},
},
},
}
if username := idpUser.GetPreferredUsername(); username != "" {
createUser.Username = &username
}
if nickName := idpUser.GetNickname(); nickName != "" {
createUser.GetHuman().GetProfile().NickName = &nickName
}
if displayName := idpUser.GetDisplayName(); displayName != "" {
createUser.GetHuman().GetProfile().DisplayName = &displayName
}
if lang := idpUser.GetPreferredLanguage().String(); lang != "" {
createUser.GetHuman().GetProfile().PreferredLanguage = &lang
}
if isEmailVerified := idpUser.IsEmailVerified(); isEmailVerified {
createUser.GetHuman().GetEmail().Verification = &user.SetHumanEmail_IsVerified{IsVerified: isEmailVerified}
}
if phone := idpUser.GetPhone(); phone != "" {
createUser.GetHuman().Phone = &user.SetHumanPhone{
Phone: string(phone),
Verification: &user.SetHumanPhone_SendCode{},
}
if isPhoneVerified := idpUser.IsPhoneVerified(); isPhoneVerified {
createUser.GetHuman().GetPhone().Verification = &user.SetHumanPhone_IsVerified{IsVerified: isPhoneVerified}
}
}
return &user.RetrieveIdentityProviderIntentResponse_CreateUser{
CreateUser: createUser,
}
}
func idpUserToUpdateUser(userID string, idpUser idp.User) *user.RetrieveIdentityProviderIntentResponse_UpdateUser {
updateUser := &user.UpdateUserRequest{
UserId: userID,
UserType: &user.UpdateUserRequest_Human_{
Human: &user.UpdateUserRequest_Human{
Profile: &user.UpdateUserRequest_Human_Profile{},
},
},
}
if username := idpUser.GetPreferredUsername(); username != "" {
updateUser.Username = &username
}
if firstName := idpUser.GetFirstName(); firstName != "" {
updateUser.GetHuman().GetProfile().GivenName = &firstName
}
if lastName := idpUser.GetLastName(); lastName != "" {
updateUser.GetHuman().GetProfile().FamilyName = &lastName
}
if nickName := idpUser.GetNickname(); nickName != "" {
updateUser.GetHuman().GetProfile().NickName = &nickName
}
if displayName := idpUser.GetDisplayName(); displayName != "" {
updateUser.GetHuman().GetProfile().DisplayName = &displayName
}
if lang := idpUser.GetPreferredLanguage().String(); lang != "" {
updateUser.GetHuman().GetProfile().PreferredLanguage = &lang
}
if email := string(idpUser.GetEmail()); email != "" {
updateUser.GetHuman().Email = &user.SetHumanEmail{
Email: email,
Verification: &user.SetHumanEmail_SendCode{},
}
if isEmailVerified := idpUser.IsEmailVerified(); isEmailVerified {
updateUser.GetHuman().GetEmail().Verification = &user.SetHumanEmail_IsVerified{IsVerified: isEmailVerified}
}
}
if phone := string(idpUser.GetPhone()); phone != "" {
updateUser.GetHuman().Phone = &user.SetHumanPhone{
Phone: phone,
Verification: &user.SetHumanPhone_SendCode{},
}
if isPhoneVerified := idpUser.IsPhoneVerified(); isPhoneVerified {
updateUser.GetHuman().GetPhone().Verification = &user.SetHumanPhone_IsVerified{IsVerified: isPhoneVerified}
}
}
return &user.RetrieveIdentityProviderIntentResponse_UpdateUser{
UpdateUser: updateUser,
}
}
+12 -2
View File
@@ -408,7 +408,6 @@ func successfulIntentHandler(cmd *command.Commands, createIntent func(ctx contex
return
}
w.Write(data)
return
}
}
@@ -499,6 +498,9 @@ func createSuccessfulAzureADIntent(ctx context.Context, cmd *command.Commands, r
func createSuccessfulOIDCIntent(ctx context.Context, cmd *command.Commands, req *SuccessfulIntentRequest) (*SuccessfulIntentResponse, error) {
intentID, err := createIntent(ctx, cmd, req.InstanceID, req.IDPID)
writeModel, err := cmd.GetIntentWriteModel(ctx, intentID, req.InstanceID)
if err != nil {
return nil, err
}
idpUser := openid.NewUser(
&oidc.UserInfo{
Subject: req.IDPUserID,
@@ -535,7 +537,9 @@ func createSuccessfulSAMLIntent(ctx context.Context, cmd *command.Commands, req
intentID, _ = createIntent(ctx, cmd, req.InstanceID, req.IDPID)
}
writeModel, err := cmd.GetIntentWriteModel(ctx, intentID, req.InstanceID)
if err != nil {
return nil, err
}
idpUser := &saml.UserMapper{
ID: req.IDPUserID,
Attributes: map[string][]string{"attribute1": {"value1"}},
@@ -564,6 +568,9 @@ func createSuccessfulSAMLIntent(ctx context.Context, cmd *command.Commands, req
func createSuccessfulLDAPIntent(ctx context.Context, cmd *command.Commands, req *SuccessfulIntentRequest) (*SuccessfulIntentResponse, error) {
intentID, err := createIntent(ctx, cmd, req.InstanceID, req.IDPID)
writeModel, err := cmd.GetIntentWriteModel(ctx, intentID, req.InstanceID)
if err != nil {
return nil, err
}
username := "username"
lang := language.Make("en")
idpUser := ldap.NewUser(
@@ -603,6 +610,9 @@ func createSuccessfulLDAPIntent(ctx context.Context, cmd *command.Commands, req
func createSuccessfulJWTIntent(ctx context.Context, cmd *command.Commands, req *SuccessfulIntentRequest) (*SuccessfulIntentResponse, error) {
intentID, err := createIntent(ctx, cmd, req.InstanceID, req.IDPID)
writeModel, err := cmd.GetIntentWriteModel(ctx, intentID, req.InstanceID)
if err != nil {
return nil, err
}
idpUser := &jwt.User{
IDTokenClaims: &oidc.IDTokenClaims{
TokenClaims: oidc.TokenClaims{
+31 -3
View File
@@ -3219,15 +3219,43 @@ message RetrieveIdentityProviderIntentRequest{
message RetrieveIdentityProviderIntentResponse{
zitadel.object.v2.Details details = 1;
IDPInformation idp_information = 2;
IDPInformation idp_information = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Information about the user returned by the external identity provider"
}
];
string user_id = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "ID of the user in Zitadel if external user is linked"
example: "\"163840776835432345\"";
}
];
AddHumanUserRequest add_human_user = 4;
UpdateHumanUserRequest update_human_user = 5;
// Deprecated: use user_action.create_user instead.
AddHumanUserRequest add_human_user = 4 [
deprecated = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Information to create a new human user with the IDP information. This field is deprecated, use create_user instead.";
}
];
// Deprecated: use user_action.update_user instead.
UpdateHumanUserRequest update_human_user = 5 [
deprecated = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Information to update an existing human user with the IDP information. This field is deprecated, use update_user instead.";
}
];
oneof user_action {
CreateUserRequest create_user = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Information to create a new human user with the IDP information.";
}
];
UpdateUserRequest update_user = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Information to update an existing human user with the IDP information.";
}
];
}
}
message AddIDPLinkRequest{