mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +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:
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID, successURL, failureURL string, idpArguments map[string]any) preparation.Validation {
|
||||
func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID, successURL, failureURL string, loginHint string, idpArguments map[string]any) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if idpID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x8j2bk", "Errors.Intent.IDPMissing")
|
||||
@@ -55,6 +55,7 @@ func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID, s
|
||||
successURL,
|
||||
failureURL,
|
||||
idpID,
|
||||
loginHint,
|
||||
idpArguments,
|
||||
),
|
||||
}, nil
|
||||
@@ -62,7 +63,7 @@ func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID, s
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) CreateIntent(ctx context.Context, intentID, idpID, successURL, failureURL, resourceOwner string, idpArguments map[string]any) (*IDPIntentWriteModel, *domain.ObjectDetails, error) {
|
||||
func (c *Commands) CreateIntent(ctx context.Context, intentID, idpID, successURL, failureURL, resourceOwner, loginHint string, idpArguments map[string]any) (*IDPIntentWriteModel, *domain.ObjectDetails, error) {
|
||||
if intentID == "" {
|
||||
var err error
|
||||
intentID, err = c.idGenerator.Next()
|
||||
@@ -73,7 +74,7 @@ func (c *Commands) CreateIntent(ctx context.Context, intentID, idpID, successURL
|
||||
writeModel := NewIDPIntentWriteModel(intentID, resourceOwner, c.maxIdPIntentLifetime)
|
||||
|
||||
//nolint: staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL, idpArguments))
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL, loginHint, idpArguments))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -135,7 +136,7 @@ func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIn
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AuthFromProvider(ctx context.Context, idpID, idpCallback, samlRootURL string) (state string, session idp.Session, err error) {
|
||||
func (c *Commands) AuthFromProvider(ctx context.Context, idpID, idpCallback, samlRootURL string, loginHint string) (state string, session idp.Session, err error) {
|
||||
state, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
@@ -144,7 +145,11 @@ func (c *Commands) AuthFromProvider(ctx context.Context, idpID, idpCallback, sam
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
session, err = provider.BeginAuth(ctx, state)
|
||||
var params []idp.Parameter
|
||||
if loginHint != "" {
|
||||
params = append(params, idp.LoginHintParam(loginHint))
|
||||
}
|
||||
session, err = provider.BeginAuth(ctx, state, params...)
|
||||
return state, session, err
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ type IDPIntentWriteModel struct {
|
||||
IDPUserID string
|
||||
IDPUserName string
|
||||
UserID string
|
||||
LoginHint string
|
||||
|
||||
IDPAccessToken *crypto.CryptoValue
|
||||
IDPIDToken string
|
||||
@@ -97,6 +98,7 @@ func (wm *IDPIntentWriteModel) reduceStartedEvent(e *idpintent.StartedEvent) {
|
||||
wm.SuccessURL = e.SuccessURL
|
||||
wm.FailureURL = e.FailureURL
|
||||
wm.IDPID = e.IDPID
|
||||
wm.LoginHint = e.LoginHint
|
||||
wm.IDPArguments = e.IDPArguments
|
||||
wm.State = domain.IDPIntentStateStarted
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
successURL string
|
||||
failureURL string
|
||||
instanceID string
|
||||
loginHint string
|
||||
idpArguments map[string]any
|
||||
}
|
||||
type res struct {
|
||||
@@ -202,6 +203,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
}(),
|
||||
@@ -257,6 +259,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"",
|
||||
map[string]interface{}{
|
||||
"verifier": "pkceOAuthVerifier",
|
||||
},
|
||||
@@ -317,6 +320,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"",
|
||||
map[string]interface{}{
|
||||
"verifier": "pkceOAuthVerifier",
|
||||
},
|
||||
@@ -377,6 +381,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"",
|
||||
map[string]interface{}{
|
||||
"verifier": "pkceOAuthVerifier",
|
||||
},
|
||||
@@ -408,7 +413,7 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
intentWriteModel, details, err := c.CreateIntent(tt.args.ctx, tt.args.intentID, tt.args.idpID, tt.args.successURL, tt.args.failureURL, tt.args.instanceID, tt.args.idpArguments)
|
||||
intentWriteModel, details, err := c.CreateIntent(tt.args.ctx, tt.args.intentID, tt.args.idpID, tt.args.successURL, tt.args.failureURL, tt.args.instanceID, tt.args.loginHint, tt.args.idpArguments)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
if intentWriteModel != nil {
|
||||
assert.Equal(t, tt.res.intentID, intentWriteModel.AggregateID)
|
||||
@@ -429,6 +434,7 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
idpID string
|
||||
loginHint string
|
||||
callbackURL string
|
||||
samlRootURL string
|
||||
}
|
||||
@@ -582,6 +588,68 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
||||
auth: &idp.RedirectAuth{RedirectURL: "auth?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&state=id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"oauth auth redirect with login_hint",
|
||||
fields{
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance",
|
||||
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate,
|
||||
"idp",
|
||||
"name",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
"auth",
|
||||
"token",
|
||||
"user",
|
||||
"idAttribute",
|
||||
nil,
|
||||
true,
|
||||
rep_idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance",
|
||||
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate,
|
||||
"idp",
|
||||
"name",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
"auth",
|
||||
"token",
|
||||
"user",
|
||||
"idAttribute",
|
||||
nil,
|
||||
true,
|
||||
rep_idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.SetCtxData(context.Background(), authz.CtxData{OrgID: "ro"}),
|
||||
idpID: "idp",
|
||||
callbackURL: "url",
|
||||
loginHint: "[email protected]",
|
||||
},
|
||||
res{
|
||||
auth: &idp.RedirectAuth{RedirectURL: "auth?client_id=clientID&login_hint=user%40example.com&redirect_uri=url&response_type=code&state=id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"migrated and push",
|
||||
fields{
|
||||
@@ -681,7 +749,7 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL, tt.args.loginHint)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
|
||||
var got idp.Auth
|
||||
@@ -705,6 +773,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
idpID string
|
||||
callbackURL string
|
||||
samlRootURL string
|
||||
loginHint string
|
||||
}
|
||||
type res struct {
|
||||
url string
|
||||
@@ -779,6 +848,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"[email protected]",
|
||||
nil,
|
||||
)
|
||||
}(),
|
||||
@@ -801,6 +871,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
idpID: "idp",
|
||||
callbackURL: "url",
|
||||
samlRootURL: "samlurl",
|
||||
loginHint: "[email protected]",
|
||||
},
|
||||
res{
|
||||
url: "http://localhost:8000/sso",
|
||||
@@ -872,6 +943,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
success,
|
||||
failure,
|
||||
"idp",
|
||||
"user123",
|
||||
nil,
|
||||
)
|
||||
}(),
|
||||
@@ -894,6 +966,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
idpID: "idp",
|
||||
callbackURL: "url",
|
||||
samlRootURL: "samlurl",
|
||||
loginHint: "user123",
|
||||
},
|
||||
res{
|
||||
url: "http://localhost:8000/sso",
|
||||
@@ -911,7 +984,7 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL, tt.args.loginHint)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
|
||||
auth, err := session.GetAuth(tt.args.ctx)
|
||||
|
||||
@@ -993,6 +993,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
"idpID",
|
||||
"",
|
||||
nil,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user