mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-14 07:38:50 +00:00
Merge commit from fork
* fix: prevent intent token reuse and add expiry * fix duplicate * fix expiration
This commit is contained in:
parent
bb56b362a7
commit
b1e60e7398
@ -735,6 +735,9 @@ SystemDefaults:
|
||||
DefaultQueryLimit: 100 # ZITADEL_SYSTEMDEFAULTS_DEFAULTQUERYLIMIT
|
||||
# MaxQueryLimit limits the number of items that can be queried in a single v3 API search request with explicitly passing a limit.
|
||||
MaxQueryLimit: 1000 # ZITADEL_SYSTEMDEFAULTS_MAXQUERYLIMIT
|
||||
# The maximum duration of the IDP intent lifetime after which the IDP intent expires and can not be retrieved or used anymore.
|
||||
# Note that this time is measured only after the IdP intent was successful and not after the IDP intent was created.
|
||||
MaxIdPIntentLifetime: 1h # ZITADEL_SYSTEMDEFAULTS_MAXIDPINTENTLIFETIME
|
||||
|
||||
Actions:
|
||||
HTTP:
|
||||
|
@ -354,7 +354,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId())
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
updateResp, err := Client.SetSession(LoginCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
@ -372,7 +372,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
||||
func TestServer_CreateSession_successfulIntent_instant(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId())
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
@ -396,7 +396,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
|
||||
|
||||
// successful intent without known / linked user
|
||||
idpUserID := "id"
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, idpUserID, "")
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, idpUserID, "", time.Now().Add(time.Hour))
|
||||
|
||||
// link the user (with info from intent)
|
||||
Instance.CreateUserIDPlink(CTX, User.GetUserId(), idpUserID, idpID, User.GetUserId())
|
||||
@ -447,6 +447,80 @@ func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_reuseIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
createResp, err := Client.CreateSession(LoginCTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
updateResp, err := Client.SetSession(LoginCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantIntentFactor)
|
||||
|
||||
// the reuse of the intent token is not allowed, not even on the same session
|
||||
session2, err := Client.SetSession(LoginCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
_ = session2
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_expiredIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
createResp, err := Client.CreateSession(LoginCTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Second))
|
||||
require.NoError(t, err)
|
||||
|
||||
// wait for the intent to expire
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
_, err = Client.SetSession(LoginCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func registerTOTP(ctx context.Context, t *testing.T, userID string) (secret string) {
|
||||
resp, err := Instance.Client.UserV2.RegisterTOTP(ctx, &user.RegisterTOTPRequest{
|
||||
UserId: userID,
|
||||
|
@ -354,7 +354,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId())
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
@ -372,7 +372,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
|
||||
func TestServer_CreateSession_successfulIntent_instant(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId())
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
@ -396,7 +396,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
|
||||
|
||||
// successful intent without known / linked user
|
||||
idpUserID := "id"
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId())
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
|
||||
// link the user (with info from intent)
|
||||
@ -448,6 +448,80 @@ func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_reuseIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
createResp, err := Client.CreateSession(IAMOwnerCTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Hour))
|
||||
require.NoError(t, err)
|
||||
updateResp, err := Client.SetSession(IAMOwnerCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId(), wantUserFactor, wantIntentFactor)
|
||||
|
||||
// the reuse of the intent token is not allowed, not even on the same session
|
||||
session2, err := Client.SetSession(IAMOwnerCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
_ = session2
|
||||
}
|
||||
|
||||
func TestServer_CreateSession_expiredIntent(t *testing.T) {
|
||||
idpID := Instance.AddGenericOAuthProvider(IAMOwnerCTX, gofakeit.AppName()).GetId()
|
||||
createResp, err := Client.CreateSession(IAMOwnerCTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: User.GetUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, User.GetUserId())
|
||||
|
||||
intentID, token, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, "id", User.GetUserId(), time.Now().Add(time.Second))
|
||||
require.NoError(t, err)
|
||||
|
||||
// wait for the intent to expire
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
_, err = Client.SetSession(IAMOwnerCTX, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: token,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func registerTOTP(ctx context.Context, t *testing.T, userID string) (secret string) {
|
||||
resp, err := Instance.Client.UserV2.RegisterTOTP(ctx, &user.RegisterTOTPRequest{
|
||||
UserId: userID,
|
||||
|
@ -2121,22 +2121,36 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
authURL, err := url.Parse(Instance.CreateIntent(CTX, oauthIdpID).GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
intentID := authURL.Query().Get("state")
|
||||
expiry := time.Now().Add(1 * time.Hour)
|
||||
expiryFormatted := expiry.Round(time.Millisecond).UTC().Format("2006-01-02T15:04:05.999Z07:00")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "")
|
||||
intentUser := Instance.CreateHumanUser(IamCTX)
|
||||
_, err = Instance.CreateUserIDPlink(IamCTX, intentUser.GetUserId(), "idpUserID", oauthIdpID, "username")
|
||||
require.NoError(t, err)
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "")
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user")
|
||||
successfulExpiredID, expiredToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user", time.Now().Add(time.Second))
|
||||
require.NoError(t, err)
|
||||
// make sure the intent is expired
|
||||
time.Sleep(2 * time.Second)
|
||||
successfulConsumedID, consumedToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "idpUserID", intentUser.GetUserId(), expiry)
|
||||
require.NoError(t, err)
|
||||
// make sure the intent is consumed
|
||||
Instance.CreateIntentSession(t, IamCTX, intentUser.GetUserId(), successfulConsumedID, consumedToken)
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user", expiry)
|
||||
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", "")
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user")
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", expiry)
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -2260,6 +2274,28 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful expired intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: successfulExpiredID,
|
||||
IdpIntentToken: expiredToken,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful consumed intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: successfulConsumedID,
|
||||
IdpIntentToken: consumedToken,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent",
|
||||
args: args{
|
||||
@ -2469,7 +2505,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
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>"),
|
||||
Assertion: []byte(fmt.Sprintf(`<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><Conditions NotBefore="0001-01-01T00:00:00Z" NotOnOrAfter="%s"></Conditions></Assertion>`, expiryFormatted)),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
@ -2518,7 +2554,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
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>"),
|
||||
Assertion: []byte(fmt.Sprintf(`<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><Conditions NotBefore="0001-01-01T00:00:00Z" NotOnOrAfter="%s"></Conditions></Assertion>`, expiryFormatted)),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
oidc_pkg "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@ -71,14 +72,14 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
externalUser, userID, attributes, err := s.ldapLogin(ctx, intentWriteModel.IDPID, ldapCredentials.GetUsername(), ldapCredentials.GetPassword())
|
||||
externalUser, userID, session, 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)
|
||||
token, err := s.command.SucceedLDAPIDPIntent(ctx, intentWriteModel, externalUser, userID, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -116,7 +117,7 @@ func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUse
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, map[string][]string, error) {
|
||||
func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, *ldap.Session, error) {
|
||||
provider, err := s.command.GetProvider(ctx, idpID, "", "")
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
@ -137,12 +138,7 @@ func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string
|
||||
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
|
||||
return externalUser, userID, session, nil
|
||||
}
|
||||
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
@ -156,6 +152,9 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.R
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-nme4gszsvx", "Errors.Intent.NotSucceeded")
|
||||
}
|
||||
if time.Now().After(intent.ExpiresAt()) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-SAf42", "Errors.Intent.Expired")
|
||||
}
|
||||
idpIntent, err := idpIntentToIDPIntentPb(intent, s.idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -2153,22 +2153,36 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
authURL, err := url.Parse(Instance.CreateIntent(CTX, oauthIdpID).GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
intentID := authURL.Query().Get("state")
|
||||
expiry := time.Now().Add(1 * time.Hour)
|
||||
expiryFormatted := expiry.Round(time.Millisecond).UTC().Format("2006-01-02T15:04:05.999Z07:00")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "")
|
||||
intentUser := Instance.CreateHumanUser(IamCTX)
|
||||
_, err = Instance.CreateUserIDPlink(IamCTX, intentUser.GetUserId(), "idpUserID", oauthIdpID, "username")
|
||||
require.NoError(t, err)
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user")
|
||||
|
||||
successfulID, token, changeDate, sequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "")
|
||||
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user")
|
||||
successfulExpiredID, expiredToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "id", "user", time.Now().Add(time.Second))
|
||||
require.NoError(t, err)
|
||||
// make sure the intent is expired
|
||||
time.Sleep(2 * time.Second)
|
||||
successfulConsumedID, consumedToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), oauthIdpID, "idpUserID", intentUser.GetUserId(), expiry)
|
||||
require.NoError(t, err)
|
||||
// make sure the intent is consumed
|
||||
Instance.CreateIntentSession(t, IamCTX, intentUser.GetUserId(), successfulConsumedID, consumedToken)
|
||||
oidcSuccessful, oidcToken, oidcChangeDate, oidcSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
oidcSuccessfulWithUserID, oidcWithUserIDToken, oidcWithUserIDChangeDate, oidcWithUserIDSequence, err := sink.SuccessfulOIDCIntent(Instance.ID(), oidcIdpID, "id", "user", expiry)
|
||||
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", "")
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", expiry)
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user")
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", expiry)
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -2281,6 +2295,28 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful expired intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: successfulExpiredID,
|
||||
IdpIntentToken: expiredToken,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful consumed intent",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RetrieveIdentityProviderIntentRequest{
|
||||
IdpIntentId: successfulConsumedID,
|
||||
IdpIntentToken: consumedToken,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "retrieve successful oidc intent",
|
||||
args: args{
|
||||
@ -2466,7 +2502,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
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>"),
|
||||
Assertion: []byte(fmt.Sprintf(`<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><Conditions NotBefore="0001-01-01T00:00:00Z" NotOnOrAfter="%s"></Conditions></Assertion>`, expiryFormatted)),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
@ -2504,7 +2540,7 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
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>"),
|
||||
Assertion: []byte(fmt.Sprintf(`<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><Conditions NotBefore="0001-01-01T00:00:00Z" NotOnOrAfter="%s"></Conditions></Assertion>`, expiryFormatted)),
|
||||
},
|
||||
},
|
||||
IdpId: samlIdpID,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@ -399,14 +400,14 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
externalUser, userID, attributes, err := s.ldapLogin(ctx, intentWriteModel.IDPID, ldapCredentials.GetUsername(), ldapCredentials.GetPassword())
|
||||
externalUser, userID, session, 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)
|
||||
token, err := s.command.SucceedLDAPIDPIntent(ctx, intentWriteModel, externalUser, userID, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -444,7 +445,7 @@ func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUse
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, map[string][]string, error) {
|
||||
func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string) (idp.User, string, *ldap.Session, error) {
|
||||
provider, err := s.command.GetProvider(ctx, idpID, "", "")
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
@ -470,7 +471,7 @@ func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string
|
||||
for _, item := range session.Entry.Attributes {
|
||||
attributes[item.Name] = item.Values
|
||||
}
|
||||
return externalUser, userID, attributes, nil
|
||||
return externalUser, userID, session, nil
|
||||
}
|
||||
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
@ -484,6 +485,9 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.R
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-nme4gszsvx", "Errors.Intent.NotSucceeded")
|
||||
}
|
||||
if time.Now().After(intent.ExpiresAt()) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "IDP-Afb2s", "Errors.Intent.Expired")
|
||||
}
|
||||
return idpIntentToIDPIntentPb(intent, s.idpAlg)
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := h.checkExternalUser(ctx, intent.IDPID, idpUser.GetID())
|
||||
logging.WithFields("intent", intent.AggregateID).OnError(err).Error("could not check if idp user already exists")
|
||||
|
||||
token, err := h.commands.SucceedSAMLIDPIntent(ctx, intent, idpUser, userID, session.Assertion)
|
||||
token, err := h.commands.SucceedSAMLIDPIntent(ctx, intent, idpUser, userID, session)
|
||||
if err != nil {
|
||||
redirectToFailureURLErr(w, r, intent, zerrors.ThrowInternal(err, "IDP-JdD3g", "Errors.Intent.TokenCreationFailed"))
|
||||
return
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@ -14,11 +15,12 @@ import (
|
||||
|
||||
func Test_redirectToSuccessURL(t *testing.T) {
|
||||
type args struct {
|
||||
id string
|
||||
userID string
|
||||
token string
|
||||
failureURL string
|
||||
successURL string
|
||||
id string
|
||||
userID string
|
||||
token string
|
||||
failureURL string
|
||||
successURL string
|
||||
maxIdPIntentLifetime time.Duration
|
||||
}
|
||||
type res struct {
|
||||
want string
|
||||
@ -59,7 +61,7 @@ func Test_redirectToSuccessURL(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id)
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id, tt.args.maxIdPIntentLifetime)
|
||||
wm.FailureURL, _ = url.Parse(tt.args.failureURL)
|
||||
wm.SuccessURL, _ = url.Parse(tt.args.successURL)
|
||||
|
||||
@ -71,11 +73,12 @@ func Test_redirectToSuccessURL(t *testing.T) {
|
||||
|
||||
func Test_redirectToFailureURL(t *testing.T) {
|
||||
type args struct {
|
||||
id string
|
||||
failureURL string
|
||||
successURL string
|
||||
err string
|
||||
desc string
|
||||
id string
|
||||
failureURL string
|
||||
successURL string
|
||||
err string
|
||||
desc string
|
||||
maxIdPIntentLifetime time.Duration
|
||||
}
|
||||
type res struct {
|
||||
want string
|
||||
@ -115,7 +118,7 @@ func Test_redirectToFailureURL(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id)
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id, tt.args.maxIdPIntentLifetime)
|
||||
wm.FailureURL, _ = url.Parse(tt.args.failureURL)
|
||||
wm.SuccessURL, _ = url.Parse(tt.args.successURL)
|
||||
|
||||
@ -127,10 +130,11 @@ func Test_redirectToFailureURL(t *testing.T) {
|
||||
|
||||
func Test_redirectToFailureURLErr(t *testing.T) {
|
||||
type args struct {
|
||||
id string
|
||||
failureURL string
|
||||
successURL string
|
||||
err error
|
||||
id string
|
||||
failureURL string
|
||||
successURL string
|
||||
err error
|
||||
maxIdPIntentLifetime time.Duration
|
||||
}
|
||||
type res struct {
|
||||
want string
|
||||
@ -158,7 +162,7 @@ func Test_redirectToFailureURLErr(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id)
|
||||
wm := command.NewIDPIntentWriteModel(tt.args.id, tt.args.id, tt.args.maxIdPIntentLifetime)
|
||||
wm.FailureURL, _ = url.Parse(tt.args.failureURL)
|
||||
wm.SuccessURL, _ = url.Parse(tt.args.successURL)
|
||||
|
||||
|
@ -81,6 +81,7 @@ type Commands struct {
|
||||
publicKeyLifetime time.Duration
|
||||
certificateLifetime time.Duration
|
||||
defaultSecretGenerators *SecretGenerators
|
||||
maxIdPIntentLifetime time.Duration
|
||||
|
||||
samlCertificateAndKeyGenerator func(id string) ([]byte, []byte, error)
|
||||
webKeyGenerator func(keyID string, alg crypto.EncryptionAlgorithm, genConfig crypto.WebKeyConfig) (encryptedPrivate *crypto.CryptoValue, public *jose.JSONWebKey, err error)
|
||||
@ -152,6 +153,7 @@ func StartCommands(
|
||||
privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime,
|
||||
publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime,
|
||||
certificateLifetime: defaults.KeyConfig.CertificateLifetime,
|
||||
maxIdPIntentLifetime: defaults.MaxIdPIntentLifetime,
|
||||
idpConfigEncryption: idpConfigEncryption,
|
||||
smtpEncryption: smtpEncryption,
|
||||
smsEncryption: smsEncryption,
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"encoding/xml"
|
||||
"net/url"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
|
||||
@ -19,8 +18,10 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
"github.com/zitadel/zitadel/internal/repository/idpintent"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -68,7 +69,7 @@ func (c *Commands) CreateIntent(ctx context.Context, intentID, idpID, successURL
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
writeModel := NewIDPIntentWriteModel(intentID, resourceOwner)
|
||||
writeModel := NewIDPIntentWriteModel(intentID, resourceOwner, c.maxIdPIntentLifetime)
|
||||
|
||||
//nolint: staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL, idpArguments))
|
||||
@ -180,6 +181,7 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
|
||||
userID,
|
||||
accessToken,
|
||||
idToken,
|
||||
idpSession.ExpiresAt(),
|
||||
)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
|
||||
if err != nil {
|
||||
@ -188,7 +190,7 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, assertion *saml.Assertion) (string, error) {
|
||||
func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, session *saml.Session) (string, error) {
|
||||
token, err := c.generateIntentToken(writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -197,7 +199,7 @@ func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPInte
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
assertionData, err := xml.Marshal(assertion)
|
||||
assertionData, err := xml.Marshal(session.Assertion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -213,6 +215,7 @@ func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPInte
|
||||
idpUser.GetPreferredUsername(),
|
||||
userID,
|
||||
assertionEnc,
|
||||
session.ExpiresAt(),
|
||||
)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
|
||||
if err != nil {
|
||||
@ -237,7 +240,7 @@ func (c *Commands) generateIntentToken(intentID string) (string, error) {
|
||||
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) {
|
||||
func (c *Commands) SucceedLDAPIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, session *ldap.Session) (string, error) {
|
||||
token, err := c.generateIntentToken(writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -246,6 +249,10 @@ func (c *Commands) SucceedLDAPIDPIntent(ctx context.Context, writeModel *IDPInte
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
attributes := make(map[string][]string, len(session.Entry.Attributes))
|
||||
for _, item := range session.Entry.Attributes {
|
||||
attributes[item.Name] = item.Values
|
||||
}
|
||||
cmd := idpintent.NewLDAPSucceededEvent(
|
||||
ctx,
|
||||
IDPIntentAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
@ -254,6 +261,7 @@ func (c *Commands) SucceedLDAPIDPIntent(ctx context.Context, writeModel *IDPInte
|
||||
idpUser.GetPreferredUsername(),
|
||||
userID,
|
||||
attributes,
|
||||
session.ExpiresAt(),
|
||||
)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
|
||||
if err != nil {
|
||||
@ -273,7 +281,7 @@ func (c *Commands) FailIDPIntent(ctx context.Context, writeModel *IDPIntentWrite
|
||||
}
|
||||
|
||||
func (c *Commands) GetIntentWriteModel(ctx context.Context, id, resourceOwner string) (*IDPIntentWriteModel, error) {
|
||||
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
||||
writeModel := NewIDPIntentWriteModel(id, resourceOwner, c.maxIdPIntentLifetime)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -29,18 +30,29 @@ type IDPIntentWriteModel struct {
|
||||
RequestID string
|
||||
Assertion *crypto.CryptoValue
|
||||
|
||||
State domain.IDPIntentState
|
||||
State domain.IDPIntentState
|
||||
succeededAt time.Time
|
||||
maxIdPIntentLifetime time.Duration
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func NewIDPIntentWriteModel(id, resourceOwner string) *IDPIntentWriteModel {
|
||||
func NewIDPIntentWriteModel(id, resourceOwner string, maxIdPIntentLifetime time.Duration) *IDPIntentWriteModel {
|
||||
return &IDPIntentWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
maxIdPIntentLifetime: maxIdPIntentLifetime,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) ExpiresAt() time.Time {
|
||||
if wm.expiresAt.IsZero() {
|
||||
return wm.succeededAt.Add(wm.maxIdPIntentLifetime)
|
||||
}
|
||||
return wm.expiresAt
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
@ -56,6 +68,8 @@ func (wm *IDPIntentWriteModel) Reduce() error {
|
||||
wm.reduceLDAPSucceededEvent(e)
|
||||
case *idpintent.FailedEvent:
|
||||
wm.reduceFailedEvent(e)
|
||||
case *idpintent.ConsumedEvent:
|
||||
wm.reduceConsumedEvent(e)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
@ -74,6 +88,7 @@ func (wm *IDPIntentWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
idpintent.SAMLRequestEventType,
|
||||
idpintent.LDAPSucceededEventType,
|
||||
idpintent.FailedEventType,
|
||||
idpintent.ConsumedEventType,
|
||||
).
|
||||
Builder()
|
||||
}
|
||||
@ -93,6 +108,8 @@ func (wm *IDPIntentWriteModel) reduceSAMLSucceededEvent(e *idpintent.SAMLSucceed
|
||||
wm.IDPUserName = e.IDPUserName
|
||||
wm.Assertion = e.Assertion
|
||||
wm.State = domain.IDPIntentStateSucceeded
|
||||
wm.succeededAt = e.CreationDate()
|
||||
wm.expiresAt = e.ExpiresAt
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceLDAPSucceededEvent(e *idpintent.LDAPSucceededEvent) {
|
||||
@ -102,6 +119,8 @@ func (wm *IDPIntentWriteModel) reduceLDAPSucceededEvent(e *idpintent.LDAPSucceed
|
||||
wm.IDPUserName = e.IDPUserName
|
||||
wm.IDPEntryAttributes = e.EntryAttributes
|
||||
wm.State = domain.IDPIntentStateSucceeded
|
||||
wm.succeededAt = e.CreationDate()
|
||||
wm.expiresAt = e.ExpiresAt
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceOAuthSucceededEvent(e *idpintent.SucceededEvent) {
|
||||
@ -112,6 +131,8 @@ func (wm *IDPIntentWriteModel) reduceOAuthSucceededEvent(e *idpintent.SucceededE
|
||||
wm.IDPAccessToken = e.IDPAccessToken
|
||||
wm.IDPIDToken = e.IDPIDToken
|
||||
wm.State = domain.IDPIntentStateSucceeded
|
||||
wm.succeededAt = e.CreationDate()
|
||||
wm.expiresAt = e.ExpiresAt
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceSAMLRequestEvent(e *idpintent.SAMLRequestEvent) {
|
||||
@ -122,6 +143,10 @@ func (wm *IDPIntentWriteModel) reduceFailedEvent(e *idpintent.FailedEvent) {
|
||||
wm.State = domain.IDPIntentStateFailed
|
||||
}
|
||||
|
||||
func (wm *IDPIntentWriteModel) reduceConsumedEvent(e *idpintent.ConsumedEvent) {
|
||||
wm.State = domain.IDPIntentStateConsumed
|
||||
}
|
||||
|
||||
func IDPIntentAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
Type: idpintent.AggregateType,
|
||||
|
@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
crewjam_saml "github.com/crewjam/saml"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -26,6 +28,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
rep_idp "github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/repository/idpintent"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@ -867,7 +870,7 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro", 0),
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInternal(nil, "id", "encryption failed"),
|
||||
@ -888,7 +891,7 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro", 0),
|
||||
idpSession: &oauth.Session{
|
||||
Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
@ -922,6 +925,7 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
|
||||
Crypted: []byte("accessToken"),
|
||||
},
|
||||
"idToken",
|
||||
time.Time{},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
@ -930,7 +934,7 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
idpSession: &openid.Session{
|
||||
Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
@ -973,7 +977,7 @@ func TestCommands_SucceedSAMLIDPIntent(t *testing.T) {
|
||||
ctx context.Context
|
||||
writeModel *IDPIntentWriteModel
|
||||
idpUser idp.User
|
||||
assertion *saml.Assertion
|
||||
session *saml.Session
|
||||
userID string
|
||||
}
|
||||
type res struct {
|
||||
@ -998,7 +1002,7 @@ func TestCommands_SucceedSAMLIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "ro", 0),
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInternal(nil, "id", "encryption failed"),
|
||||
@ -1023,14 +1027,17 @@ func TestCommands_SucceedSAMLIDPIntent(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []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>"),
|
||||
},
|
||||
time.Time{},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
assertion: &saml.Assertion{ID: "id"},
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
session: &saml.Session{
|
||||
Assertion: &crewjam_saml.Assertion{ID: "id"},
|
||||
},
|
||||
idpUser: openid.NewUser(&oidc.UserInfo{
|
||||
Subject: "id",
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
@ -1061,14 +1068,17 @@ func TestCommands_SucceedSAMLIDPIntent(t *testing.T) {
|
||||
KeyID: "id",
|
||||
Crypted: []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>"),
|
||||
},
|
||||
time.Time{},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
assertion: &saml.Assertion{ID: "id"},
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
session: &saml.Session{
|
||||
Assertion: &crewjam_saml.Assertion{ID: "id"},
|
||||
},
|
||||
idpUser: openid.NewUser(&oidc.UserInfo{
|
||||
Subject: "id",
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
@ -1088,7 +1098,7 @@ func TestCommands_SucceedSAMLIDPIntent(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idpConfigEncryption: tt.fields.idpConfigEncryption,
|
||||
}
|
||||
got, err := c.SucceedSAMLIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.assertion)
|
||||
got, err := c.SucceedSAMLIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.session)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.token, got)
|
||||
})
|
||||
@ -1128,7 +1138,7 @@ func TestCommands_RequestSAMLIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
request: "request",
|
||||
},
|
||||
res{},
|
||||
@ -1156,7 +1166,7 @@ func TestCommands_SucceedLDAPIDPIntent(t *testing.T) {
|
||||
writeModel *IDPIntentWriteModel
|
||||
idpUser idp.User
|
||||
userID string
|
||||
attributes map[string][]string
|
||||
session *ldap.Session
|
||||
}
|
||||
type res struct {
|
||||
token string
|
||||
@ -1180,7 +1190,7 @@ func TestCommands_SucceedLDAPIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInternal(nil, "id", "encryption failed"),
|
||||
@ -1200,14 +1210,24 @@ func TestCommands_SucceedLDAPIDPIntent(t *testing.T) {
|
||||
"username",
|
||||
"",
|
||||
map[string][]string{"id": {"id"}},
|
||||
time.Time{},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
attributes: map[string][]string{"id": {"id"}},
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
session: &ldap.Session{
|
||||
Entry: &goldap.Entry{
|
||||
Attributes: []*goldap.EntryAttribute{
|
||||
{
|
||||
Name: "id",
|
||||
Values: []string{"id"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
idpUser: ldap.NewUser(
|
||||
"id",
|
||||
"",
|
||||
@ -1235,7 +1255,7 @@ func TestCommands_SucceedLDAPIDPIntent(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idpConfigEncryption: tt.fields.idpConfigEncryption,
|
||||
}
|
||||
got, err := c.SucceedLDAPIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.attributes)
|
||||
got, err := c.SucceedLDAPIDPIntent(tt.args.ctx, tt.args.writeModel, tt.args.idpUser, tt.args.userID, tt.args.session)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.token, got)
|
||||
})
|
||||
@ -1275,7 +1295,7 @@ func TestCommands_FailIDPIntent(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance"),
|
||||
writeModel: NewIDPIntentWriteModel("id", "instance", 0),
|
||||
reason: "reason",
|
||||
},
|
||||
res{
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/notification/senders"
|
||||
"github.com/zitadel/zitadel/internal/repository/idpintent"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -32,31 +33,33 @@ type SessionCommands struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventCommands []eventstore.Command
|
||||
|
||||
hasher *crypto.Hasher
|
||||
intentAlg crypto.EncryptionAlgorithm
|
||||
totpAlg crypto.EncryptionAlgorithm
|
||||
otpAlg crypto.EncryptionAlgorithm
|
||||
createCode encryptedCodeWithDefaultFunc
|
||||
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
|
||||
createToken func(sessionID string) (id string, token string, err error)
|
||||
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
||||
now func() time.Time
|
||||
hasher *crypto.Hasher
|
||||
intentAlg crypto.EncryptionAlgorithm
|
||||
totpAlg crypto.EncryptionAlgorithm
|
||||
otpAlg crypto.EncryptionAlgorithm
|
||||
createCode encryptedCodeWithDefaultFunc
|
||||
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
|
||||
createToken func(sessionID string) (id string, token string, err error)
|
||||
getCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
|
||||
now func() time.Time
|
||||
maxIdPIntentLifetime time.Duration
|
||||
}
|
||||
|
||||
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
|
||||
return &SessionCommands{
|
||||
sessionCommands: cmds,
|
||||
sessionWriteModel: session,
|
||||
eventstore: c.eventstore,
|
||||
hasher: c.userPasswordHasher,
|
||||
intentAlg: c.idpConfigEncryption,
|
||||
totpAlg: c.multifactors.OTP.CryptoMFA,
|
||||
otpAlg: c.userEncryption,
|
||||
createCode: c.newEncryptedCodeWithDefault,
|
||||
createPhoneCode: c.newPhoneCode,
|
||||
createToken: c.sessionTokenCreator,
|
||||
getCodeVerifier: c.phoneCodeVerifierFromConfig,
|
||||
now: time.Now,
|
||||
sessionCommands: cmds,
|
||||
sessionWriteModel: session,
|
||||
eventstore: c.eventstore,
|
||||
hasher: c.userPasswordHasher,
|
||||
intentAlg: c.idpConfigEncryption,
|
||||
totpAlg: c.multifactors.OTP.CryptoMFA,
|
||||
otpAlg: c.userEncryption,
|
||||
createCode: c.newEncryptedCodeWithDefault,
|
||||
createPhoneCode: c.newPhoneCode,
|
||||
createToken: c.sessionTokenCreator,
|
||||
getCodeVerifier: c.phoneCodeVerifierFromConfig,
|
||||
now: time.Now,
|
||||
maxIdPIntentLifetime: c.maxIdPIntentLifetime,
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +95,7 @@ func CheckIntent(intentID, token string) SessionCommand {
|
||||
if err := crypto.CheckToken(cmd.intentAlg, token, intentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.intentWriteModel = NewIDPIntentWriteModel(intentID, "")
|
||||
cmd.intentWriteModel = NewIDPIntentWriteModel(intentID, "", cmd.maxIdPIntentLifetime)
|
||||
err := cmd.eventstore.FilterToQueryReducer(ctx, cmd.intentWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -100,6 +103,9 @@ func CheckIntent(intentID, token string) SessionCommand {
|
||||
if cmd.intentWriteModel.State != domain.IDPIntentStateSucceeded {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Df4bw", "Errors.Intent.NotSucceeded")
|
||||
}
|
||||
if time.Now().After(cmd.intentWriteModel.ExpiresAt()) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SAf42", "Errors.Intent.Expired")
|
||||
}
|
||||
if cmd.intentWriteModel.UserID != "" {
|
||||
if cmd.intentWriteModel.UserID != cmd.sessionWriteModel.UserID {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-O8xk3w", "Errors.Intent.OtherUser")
|
||||
@ -168,6 +174,7 @@ func (s *SessionCommands) PasswordChecked(ctx context.Context, checkedAt time.Ti
|
||||
|
||||
func (s *SessionCommands) IntentChecked(ctx context.Context, checkedAt time.Time) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewIntentCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
||||
s.eventCommands = append(s.eventCommands, idpintent.NewConsumedEvent(ctx, IDPIntentAggregateFromWriteModel(&s.intentWriteModel.WriteModel)))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) WebAuthNChallenged(ctx context.Context, challenge string, allowedCrentialIDs [][]byte, userVerification domain.UserVerificationRequirement, rpid string) {
|
||||
|
@ -695,6 +695,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
"userID2",
|
||||
nil,
|
||||
"",
|
||||
time.Now().Add(time.Hour),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -757,6 +758,111 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
err: zerrors.ThrowPermissionDenied(nil, "CRYPTO-CRYPTO", "Errors.Intent.InvalidToken"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"set user, intent token already consumed",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username", "", "", "", "", language.English, domain.GenderUnspecified, "", false),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
idpintent.NewSucceededEvent(context.Background(),
|
||||
&idpintent.NewAggregate("intent", "instance1").Aggregate,
|
||||
nil,
|
||||
"idpUserID",
|
||||
"idpUsername",
|
||||
"userID",
|
||||
nil,
|
||||
"",
|
||||
time.Now().Add(time.Hour),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
idpintent.NewConsumedEvent(context.Background(),
|
||||
&idpintent.NewAggregate("intent", "instance1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instance1", "", ""),
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
|
||||
sessionCommands: []SessionCommand{
|
||||
CheckUser("userID", "org1", &language.Afrikaans),
|
||||
CheckIntent("intent", "aW50ZW50"),
|
||||
},
|
||||
createToken: func(sessionID string) (string, string, error) {
|
||||
return "tokenID",
|
||||
"token",
|
||||
nil
|
||||
},
|
||||
intentAlg: decryption(nil),
|
||||
now: func() time.Time {
|
||||
return testNow
|
||||
},
|
||||
},
|
||||
metadata: map[string][]byte{
|
||||
"key": []byte("value"),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Df4bw", "Errors.Intent.NotSucceeded"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"set user, intent token already expired",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username", "", "", "", "", language.English, domain.GenderUnspecified, "", false),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
idpintent.NewSucceededEvent(context.Background(),
|
||||
&idpintent.NewAggregate("intent", "instance1").Aggregate,
|
||||
nil,
|
||||
"idpUserID",
|
||||
"idpUsername",
|
||||
"userID",
|
||||
nil,
|
||||
"",
|
||||
time.Now().Add(-time.Hour),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instance1", "", ""),
|
||||
checks: &SessionCommands{
|
||||
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
|
||||
sessionCommands: []SessionCommand{
|
||||
CheckUser("userID", "org1", &language.Afrikaans),
|
||||
CheckIntent("intent", "aW50ZW50"),
|
||||
},
|
||||
createToken: func(sessionID string) (string, string, error) {
|
||||
return "tokenID",
|
||||
"token",
|
||||
nil
|
||||
},
|
||||
intentAlg: decryption(nil),
|
||||
now: func() time.Time {
|
||||
return testNow
|
||||
},
|
||||
},
|
||||
metadata: map[string][]byte{
|
||||
"key": []byte("value"),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-SAf42", "Errors.Intent.Expired"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"set user, intent, metadata and token",
|
||||
fields{
|
||||
@ -768,13 +874,14 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
),
|
||||
eventFromEventPusher(
|
||||
idpintent.NewSucceededEvent(context.Background(),
|
||||
&idpintent.NewAggregate("id", "instance1").Aggregate,
|
||||
&idpintent.NewAggregate("intent", "instance1").Aggregate,
|
||||
nil,
|
||||
"idpUserID",
|
||||
"idpUsername",
|
||||
"userID",
|
||||
nil,
|
||||
"",
|
||||
time.Now().Add(time.Hour),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -783,6 +890,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
"userID", "org1", testNow, &language.Afrikaans),
|
||||
session.NewIntentCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
testNow),
|
||||
idpintent.NewConsumedEvent(context.Background(), &idpintent.NewAggregate("intent", "org1").Aggregate),
|
||||
session.NewMetadataSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
map[string][]byte{"key": []byte("value")}),
|
||||
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
@ -842,13 +950,14 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
),
|
||||
eventFromEventPusher(
|
||||
idpintent.NewSucceededEvent(context.Background(),
|
||||
&idpintent.NewAggregate("id", "instance1").Aggregate,
|
||||
&idpintent.NewAggregate("intent", "instance1").Aggregate,
|
||||
nil,
|
||||
"idpUserID",
|
||||
"idpUsername",
|
||||
"",
|
||||
nil,
|
||||
"",
|
||||
time.Now().Add(time.Hour),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -866,6 +975,7 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
"userID", "org1", testNow, &language.Afrikaans),
|
||||
session.NewIntentCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
testNow),
|
||||
idpintent.NewConsumedEvent(context.Background(), &idpintent.NewAggregate("intent", "org1").Aggregate),
|
||||
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
"tokenID"),
|
||||
),
|
||||
|
@ -7,15 +7,16 @@ import (
|
||||
)
|
||||
|
||||
type SystemDefaults struct {
|
||||
SecretGenerators SecretGenerators
|
||||
PasswordHasher crypto.HashConfig
|
||||
SecretHasher crypto.HashConfig
|
||||
Multifactors MultifactorConfig
|
||||
DomainVerification DomainVerification
|
||||
Notifications Notifications
|
||||
KeyConfig KeyConfig
|
||||
DefaultQueryLimit uint64
|
||||
MaxQueryLimit uint64
|
||||
SecretGenerators SecretGenerators
|
||||
PasswordHasher crypto.HashConfig
|
||||
SecretHasher crypto.HashConfig
|
||||
Multifactors MultifactorConfig
|
||||
DomainVerification DomainVerification
|
||||
Notifications Notifications
|
||||
KeyConfig KeyConfig
|
||||
DefaultQueryLimit uint64
|
||||
MaxQueryLimit uint64
|
||||
MaxIdPIntentLifetime time.Duration
|
||||
}
|
||||
|
||||
type SecretGenerators struct {
|
||||
|
@ -115,6 +115,7 @@ const (
|
||||
IDPIntentStateStarted
|
||||
IDPIntentStateSucceeded
|
||||
IDPIntentStateFailed
|
||||
IDPIntentStateConsumed
|
||||
|
||||
idpIntentStateCount
|
||||
)
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
)
|
||||
|
||||
var _ idp.Session = (*Session)(nil)
|
||||
|
||||
// Session extends the [oidc.Session] with the formValues returned from the callback.
|
||||
// This enables to parse the user (name and email), which Apple only returns as form params on registration
|
||||
type Session struct {
|
||||
|
@ -3,6 +3,7 @@ package azuread
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
@ -12,6 +13,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
)
|
||||
|
||||
var _ idp.Session = (*Session)(nil)
|
||||
|
||||
// Session extends the [oauth.Session] to be able to handle the id_token and to implement the [idp.SessionSupportsMigration] functionality
|
||||
type Session struct {
|
||||
*Provider
|
||||
@ -79,6 +82,13 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.OAuthSession == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.OAuthSession.ExpiresAt()
|
||||
}
|
||||
|
||||
// Tokens returns the [oidc.Tokens] of the underlying [oauth.Session].
|
||||
func (s *Session) Tokens() *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
return s.oauth().Tokens
|
||||
|
@ -57,6 +57,13 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return &User{s.Tokens.IDTokenClaims}, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.Tokens == nil || s.Tokens.IDTokenClaims == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.Tokens.IDTokenClaims.GetExpiration()
|
||||
}
|
||||
|
||||
func (s *Session) validateToken(ctx context.Context, token string) (*oidc.IDTokenClaims, error) {
|
||||
logging.Debug("begin token validation")
|
||||
// TODO: be able to specify them in the template: https://github.com/zitadel/zitadel/issues/5322
|
||||
|
@ -96,6 +96,10 @@ func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
return time.Time{} // falls back to the default expiration time
|
||||
}
|
||||
|
||||
func tryBind(
|
||||
server string,
|
||||
startTLS bool,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
@ -69,6 +70,13 @@ func (s *Session) FetchUser(ctx context.Context) (_ idp.User, err error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.Tokens == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.Tokens.Expiry
|
||||
}
|
||||
|
||||
func (s *Session) authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
|
@ -3,6 +3,7 @@ package oidc
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
@ -72,6 +73,13 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.Tokens == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.Tokens.Expiry
|
||||
}
|
||||
|
||||
func (s *Session) Authorize(ctx context.Context) (err error) {
|
||||
if s.Code == "" {
|
||||
return ErrCodeMissing
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
@ -107,6 +108,13 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return userMapper, nil
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
if s.Assertion == nil || s.Assertion.Conditions == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return s.Assertion.Conditions.NotOnOrAfter
|
||||
}
|
||||
|
||||
func (s *Session) transientMappingID() (string, error) {
|
||||
for _, statement := range s.Assertion.AttributeStatements {
|
||||
for _, attribute := range statement.Attributes {
|
||||
|
@ -2,6 +2,7 @@ package idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Session is the minimal implementation for a session of a 3rd party authentication [Provider]
|
||||
@ -9,6 +10,7 @@ type Session interface {
|
||||
GetAuth(ctx context.Context) (content string, redirect bool)
|
||||
PersistentParameters() map[string]any
|
||||
FetchUser(ctx context.Context) (User, error)
|
||||
ExpiresAt() time.Time
|
||||
}
|
||||
|
||||
// SessionSupportsMigration is an optional extension to the Session interface.
|
||||
|
@ -672,6 +672,23 @@ func (i *Instance) CreatePasswordSession(t *testing.T, ctx context.Context, user
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (i *Instance) CreateIntentSession(t *testing.T, ctx context.Context, userID, intentID, intentToken string) (id, token string, start, change time.Time) {
|
||||
createResp, err := i.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{UserId: userID},
|
||||
},
|
||||
IdpIntent: &session.CheckIDPIntent{
|
||||
IdpIntentId: intentID,
|
||||
IdpIntentToken: intentToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return createResp.GetSessionId(), createResp.GetSessionToken(),
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrant(ctx context.Context, projectID, grantedOrgID string) *mgmt.AddProjectGrantResponse {
|
||||
resp, err := i.Client.Mgmt.AddProjectGrant(ctx, &mgmt.AddProjectGrantRequest{
|
||||
GrantedOrgId: grantedOrgID,
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
crewjam_saml "github.com/crewjam/saml"
|
||||
"github.com/go-chi/chi/v5"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/zitadel/logging"
|
||||
@ -48,7 +49,7 @@ func CallURL(ch Channel) string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func SuccessfulOAuthIntent(instanceID, idpID, idpUserID, userID string) (string, string, time.Time, uint64, error) {
|
||||
func SuccessfulOAuthIntent(instanceID, idpID, idpUserID, userID string, expiry time.Time) (string, string, time.Time, uint64, error) {
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
@ -59,6 +60,7 @@ func SuccessfulOAuthIntent(instanceID, idpID, idpUserID, userID string) (string,
|
||||
IDPID: idpID,
|
||||
IDPUserID: idpUserID,
|
||||
UserID: userID,
|
||||
Expiry: expiry,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, uint64(0), err
|
||||
@ -66,7 +68,7 @@ func SuccessfulOAuthIntent(instanceID, idpID, idpUserID, userID string) (string,
|
||||
return resp.IntentID, resp.Token, resp.ChangeDate, resp.Sequence, nil
|
||||
}
|
||||
|
||||
func SuccessfulOIDCIntent(instanceID, idpID, idpUserID, userID string) (string, string, time.Time, uint64, error) {
|
||||
func SuccessfulOIDCIntent(instanceID, idpID, idpUserID, userID string, expiry time.Time) (string, string, time.Time, uint64, error) {
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
@ -77,6 +79,7 @@ func SuccessfulOIDCIntent(instanceID, idpID, idpUserID, userID string) (string,
|
||||
IDPID: idpID,
|
||||
IDPUserID: idpUserID,
|
||||
UserID: userID,
|
||||
Expiry: expiry,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, uint64(0), err
|
||||
@ -84,7 +87,7 @@ func SuccessfulOIDCIntent(instanceID, idpID, idpUserID, userID string) (string,
|
||||
return resp.IntentID, resp.Token, resp.ChangeDate, resp.Sequence, nil
|
||||
}
|
||||
|
||||
func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID string) (string, string, time.Time, uint64, error) {
|
||||
func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID string, expiry time.Time) (string, string, time.Time, uint64, error) {
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
@ -95,6 +98,7 @@ func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID string) (string,
|
||||
IDPID: idpID,
|
||||
IDPUserID: idpUserID,
|
||||
UserID: userID,
|
||||
Expiry: expiry,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, uint64(0), err
|
||||
@ -282,10 +286,11 @@ func readLoop(ws *websocket.Conn) (done chan error) {
|
||||
}
|
||||
|
||||
type SuccessfulIntentRequest struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
IDPID string `json:"idp_id"`
|
||||
IDPUserID string `json:"idp_user_id"`
|
||||
UserID string `json:"user_id"`
|
||||
InstanceID string `json:"instance_id"`
|
||||
IDPID string `json:"idp_id"`
|
||||
IDPUserID string `json:"idp_user_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Expiry time.Time `json:"expiry"`
|
||||
}
|
||||
type SuccessfulIntentResponse struct {
|
||||
IntentID string `json:"intent_id"`
|
||||
@ -376,6 +381,7 @@ func createSuccessfulOAuthIntent(ctx context.Context, cmd *command.Commands, req
|
||||
Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
Expiry: req.Expiry,
|
||||
},
|
||||
IDToken: "idToken",
|
||||
},
|
||||
@ -407,6 +413,7 @@ func createSuccessfulOIDCIntent(ctx context.Context, cmd *command.Commands, req
|
||||
Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: "accessToken",
|
||||
Expiry: req.Expiry,
|
||||
},
|
||||
IDToken: "idToken",
|
||||
},
|
||||
@ -431,9 +438,16 @@ func createSuccessfulSAMLIntent(ctx context.Context, cmd *command.Commands, req
|
||||
ID: req.IDPUserID,
|
||||
Attributes: map[string][]string{"attribute1": {"value1"}},
|
||||
}
|
||||
assertion := &crewjam_saml.Assertion{ID: "id"}
|
||||
session := &saml.Session{
|
||||
Assertion: &crewjam_saml.Assertion{
|
||||
ID: "id",
|
||||
Conditions: &crewjam_saml.Conditions{
|
||||
NotOnOrAfter: req.Expiry,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
token, err := cmd.SucceedSAMLIDPIntent(ctx, writeModel, idpUser, req.UserID, assertion)
|
||||
token, err := cmd.SucceedSAMLIDPIntent(ctx, writeModel, idpUser, req.UserID, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -465,8 +479,14 @@ func createSuccessfulLDAPIntent(ctx context.Context, cmd *command.Commands, req
|
||||
"",
|
||||
"",
|
||||
)
|
||||
attributes := map[string][]string{"id": {req.IDPUserID}, "username": {username}, "language": {lang.String()}}
|
||||
token, err := cmd.SucceedLDAPIDPIntent(ctx, writeModel, idpUser, req.UserID, attributes)
|
||||
session := &ldap.Session{Entry: &goldap.Entry{
|
||||
Attributes: []*goldap.EntryAttribute{
|
||||
{Name: "id", Values: []string{req.IDPUserID}},
|
||||
{Name: "username", Values: []string{username}},
|
||||
{Name: "language", Values: []string{lang.String()}},
|
||||
},
|
||||
}}
|
||||
token, err := cmd.SucceedLDAPIDPIntent(ctx, writeModel, idpUser, req.UserID, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -11,4 +11,5 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SAMLRequestEventType, SAMLRequestEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, LDAPSucceededEventType, LDAPSucceededEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, FailedEventType, FailedEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, ConsumedEventType, eventstore.GenericEventMapper[ConsumedEvent])
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package idpintent
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@ -16,6 +17,7 @@ const (
|
||||
SAMLRequestEventType = instanceEventTypePrefix + "saml.requested"
|
||||
LDAPSucceededEventType = instanceEventTypePrefix + "ldap.succeeded"
|
||||
FailedEventType = instanceEventTypePrefix + "failed"
|
||||
ConsumedEventType = instanceEventTypePrefix + "consumed"
|
||||
)
|
||||
|
||||
type StartedEvent struct {
|
||||
@ -79,6 +81,7 @@ type SucceededEvent struct {
|
||||
|
||||
IDPAccessToken *crypto.CryptoValue `json:"idpAccessToken,omitempty"`
|
||||
IDPIDToken string `json:"idpIdToken,omitempty"`
|
||||
ExpiresAt time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
func NewSucceededEvent(
|
||||
@ -90,6 +93,7 @@ func NewSucceededEvent(
|
||||
userID string,
|
||||
idpAccessToken *crypto.CryptoValue,
|
||||
idpIDToken string,
|
||||
expiresAt time.Time,
|
||||
) *SucceededEvent {
|
||||
return &SucceededEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -103,6 +107,7 @@ func NewSucceededEvent(
|
||||
UserID: userID,
|
||||
IDPAccessToken: idpAccessToken,
|
||||
IDPIDToken: idpIDToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +141,7 @@ type SAMLSucceededEvent struct {
|
||||
UserID string `json:"userId,omitempty"`
|
||||
|
||||
Assertion *crypto.CryptoValue `json:"assertion,omitempty"`
|
||||
ExpiresAt time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
func NewSAMLSucceededEvent(
|
||||
@ -146,6 +152,7 @@ func NewSAMLSucceededEvent(
|
||||
idpUserName,
|
||||
userID string,
|
||||
assertion *crypto.CryptoValue,
|
||||
expiresAt time.Time,
|
||||
) *SAMLSucceededEvent {
|
||||
return &SAMLSucceededEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -158,6 +165,7 @@ func NewSAMLSucceededEvent(
|
||||
IDPUserName: idpUserName,
|
||||
UserID: userID,
|
||||
Assertion: assertion,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +241,7 @@ type LDAPSucceededEvent struct {
|
||||
UserID string `json:"userId,omitempty"`
|
||||
|
||||
EntryAttributes map[string][]string `json:"user,omitempty"`
|
||||
ExpiresAt time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
func NewLDAPSucceededEvent(
|
||||
@ -243,6 +252,7 @@ func NewLDAPSucceededEvent(
|
||||
idpUserName,
|
||||
userID string,
|
||||
attributes map[string][]string,
|
||||
expiresAt time.Time,
|
||||
) *LDAPSucceededEvent {
|
||||
return &LDAPSucceededEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -255,6 +265,7 @@ func NewLDAPSucceededEvent(
|
||||
IDPUserName: idpUserName,
|
||||
UserID: userID,
|
||||
EntryAttributes: attributes,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,3 +331,32 @@ func FailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type ConsumedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func NewConsumedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *ConsumedEvent {
|
||||
return &ConsumedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ConsumedEventType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ConsumedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *ConsumedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ConsumedEvent) SetBaseEvent(base *eventstore.BaseEvent) {
|
||||
e.BaseEvent = *base
|
||||
}
|
||||
|
@ -554,6 +554,7 @@ Errors:
|
||||
StateMissing: В заявката липсва параметър състояние
|
||||
NotStarted: Намерението не е стартирано или вече е прекратено
|
||||
NotSucceeded: Намерението не е успешно
|
||||
Expired: Намерението е изтекло
|
||||
TokenCreationFailed: Неуспешно създаване на токен
|
||||
InvalidToken: Знакът за намерение е невалиден
|
||||
OtherUser: Намерение, предназначено за друг потребител
|
||||
|
@ -534,6 +534,7 @@ Errors:
|
||||
StateMissing: V požadavku chybí parametr stavu
|
||||
NotStarted: Záměr nebyl zahájen nebo již byl ukončen
|
||||
NotSucceeded: Záměr nebyl úspěšný
|
||||
Expired: Záměr vypršel
|
||||
TokenCreationFailed: Vytvoření tokenu selhalo
|
||||
InvalidToken: Token záměru je neplatný
|
||||
OtherUser: Záměr určený pro jiného uživatele
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: State parameter fehlt im Request
|
||||
NotStarted: Intent wurde nicht gestartet oder wurde bereits beendet
|
||||
NotSucceeded: Intent war nicht erfolgreich
|
||||
Expired: Intent ist abgelaufen
|
||||
TokenCreationFailed: Tokenerstellung schlug fehl
|
||||
InvalidToken: Intent Token ist ungültig
|
||||
OtherUser: Intent ist für anderen Benutzer gedacht
|
||||
|
@ -537,6 +537,7 @@ Errors:
|
||||
StateMissing: State parameter is missing in the request
|
||||
NotStarted: Intent is not started or was already terminated
|
||||
NotSucceeded: Intent has not succeeded
|
||||
Expired: Intent has expired
|
||||
TokenCreationFailed: Token creation failed
|
||||
InvalidToken: Intent Token is invalid
|
||||
OtherUser: Intent meant for another user
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: Falta un parámetro de estado en la solicitud
|
||||
NotStarted: La intención no se ha iniciado o ya ha finalizado
|
||||
NotSucceeded: Intento fallido
|
||||
Expired: La intención ha expirado
|
||||
TokenCreationFailed: Fallo en la creación del token
|
||||
InvalidToken: El token de la intención no es válido
|
||||
OtherUser: Destinado a otro usuario
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: Paramètre d'état manquant dans la requête
|
||||
NotStarted: Intent n'a pas démarré ou s'est déjà terminé
|
||||
NotSucceeded: l'intention n'a pas abouti
|
||||
Expired: L'intention a expiré
|
||||
TokenCreationFailed: La création du token a échoué
|
||||
InvalidToken: Le jeton d'intention n'est pas valide
|
||||
OtherUser: Intention destinée à un autre utilisateur
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: A kérésből hiányzik a State paraméter
|
||||
NotStarted: Az intent nem indult el, vagy már befejeződött
|
||||
NotSucceeded: Az intent nem sikerült
|
||||
Expired: A kérésből lejárt
|
||||
TokenCreationFailed: A token létrehozása nem sikerült
|
||||
InvalidToken: Az Intent Token érvénytelen
|
||||
OtherUser: Az intent egy másik felhasználónak szól
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: Parameter status tidak ada dalam permintaan
|
||||
NotStarted: Niat belum dimulai atau sudah dihentikan
|
||||
NotSucceeded: Niatnya belum berhasil
|
||||
Expired: Kode sudah habis masa berlakunya
|
||||
TokenCreationFailed: Pembuatan token gagal
|
||||
InvalidToken: Token Niat tidak valid
|
||||
OtherUser: Maksudnya ditujukan untuk pengguna lain
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: parametro di stato mancante nella richiesta
|
||||
NotStarted: l'intento non è stato avviato o è già stato terminato
|
||||
NotSucceeded: l'intento non è andato a buon fine
|
||||
Expired: L'intento è scaduto
|
||||
TokenCreationFailed: creazione del token fallita
|
||||
InvalidToken: Il token dell'intento non è valido
|
||||
OtherUser: Intento destinato a un altro utente
|
||||
|
@ -537,6 +537,7 @@ Errors:
|
||||
StateMissing: リクエストに State パラメータがありません
|
||||
NotStarted: インテントが開始されなかったか、既に終了している
|
||||
NotSucceeded: インテントが成功しなかった
|
||||
Expired: 意図の有効期限が切れました
|
||||
TokenCreationFailed: トークンの作成に失敗しました
|
||||
InvalidToken: インテントのトークンが無効である
|
||||
OtherUser: 他のユーザーを意図している
|
||||
|
@ -537,6 +537,7 @@ Errors:
|
||||
StateMissing: 요청에 상태 매개변수가 누락되었습니다
|
||||
NotStarted: 의도가 시작되지 않았거나 이미 종료되었습니다
|
||||
NotSucceeded: 의도가 성공하지 않았습니다
|
||||
Expired: 의도의 유효 기간이 만료되었습니다
|
||||
TokenCreationFailed: 토큰 생성 실패
|
||||
InvalidToken: 의도 토큰이 유효하지 않습니다
|
||||
OtherUser: 다른 사용자를 위한 의도입니다
|
||||
|
@ -535,6 +535,7 @@ Errors:
|
||||
StateMissing: Параметарот State недостасува во барањето
|
||||
NotStarted: Намерата не е започната или веќе завршена
|
||||
NotSucceeded: Намерата не е успешна
|
||||
Expired: Намерата е истечена
|
||||
TokenCreationFailed: Неуспешно креирање на токен
|
||||
InvalidToken: Токенот за намера е невалиден
|
||||
OtherUser: Намерата е за друг корисник
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: Staat parameter ontbreekt in het verzoek
|
||||
NotStarted: Intentie is niet gestart of was al beëindigd
|
||||
NotSucceeded: Intentie is niet geslaagd
|
||||
Expired: Intentie is verlopen
|
||||
TokenCreationFailed: Token aanmaken mislukt
|
||||
InvalidToken: Intentie Token is ongeldig
|
||||
OtherUser: Intentie bedoeld voor een andere gebruiker
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: Brak parametru stanu w żądaniu
|
||||
NotStarted: Intencja nie została rozpoczęta lub już się zakończyła
|
||||
NotSucceeded: intencja nie powiodła się
|
||||
Expired: Intencja wygasła
|
||||
TokenCreationFailed: Tworzenie tokena nie powiodło się
|
||||
InvalidToken: Token intencji jest nieprawidłowy
|
||||
OtherUser: Intencja przeznaczona dla innego użytkownika
|
||||
|
@ -535,6 +535,7 @@ Errors:
|
||||
StateMissing: O parâmetro de estado está faltando na solicitação
|
||||
NotStarted: A intenção não foi iniciada ou já foi encerrada
|
||||
NotSucceeded: A intenção não teve sucesso
|
||||
Expired: A intenção expirou
|
||||
TokenCreationFailed: Falha na criação do token
|
||||
InvalidToken: O token da intenção é inválido
|
||||
OtherUser: Intenção destinada a outro usuário
|
||||
|
@ -537,6 +537,7 @@ Errors:
|
||||
StateMissing: Parametrul de stare lipsește în cerere
|
||||
NotStarted: Intenția nu este pornită sau a fost deja terminată
|
||||
NotSucceeded: Intenția nu a reușit
|
||||
Expired: Intenția a expirat
|
||||
TokenCreationFailed: Crearea token-ului a eșuat
|
||||
InvalidToken: Token-ul intenției este invalid
|
||||
OtherUser: Intenția este destinată altui utilizator
|
||||
|
@ -525,6 +525,7 @@ Errors:
|
||||
StateMissing: В запросе отсутствует параметр State
|
||||
NotStarted: Намерение не начато или уже прекращено
|
||||
NotSucceeded: Намерение не увенчалось успехом
|
||||
Epired: Намерение истекло
|
||||
TokenCreationFailed: Не удалось создать токен
|
||||
InvalidToken: Маркер намерения недействителен
|
||||
OtherUser: Намерение, предназначенное для другого пользователя
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: State-parameter saknas i begäran
|
||||
NotStarted: Avsikten har inte startat eller har redan avslutats
|
||||
NotSucceeded: Avsikten har inte lyckats
|
||||
Expired: Avsikten har gått ut
|
||||
TokenCreationFailed: Token-skapande misslyckades
|
||||
InvalidToken: Avsiktstoken är ogiltig
|
||||
OtherUser: Avsikten är avsedd för en annan användare
|
||||
|
@ -536,6 +536,7 @@ Errors:
|
||||
StateMissing: 请求中缺少状态参数
|
||||
NotStarted: 意图没有开始或已经结束
|
||||
NotSucceeded: 意图不成功
|
||||
Expired: 意图已过期
|
||||
TokenCreationFailed: 令牌创建失败
|
||||
InvalidToken: 意图令牌是无效的
|
||||
OtherUser: 意图是为另一个用户准备的
|
||||
|
Loading…
x
Reference in New Issue
Block a user