mirror of
https://github.com/zitadel/zitadel.git
synced 2025-06-19 23:18: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:
parent
1b923425cd
commit
52f68f8db8
@ -258,7 +258,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)
|
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)
|
||||||
|
|
||||||
intentID, token, _, _ := Tester.CreateSuccessfulIntent(t, idpID, User.GetUserId(), "id")
|
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, User.GetUserId(), "id")
|
||||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
SessionId: createResp.GetSessionId(),
|
SessionId: createResp.GetSessionId(),
|
||||||
SessionToken: createResp.GetSessionToken(),
|
SessionToken: createResp.GetSessionToken(),
|
||||||
@ -289,7 +289,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
|
|||||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)
|
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)
|
||||||
|
|
||||||
idpUserID := "id"
|
idpUserID := "id"
|
||||||
intentID, token, _, _ := Tester.CreateSuccessfulIntent(t, idpID, "", idpUserID)
|
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", idpUserID)
|
||||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||||
SessionId: createResp.GetSessionId(),
|
SessionId: createResp.GetSessionId(),
|
||||||
SessionToken: createResp.GetSessionToken(),
|
SessionToken: createResp.GetSessionToken(),
|
||||||
|
@ -2,6 +2,7 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
errs "errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@ -14,6 +15,9 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
|
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
|
||||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
|
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
|
||||||
)
|
)
|
||||||
@ -126,11 +130,22 @@ func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) StartIdentityProviderFlow(ctx context.Context, req *user.StartIdentityProviderFlowRequest) (_ *user.StartIdentityProviderFlowResponse, err error) {
|
func (s *Server) StartIdentityProviderFlow(ctx context.Context, req *user.StartIdentityProviderFlowRequest) (_ *user.StartIdentityProviderFlowResponse, err error) {
|
||||||
id, details, err := s.command.CreateIntent(ctx, req.GetIdpId(), req.GetSuccessUrl(), req.GetFailureUrl(), authz.GetCtxData(ctx).OrgID)
|
switch t := req.GetContent().(type) {
|
||||||
|
case *user.StartIdentityProviderFlowRequest_Urls:
|
||||||
|
return s.startIDPIntent(ctx, req.GetIdpId(), t.Urls)
|
||||||
|
case *user.StartIdentityProviderFlowRequest_Ldap:
|
||||||
|
return s.startLDAPIntent(ctx, req.GetIdpId(), t.Ldap)
|
||||||
|
default:
|
||||||
|
return nil, errors.ThrowUnimplementedf(nil, "USERv2-S2g21", "type oneOf %T in method StartIdentityProviderFlow not implemented", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*user.StartIdentityProviderFlowResponse, error) {
|
||||||
|
intentWriteModel, details, err := s.command.CreateIntent(ctx, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authURL, err := s.command.AuthURLFromProvider(ctx, req.GetIdpId(), id, s.idpCallback(ctx))
|
authURL, err := s.command.AuthURLFromProvider(ctx, idpID, intentWriteModel.AggregateID, s.idpCallback(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -140,6 +155,79 @@ func (s *Server) StartIdentityProviderFlow(ctx context.Context, req *user.StartI
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderFlowResponse, error) {
|
||||||
|
intentWriteModel, details, err := s.command.CreateIntent(ctx, idpID, "", "", authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
externalUser, userID, attributes, err := s.ldapLogin(ctx, intentWriteModel.IDPID, ldapCredentials.GetUsername(), ldapCredentials.GetPassword())
|
||||||
|
if err != nil {
|
||||||
|
if err := s.command.FailIDPIntent(ctx, intentWriteModel, err.Error()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token, err := s.command.SucceedLDAPIDPIntent(ctx, intentWriteModel, externalUser, userID, attributes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user.StartIdentityProviderFlowResponse{
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
NextStep: &user.StartIdentityProviderFlowResponse_Intent{Intent: &user.Intent{IntentId: intentWriteModel.AggregateID, Token: token}},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUserID string) (string, error) {
|
||||||
|
idQuery, err := query.NewIDPUserLinkIDPIDSearchQuery(idpID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
externalIDQuery, err := query.NewIDPUserLinksExternalIDSearchQuery(externalUserID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
queries := []query.SearchQuery{
|
||||||
|
idQuery, externalIDQuery,
|
||||||
|
}
|
||||||
|
links, err := s.query.IDPUserLinks(ctx, &query.IDPUserLinksSearchQuery{Queries: queries}, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(links.Links) == 1 {
|
||||||
|
return links.Links[0].UserID, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, map[string][]string, error) {
|
||||||
|
provider, err := s.command.GetProvider(ctx, idpID, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
ldapProvider, ok := provider.(*ldap.Provider)
|
||||||
|
if !ok {
|
||||||
|
return nil, "", nil, errors.ThrowInvalidArgument(nil, "IDP-9a02j2n2bh", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||||
|
}
|
||||||
|
session := ldapProvider.GetSession(username, password)
|
||||||
|
externalUser, err := session.FetchUser(ctx)
|
||||||
|
if errs.Is(err, ldap.ErrFailedLogin) || errs.Is(err, ldap.ErrNoSingleUser) {
|
||||||
|
return nil, "", nil, errors.ThrowInvalidArgument(nil, "COMMAND-nzun2i", "Errors.User.ExternalIDP.LoginFailed")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
userID, err := s.checkLinkedExternalUser(ctx, idpID, externalUser.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := make(map[string][]string, 0)
|
||||||
|
for _, item := range session.Entry.Attributes {
|
||||||
|
attributes[item.Name] = item.Values
|
||||||
|
}
|
||||||
|
return externalUser, userID, attributes, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) RetrieveIdentityProviderInformation(ctx context.Context, req *user.RetrieveIdentityProviderInformationRequest) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
|
func (s *Server) RetrieveIdentityProviderInformation(ctx context.Context, req *user.RetrieveIdentityProviderInformationRequest) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
|
||||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIntentId(), authz.GetCtxData(ctx).OrgID)
|
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIntentId(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -155,41 +243,83 @@ func (s *Server) RetrieveIdentityProviderInformation(ctx context.Context, req *u
|
|||||||
}
|
}
|
||||||
|
|
||||||
func intentToIDPInformationPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
|
func intentToIDPInformationPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderInformationResponse, err error) {
|
||||||
var idToken *string
|
|
||||||
if intent.IDPIDToken != "" {
|
|
||||||
idToken = &intent.IDPIDToken
|
|
||||||
}
|
|
||||||
var accessToken string
|
|
||||||
if intent.IDPAccessToken != nil {
|
|
||||||
accessToken, err = crypto.DecryptString(intent.IDPAccessToken, alg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rawInformation := new(structpb.Struct)
|
rawInformation := new(structpb.Struct)
|
||||||
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
information := &user.RetrieveIdentityProviderInformationResponse{
|
||||||
return &user.RetrieveIdentityProviderInformationResponse{
|
Details: intentToDetailsPb(intent),
|
||||||
Details: &object_pb.Details{
|
|
||||||
Sequence: intent.ProcessedSequence,
|
|
||||||
ChangeDate: timestamppb.New(intent.ChangeDate),
|
|
||||||
ResourceOwner: intent.ResourceOwner,
|
|
||||||
},
|
|
||||||
IdpInformation: &user.IDPInformation{
|
IdpInformation: &user.IDPInformation{
|
||||||
Access: &user.IDPInformation_Oauth{
|
|
||||||
Oauth: &user.IDPOAuthAccessInformation{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
IdToken: idToken,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IdpId: intent.IDPID,
|
IdpId: intent.IDPID,
|
||||||
UserId: intent.IDPUserID,
|
UserId: intent.IDPUserID,
|
||||||
UserName: intent.IDPUserName,
|
UserName: intent.IDPUserName,
|
||||||
RawInformation: rawInformation,
|
RawInformation: rawInformation,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
if intent.IDPIDToken != "" || intent.IDPAccessToken != nil {
|
||||||
|
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if intent.IDPEntryAttributes != nil {
|
||||||
|
access, err := IDPEntryAttributesToPb(intent.IDPEntryAttributes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
information.IdpInformation.Access = access
|
||||||
|
}
|
||||||
|
|
||||||
|
return information, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
|
||||||
|
var idToken *string
|
||||||
|
if idpIDToken != "" {
|
||||||
|
idToken = &idpIDToken
|
||||||
|
}
|
||||||
|
var accessToken string
|
||||||
|
if idpAccessToken != nil {
|
||||||
|
accessToken, err = crypto.DecryptString(idpAccessToken, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &user.IDPInformation_Oauth{
|
||||||
|
Oauth: &user.IDPOAuthAccessInformation{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
IdToken: idToken,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func intentToDetailsPb(intent *command.IDPIntentWriteModel) *object_pb.Details {
|
||||||
|
return &object_pb.Details{
|
||||||
|
Sequence: intent.ProcessedSequence,
|
||||||
|
ChangeDate: timestamppb.New(intent.ChangeDate),
|
||||||
|
ResourceOwner: intent.ResourceOwner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IDPEntryAttributesToPb(entryAttributes map[string][]string) (*user.IDPInformation_Ldap, error) {
|
||||||
|
values := make(map[string]interface{}, 0)
|
||||||
|
for k, v := range entryAttributes {
|
||||||
|
intValues := make([]interface{}, len(v))
|
||||||
|
for i, value := range v {
|
||||||
|
intValues[i] = value
|
||||||
|
}
|
||||||
|
values[k] = intValues
|
||||||
|
}
|
||||||
|
attributes, err := structpb.NewStruct(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user.IDPInformation_Ldap{
|
||||||
|
Ldap: &user.IDPLDAPAccessInformation{
|
||||||
|
Attributes: attributes,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,9 +650,13 @@ func TestServer_StartIdentityProviderFlow(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
CTX,
|
CTX,
|
||||||
&user.StartIdentityProviderFlowRequest{
|
&user.StartIdentityProviderFlowRequest{
|
||||||
IdpId: idpID,
|
IdpId: idpID,
|
||||||
SuccessUrl: "https://example.com/success",
|
Content: &user.StartIdentityProviderFlowRequest_Urls{
|
||||||
FailureUrl: "https://example.com/failure",
|
Urls: &user.RedirectURLs{
|
||||||
|
SuccessUrl: "https://example.com/success",
|
||||||
|
FailureUrl: "https://example.com/failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.StartIdentityProviderFlowResponse{
|
want: &user.StartIdentityProviderFlowResponse{
|
||||||
@ -689,7 +693,8 @@ func TestServer_StartIdentityProviderFlow(t *testing.T) {
|
|||||||
func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
||||||
idpID := Tester.AddGenericOAuthProvider(t)
|
idpID := Tester.AddGenericOAuthProvider(t)
|
||||||
intentID := Tester.CreateIntent(t, idpID)
|
intentID := Tester.CreateIntent(t, idpID)
|
||||||
successfulID, token, changeDate, sequence := Tester.CreateSuccessfulIntent(t, idpID, "", "id")
|
successfulID, token, changeDate, sequence := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", "id")
|
||||||
|
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence := Tester.CreateSuccessfulLDAPIntent(t, idpID, "", "id")
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
req *user.RetrieveIdentityProviderInformationRequest
|
req *user.RetrieveIdentityProviderInformationRequest
|
||||||
@ -759,6 +764,51 @@ func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "retrieve successful ldap intent",
|
||||||
|
args: args{
|
||||||
|
CTX,
|
||||||
|
&user.RetrieveIdentityProviderInformationRequest{
|
||||||
|
IntentId: ldapSuccessfulID,
|
||||||
|
Token: ldapToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.RetrieveIdentityProviderInformationResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.New(ldapChangeDate),
|
||||||
|
ResourceOwner: Tester.Organisation.ID,
|
||||||
|
Sequence: ldapSequence,
|
||||||
|
},
|
||||||
|
IdpInformation: &user.IDPInformation{
|
||||||
|
Access: &user.IDPInformation_Ldap{
|
||||||
|
Ldap: &user.IDPLDAPAccessInformation{
|
||||||
|
Attributes: func() *structpb.Struct {
|
||||||
|
s, err := structpb.NewStruct(map[string]interface{}{
|
||||||
|
"id": []interface{}{"id"},
|
||||||
|
"username": []interface{}{"username"},
|
||||||
|
"language": []interface{}{"en"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return s
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IdpId: idpID,
|
||||||
|
UserId: "id",
|
||||||
|
UserName: "username",
|
||||||
|
RawInformation: func() *structpb.Struct {
|
||||||
|
s, err := structpb.NewStruct(map[string]interface{}{
|
||||||
|
"id": "id",
|
||||||
|
"preferredUsername": "username",
|
||||||
|
"preferredLanguage": "en",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return s
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -769,7 +819,7 @@ func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
grpc.AllFieldsEqual(t, got.ProtoReflect(), tt.want.ProtoReflect(), grpc.CustomMappers)
|
grpc.AllFieldsEqual(t, tt.want.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,10 @@ func Test_intentToIDPInformationPb(t *testing.T) {
|
|||||||
KeyID: "id",
|
KeyID: "id",
|
||||||
Crypted: []byte("accessToken"),
|
Crypted: []byte("accessToken"),
|
||||||
},
|
},
|
||||||
IDPIDToken: "idToken",
|
IDPIDToken: "idToken",
|
||||||
UserID: "userID",
|
IDPEntryAttributes: map[string][]string{},
|
||||||
State: domain.IDPIntentStateSucceeded,
|
UserID: "userID",
|
||||||
|
State: domain.IDPIntentStateSucceeded,
|
||||||
},
|
},
|
||||||
alg: decryption(caos_errs.ThrowInternal(nil, "id", "invalid key id")),
|
alg: decryption(caos_errs.ThrowInternal(nil, "id", "invalid key id")),
|
||||||
},
|
},
|
||||||
@ -85,7 +86,7 @@ func Test_intentToIDPInformationPb(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"successful",
|
"successful oauth",
|
||||||
args{
|
args{
|
||||||
intent: &command.IDPIntentWriteModel{
|
intent: &command.IDPIntentWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
@ -140,16 +141,73 @@ func Test_intentToIDPInformationPb(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
"successful ldap",
|
||||||
|
args{
|
||||||
|
intent: &command.IDPIntentWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: "intentID",
|
||||||
|
ProcessedSequence: 123,
|
||||||
|
ResourceOwner: "ro",
|
||||||
|
InstanceID: "instanceID",
|
||||||
|
ChangeDate: time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local),
|
||||||
|
},
|
||||||
|
IDPID: "idpID",
|
||||||
|
IDPUser: []byte(`{"userID": "idpUserID", "username": "username"}`),
|
||||||
|
IDPUserID: "idpUserID",
|
||||||
|
IDPUserName: "username",
|
||||||
|
IDPEntryAttributes: map[string][]string{
|
||||||
|
"id": {"idpUserID"},
|
||||||
|
"firstName": {"firstname1", "firstname2"},
|
||||||
|
"lastName": {"lastname"},
|
||||||
|
},
|
||||||
|
UserID: "userID",
|
||||||
|
State: domain.IDPIntentStateSucceeded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
resp: &user.RetrieveIdentityProviderInformationResponse{
|
||||||
|
Details: &object_pb.Details{
|
||||||
|
Sequence: 123,
|
||||||
|
ChangeDate: timestamppb.New(time.Date(2019, 4, 1, 1, 1, 1, 1, time.Local)),
|
||||||
|
ResourceOwner: "ro",
|
||||||
|
},
|
||||||
|
IdpInformation: &user.IDPInformation{
|
||||||
|
Access: &user.IDPInformation_Ldap{
|
||||||
|
Ldap: &user.IDPLDAPAccessInformation{
|
||||||
|
Attributes: func() *structpb.Struct {
|
||||||
|
s, err := structpb.NewStruct(map[string]interface{}{
|
||||||
|
"id": []interface{}{"idpUserID"},
|
||||||
|
"firstName": []interface{}{"firstname1", "firstname2"},
|
||||||
|
"lastName": []interface{}{"lastname"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return s
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IdpId: "idpID",
|
||||||
|
UserId: "idpUserID",
|
||||||
|
UserName: "username",
|
||||||
|
RawInformation: func() *structpb.Struct {
|
||||||
|
s, err := structpb.NewStruct(map[string]interface{}{
|
||||||
|
"userID": "idpUserID",
|
||||||
|
"username": "username",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return s
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := intentToIDPInformationPb(tt.args.intent, tt.args.alg)
|
got, err := intentToIDPInformationPb(tt.args.intent, tt.args.alg)
|
||||||
require.ErrorIs(t, err, tt.res.err)
|
require.ErrorIs(t, err, tt.res.err)
|
||||||
grpc.AllFieldsEqual(t, got.ProtoReflect(), tt.res.resp.ProtoReflect(), grpc.CustomMappers)
|
grpc.AllFieldsEqual(t, tt.res.resp.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||||
if tt.res.resp != nil {
|
|
||||||
grpc.AllFieldsSet(t, got.ProtoReflect(), ignoreTypes...)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HandlerPrefix = "/idps"
|
HandlerPrefix = "/idps"
|
||||||
callbackPath = "/callback"
|
callbackPath = "/callback"
|
||||||
|
ldapCallbackPath = callbackPath + "/ldap"
|
||||||
|
|
||||||
paramIntentID = "id"
|
paramIntentID = "id"
|
||||||
paramToken = "token"
|
paramToken = "token"
|
||||||
@ -82,18 +83,22 @@ func NewHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
data, err := h.parseCallbackRequest(r)
|
data, err := h.parseCallbackRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
intent := h.getActiveIntent(w, r, data.State)
|
intent, err := h.commands.GetActiveIntent(ctx, data.State)
|
||||||
if intent == nil {
|
if err != nil {
|
||||||
// if we didn't get an active intent the error was already handled (either redirected or display directly)
|
if z_errs.IsNotFound(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirectToFailureURLErr(w, r, intent, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
|
||||||
// the provider might have returned an error
|
// the provider might have returned an error
|
||||||
if data.Error != "" {
|
if data.Error != "" {
|
||||||
cmdErr := h.commands.FailIDPIntent(ctx, intent, reason(data.Error, data.ErrorDescription))
|
cmdErr := h.commands.FailIDPIntent(ctx, intent, reason(data.Error, data.ErrorDescription))
|
||||||
|
@ -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()
|
id, err := c.idGenerator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL))
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
err = AppendAndReduce(writeModel, pushedEvents...)
|
err = AppendAndReduce(writeModel, pushedEvents...)
|
||||||
if err != nil {
|
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)
|
writeModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, idpID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -83,7 +83,21 @@ func (c *Commands) GetProvider(ctx context.Context, idpID, callbackURL string) (
|
|||||||
return writeModel.ToProvider(callbackURL, c.idpConfigEncryption)
|
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)
|
provider, err := c.GetProvider(ctx, idpID, callbackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -134,9 +148,42 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
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 {
|
func (c *Commands) FailIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, reason string) error {
|
||||||
cmd := idpintent.NewFailedEvent(
|
cmd := idpintent.NewFailedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -12,15 +12,18 @@ import (
|
|||||||
type IDPIntentWriteModel struct {
|
type IDPIntentWriteModel struct {
|
||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
SuccessURL *url.URL
|
SuccessURL *url.URL
|
||||||
FailureURL *url.URL
|
FailureURL *url.URL
|
||||||
IDPID string
|
IDPID string
|
||||||
IDPUser []byte
|
IDPUser []byte
|
||||||
IDPUserID string
|
IDPUserID string
|
||||||
IDPUserName string
|
IDPUserName string
|
||||||
|
UserID string
|
||||||
|
|
||||||
IDPAccessToken *crypto.CryptoValue
|
IDPAccessToken *crypto.CryptoValue
|
||||||
IDPIDToken string
|
IDPIDToken string
|
||||||
UserID string
|
|
||||||
|
IDPEntryAttributes map[string][]string
|
||||||
|
|
||||||
State domain.IDPIntentState
|
State domain.IDPIntentState
|
||||||
aggregate *eventstore.Aggregate
|
aggregate *eventstore.Aggregate
|
||||||
@ -42,7 +45,9 @@ func (wm *IDPIntentWriteModel) Reduce() error {
|
|||||||
case *idpintent.StartedEvent:
|
case *idpintent.StartedEvent:
|
||||||
wm.reduceStartedEvent(e)
|
wm.reduceStartedEvent(e)
|
||||||
case *idpintent.SucceededEvent:
|
case *idpintent.SucceededEvent:
|
||||||
wm.reduceSucceededEvent(e)
|
wm.reduceOAuthSucceededEvent(e)
|
||||||
|
case *idpintent.LDAPSucceededEvent:
|
||||||
|
wm.reduceLDAPSucceededEvent(e)
|
||||||
case *idpintent.FailedEvent:
|
case *idpintent.FailedEvent:
|
||||||
wm.reduceFailedEvent(e)
|
wm.reduceFailedEvent(e)
|
||||||
}
|
}
|
||||||
@ -59,6 +64,7 @@ func (wm *IDPIntentWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
EventTypes(
|
EventTypes(
|
||||||
idpintent.StartedEventType,
|
idpintent.StartedEventType,
|
||||||
idpintent.SucceededEventType,
|
idpintent.SucceededEventType,
|
||||||
|
idpintent.LDAPSucceededEventType,
|
||||||
idpintent.FailedEventType,
|
idpintent.FailedEventType,
|
||||||
).
|
).
|
||||||
Builder()
|
Builder()
|
||||||
@ -71,7 +77,16 @@ func (wm *IDPIntentWriteModel) reduceStartedEvent(e *idpintent.StartedEvent) {
|
|||||||
wm.State = domain.IDPIntentStateStarted
|
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.UserID = e.UserID
|
||||||
wm.IDPUser = e.IDPUser
|
wm.IDPUser = e.IDPUser
|
||||||
wm.IDPUserID = e.IDPUserID
|
wm.IDPUserID = e.IDPUserID
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
@ -199,9 +200,13 @@ func TestCommands_CreateIntent(t *testing.T) {
|
|||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore,
|
||||||
idGenerator: tt.fields.idGenerator,
|
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)
|
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)
|
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) {
|
func TestCommands_FailIDPIntent(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
|
@ -218,6 +218,14 @@ func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) GetSession(username, password string) *Session {
|
||||||
|
return &Session{
|
||||||
|
Provider: p,
|
||||||
|
User: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) IsLinkingAllowed() bool {
|
func (p *Provider) IsLinkingAllowed() bool {
|
||||||
return p.isLinkingAllowed
|
return p.isLinkingAllowed
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ type Session struct {
|
|||||||
loginUrl string
|
loginUrl string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
|
Entry *ldap.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) GetAuthURL() string {
|
func (s *Session) GetAuthURL() string {
|
||||||
@ -57,6 +58,7 @@ func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.Entry = user
|
||||||
|
|
||||||
return mapLDAPEntryToUser(
|
return mapLDAPEntryToUser(
|
||||||
user,
|
user,
|
||||||
|
@ -219,19 +219,19 @@ func TestProvider_mapLDAPEntryToUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
user: &User{
|
user: &User{
|
||||||
id: "",
|
ID: "",
|
||||||
firstName: "",
|
FirstName: "",
|
||||||
lastName: "",
|
LastName: "",
|
||||||
displayName: "",
|
DisplayName: "",
|
||||||
nickName: "",
|
NickName: "",
|
||||||
preferredUsername: "",
|
PreferredUsername: "",
|
||||||
email: "",
|
Email: "",
|
||||||
emailVerified: false,
|
EmailVerified: false,
|
||||||
phone: "",
|
Phone: "",
|
||||||
phoneVerified: false,
|
PhoneVerified: false,
|
||||||
preferredLanguage: language.Tag{},
|
PreferredLanguage: language.Tag{},
|
||||||
avatarURL: "",
|
AvatarURL: "",
|
||||||
profile: "",
|
Profile: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -351,19 +351,19 @@ func TestProvider_mapLDAPEntryToUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
user: &User{
|
user: &User{
|
||||||
id: "id",
|
ID: "id",
|
||||||
firstName: "first",
|
FirstName: "first",
|
||||||
lastName: "last",
|
LastName: "last",
|
||||||
displayName: "display",
|
DisplayName: "display",
|
||||||
nickName: "nick",
|
NickName: "nick",
|
||||||
preferredUsername: "preferred",
|
PreferredUsername: "preferred",
|
||||||
email: "email",
|
Email: "email",
|
||||||
emailVerified: false,
|
EmailVerified: false,
|
||||||
phone: "phone",
|
Phone: "phone",
|
||||||
phoneVerified: false,
|
PhoneVerified: false,
|
||||||
preferredLanguage: language.Make("und"),
|
PreferredLanguage: language.Make("und"),
|
||||||
avatarURL: "avatar",
|
AvatarURL: "avatar",
|
||||||
profile: "profile",
|
Profile: "profile",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -7,19 +7,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
id string
|
ID string `json:"id,omitempty"`
|
||||||
firstName string
|
FirstName string `json:"firstName,omitempty"`
|
||||||
lastName string
|
LastName string `json:"lastName,omitempty"`
|
||||||
displayName string
|
DisplayName string `json:"displayName,omitempty"`
|
||||||
nickName string
|
NickName string `json:"nickName,omitempty"`
|
||||||
preferredUsername string
|
PreferredUsername string `json:"preferredUsername,omitempty"`
|
||||||
email domain.EmailAddress
|
Email domain.EmailAddress `json:"email,omitempty"`
|
||||||
emailVerified bool
|
EmailVerified bool `json:"emailVerified,omitempty"`
|
||||||
phone domain.PhoneNumber
|
Phone domain.PhoneNumber `json:"phone,omitempty"`
|
||||||
phoneVerified bool
|
PhoneVerified bool `json:"phoneVerified,omitempty"`
|
||||||
preferredLanguage language.Tag
|
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
|
||||||
avatarURL string
|
AvatarURL string `json:"avatarURL,omitempty"`
|
||||||
profile string
|
Profile string `json:"profile,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(
|
func NewUser(
|
||||||
@ -55,41 +55,41 @@ func NewUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GetID() string {
|
func (u *User) GetID() string {
|
||||||
return u.id
|
return u.ID
|
||||||
}
|
}
|
||||||
func (u *User) GetFirstName() string {
|
func (u *User) GetFirstName() string {
|
||||||
return u.firstName
|
return u.FirstName
|
||||||
}
|
}
|
||||||
func (u *User) GetLastName() string {
|
func (u *User) GetLastName() string {
|
||||||
return u.lastName
|
return u.LastName
|
||||||
}
|
}
|
||||||
func (u *User) GetDisplayName() string {
|
func (u *User) GetDisplayName() string {
|
||||||
return u.displayName
|
return u.DisplayName
|
||||||
}
|
}
|
||||||
func (u *User) GetNickname() string {
|
func (u *User) GetNickname() string {
|
||||||
return u.nickName
|
return u.NickName
|
||||||
}
|
}
|
||||||
func (u *User) GetPreferredUsername() string {
|
func (u *User) GetPreferredUsername() string {
|
||||||
return u.preferredUsername
|
return u.PreferredUsername
|
||||||
}
|
}
|
||||||
func (u *User) GetEmail() domain.EmailAddress {
|
func (u *User) GetEmail() domain.EmailAddress {
|
||||||
return u.email
|
return u.Email
|
||||||
}
|
}
|
||||||
func (u *User) IsEmailVerified() bool {
|
func (u *User) IsEmailVerified() bool {
|
||||||
return u.emailVerified
|
return u.EmailVerified
|
||||||
}
|
}
|
||||||
func (u *User) GetPhone() domain.PhoneNumber {
|
func (u *User) GetPhone() domain.PhoneNumber {
|
||||||
return u.phone
|
return u.Phone
|
||||||
}
|
}
|
||||||
func (u *User) IsPhoneVerified() bool {
|
func (u *User) IsPhoneVerified() bool {
|
||||||
return u.phoneVerified
|
return u.PhoneVerified
|
||||||
}
|
}
|
||||||
func (u *User) GetPreferredLanguage() language.Tag {
|
func (u *User) GetPreferredLanguage() language.Tag {
|
||||||
return u.preferredLanguage
|
return u.PreferredLanguage
|
||||||
}
|
}
|
||||||
func (u *User) GetAvatarURL() string {
|
func (u *User) GetAvatarURL() string {
|
||||||
return u.avatarURL
|
return u.AvatarURL
|
||||||
}
|
}
|
||||||
func (u *User) GetProfile() string {
|
func (u *User) GetProfile() string {
|
||||||
return u.profile
|
return u.Profile
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,12 @@ import (
|
|||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||||
@ -196,12 +198,12 @@ func (s *Tester) AddGenericOAuthProvider(t *testing.T) string {
|
|||||||
|
|
||||||
func (s *Tester) CreateIntent(t *testing.T, idpID string) string {
|
func (s *Tester) CreateIntent(t *testing.T, idpID string) string {
|
||||||
ctx := authz.WithInstance(context.Background(), s.Instance)
|
ctx := authz.WithInstance(context.Background(), s.Instance)
|
||||||
id, _, err := s.Commands.CreateIntent(ctx, idpID, "https://example.com/success", "https://example.com/failure", s.Organisation.ID)
|
writeModel, _, err := s.Commands.CreateIntent(ctx, idpID, "https://example.com/success", "https://example.com/failure", s.Organisation.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return id
|
return writeModel.AggregateID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Tester) CreateSuccessfulIntent(t *testing.T, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
func (s *Tester) CreateSuccessfulOAuthIntent(t *testing.T, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||||
ctx := authz.WithInstance(context.Background(), s.Instance)
|
ctx := authz.WithInstance(context.Background(), s.Instance)
|
||||||
intentID := s.CreateIntent(t, idpID)
|
intentID := s.CreateIntent(t, idpID)
|
||||||
writeModel, err := s.Commands.GetIntentWriteModel(ctx, intentID, s.Organisation.ID)
|
writeModel, err := s.Commands.GetIntentWriteModel(ctx, intentID, s.Organisation.ID)
|
||||||
@ -227,6 +229,34 @@ func (s *Tester) CreateSuccessfulIntent(t *testing.T, idpID, userID, idpUserID s
|
|||||||
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Tester) CreateSuccessfulLDAPIntent(t *testing.T, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||||
|
ctx := authz.WithInstance(context.Background(), s.Instance)
|
||||||
|
intentID := s.CreateIntent(t, idpID)
|
||||||
|
writeModel, err := s.Commands.GetIntentWriteModel(ctx, intentID, s.Organisation.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
username := "username"
|
||||||
|
lang := language.Make("en")
|
||||||
|
idpUser := ldap.NewUser(
|
||||||
|
idpUserID,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
username,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
lang,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
attributes := map[string][]string{"id": {idpUserID}, "username": {username}, "language": {lang.String()}}
|
||||||
|
token, err := s.Commands.SucceedLDAPIDPIntent(ctx, writeModel, idpUser, userID, attributes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Tester) CreateVerfiedWebAuthNSession(t *testing.T, ctx context.Context, userID string) (id, token string, start, change time.Time) {
|
func (s *Tester) CreateVerfiedWebAuthNSession(t *testing.T, ctx context.Context, userID string) (id, token string, start, change time.Time) {
|
||||||
createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||||
Checks: &session.Checks{
|
Checks: &session.Checks{
|
||||||
|
@ -7,5 +7,6 @@ import (
|
|||||||
func RegisterEventMappers(es *eventstore.Eventstore) {
|
func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||||
es.RegisterFilterEventMapper(AggregateType, StartedEventType, StartedEventMapper).
|
es.RegisterFilterEventMapper(AggregateType, StartedEventType, StartedEventMapper).
|
||||||
RegisterFilterEventMapper(AggregateType, SucceededEventType, SucceededEventMapper).
|
RegisterFilterEventMapper(AggregateType, SucceededEventType, SucceededEventMapper).
|
||||||
|
RegisterFilterEventMapper(AggregateType, LDAPSucceededEventType, LDAPSucceededEventMapper).
|
||||||
RegisterFilterEventMapper(AggregateType, FailedEventType, FailedEventMapper)
|
RegisterFilterEventMapper(AggregateType, FailedEventType, FailedEventMapper)
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StartedEventType = instanceEventTypePrefix + "started"
|
StartedEventType = instanceEventTypePrefix + "started"
|
||||||
SucceededEventType = instanceEventTypePrefix + "succeeded"
|
SucceededEventType = instanceEventTypePrefix + "succeeded"
|
||||||
FailedEventType = instanceEventTypePrefix + "failed"
|
LDAPSucceededEventType = instanceEventTypePrefix + "ldap.succeeded"
|
||||||
|
FailedEventType = instanceEventTypePrefix + "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StartedEvent struct {
|
type StartedEvent struct {
|
||||||
@ -68,10 +69,11 @@ func StartedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
|||||||
type SucceededEvent struct {
|
type SucceededEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
IDPUser []byte `json:"idpUser"`
|
IDPUser []byte `json:"idpUser"`
|
||||||
IDPUserID string `json:"idpUserId,omitempty"`
|
IDPUserID string `json:"idpUserId,omitempty"`
|
||||||
IDPUserName string `json:"idpUserName,omitempty"`
|
IDPUserName string `json:"idpUserName,omitempty"`
|
||||||
UserID string `json:"userId,omitempty"`
|
UserID string `json:"userId,omitempty"`
|
||||||
|
|
||||||
IDPAccessToken *crypto.CryptoValue `json:"idpAccessToken,omitempty"`
|
IDPAccessToken *crypto.CryptoValue `json:"idpAccessToken,omitempty"`
|
||||||
IDPIDToken string `json:"idpIdToken,omitempty"`
|
IDPIDToken string `json:"idpIdToken,omitempty"`
|
||||||
}
|
}
|
||||||
@ -122,6 +124,61 @@ func SucceededEventMapper(event *repository.Event) (eventstore.Event, error) {
|
|||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LDAPSucceededEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
IDPUser []byte `json:"idpUser"`
|
||||||
|
IDPUserID string `json:"idpUserId,omitempty"`
|
||||||
|
IDPUserName string `json:"idpUserName,omitempty"`
|
||||||
|
UserID string `json:"userId,omitempty"`
|
||||||
|
|
||||||
|
EntryAttributes map[string][]string `json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLDAPSucceededEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
idpUser []byte,
|
||||||
|
idpUserID,
|
||||||
|
idpUserName,
|
||||||
|
userID string,
|
||||||
|
attributes map[string][]string,
|
||||||
|
) *LDAPSucceededEvent {
|
||||||
|
return &LDAPSucceededEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
LDAPSucceededEventType,
|
||||||
|
),
|
||||||
|
IDPUser: idpUser,
|
||||||
|
IDPUserID: idpUserID,
|
||||||
|
IDPUserName: idpUserName,
|
||||||
|
UserID: userID,
|
||||||
|
EntryAttributes: attributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *LDAPSucceededEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *LDAPSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LDAPSucceededEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||||
|
e := &LDAPSucceededEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "IDP-HBreq", "unable to unmarshal event")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
type FailedEvent struct {
|
type FailedEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Трябва да се добави поне един IDP
|
MinimumExternalIDPNeeded: Трябва да се добави поне един IDP
|
||||||
AlreadyExists: Външен IDP вече е зает
|
AlreadyExists: Външен IDP вече е зает
|
||||||
NotFound: Външен IDP не е намерен
|
NotFound: Външен IDP не е намерен
|
||||||
|
LoginFailed: Влизането във Външен IDP е неуспешно
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Многофакторният OTP (OneTimePassword) вече е настроен
|
AlreadyReady: Многофакторният OTP (OneTimePassword) вече е настроен
|
||||||
|
@ -136,7 +136,8 @@ Errors:
|
|||||||
NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt.
|
NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt.
|
||||||
MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden.
|
MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden.
|
||||||
AlreadyExists: External IDP ist bereits vergeben
|
AlreadyExists: External IDP ist bereits vergeben
|
||||||
NotFound: Externe IDP nicht gefunden
|
NotFound: Externer IDP nicht gefunden
|
||||||
|
LoginFailed: Externer IDP Login fehlgeschlagen
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet
|
AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: At least one IDP must be added
|
MinimumExternalIDPNeeded: At least one IDP must be added
|
||||||
AlreadyExists: External IDP already taken
|
AlreadyExists: External IDP already taken
|
||||||
NotFound: External IDP not found
|
NotFound: External IDP not found
|
||||||
|
LoginFailed: Login at External IDP failed
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Multifactor OTP (OneTimePassword) is already set up
|
AlreadyReady: Multifactor OTP (OneTimePassword) is already set up
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Al menos de añadirse un IDP
|
MinimumExternalIDPNeeded: Al menos de añadirse un IDP
|
||||||
AlreadyExists: IDP externo ya cogido
|
AlreadyExists: IDP externo ya cogido
|
||||||
NotFound: IDP no encontrado
|
NotFound: IDP no encontrado
|
||||||
|
LoginFailed: Error de inicio de sesión en IDP externo
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Multifactor OTP (OneTimePassword) ya está configurado
|
AlreadyReady: Multifactor OTP (OneTimePassword) ya está configurado
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Au moins un IDP doit être ajouté
|
MinimumExternalIDPNeeded: Au moins un IDP doit être ajouté
|
||||||
AlreadyExists: External IDP déjà pris
|
AlreadyExists: External IDP déjà pris
|
||||||
NotFound: IDP externe non trouvé
|
NotFound: IDP externe non trouvé
|
||||||
|
LoginFailed: Échec de la connexion à l'IDP externe
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: L'OTP (mot de passe à usage unique) multifactoriel est déjà configuré.
|
AlreadyReady: L'OTP (mot de passe à usage unique) multifactoriel est déjà configuré.
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Almeno un IDP deve essere aggiunto
|
MinimumExternalIDPNeeded: Almeno un IDP deve essere aggiunto
|
||||||
AlreadyExists: IDP esterno già preso
|
AlreadyExists: IDP esterno già preso
|
||||||
NotFound: IDP esterno non trovato
|
NotFound: IDP esterno non trovato
|
||||||
|
LoginFailed: Accesso all'IDP esterno non riuscito
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Multifattore OTP (OneTimePassword) è già impostato
|
AlreadyReady: Multifattore OTP (OneTimePassword) è già impostato
|
||||||
|
@ -129,6 +129,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: 少なくとも1つのIDPを追加する必要があります
|
MinimumExternalIDPNeeded: 少なくとも1つのIDPを追加する必要があります
|
||||||
AlreadyExists: 外部IDPはすでに使用されています
|
AlreadyExists: 外部IDPはすでに使用されています
|
||||||
NotFound: 外部IDPが見つかりません
|
NotFound: 外部IDPが見つかりません
|
||||||
|
LoginFailed: 外部IDPでのログインに失敗
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: 多要素OTP(ワンタイムパスワード)は設定済みです
|
AlreadyReady: 多要素OTP(ワンタイムパスワード)は設定済みです
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Мора да се додаде најмалку еден надворешен IDP
|
MinimumExternalIDPNeeded: Мора да се додаде најмалку еден надворешен IDP
|
||||||
AlreadyExists: Надворешниот IDP е веќе зафатен
|
AlreadyExists: Надворешниот IDP е веќе зафатен
|
||||||
NotFound: Надворешниот IDP не е пронајден
|
NotFound: Надворешниот IDP не е пронајден
|
||||||
|
LoginFailed: Пријавувањето на Надворешниот ВРЛ не успеа
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Мултифактор OTP (Еднократна Лозинка) e веќе поставен
|
AlreadyReady: Мултифактор OTP (Еднократна Лозинка) e веќе поставен
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: Przynajmniej jeden IDP musi być dodany
|
MinimumExternalIDPNeeded: Przynajmniej jeden IDP musi być dodany
|
||||||
AlreadyExists: IDP zewnętrzne już istnieje
|
AlreadyExists: IDP zewnętrzne już istnieje
|
||||||
NotFound: IDP zewnętrzne nie znaleziony
|
NotFound: IDP zewnętrzne nie znaleziony
|
||||||
|
LoginFailed: Logowanie w zewnętrznym IDP nie powiodło się
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: Wieloskładnikowe OTP (OneTimePassword) jest już skonfigurowane
|
AlreadyReady: Wieloskładnikowe OTP (OneTimePassword) jest już skonfigurowane
|
||||||
|
@ -137,6 +137,7 @@ Errors:
|
|||||||
MinimumExternalIDPNeeded: 必须添加至少一个 IDP
|
MinimumExternalIDPNeeded: 必须添加至少一个 IDP
|
||||||
AlreadyExists: 外部 IDP 已存在
|
AlreadyExists: 外部 IDP 已存在
|
||||||
NotFound: 未找到外部 IDP
|
NotFound: 未找到外部 IDP
|
||||||
|
LoginFailed: 外部 IDP 登录失败
|
||||||
MFA:
|
MFA:
|
||||||
OTP:
|
OTP:
|
||||||
AlreadyReady: OTP (一次性密码) 已经设置好了
|
AlreadyReady: OTP (一次性密码) 已经设置好了
|
||||||
|
@ -9,6 +9,67 @@ import "google/protobuf/struct.proto";
|
|||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
|
|
||||||
|
message LDAPCredentials {
|
||||||
|
string username = 1[
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Username used to login through LDAP"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"username\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string password = 2[
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Password used to login through LDAP"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"Password1!\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RedirectURLs {
|
||||||
|
string success_url = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "URL on which the user will be redirected after a successful login"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"https://custom.com/login/idp/success\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string failure_url = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "URL on which the user will be redirected after a failed login"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"https://custom.com/login/idp/fail\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Intent {
|
||||||
|
string intent_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "ID of the intent"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"163840776835432705=\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string token = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "token of the intent"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"SJKL3ioIDpo342ioqw98fjp3sdf32wahb=\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
message IDPInformation{
|
message IDPInformation{
|
||||||
oneof access{
|
oneof access{
|
||||||
IDPOAuthAccessInformation oauth = 1 [
|
IDPOAuthAccessInformation oauth = 1 [
|
||||||
@ -16,6 +77,11 @@ message IDPInformation{
|
|||||||
description: "OAuth/OIDC access (and id_token) returned by the identity provider"
|
description: "OAuth/OIDC access (and id_token) returned by the identity provider"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
IDPLDAPAccessInformation ldap = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "LDAP entity attributes returned by the identity provider"
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
string idp_id = 2 [
|
string idp_id = 2 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
@ -47,6 +113,10 @@ message IDPOAuthAccessInformation{
|
|||||||
optional string id_token = 2;
|
optional string id_token = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message IDPLDAPAccessInformation{
|
||||||
|
google.protobuf.Struct attributes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message IDPLink {
|
message IDPLink {
|
||||||
string idp_id = 1 [
|
string idp_id = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
@ -1082,24 +1082,11 @@ message StartIdentityProviderFlowRequest{
|
|||||||
example: "\"163840776835432705\"";
|
example: "\"163840776835432705\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
string success_url = 2 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
oneof content {
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
RedirectURLs urls = 2;
|
||||||
description: "URL on which the user will be redirected after a successful login"
|
LDAPCredentials ldap = 3;
|
||||||
min_length: 1;
|
}
|
||||||
max_length: 200;
|
|
||||||
example: "\"https://custom.com/login/idp/success\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
string failure_url = 3 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200, uri_ref: true},
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
description: "URL on which the user will be redirected after a failed login"
|
|
||||||
min_length: 1;
|
|
||||||
max_length: 200;
|
|
||||||
example: "\"https://custom.com/login/idp/fail\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartIdentityProviderFlowResponse{
|
message StartIdentityProviderFlowResponse{
|
||||||
@ -1111,6 +1098,11 @@ message StartIdentityProviderFlowResponse{
|
|||||||
example: "\"https://accounts.google.com/o/oauth2/v2/auth?client_id=clientID&callback=https%3A%2F%2Fzitadel.cloud%2Fidps%2Fcallback\"";
|
example: "\"https://accounts.google.com/o/oauth2/v2/auth?client_id=clientID&callback=https%3A%2F%2Fzitadel.cloud%2Fidps%2Fcallback\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
Intent intent = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Intent information"
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user