mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
feat: actions v2 for functions (#9420)
# Which Problems Are Solved Actions v2 are not executed in different functions, as provided by the actions v1. # How the Problems Are Solved Add functionality to call actions v2 through OIDC and SAML logic to complement tokens and SAMLResponses. # Additional Changes - Corrected testing for retrieved intent information - Added testing for IDP types - Corrected handling of context for issuer in SAML logic # Additional Context - Closes #7247 - Dependent on https://github.com/zitadel/saml/pull/97 - docs for migration are done in separate issue: https://github.com/zitadel/zitadel/issues/9456 --------- Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,6 @@ import (
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/sink"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
@@ -2114,18 +2113,29 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
intentID := Instance.CreateIntent(CTX, idpID).GetIdpIntent().GetIdpIntentId()
|
||||
oauthIdpID := Instance.AddGenericOAuthProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
oidcIdpID := Instance.AddGenericOIDCProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
samlIdpID := Instance.AddSAMLPostProvider(IamCTX)
|
||||
ldapIdpID := Instance.AddLDAPProvider(IamCTX)
|
||||
authURL, err := url.Parse(Instance.CreateIntent(CTX, oauthIdpID).GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
intentID := authURL.Query().Get("state")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", "")
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", "user")
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), idpID, "id", "")
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), idpID, "id", "user")
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), idpID, "id", "")
|
||||
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -2160,7 +2170,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful intent",
|
||||
name: "retrieve successful oauth intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
@@ -2181,18 +2191,31 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: oauthIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"sub": "id",
|
||||
"preferred_username": "username",
|
||||
"RawInfo": map[string]interface{}{
|
||||
"id": "id",
|
||||
"preferred_username": "username",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
AddHumanUser: &user.AddHumanUserRequest{
|
||||
Profile: &user.SetHumanProfile{
|
||||
PreferredLanguage: gu.Ptr("und"),
|
||||
},
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{IdpId: oauthIdpID, UserId: "id"},
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -2219,7 +2242,97 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: oauthIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"RawInfo": map[string]interface{}{
|
||||
"id": "id",
|
||||
"preferred_username": "username",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: oidcSuccessful,
|
||||
IdpIntentToken: oidcToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(oidcChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: oidcSequence,
|
||||
},
|
||||
UserId: "",
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Oauth{
|
||||
Oauth: &user.IDPOAuthAccessInformation{
|
||||
AccessToken: "accessToken",
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: oidcIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"sub": "id",
|
||||
"preferred_username": "username",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
AddHumanUser: &user.AddHumanUserRequest{
|
||||
Username: gu.Ptr("username"),
|
||||
Profile: &user.SetHumanProfile{
|
||||
PreferredLanguage: gu.Ptr("und"),
|
||||
},
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{IdpId: oidcIdpID, UserId: "id", UserName: "username"},
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent with linked user",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: oidcSuccessfulWithUserID,
|
||||
IdpIntentToken: oidcWithUserIDToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(oidcWithUserIDChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: oidcWithUserIDSequence,
|
||||
},
|
||||
UserId: "user",
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Oauth{
|
||||
Oauth: &user.IDPOAuthAccessInformation{
|
||||
AccessToken: "accessToken",
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: oidcIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2263,7 +2376,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
}(),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: ldapIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2276,6 +2389,18 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
AddHumanUser: &user.AddHumanUserRequest{
|
||||
Username: gu.Ptr("username"),
|
||||
Profile: &user.SetHumanProfile{
|
||||
PreferredLanguage: gu.Ptr("en"),
|
||||
},
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{IdpId: ldapIdpID, UserId: "id", UserName: "username"},
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -2309,7 +2434,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
}(),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: ldapIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2346,7 +2471,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
Assertion: []byte("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id\" IssueInstant=\"0001-01-01T00:00:00Z\" Version=\"\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"\" SPNameQualifier=\"\" Format=\"\" SPProvidedID=\"\"></Issuer></Assertion>"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: samlIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2360,6 +2485,56 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
AddHumanUser: &user.AddHumanUserRequest{
|
||||
Profile: &user.SetHumanProfile{
|
||||
PreferredLanguage: gu.Ptr("und"),
|
||||
},
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{IdpId: samlIdpID, UserId: "id"},
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Verification: &user.SetHumanEmail_SendCode{SendCode: &user.SendEmailVerificationCode{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful saml intent with linked user",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: samlSuccessfulWithUserID,
|
||||
IdpIntentToken: samlWithUserToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(samlWithUserChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: samlWithUserSequence,
|
||||
},
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Saml{
|
||||
Saml: &user.IDPSAMLAccessInformation{
|
||||
Assertion: []byte("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id\" IssueInstant=\"0001-01-01T00:00:00Z\" Version=\"\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"\" SPNameQualifier=\"\" Format=\"\" SPProvidedID=\"\"></Issuer></Assertion>"),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"id": "id",
|
||||
"attributes": map[string]interface{}{
|
||||
"attribute1": []interface{}{"value1"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
UserId: "user",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -2369,11 +2544,11 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
got, err := Client.RetrieveIdentityProviderIntent(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
grpc.AllFieldsEqual(t, tt.want.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
assert.EqualExportedValues(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
370
internal/api/grpc/user/v2/intent.go
Normal file
370
internal/api/grpc/user/v2/intent.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
oidc_pkg "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/google"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *user.StartIdentityProviderIntentRequest) (_ *user.StartIdentityProviderIntentResponse, err error) {
|
||||
switch t := req.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.GetIdpId(), t.Urls)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-S2g21", "type oneOf %T in method StartIdentityProviderIntent not implemented", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), session.PersistentParameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, redirect := session.GetAuth(ctx)
|
||||
if redirect {
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
||||
}, nil
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
||||
PostForm: []byte(content),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
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.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{
|
||||
IdpIntent: &user.IDPIntent{
|
||||
IdpIntentId: intentWriteModel.AggregateID,
|
||||
IdpIntentToken: token,
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
}, 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}, nil)
|
||||
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, zerrors.ThrowInvalidArgument(nil, "IDP-9a02j2n2bh", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||
}
|
||||
session := ldapProvider.GetSession(username, password)
|
||||
externalUser, err := session.FetchUser(ctx)
|
||||
if errors.Is(err, ldap.ErrFailedLogin) || errors.Is(err, ldap.ErrNoSingleUser) {
|
||||
return nil, "", nil, zerrors.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) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIdpIntentId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.checkIntentToken(req.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-nme4gszsvx", "Errors.Intent.NotSucceeded")
|
||||
}
|
||||
idpIntent, err := idpIntentToIDPIntentPb(intent, s.idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idpIntent.UserId == "" {
|
||||
provider, err := s.command.GetProvider(ctx, idpIntent.IdpInformation.IdpId, "", "")
|
||||
if err != nil && !errors.Is(err, oidc_pkg.ErrDiscoveryFailed) {
|
||||
return nil, err
|
||||
}
|
||||
var idpUser idp.User
|
||||
switch p := provider.(type) {
|
||||
case *apple.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &apple.User{})
|
||||
case *oauth.Provider:
|
||||
idpUser, err = unmarshalRawIdpUser(intent.IDPUser, p.User())
|
||||
case *oidc.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &oidc.User{UserInfo: &oidc_pkg.UserInfo{}})
|
||||
case *jwt.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &jwt.User{})
|
||||
case *azuread.Provider:
|
||||
idpUser, err = unmarshalRawIdpUser(intent.IDPUser, p.User())
|
||||
case *github.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &github.User{})
|
||||
case *gitlab.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &oidc.User{UserInfo: &oidc_pkg.UserInfo{}})
|
||||
case *google.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &oidc.User{UserInfo: &oidc_pkg.UserInfo{}})
|
||||
case *saml.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &saml.UserMapper{})
|
||||
case *ldap.Provider:
|
||||
idpUser, err = unmarshalIdpUser(intent.IDPUser, &ldap.User{})
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "IDP-7rPBbls4Zn", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpIntent.AddHumanUser = idpUserToAddHumanUser(idpUser, idpIntent.IdpInformation.IdpId)
|
||||
}
|
||||
return idpIntent, nil
|
||||
}
|
||||
|
||||
type rawUserMapper struct {
|
||||
RawInfo map[string]interface{}
|
||||
}
|
||||
|
||||
func unmarshalRawIdpUser(idpUserData []byte, idpUser idp.User) (idp.User, error) {
|
||||
userMapper := &rawUserMapper{}
|
||||
if err := json.Unmarshal(idpUserData, userMapper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpUserData, err := json.Marshal(userMapper.RawInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return unmarshalIdpUser(idpUserData, idpUser)
|
||||
}
|
||||
|
||||
func unmarshalIdpUser(idpUserData []byte, idpUser idp.User) (idp.User, error) {
|
||||
if err := json.Unmarshal(idpUserData, idpUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return idpUser, nil
|
||||
}
|
||||
|
||||
func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
rawInformation := new(structpb.Struct)
|
||||
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
information := &user.RetrieveIdentityProviderIntentResponse{
|
||||
IdpInformation: &user.IDPInformation{
|
||||
IdpId: intent.IDPID,
|
||||
UserId: intent.IDPUserID,
|
||||
UserName: intent.IDPUserName,
|
||||
RawInformation: rawInformation,
|
||||
},
|
||||
UserId: intent.UserID,
|
||||
}
|
||||
information.Details = intentToDetailsPb(intent)
|
||||
// OAuth / OIDC
|
||||
if intent.IDPIDToken != "" || intent.IDPAccessToken != nil {
|
||||
information.IdpInformation.Access, err = idpOAuthTokensToPb(intent.IDPIDToken, intent.IDPAccessToken, alg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// LDAP
|
||||
if intent.IDPEntryAttributes != nil {
|
||||
access, err := IDPEntryAttributesToPb(intent.IDPEntryAttributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
information.IdpInformation.Access = access
|
||||
}
|
||||
// SAML
|
||||
if intent.Assertion != nil {
|
||||
assertion, err := crypto.Decrypt(intent.Assertion, alg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
information.IdpInformation.Access = IDPSAMLResponseToPb(assertion)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func IDPSAMLResponseToPb(assertion []byte) *user.IDPInformation_Saml {
|
||||
return &user.IDPInformation_Saml{
|
||||
Saml: &user.IDPSAMLAccessInformation{
|
||||
Assertion: assertion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) checkIntentToken(token string, intentID string) error {
|
||||
return crypto.CheckToken(s.idpAlg, token, intentID)
|
||||
}
|
||||
|
||||
func idpUserToAddHumanUser(idpUser idp.User, idpID string) *user.AddHumanUserRequest {
|
||||
addHumanUser := &user.AddHumanUserRequest{
|
||||
Profile: &user.SetHumanProfile{
|
||||
GivenName: idpUser.GetFirstName(),
|
||||
FamilyName: idpUser.GetLastName(),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: string(idpUser.GetEmail()),
|
||||
Verification: &user.SetHumanEmail_SendCode{},
|
||||
},
|
||||
Metadata: make([]*user.SetMetadataEntry, 0),
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{
|
||||
IdpId: idpID,
|
||||
UserId: idpUser.GetID(),
|
||||
UserName: idpUser.GetPreferredUsername(),
|
||||
},
|
||||
},
|
||||
}
|
||||
if username := idpUser.GetPreferredUsername(); username != "" {
|
||||
addHumanUser.Username = &username
|
||||
}
|
||||
if nickName := idpUser.GetNickname(); nickName != "" {
|
||||
addHumanUser.Profile.NickName = &nickName
|
||||
}
|
||||
if displayName := idpUser.GetDisplayName(); displayName != "" {
|
||||
addHumanUser.Profile.DisplayName = &displayName
|
||||
}
|
||||
if lang := idpUser.GetPreferredLanguage().String(); lang != "" {
|
||||
addHumanUser.Profile.PreferredLanguage = &lang
|
||||
}
|
||||
if isEmailVerified := idpUser.IsEmailVerified(); isEmailVerified {
|
||||
addHumanUser.Email.Verification = &user.SetHumanEmail_IsVerified{IsVerified: isEmailVerified}
|
||||
}
|
||||
if phone := idpUser.GetPhone(); phone != "" {
|
||||
addHumanUser.Phone = &user.SetHumanPhone{
|
||||
Phone: string(phone),
|
||||
Verification: &user.SetHumanPhone_SendCode{},
|
||||
}
|
||||
if isPhoneVerified := idpUser.IsPhoneVerified(); isPhoneVerified {
|
||||
addHumanUser.Phone.Verification = &user.SetHumanPhone_IsVerified{IsVerified: isPhoneVerified}
|
||||
}
|
||||
}
|
||||
return addHumanUser
|
||||
}
|
@@ -2,28 +2,19 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest) (_ *user.AddHumanUserResponse, err error) {
|
||||
|
||||
human, err := AddUserRequestToAddHuman(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -356,236 +347,6 @@ func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||
return converted
|
||||
}
|
||||
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *user.StartIdentityProviderIntentRequest) (_ *user.StartIdentityProviderIntentResponse, err error) {
|
||||
switch t := req.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.GetIdpId(), t.Urls)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-S2g21", "type oneOf %T in method StartIdentityProviderIntent not implemented", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, details, err := s.command.CreateIntent(ctx, state, idpID, urls.GetSuccessUrl(), urls.GetFailureUrl(), authz.GetInstance(ctx).InstanceID(), session.PersistentParameters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, redirect := session.GetAuth(ctx)
|
||||
if redirect {
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
||||
}, nil
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
||||
PostForm: []byte(content),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
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.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{
|
||||
IdpIntent: &user.IDPIntent{
|
||||
IdpIntentId: intentWriteModel.AggregateID,
|
||||
IdpIntentToken: token,
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
}, 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}, nil)
|
||||
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, zerrors.ThrowInvalidArgument(nil, "IDP-9a02j2n2bh", "Errors.ExternalIDP.IDPTypeNotImplemented")
|
||||
}
|
||||
session := ldapProvider.GetSession(username, password)
|
||||
externalUser, err := session.FetchUser(ctx)
|
||||
if errors.Is(err, ldap.ErrFailedLogin) || errors.Is(err, ldap.ErrNoSingleUser) {
|
||||
return nil, "", nil, zerrors.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) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIdpIntentId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.checkIntentToken(req.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-nme4gszsvx", "Errors.Intent.NotSucceeded")
|
||||
}
|
||||
return idpIntentToIDPIntentPb(intent, s.idpAlg)
|
||||
}
|
||||
|
||||
func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
rawInformation := new(structpb.Struct)
|
||||
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
information := &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: intentToDetailsPb(intent),
|
||||
IdpInformation: &user.IDPInformation{
|
||||
IdpId: intent.IDPID,
|
||||
UserId: intent.IDPUserID,
|
||||
UserName: intent.IDPUserName,
|
||||
RawInformation: rawInformation,
|
||||
},
|
||||
UserId: intent.UserID,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if intent.Assertion != nil {
|
||||
assertion, err := crypto.Decrypt(intent.Assertion, alg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
information.IdpInformation.Access = IDPSAMLResponseToPb(assertion)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func IDPSAMLResponseToPb(assertion []byte) *user.IDPInformation_Saml {
|
||||
return &user.IDPInformation_Saml{
|
||||
Saml: &user.IDPSAMLAccessInformation{
|
||||
Assertion: assertion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) checkIntentToken(token string, intentID string) error {
|
||||
return crypto.CheckToken(s.idpAlg, token, intentID)
|
||||
}
|
||||
|
||||
func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.ListAuthenticationMethodTypesRequest) (*user.ListAuthenticationMethodTypesResponse, error) {
|
||||
authMethods, err := s.query.ListUserAuthMethodTypes(ctx, req.GetUserId(), true, req.GetDomainQuery().GetIncludeWithoutDomain(), req.GetDomainQuery().GetDomain())
|
||||
if err != nil {
|
||||
|
@@ -11,7 +11,6 @@ import (
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -322,7 +321,7 @@ func Test_idpIntentToIDPIntentPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := idpIntentToIDPIntentPb(tt.args.intent, tt.args.alg)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
grpc.AllFieldsEqual(t, tt.res.resp.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
assert.EqualExportedValues(t, tt.res.resp, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -2146,17 +2146,29 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
intentID := Instance.CreateIntent(CTX, idpID).GetIdpIntent().GetIdpIntentId()
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", "")
|
||||
oauthIdpID := Instance.AddGenericOAuthProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
oidcIdpID := Instance.AddGenericOIDCProvider(IamCTX, gofakeit.AppName()).GetId()
|
||||
samlIdpID := Instance.AddSAMLPostProvider(IamCTX)
|
||||
ldapIdpID := Instance.AddLDAPProvider(IamCTX)
|
||||
authURL, err := url.Parse(Instance.CreateIntent(CTX, oauthIdpID).GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", "user")
|
||||
intentID := authURL.Query().Get("state")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), idpID, "id", "")
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), idpID, "id", "user")
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), idpID, "id", "")
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -2191,7 +2203,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful intent",
|
||||
name: "retrieve successful oauth intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
@@ -2212,13 +2224,15 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: oauthIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"sub": "id",
|
||||
"preferred_username": "username",
|
||||
"RawInfo": map[string]interface{}{
|
||||
"id": "id",
|
||||
"preferred_username": "username",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
@@ -2250,7 +2264,85 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: oauthIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"RawInfo": map[string]interface{}{
|
||||
"id": "id",
|
||||
"preferred_username": "username",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: oidcSuccessful,
|
||||
IdpIntentToken: oidcToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(oidcChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: oidcSequence,
|
||||
},
|
||||
UserId: "",
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Oauth{
|
||||
Oauth: &user.IDPOAuthAccessInformation{
|
||||
AccessToken: "accessToken",
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: oidcIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"sub": "id",
|
||||
"preferred_username": "username",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent with linked user",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: oidcSuccessfulWithUserID,
|
||||
IdpIntentToken: oidcWithUserIDToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(oidcWithUserIDChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: oidcWithUserIDSequence,
|
||||
},
|
||||
UserId: "user",
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Oauth{
|
||||
Oauth: &user.IDPOAuthAccessInformation{
|
||||
AccessToken: "accessToken",
|
||||
IdToken: gu.Ptr("idToken"),
|
||||
},
|
||||
},
|
||||
IdpId: oidcIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2294,7 +2386,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
}(),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: ldapIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2340,7 +2432,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
}(),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: ldapIdpID,
|
||||
UserId: "id",
|
||||
UserName: "username",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2377,7 +2469,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
Assertion: []byte("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id\" IssueInstant=\"0001-01-01T00:00:00Z\" Version=\"\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"\" SPNameQualifier=\"\" Format=\"\" SPProvidedID=\"\"></Issuer></Assertion>"),
|
||||
},
|
||||
},
|
||||
IdpId: idpID,
|
||||
IdpId: samlIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
@@ -2394,6 +2486,45 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful saml intent with linked user",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: samlSuccessfulWithUserID,
|
||||
IdpIntentToken: samlWithUserToken,
|
||||
},
|
||||
},
|
||||
want: &user.RetrieveIdentityProviderIntentResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.New(samlWithUserChangeDate),
|
||||
ResourceOwner: Instance.ID(),
|
||||
Sequence: samlWithUserSequence,
|
||||
},
|
||||
IdpInformation: &user.IDPInformation{
|
||||
Access: &user.IDPInformation_Saml{
|
||||
Saml: &user.IDPSAMLAccessInformation{
|
||||
Assertion: []byte("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id\" IssueInstant=\"0001-01-01T00:00:00Z\" Version=\"\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"\" SPNameQualifier=\"\" Format=\"\" SPProvidedID=\"\"></Issuer></Assertion>"),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
UserId: "id",
|
||||
UserName: "",
|
||||
RawInformation: func() *structpb.Struct {
|
||||
s, err := structpb.NewStruct(map[string]interface{}{
|
||||
"id": "id",
|
||||
"attributes": map[string]interface{}{
|
||||
"attribute1": []interface{}{"value1"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
UserId: "user",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user