mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-01 11:52:15 +00:00
fix: added login_hint to the idp intent (#11552)
# Which Problems Are Solved In login V2, when the user started the login flow of an external IDP, there was no login_hint passed to the external provider. This required the user to enter their username again. Whilst they already entered it in Zitadel. By adding the `login_hint` parameter, the username should already be filled in on the external idp. # How the Problems Are Solved Pass the `login_hint` parameter to the external idp. # Additional Changes Also added the `login_hint` to the event-store for audit trailing. # Additional Context - Closes #11392 --------- Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -1038,3 +1038,109 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_StartIdentityProviderIntent_WithLoginHint(t *testing.T) {
|
||||
oauthIdpResp := Instance.AddGenericOAuthProvider(IamCTX, integration.IDPName())
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.StartIdentityProviderIntentRequest
|
||||
}
|
||||
type want struct {
|
||||
url string
|
||||
parametersExisting []string
|
||||
parametersEqual map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "OAuth with login_hint",
|
||||
args: args{
|
||||
OrgCTX,
|
||||
&user.StartIdentityProviderIntentRequest{
|
||||
IdpId: oauthIdpResp.Id,
|
||||
Content: &user.StartIdentityProviderIntentRequest_Urls{
|
||||
Urls: &user.RedirectURLs{
|
||||
SuccessUrl: "https://example.com/success",
|
||||
FailureUrl: "https://example.com/failure",
|
||||
LoginHint: "[email protected]",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
url: "https://example.com/oauth/v2/authorize",
|
||||
parametersEqual: map[string]string{
|
||||
"client_id": "clientID",
|
||||
"redirect_uri": "http://" + Instance.Domain + ":8082/idps/callback",
|
||||
"response_type": "code",
|
||||
"scope": "openid profile email",
|
||||
"login_hint": "[email protected]",
|
||||
},
|
||||
parametersExisting: []string{"state"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Without login_hint",
|
||||
args: args{
|
||||
OrgCTX,
|
||||
&user.StartIdentityProviderIntentRequest{
|
||||
IdpId: oauthIdpResp.Id,
|
||||
Content: &user.StartIdentityProviderIntentRequest_Urls{
|
||||
Urls: &user.RedirectURLs{
|
||||
SuccessUrl: "https://example.com/success",
|
||||
FailureUrl: "https://example.com/failure",
|
||||
// No LoginHint provided
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
url: "https://example.com/oauth/v2/authorize",
|
||||
parametersEqual: map[string]string{
|
||||
"client_id": "clientID",
|
||||
"prompt": "select_account",
|
||||
"redirect_uri": "http://" + Instance.Domain + ":8082/idps/callback",
|
||||
"response_type": "code",
|
||||
"scope": "openid profile email",
|
||||
},
|
||||
parametersExisting: []string{"state"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.StartIdentityProviderIntent(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
authUrl, err := url.Parse(got.GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want.url, authUrl.Scheme+"://"+authUrl.Host+authUrl.Path)
|
||||
|
||||
for _, param := range tt.want.parametersExisting {
|
||||
assert.True(t, authUrl.Query().Has(param), "Expected parameter %s to exist", param)
|
||||
}
|
||||
|
||||
for key, expectedValue := range tt.want.parametersEqual {
|
||||
actualValue := authUrl.Query().Get(key)
|
||||
assert.Equal(t, expectedValue, actualValue, "Parameter %s should have value %s but got %s", key, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
if tt.args.req.GetContent().(*user.StartIdentityProviderIntentRequest_Urls).Urls.LoginHint == "" {
|
||||
assert.False(t, authUrl.Query().Has("login_hint"), "login_hint should not be in URL when not provided or empty")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.Request[user.StartIdentityProviderIntentRequest]) (_ *connect.Response[user.StartIdentityProviderIntentResponse], err error) {
|
||||
switch t := req.Msg.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls)
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls, t.Urls.LoginHint)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.Msg.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
@@ -44,12 +44,12 @@ func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.R
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs, loginHint string) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID), loginHint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), session.PersistentParameters())
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), loginHint, session.PersistentParameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.Request[user.StartIdentityProviderIntentRequest]) (_ *connect.Response[user.StartIdentityProviderIntentResponse], err error) {
|
||||
switch t := req.Msg.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls)
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls, t.Urls.LoginHint)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.Msg.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
@@ -376,12 +376,12 @@ func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.R
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs, loginHint string) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID), loginHint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), session.PersistentParameters())
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), loginHint, session.PersistentParameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -410,7 +410,7 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user