mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 00:57:33 +00:00
feat: add ldap external idp to login api (#5938)
* fix: handling of ldap login through separate endpoint * fix: handling of ldap login through separate endpoint * fix: handling of ldap login through separate endpoint * fix: successful intent for ldap * fix: successful intent for ldap * fix: successful intent for ldap * fix: add changes from code review * fix: remove set intent credentials and handle ldap errors * fix: remove set intent credentials and handle ldap errors * refactor into separate methods and fix merge * remove mocks --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -50,32 +50,32 @@ func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID st
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) CreateIntent(ctx context.Context, idpID, successURL, failureURL, resourceOwner string) (string, *domain.ObjectDetails, error) {
|
||||
func (c *Commands) CreateIntent(ctx context.Context, idpID, successURL, failureURL, resourceOwner string) (*IDPIntentWriteModel, *domain.ObjectDetails, error) {
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
err = AppendAndReduce(writeModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return id, writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
return writeModel, writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) GetProvider(ctx context.Context, idpID, callbackURL string) (idp.Provider, error) {
|
||||
func (c *Commands) GetProvider(ctx context.Context, idpID string, callbackURL string) (idp.Provider, error) {
|
||||
writeModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, idpID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -83,7 +83,21 @@ func (c *Commands) GetProvider(ctx context.Context, idpID, callbackURL string) (
|
||||
return writeModel.ToProvider(callbackURL, c.idpConfigEncryption)
|
||||
}
|
||||
|
||||
func (c *Commands) AuthURLFromProvider(ctx context.Context, idpID, state, callbackURL string) (string, error) {
|
||||
func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIntentWriteModel, error) {
|
||||
intent, err := c.GetIntentWriteModel(ctx, intentID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intent.State == domain.IDPIntentStateUnspecified {
|
||||
return nil, errors.ThrowNotFound(nil, "IDP-Hk38e", "Errors.Intent.NotStarted")
|
||||
}
|
||||
if intent.State != domain.IDPIntentStateStarted {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "IDP-Sfrgs", "Errors.Intent.NotStarted")
|
||||
}
|
||||
return intent, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AuthURLFromProvider(ctx context.Context, idpID, state string, callbackURL string) (string, error) {
|
||||
provider, err := c.GetProvider(ctx, idpID, callbackURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -108,7 +122,7 @@ func getIDPIntentWriteModel(ctx context.Context, writeModel *IDPIntentWriteModel
|
||||
}
|
||||
|
||||
func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, idpSession idp.Session, userID string) (string, error) {
|
||||
token, err := c.idpConfigEncryption.Encrypt([]byte(writeModel.AggregateID))
|
||||
token, err := c.generateIntentToken(writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -134,9 +148,42 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c *Commands) generateIntentToken(intentID string) (string, error) {
|
||||
token, err := c.idpConfigEncryption.Encrypt([]byte(intentID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(token), nil
|
||||
}
|
||||
|
||||
func (c *Commands) SucceedLDAPIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, attributes map[string][]string) (string, error) {
|
||||
token, err := c.generateIntentToken(writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
idpInfo, err := json.Marshal(idpUser)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cmd := idpintent.NewLDAPSucceededEvent(
|
||||
ctx,
|
||||
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
|
||||
idpInfo,
|
||||
idpUser.GetID(),
|
||||
idpUser.GetPreferredUsername(),
|
||||
userID,
|
||||
attributes,
|
||||
)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c *Commands) FailIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, reason string) error {
|
||||
cmd := idpintent.NewFailedEvent(
|
||||
ctx,
|
||||
|
@@ -12,15 +12,18 @@ import (
|
||||
type IDPIntentWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
SuccessURL *url.URL
|
||||
FailureURL *url.URL
|
||||
IDPID string
|
||||
IDPUser []byte
|
||||
IDPUserID string
|
||||
IDPUserName string
|
||||
SuccessURL *url.URL
|
||||
FailureURL *url.URL
|
||||
IDPID string
|
||||
IDPUser []byte
|
||||
IDPUserID string
|
||||
IDPUserName string
|
||||
UserID string
|
||||
|
||||
IDPAccessToken *crypto.CryptoValue
|
||||
IDPIDToken string
|
||||
UserID string
|
||||
|
||||
IDPEntryAttributes map[string][]string
|
||||
|
||||
State domain.IDPIntentState
|
||||
aggregate *eventstore.Aggregate
|
||||
@@ -42,7 +45,9 @@ func (wm *IDPIntentWriteModel) Reduce() error {
|
||||
case *idpintent.StartedEvent:
|
||||
wm.reduceStartedEvent(e)
|
||||
case *idpintent.SucceededEvent:
|
||||
wm.reduceSucceededEvent(e)
|
||||
wm.reduceOAuthSucceededEvent(e)
|
||||
case *idpintent.LDAPSucceededEvent:
|
||||
wm.reduceLDAPSucceededEvent(e)
|
||||
case *idpintent.FailedEvent:
|
||||
wm.reduceFailedEvent(e)
|
||||
}
|
||||
@@ -59,6 +64,7 @@ func (wm *IDPIntentWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
EventTypes(
|
||||
idpintent.StartedEventType,
|
||||
idpintent.SucceededEventType,
|
||||
idpintent.LDAPSucceededEventType,
|
||||
idpintent.FailedEventType,
|
||||
).
|
||||
Builder()
|
||||
@@ -71,7 +77,16 @@ func (wm *IDPIntentWriteModel) reduceStartedEvent(e *idpintent.StartedEvent) {
|
||||
wm.State = domain.IDPIntentStateStarted
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceSucceededEvent(e *idpintent.SucceededEvent) {
|
||||
func (wm *IDPIntentWriteModel) reduceLDAPSucceededEvent(e *idpintent.LDAPSucceededEvent) {
|
||||
wm.UserID = e.UserID
|
||||
wm.IDPUser = e.IDPUser
|
||||
wm.IDPUserID = e.IDPUserID
|
||||
wm.IDPUserName = e.IDPUserName
|
||||
wm.IDPEntryAttributes = e.EntryAttributes
|
||||
wm.State = domain.IDPIntentStateSucceeded
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceOAuthSucceededEvent(e *idpintent.SucceededEvent) {
|
||||
wm.UserID = e.UserID
|
||||
wm.IDPUser = e.IDPUser
|
||||
wm.IDPUserID = e.IDPUserID
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
@@ -199,9 +200,13 @@ func TestCommands_CreateIntent(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
intentID, details, err := c.CreateIntent(tt.args.ctx, tt.args.idpID, tt.args.successURL, tt.args.failureURL, tt.args.resourceOwner)
|
||||
intentWriteModel, details, err := c.CreateIntent(tt.args.ctx, tt.args.idpID, tt.args.successURL, tt.args.failureURL, tt.args.resourceOwner)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.intentID, intentID)
|
||||
if intentWriteModel != nil {
|
||||
assert.Equal(t, tt.res.intentID, intentWriteModel.AggregateID)
|
||||
} else {
|
||||
assert.Equal(t, tt.res.intentID, "")
|
||||
}
|
||||
assert.Equal(t, tt.res.details, details)
|
||||
})
|
||||
}
|
||||
@@ -580,6 +585,103 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_SucceedLDAPIDPIntent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idpConfigEncryption crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
writeModel *IDPIntentWriteModel
|
||||
idpUser idp.User
|
||||
userID string
|
||||
attributes map[string][]string
|
||||
}
|
||||
type res struct {
|
||||
token string
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"encryption fails",
|
||||
fields{
|
||||
idpConfigEncryption: func() crypto.EncryptionAlgorithm {
|
||||
m := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t))
|
||||
m.EXPECT().Encrypt(gomock.Any()).Return(nil, z_errors.ThrowInternal(nil, "id", "encryption failed"))
|
||||
return m
|
||||
}(),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro"),
|
||||
},
|
||||
res{
|
||||
err: z_errors.ThrowInternal(nil, "id", "encryption failed"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"push",
|
||||
fields{
|
||||
idpConfigEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
idpintent.NewLDAPSucceededEvent(
|
||||
context.Background(),
|
||||
&idpintent.NewAggregate("id", "ro").Aggregate,
|
||||
[]byte(`{"id":"id","preferredUsername":"username","preferredLanguage":"und"}`),
|
||||
"id",
|
||||
"username",
|
||||
"",
|
||||
map[string][]string{"id": {"id"}},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro"),
|
||||
attributes: map[string][]string{"id": {"id"}},
|
||||
idpUser: ldap.NewUser(
|
||||
"id",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"username",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
false,
|
||||
language.Tag{},
|
||||
"",
|
||||
"",
|
||||
),
|
||||
},
|
||||
res{
|
||||
token: "aWQ",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigEncryption: tt.fields.idpConfigEncryption,
|
||||
}
|
||||
got, err := c.SucceedLDAPIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.attributes)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.token, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_FailIDPIntent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
|
Reference in New Issue
Block a user