mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 19:07:26 +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)
|
||||
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{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
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)
|
||||
|
||||
idpUserID := "id"
|
||||
intentID, token, _, _ := Tester.CreateSuccessfulIntent(t, idpID, "", idpUserID)
|
||||
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", idpUserID)
|
||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
SessionToken: createResp.GetSessionToken(),
|
||||
|
@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
errs "errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
@ -14,6 +15,9 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"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"
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -140,6 +155,79 @@ func (s *Server) StartIdentityProviderFlow(ctx context.Context, req *user.StartI
|
||||
}, 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) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIntentId(), authz.GetCtxData(ctx).OrgID)
|
||||
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) {
|
||||
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)
|
||||
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.RetrieveIdentityProviderInformationResponse{
|
||||
Details: &object_pb.Details{
|
||||
Sequence: intent.ProcessedSequence,
|
||||
ChangeDate: timestamppb.New(intent.ChangeDate),
|
||||
ResourceOwner: intent.ResourceOwner,
|
||||
},
|
||||
information := &user.RetrieveIdentityProviderInformationResponse{
|
||||
Details: intentToDetailsPb(intent),
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Oauth{
|
||||
Oauth: &user.IDPOAuthAccessInformation{
|
||||
AccessToken: accessToken,
|
||||
IdToken: idToken,
|
||||
},
|
||||
},
|
||||
IdpId: intent.IDPID,
|
||||
UserId: intent.IDPUserID,
|
||||
UserName: intent.IDPUserName,
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -650,9 +650,13 @@ func TestServer_StartIdentityProviderFlow(t *testing.T) {
|
||||
args: args{
|
||||
CTX,
|
||||
&user.StartIdentityProviderFlowRequest{
|
||||
IdpId: idpID,
|
||||
SuccessUrl: "https://example.com/success",
|
||||
FailureUrl: "https://example.com/failure",
|
||||
IdpId: idpID,
|
||||
Content: &user.StartIdentityProviderFlowRequest_Urls{
|
||||
Urls: &user.RedirectURLs{
|
||||
SuccessUrl: "https://example.com/success",
|
||||
FailureUrl: "https://example.com/failure",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.StartIdentityProviderFlowResponse{
|
||||
@ -689,7 +693,8 @@ func TestServer_StartIdentityProviderFlow(t *testing.T) {
|
||||
func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
||||
idpID := Tester.AddGenericOAuthProvider(t)
|
||||
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 {
|
||||
ctx context.Context
|
||||
req *user.RetrieveIdentityProviderInformationRequest
|
||||
@ -759,6 +764,51 @@ func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
||||
},
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -769,7 +819,7 @@ func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
|
||||
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",
|
||||
Crypted: []byte("accessToken"),
|
||||
},
|
||||
IDPIDToken: "idToken",
|
||||
UserID: "userID",
|
||||
State: domain.IDPIntentStateSucceeded,
|
||||
IDPIDToken: "idToken",
|
||||
IDPEntryAttributes: map[string][]string{},
|
||||
UserID: "userID",
|
||||
State: domain.IDPIntentStateSucceeded,
|
||||
},
|
||||
alg: decryption(caos_errs.ThrowInternal(nil, "id", "invalid key id")),
|
||||
},
|
||||
@ -85,7 +86,7 @@ func Test_intentToIDPInformationPb(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"successful",
|
||||
"successful oauth",
|
||||
args{
|
||||
intent: &command.IDPIntentWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
@ -140,16 +141,73 @@ func Test_intentToIDPInformationPb(t *testing.T) {
|
||||
},
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := intentToIDPInformationPb(tt.args.intent, tt.args.alg)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
grpc.AllFieldsEqual(t, got.ProtoReflect(), tt.res.resp.ProtoReflect(), grpc.CustomMappers)
|
||||
if tt.res.resp != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect(), ignoreTypes...)
|
||||
}
|
||||
grpc.AllFieldsEqual(t, tt.res.resp.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
HandlerPrefix = "/idps"
|
||||
callbackPath = "/callback"
|
||||
HandlerPrefix = "/idps"
|
||||
callbackPath = "/callback"
|
||||
ldapCallbackPath = callbackPath + "/ldap"
|
||||
|
||||
paramIntentID = "id"
|
||||
paramToken = "token"
|
||||
@ -82,18 +83,22 @@ func NewHandler(
|
||||
}
|
||||
|
||||
func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
data, err := h.parseCallbackRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
intent := h.getActiveIntent(w, r, data.State)
|
||||
if intent == nil {
|
||||
// if we didn't get an active intent the error was already handled (either redirected or display directly)
|
||||
intent, err := h.commands.GetActiveIntent(ctx, data.State)
|
||||
if err != nil {
|
||||
if z_errs.IsNotFound(err) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
redirectToFailureURLErr(w, r, intent, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
// the provider might have returned an error
|
||||
if data.Error != "" {
|
||||
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()
|
||||
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
|
||||
|
@ -218,6 +218,14 @@ func (p *Provider) BeginAuth(ctx context.Context, state string, params ...any) (
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetSession(username, password string) *Session {
|
||||
return &Session{
|
||||
Provider: p,
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) IsLinkingAllowed() bool {
|
||||
return p.isLinkingAllowed
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ type Session struct {
|
||||
loginUrl string
|
||||
User string
|
||||
Password string
|
||||
Entry *ldap.Entry
|
||||
}
|
||||
|
||||
func (s *Session) GetAuthURL() string {
|
||||
@ -57,6 +58,7 @@ func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Entry = user
|
||||
|
||||
return mapLDAPEntryToUser(
|
||||
user,
|
||||
|
@ -219,19 +219,19 @@ func TestProvider_mapLDAPEntryToUser(t *testing.T) {
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
id: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
displayName: "",
|
||||
nickName: "",
|
||||
preferredUsername: "",
|
||||
email: "",
|
||||
emailVerified: false,
|
||||
phone: "",
|
||||
phoneVerified: false,
|
||||
preferredLanguage: language.Tag{},
|
||||
avatarURL: "",
|
||||
profile: "",
|
||||
ID: "",
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
DisplayName: "",
|
||||
NickName: "",
|
||||
PreferredUsername: "",
|
||||
Email: "",
|
||||
EmailVerified: false,
|
||||
Phone: "",
|
||||
PhoneVerified: false,
|
||||
PreferredLanguage: language.Tag{},
|
||||
AvatarURL: "",
|
||||
Profile: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -351,19 +351,19 @@ func TestProvider_mapLDAPEntryToUser(t *testing.T) {
|
||||
},
|
||||
want: want{
|
||||
user: &User{
|
||||
id: "id",
|
||||
firstName: "first",
|
||||
lastName: "last",
|
||||
displayName: "display",
|
||||
nickName: "nick",
|
||||
preferredUsername: "preferred",
|
||||
email: "email",
|
||||
emailVerified: false,
|
||||
phone: "phone",
|
||||
phoneVerified: false,
|
||||
preferredLanguage: language.Make("und"),
|
||||
avatarURL: "avatar",
|
||||
profile: "profile",
|
||||
ID: "id",
|
||||
FirstName: "first",
|
||||
LastName: "last",
|
||||
DisplayName: "display",
|
||||
NickName: "nick",
|
||||
PreferredUsername: "preferred",
|
||||
Email: "email",
|
||||
EmailVerified: false,
|
||||
Phone: "phone",
|
||||
PhoneVerified: false,
|
||||
PreferredLanguage: language.Make("und"),
|
||||
AvatarURL: "avatar",
|
||||
Profile: "profile",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -7,19 +7,19 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
id string
|
||||
firstName string
|
||||
lastName string
|
||||
displayName string
|
||||
nickName string
|
||||
preferredUsername string
|
||||
email domain.EmailAddress
|
||||
emailVerified bool
|
||||
phone domain.PhoneNumber
|
||||
phoneVerified bool
|
||||
preferredLanguage language.Tag
|
||||
avatarURL string
|
||||
profile string
|
||||
ID string `json:"id,omitempty"`
|
||||
FirstName string `json:"firstName,omitempty"`
|
||||
LastName string `json:"lastName,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
NickName string `json:"nickName,omitempty"`
|
||||
PreferredUsername string `json:"preferredUsername,omitempty"`
|
||||
Email domain.EmailAddress `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"emailVerified,omitempty"`
|
||||
Phone domain.PhoneNumber `json:"phone,omitempty"`
|
||||
PhoneVerified bool `json:"phoneVerified,omitempty"`
|
||||
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
|
||||
AvatarURL string `json:"avatarURL,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
}
|
||||
|
||||
func NewUser(
|
||||
@ -55,41 +55,41 @@ func NewUser(
|
||||
}
|
||||
|
||||
func (u *User) GetID() string {
|
||||
return u.id
|
||||
return u.ID
|
||||
}
|
||||
func (u *User) GetFirstName() string {
|
||||
return u.firstName
|
||||
return u.FirstName
|
||||
}
|
||||
func (u *User) GetLastName() string {
|
||||
return u.lastName
|
||||
return u.LastName
|
||||
}
|
||||
func (u *User) GetDisplayName() string {
|
||||
return u.displayName
|
||||
return u.DisplayName
|
||||
}
|
||||
func (u *User) GetNickname() string {
|
||||
return u.nickName
|
||||
return u.NickName
|
||||
}
|
||||
func (u *User) GetPreferredUsername() string {
|
||||
return u.preferredUsername
|
||||
return u.PreferredUsername
|
||||
}
|
||||
func (u *User) GetEmail() domain.EmailAddress {
|
||||
return u.email
|
||||
return u.Email
|
||||
}
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
return u.emailVerified
|
||||
return u.EmailVerified
|
||||
}
|
||||
func (u *User) GetPhone() domain.PhoneNumber {
|
||||
return u.phone
|
||||
return u.Phone
|
||||
}
|
||||
func (u *User) IsPhoneVerified() bool {
|
||||
return u.phoneVerified
|
||||
return u.PhoneVerified
|
||||
}
|
||||
func (u *User) GetPreferredLanguage() language.Tag {
|
||||
return u.preferredLanguage
|
||||
return u.PreferredLanguage
|
||||
}
|
||||
func (u *User) GetAvatarURL() string {
|
||||
return u.avatarURL
|
||||
return u.AvatarURL
|
||||
}
|
||||
func (u *User) GetProfile() string {
|
||||
return u.profile
|
||||
return u.Profile
|
||||
}
|
||||
|
@ -10,10 +10,12 @@ import (
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"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 {
|
||||
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)
|
||||
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)
|
||||
intentID := s.CreateIntent(t, idpID)
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
|
@ -7,5 +7,6 @@ import (
|
||||
func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
es.RegisterFilterEventMapper(AggregateType, StartedEventType, StartedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, SucceededEventType, SucceededEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, LDAPSucceededEventType, LDAPSucceededEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, FailedEventType, FailedEventMapper)
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
StartedEventType = instanceEventTypePrefix + "started"
|
||||
SucceededEventType = instanceEventTypePrefix + "succeeded"
|
||||
FailedEventType = instanceEventTypePrefix + "failed"
|
||||
StartedEventType = instanceEventTypePrefix + "started"
|
||||
SucceededEventType = instanceEventTypePrefix + "succeeded"
|
||||
LDAPSucceededEventType = instanceEventTypePrefix + "ldap.succeeded"
|
||||
FailedEventType = instanceEventTypePrefix + "failed"
|
||||
)
|
||||
|
||||
type StartedEvent struct {
|
||||
@ -68,10 +69,11 @@ func StartedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
type SucceededEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
IDPUser []byte `json:"idpUser"`
|
||||
IDPUserID string `json:"idpUserId,omitempty"`
|
||||
IDPUserName string `json:"idpUserName,omitempty"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
IDPUser []byte `json:"idpUser"`
|
||||
IDPUserID string `json:"idpUserId,omitempty"`
|
||||
IDPUserName string `json:"idpUserName,omitempty"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
|
||||
IDPAccessToken *crypto.CryptoValue `json:"idpAccessToken,omitempty"`
|
||||
IDPIDToken string `json:"idpIdToken,omitempty"`
|
||||
}
|
||||
@ -122,6 +124,61 @@ func SucceededEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
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 {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
|
@ -139,6 +139,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: Трябва да се добави поне един IDP
|
||||
AlreadyExists: Външен IDP вече е зает
|
||||
NotFound: Външен IDP не е намерен
|
||||
LoginFailed: Влизането във Външен IDP е неуспешно
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Многофакторният OTP (OneTimePassword) вече е настроен
|
||||
|
@ -136,7 +136,8 @@ Errors:
|
||||
NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt.
|
||||
MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden.
|
||||
AlreadyExists: External IDP ist bereits vergeben
|
||||
NotFound: Externe IDP nicht gefunden
|
||||
NotFound: Externer IDP nicht gefunden
|
||||
LoginFailed: Externer IDP Login fehlgeschlagen
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: At least one IDP must be added
|
||||
AlreadyExists: External IDP already taken
|
||||
NotFound: External IDP not found
|
||||
LoginFailed: Login at External IDP failed
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Multifactor OTP (OneTimePassword) is already set up
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: Al menos de añadirse un IDP
|
||||
AlreadyExists: IDP externo ya cogido
|
||||
NotFound: IDP no encontrado
|
||||
LoginFailed: Error de inicio de sesión en IDP externo
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Multifactor OTP (OneTimePassword) ya está configurado
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: Au moins un IDP doit être ajouté
|
||||
AlreadyExists: External IDP déjà pris
|
||||
NotFound: IDP externe non trouvé
|
||||
LoginFailed: Échec de la connexion à l'IDP externe
|
||||
MFA:
|
||||
OTP:
|
||||
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
|
||||
AlreadyExists: IDP esterno già preso
|
||||
NotFound: IDP esterno non trovato
|
||||
LoginFailed: Accesso all'IDP esterno non riuscito
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Multifattore OTP (OneTimePassword) è già impostato
|
||||
|
@ -129,6 +129,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: 少なくとも1つのIDPを追加する必要があります
|
||||
AlreadyExists: 外部IDPはすでに使用されています
|
||||
NotFound: 外部IDPが見つかりません
|
||||
LoginFailed: 外部IDPでのログインに失敗
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: 多要素OTP(ワンタイムパスワード)は設定済みです
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: Мора да се додаде најмалку еден надворешен IDP
|
||||
AlreadyExists: Надворешниот IDP е веќе зафатен
|
||||
NotFound: Надворешниот IDP не е пронајден
|
||||
LoginFailed: Пријавувањето на Надворешниот ВРЛ не успеа
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Мултифактор OTP (Еднократна Лозинка) e веќе поставен
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: Przynajmniej jeden IDP musi być dodany
|
||||
AlreadyExists: IDP zewnętrzne już istnieje
|
||||
NotFound: IDP zewnętrzne nie znaleziony
|
||||
LoginFailed: Logowanie w zewnętrznym IDP nie powiodło się
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: Wieloskładnikowe OTP (OneTimePassword) jest już skonfigurowane
|
||||
|
@ -137,6 +137,7 @@ Errors:
|
||||
MinimumExternalIDPNeeded: 必须添加至少一个 IDP
|
||||
AlreadyExists: 外部 IDP 已存在
|
||||
NotFound: 未找到外部 IDP
|
||||
LoginFailed: 外部 IDP 登录失败
|
||||
MFA:
|
||||
OTP:
|
||||
AlreadyReady: OTP (一次性密码) 已经设置好了
|
||||
|
@ -9,6 +9,67 @@ import "google/protobuf/struct.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.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{
|
||||
oneof access{
|
||||
IDPOAuthAccessInformation oauth = 1 [
|
||||
@ -16,6 +77,11 @@ message IDPInformation{
|
||||
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 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
@ -47,6 +113,10 @@ message IDPOAuthAccessInformation{
|
||||
optional string id_token = 2;
|
||||
}
|
||||
|
||||
message IDPLDAPAccessInformation{
|
||||
google.protobuf.Struct attributes = 1;
|
||||
}
|
||||
|
||||
message IDPLink {
|
||||
string idp_id = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
|
@ -1082,24 +1082,11 @@ message StartIdentityProviderFlowRequest{
|
||||
example: "\"163840776835432705\"";
|
||||
}
|
||||
];
|
||||
string success_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 successful login"
|
||||
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\"";
|
||||
}
|
||||
];
|
||||
|
||||
oneof content {
|
||||
RedirectURLs urls = 2;
|
||||
LDAPCredentials ldap = 3;
|
||||
}
|
||||
}
|
||||
|
||||
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\"";
|
||||
}
|
||||
];
|
||||
Intent intent = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Intent information"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user