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