mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +00:00
chore: improve integration tests (#8727)
Improve integration tests: - spliting the tests in TokenExchange to isolated instances and in parallel - corrected some test structure so that the check for Details is no done anymore if the test already failed - replace required-calls with assert-calls to not stop the testing - add gofakeit for application, project and usernames(emails) - add eventually checks for testing in actions v2, so the request only get called when the execution is defined - check for length of results in list/search endpoints to avoid index errors
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
@@ -121,7 +122,7 @@ func Test_ZITADEL_API_missing_authentication(t *testing.T) {
|
||||
|
||||
func Test_ZITADEL_API_missing_mfa_policy(t *testing.T) {
|
||||
clientID, _ := createClient(t, Instance)
|
||||
org := Instance.CreateOrganization(CTXIAM, fmt.Sprintf("ZITADEL_API_MISSING_MFA_%d", time.Now().UnixNano()), fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()))
|
||||
org := Instance.CreateOrganization(CTXIAM, fmt.Sprintf("ZITADEL_API_MISSING_MFA_%s", gofakeit.AppName()), gofakeit.Email())
|
||||
userID := org.CreatedAdmins[0].GetUserId()
|
||||
Instance.SetUserPassword(CTXIAM, userID, integration.UserPassword, false)
|
||||
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope)
|
||||
|
@@ -25,40 +25,71 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
func setTokenExchangeFeature(t *testing.T, value bool) {
|
||||
iamCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
func setTokenExchangeFeature(t *testing.T, instance *integration.Instance, value bool) {
|
||||
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
_, err := Instance.Client.FeatureV2.SetInstanceFeatures(iamCTX, &feature.SetInstanceFeaturesRequest{
|
||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(iamCTX, &feature.SetInstanceFeaturesRequest{
|
||||
OidcTokenExchange: proto.Bool(value),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(iamCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
assert.NoError(ttt, err)
|
||||
if f.OidcTokenExchange.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
func resetFeatures(t *testing.T) {
|
||||
iamCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
_, err := Instance.Client.FeatureV2.ResetInstanceFeatures(iamCTX, &feature.ResetInstanceFeaturesRequest{})
|
||||
func resetFeatures(t *testing.T, instance *integration.Instance) {
|
||||
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
_, err := instance.Client.FeatureV2.ResetInstanceFeatures(iamCTX, &feature.ResetInstanceFeaturesRequest{})
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
func setImpersonationPolicy(t *testing.T, value bool) {
|
||||
iamCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
func setImpersonationPolicy(t *testing.T, instance *integration.Instance, value bool) {
|
||||
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
policy, err := Instance.Client.Admin.GetSecurityPolicy(iamCTX, &admin.GetSecurityPolicyRequest{})
|
||||
policy, err := instance.Client.Admin.GetSecurityPolicy(iamCTX, &admin.GetSecurityPolicyRequest{})
|
||||
require.NoError(t, err)
|
||||
if policy.GetPolicy().GetEnableImpersonation() != value {
|
||||
_, err = Instance.Client.Admin.SetSecurityPolicy(iamCTX, &admin.SetSecurityPolicyRequest{
|
||||
_, err = instance.Client.Admin.SetSecurityPolicy(iamCTX, &admin.SetSecurityPolicyRequest{
|
||||
EnableImpersonation: value,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := instance.Client.Admin.GetSecurityPolicy(iamCTX, &admin.GetSecurityPolicyRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
if f.GetPolicy().GetEnableImpersonation() != value {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring impersonation policy")
|
||||
}
|
||||
|
||||
func createMachineUserPATWithMembership(t *testing.T, roles ...string) (userID, pat string) {
|
||||
iamCTX := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
userID, pat, err := Instance.CreateMachineUserPATWithMembership(iamCTX, roles...)
|
||||
func createMachineUserPATWithMembership(ctx context.Context, t *testing.T, instance *integration.Instance, roles ...string) (userID, pat string) {
|
||||
userID, pat, err := instance.CreateMachineUserPATWithMembership(ctx, roles...)
|
||||
require.NoError(t, err)
|
||||
return userID, pat
|
||||
}
|
||||
@@ -114,40 +145,34 @@ func refreshTokenVerifier(ctx context.Context, provider rp.RelyingParty, subject
|
||||
func TestServer_TokenExchange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetFeatures(t)
|
||||
setImpersonationPolicy(t, false)
|
||||
})
|
||||
instance := integration.NewInstance(CTX)
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
userResp := instance.CreateHumanUser(ctx)
|
||||
|
||||
client, keyData, err := Instance.CreateOIDCTokenExchangeClient(CTX)
|
||||
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
|
||||
require.NoError(t, err)
|
||||
signer, err := rp.SignerFromKeyFile(keyData)()
|
||||
require.NoError(t, err)
|
||||
exchanger, err := tokenexchange.NewTokenExchangerJWTProfile(CTX, Instance.OIDCIssuer(), client.GetClientId(), signer)
|
||||
exchanger, err := tokenexchange.NewTokenExchangerJWTProfile(ctx, instance.OIDCIssuer(), client.GetClientId(), signer)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
_, orgImpersonatorPAT := createMachineUserPATWithMembership(ctx, t, instance, "ORG_ADMIN_IMPERSONATOR")
|
||||
serviceUserID, noPermPAT := createMachineUserPATWithMembership(ctx, t, instance)
|
||||
|
||||
iamUserID, iamImpersonatorPAT := createMachineUserPATWithMembership(t, "IAM_ADMIN_IMPERSONATOR")
|
||||
orgUserID, orgImpersonatorPAT := createMachineUserPATWithMembership(t, "ORG_ADMIN_IMPERSONATOR")
|
||||
serviceUserID, noPermPAT := createMachineUserPATWithMembership(t)
|
||||
|
||||
// exchange some tokens for later use
|
||||
setTokenExchangeFeature(t, true)
|
||||
teResp, err := tokenexchange.ExchangeToken(CTX, exchanger, noPermPAT, oidc.AccessTokenType, "", "", nil, nil, nil, oidc.AccessTokenType)
|
||||
// test that feature is disabled per default
|
||||
teResp, err := tokenexchange.ExchangeToken(ctx, exchanger, noPermPAT, oidc.AccessTokenType, "", "", nil, nil, nil, oidc.AccessTokenType)
|
||||
require.Error(t, err)
|
||||
setTokenExchangeFeature(t, instance, true)
|
||||
teResp, err = tokenexchange.ExchangeToken(ctx, exchanger, noPermPAT, oidc.AccessTokenType, "", "", nil, nil, nil, oidc.AccessTokenType)
|
||||
require.NoError(t, err)
|
||||
|
||||
patScopes := oidc.SpaceDelimitedArray{"openid", "profile", "urn:zitadel:iam:user:metadata", "urn:zitadel:iam:user:resourceowner"}
|
||||
|
||||
relyingParty, err := rp.NewRelyingPartyOIDC(CTX, Instance.OIDCIssuer(), client.GetClientId(), "", "", []string{"openid"}, rp.WithJWTProfile(rp.SignerFromKeyFile(keyData)))
|
||||
relyingParty, err := rp.NewRelyingPartyOIDC(ctx, instance.OIDCIssuer(), client.GetClientId(), "", "", []string{"openid"}, rp.WithJWTProfile(rp.SignerFromKeyFile(keyData)))
|
||||
require.NoError(t, err)
|
||||
resourceServer, err := Instance.CreateResourceServerJWTProfile(CTX, keyData)
|
||||
resourceServer, err := instance.CreateResourceServerJWTProfile(ctx, keyData)
|
||||
require.NoError(t, err)
|
||||
|
||||
type settings struct {
|
||||
tokenExchangeFeature bool
|
||||
impersonationPolicy bool
|
||||
}
|
||||
type args struct {
|
||||
SubjectToken string
|
||||
SubjectTokenType oidc.TokenType
|
||||
@@ -168,30 +193,13 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
verifyIDToken func(t *testing.T, token string)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
settings settings
|
||||
args args
|
||||
want result
|
||||
wantErr bool
|
||||
name string
|
||||
args args
|
||||
want result
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "feature disabled error",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: false,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported resource parameter",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -201,10 +209,6 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid subject token",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: "foo",
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -213,10 +217,6 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token to default",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -226,16 +226,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, ""),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token to access token",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -246,16 +242,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, ""),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token to JWT",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -266,16 +258,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, ""),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token to ID Token",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -286,7 +274,7 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: "N_A",
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: idTokenVerifier(CTX, relyingParty, serviceUserID, ""),
|
||||
verifyAccessToken: idTokenVerifier(ctx, relyingParty, serviceUserID, ""),
|
||||
verifyIDToken: func(t *testing.T, token string) {
|
||||
assert.Empty(t, token)
|
||||
},
|
||||
@@ -294,10 +282,6 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: refresh token not allowed",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: teResp.RefreshToken,
|
||||
SubjectTokenType: oidc.RefreshTokenType,
|
||||
@@ -307,10 +291,6 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: alternate scope for refresh token",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -322,17 +302,13 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile"},
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, ""),
|
||||
verifyRefreshToken: refreshTokenVerifier(CTX, relyingParty, "", ""),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, ""),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, ""),
|
||||
verifyRefreshToken: refreshTokenVerifier(ctx, relyingParty, "", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token, requested token type not supported error",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -342,10 +318,6 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE: access token, invalid audience",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: noPermPAT,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -356,12 +328,8 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "IMPERSONATION: subject: userID, actor: access token, policy disabled error",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: false,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: User.GetUserId(),
|
||||
SubjectToken: userResp.GetUserId(),
|
||||
SubjectTokenType: oidc_api.UserIDTokenType,
|
||||
RequestedTokenType: oidc.AccessTokenType,
|
||||
ActorToken: orgImpersonatorPAT,
|
||||
@@ -369,14 +337,94 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tokenexchange.ExchangeToken(ctx, exchanger, tt.args.SubjectToken, tt.args.SubjectTokenType, tt.args.ActorToken, tt.args.ActorTokenType, tt.args.Resource, tt.args.Audience, tt.args.Scopes, tt.args.RequestedTokenType)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want.issuedTokenType, got.IssuedTokenType)
|
||||
assert.Equal(t, tt.want.tokenType, got.TokenType)
|
||||
assert.Greater(t, got.ExpiresIn, tt.want.expiresIn)
|
||||
assert.Equal(t, tt.want.scopes, got.Scopes)
|
||||
if tt.want.verifyAccessToken != nil {
|
||||
tt.want.verifyAccessToken(t, got.AccessToken)
|
||||
}
|
||||
if tt.want.verifyRefreshToken != nil {
|
||||
tt.want.verifyRefreshToken(t, got.RefreshToken)
|
||||
}
|
||||
if tt.want.verifyIDToken != nil {
|
||||
tt.want.verifyIDToken(t, got.IDToken)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_TokenExchangeImpersonation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
instance := integration.NewInstance(CTX)
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
userResp := instance.CreateHumanUser(ctx)
|
||||
|
||||
// exchange some tokens for later use
|
||||
setTokenExchangeFeature(t, instance, true)
|
||||
setImpersonationPolicy(t, instance, true)
|
||||
|
||||
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
|
||||
require.NoError(t, err)
|
||||
signer, err := rp.SignerFromKeyFile(keyData)()
|
||||
require.NoError(t, err)
|
||||
exchanger, err := tokenexchange.NewTokenExchangerJWTProfile(ctx, instance.OIDCIssuer(), client.GetClientId(), signer)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamUserID, iamImpersonatorPAT := createMachineUserPATWithMembership(ctx, t, instance, "IAM_ADMIN_IMPERSONATOR")
|
||||
orgUserID, orgImpersonatorPAT := createMachineUserPATWithMembership(ctx, t, instance, "ORG_ADMIN_IMPERSONATOR")
|
||||
serviceUserID, noPermPAT := createMachineUserPATWithMembership(ctx, t, instance)
|
||||
|
||||
teResp, err := tokenexchange.ExchangeToken(ctx, exchanger, noPermPAT, oidc.AccessTokenType, "", "", nil, nil, nil, oidc.AccessTokenType)
|
||||
require.NoError(t, err)
|
||||
|
||||
patScopes := oidc.SpaceDelimitedArray{"openid", "profile", "urn:zitadel:iam:user:metadata", "urn:zitadel:iam:user:resourceowner"}
|
||||
|
||||
relyingParty, err := rp.NewRelyingPartyOIDC(ctx, instance.OIDCIssuer(), client.GetClientId(), "", "", []string{"openid"}, rp.WithJWTProfile(rp.SignerFromKeyFile(keyData)))
|
||||
require.NoError(t, err)
|
||||
resourceServer, err := instance.CreateResourceServerJWTProfile(ctx, keyData)
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
SubjectToken string
|
||||
SubjectTokenType oidc.TokenType
|
||||
ActorToken string
|
||||
ActorTokenType oidc.TokenType
|
||||
Resource []string
|
||||
Audience []string
|
||||
Scopes []string
|
||||
RequestedTokenType oidc.TokenType
|
||||
}
|
||||
type result struct {
|
||||
issuedTokenType oidc.TokenType
|
||||
tokenType string
|
||||
expiresIn uint64
|
||||
scopes oidc.SpaceDelimitedArray
|
||||
verifyAccessToken func(t *testing.T, token string)
|
||||
verifyRefreshToken func(t *testing.T, token string)
|
||||
verifyIDToken func(t *testing.T, token string)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want result
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "IMPERSONATION: subject: userID, actor: access token, membership not found error",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: User.GetUserId(),
|
||||
SubjectToken: userResp.GetUserId(),
|
||||
SubjectTokenType: oidc_api.UserIDTokenType,
|
||||
RequestedTokenType: oidc.AccessTokenType,
|
||||
ActorToken: noPermPAT,
|
||||
@@ -386,12 +434,8 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "IAM IMPERSONATION: subject: userID, actor: access token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: User.GetUserId(),
|
||||
SubjectToken: userResp.GetUserId(),
|
||||
SubjectTokenType: oidc_api.UserIDTokenType,
|
||||
RequestedTokenType: oidc.AccessTokenType,
|
||||
ActorToken: iamImpersonatorPAT,
|
||||
@@ -402,18 +446,14 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, User.GetUserId(), iamUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, User.GetUserId(), iamUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, userResp.GetUserId(), iamUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, userResp.GetUserId(), iamUserID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ORG IMPERSONATION: subject: userID, actor: access token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: User.GetUserId(),
|
||||
SubjectToken: userResp.GetUserId(),
|
||||
SubjectTokenType: oidc_api.UserIDTokenType,
|
||||
RequestedTokenType: oidc.AccessTokenType,
|
||||
ActorToken: orgImpersonatorPAT,
|
||||
@@ -424,16 +464,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, User.GetUserId(), orgUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, User.GetUserId(), orgUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, userResp.GetUserId(), orgUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, userResp.GetUserId(), orgUserID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ORG IMPERSONATION: subject: access token, actor: access token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: teResp.AccessToken,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -446,16 +482,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, orgUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, orgUserID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ORG IMPERSONATION: subject: ID token, actor: access token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: teResp.IDToken,
|
||||
SubjectTokenType: oidc.IDTokenType,
|
||||
@@ -468,22 +500,18 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, orgUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, orgUserID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ORG IMPERSONATION: subject: JWT, actor: access token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: func() string {
|
||||
token, err := crypto.Sign(&oidc.JWTTokenRequest{
|
||||
Issuer: client.GetClientId(),
|
||||
Subject: User.GetUserId(),
|
||||
Audience: oidc.Audience{Instance.OIDCIssuer()},
|
||||
Subject: userResp.GetUserId(),
|
||||
Audience: oidc.Audience{instance.OIDCIssuer()},
|
||||
ExpiresAt: oidc.FromTime(time.Now().Add(time.Hour)),
|
||||
IssuedAt: oidc.FromTime(time.Now().Add(-time.Second)),
|
||||
}, signer)
|
||||
@@ -500,16 +528,12 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: patScopes,
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, User.GetUserId(), orgUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, User.GetUserId(), orgUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, userResp.GetUserId(), orgUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, userResp.GetUserId(), orgUserID),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ORG IMPERSONATION: subject: access token, actor: access token, with refresh token, success",
|
||||
settings: settings{
|
||||
tokenExchangeFeature: true,
|
||||
impersonationPolicy: true,
|
||||
},
|
||||
args: args{
|
||||
SubjectToken: teResp.AccessToken,
|
||||
SubjectTokenType: oidc.AccessTokenType,
|
||||
@@ -523,19 +547,15 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
tokenType: oidc.BearerToken,
|
||||
expiresIn: 43100,
|
||||
scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess},
|
||||
verifyAccessToken: accessTokenVerifier(CTX, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(CTX, relyingParty, serviceUserID, orgUserID),
|
||||
verifyRefreshToken: refreshTokenVerifier(CTX, relyingParty, serviceUserID, orgUserID),
|
||||
verifyAccessToken: accessTokenVerifier(ctx, resourceServer, serviceUserID, orgUserID),
|
||||
verifyIDToken: idTokenVerifier(ctx, relyingParty, serviceUserID, orgUserID),
|
||||
verifyRefreshToken: refreshTokenVerifier(ctx, relyingParty, serviceUserID, orgUserID),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setTokenExchangeFeature(t, tt.settings.tokenExchangeFeature)
|
||||
setImpersonationPolicy(t, tt.settings.impersonationPolicy)
|
||||
|
||||
got, err := tokenexchange.ExchangeToken(CTX, exchanger, tt.args.SubjectToken, tt.args.SubjectTokenType, tt.args.ActorToken, tt.args.ActorTokenType, tt.args.Resource, tt.args.Audience, tt.args.Scopes, tt.args.RequestedTokenType)
|
||||
got, err := tokenexchange.ExchangeToken(ctx, exchanger, tt.args.SubjectToken, tt.args.SubjectTokenType, tt.args.ActorToken, tt.args.ActorTokenType, tt.args.Resource, tt.args.Audience, tt.args.Scopes, tt.args.RequestedTokenType)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
@@ -561,32 +581,33 @@ func TestServer_TokenExchange(t *testing.T) {
|
||||
// This test tries to call the zitadel API with an impersonated token,
|
||||
// which should fail.
|
||||
func TestImpersonation_API_Call(t *testing.T) {
|
||||
client, keyData, err := Instance.CreateOIDCTokenExchangeClient(CTX)
|
||||
t.Parallel()
|
||||
|
||||
instance := integration.NewInstance(CTX)
|
||||
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
client, keyData, err := instance.CreateOIDCTokenExchangeClient(ctx)
|
||||
require.NoError(t, err)
|
||||
signer, err := rp.SignerFromKeyFile(keyData)()
|
||||
require.NoError(t, err)
|
||||
exchanger, err := tokenexchange.NewTokenExchangerJWTProfile(CTX, Instance.OIDCIssuer(), client.GetClientId(), signer)
|
||||
exchanger, err := tokenexchange.NewTokenExchangerJWTProfile(ctx, instance.OIDCIssuer(), client.GetClientId(), signer)
|
||||
require.NoError(t, err)
|
||||
resourceServer, err := Instance.CreateResourceServerJWTProfile(CTX, keyData)
|
||||
resourceServer, err := instance.CreateResourceServerJWTProfile(ctx, keyData)
|
||||
require.NoError(t, err)
|
||||
|
||||
setTokenExchangeFeature(t, true)
|
||||
setImpersonationPolicy(t, true)
|
||||
t.Cleanup(func() {
|
||||
resetFeatures(t)
|
||||
setImpersonationPolicy(t, false)
|
||||
})
|
||||
setTokenExchangeFeature(t, instance, true)
|
||||
setImpersonationPolicy(t, instance, true)
|
||||
|
||||
iamUserID, iamImpersonatorPAT := createMachineUserPATWithMembership(t, "IAM_ADMIN_IMPERSONATOR")
|
||||
iamOwner := Instance.Users.Get(integration.UserTypeIAMOwner)
|
||||
iamUserID, iamImpersonatorPAT := createMachineUserPATWithMembership(ctx, t, instance, "IAM_ADMIN_IMPERSONATOR")
|
||||
iamOwner := instance.Users.Get(integration.UserTypeIAMOwner)
|
||||
|
||||
// impersonating the IAM owner!
|
||||
resp, err := tokenexchange.ExchangeToken(CTX, exchanger, iamOwner.Token, oidc.AccessTokenType, iamImpersonatorPAT, oidc.AccessTokenType, nil, nil, nil, oidc.AccessTokenType)
|
||||
resp, err := tokenexchange.ExchangeToken(ctx, exchanger, iamOwner.Token, oidc.AccessTokenType, iamImpersonatorPAT, oidc.AccessTokenType, nil, nil, nil, oidc.AccessTokenType)
|
||||
require.NoError(t, err)
|
||||
accessTokenVerifier(CTX, resourceServer, iamOwner.ID, iamUserID)
|
||||
accessTokenVerifier(ctx, resourceServer, iamOwner.ID, iamUserID)
|
||||
|
||||
impersonatedCTX := integration.WithAuthorizationToken(CTX, resp.AccessToken)
|
||||
_, err = Instance.Client.Admin.GetAllowedLanguages(impersonatedCTX, &admin.GetAllowedLanguagesRequest{})
|
||||
impersonatedCTX := integration.WithAuthorizationToken(ctx, resp.AccessToken)
|
||||
_, err = instance.Client.Admin.GetAllowedLanguages(impersonatedCTX, &admin.GetAllowedLanguagesRequest{})
|
||||
status := status.Convert(err)
|
||||
assert.Equal(t, codes.PermissionDenied, status.Code())
|
||||
assert.Equal(t, "Errors.TokenExchange.Token.NotForAPI (APP-Shi0J)", status.Message())
|
||||
|
@@ -6,8 +6,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
@@ -135,14 +135,14 @@ func testServer_UserInfo(t *testing.T) {
|
||||
prepare: func(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims] {
|
||||
_, err := Instance.Client.Mgmt.UpdateProject(CTX, &management.UpdateProjectRequest{
|
||||
Id: projectID,
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
Name: fmt.Sprintf("project-%s", gofakeit.AppName()),
|
||||
ProjectRoleAssertion: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err := Instance.Client.Mgmt.UpdateProject(CTX, &management.UpdateProjectRequest{
|
||||
Id: projectID,
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
Name: fmt.Sprintf("project-%s", gofakeit.AppName()),
|
||||
ProjectRoleAssertion: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -245,7 +245,7 @@ func TestServer_UserInfo_OrgIDRoles(t *testing.T) {
|
||||
|
||||
_, err := Instance.Client.Mgmt.UpdateProject(CTX, &management.UpdateProjectRequest{
|
||||
Id: projectID,
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
Name: fmt.Sprintf("project-%s", gofakeit.AppName()),
|
||||
ProjectRoleAssertion: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -356,7 +356,7 @@ func addProjectRolesGrants(t *testing.T, userID, projectID string, roles ...stri
|
||||
// addProjectOrgGrant adds a new organization which will be granted on the projectID with the specified roles.
|
||||
// The userID will be granted in the new organization to the project with the same roles.
|
||||
func addProjectOrgGrant(t *testing.T, userID, projectID string, roles ...string) (grantedOrgID string) {
|
||||
grantedOrg := Instance.CreateOrganization(CTXIAM, fmt.Sprintf("ZITADEL_GRANTED_%d", time.Now().UnixNano()), fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()))
|
||||
grantedOrg := Instance.CreateOrganization(CTXIAM, fmt.Sprintf("ZITADEL_GRANTED_%s", gofakeit.AppName()), gofakeit.Email())
|
||||
projectGrant, err := Instance.Client.Mgmt.AddProjectGrant(CTX, &management.AddProjectGrantRequest{
|
||||
ProjectId: projectID,
|
||||
GrantedOrgId: grantedOrg.GetOrganizationId(),
|
||||
|
Reference in New Issue
Block a user