mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-15 20:38:00 +00:00
64a3bb3149
# Which Problems Are Solved Implement a new API service that allows management of OIDC signing web keys. This allows users to manage rotation of the instance level keys. which are currently managed based on expiry. The API accepts the generation of the following key types and parameters: - RSA keys with 2048, 3072 or 4096 bit in size and: - Signing with SHA-256 (RS256) - Signing with SHA-384 (RS384) - Signing with SHA-512 (RS512) - ECDSA keys with - P256 curve - P384 curve - P512 curve - ED25519 keys # How the Problems Are Solved Keys are serialized for storage using the JSON web key format from the `jose` library. This is the format that will be used by OIDC for signing, verification and publication. Each instance can have a number of key pairs. All existing public keys are meant to be used for token verification and publication the keys endpoint. Keys can be activated and the active private key is meant to sign new tokens. There is always exactly 1 active signing key: 1. When the first key for an instance is generated, it is automatically activated. 2. Activation of the next key automatically deactivates the previously active key. 3. Keys cannot be manually deactivated from the API 4. Active keys cannot be deleted # Additional Changes - Query methods that later will be used by the OIDC package are already implemented. Preparation for #8031 - Fix indentation in french translation for instance event - Move user_schema translations to consistent positions in all translation files # Additional Context - Closes #8030 - Part of #7809 --------- Co-authored-by: Elio Bischof <elio@zitadel.com>
899 lines
30 KiB
Go
899 lines
30 KiB
Go
//go:build integration
|
|
|
|
package action_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/integration"
|
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
|
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
|
)
|
|
|
|
func TestServer_GetTarget(t *testing.T) {
|
|
_, _, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
|
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
|
type args struct {
|
|
ctx context.Context
|
|
dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) error
|
|
req *action.GetTargetRequest
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *action.GetTargetResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "missing permission",
|
|
args: args{
|
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "not found",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
req: &action.GetTargetRequest{Id: "notexisting"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "get, ok",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
request.Id = resp.GetDetails().GetId()
|
|
response.Target.Config.Name = name
|
|
response.Target.Details = resp.GetDetails()
|
|
return nil
|
|
},
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
want: &action.GetTargetResponse{
|
|
Target: &action.GetTarget{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestWebhook{
|
|
RestWebhook: &action.SetRESTWebhook{},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "get, async, ok",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
|
request.Id = resp.GetDetails().GetId()
|
|
response.Target.Config.Name = name
|
|
response.Target.Details = resp.GetDetails()
|
|
return nil
|
|
},
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
want: &action.GetTargetResponse{
|
|
Target: &action.GetTarget{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestAsync{
|
|
RestAsync: &action.SetRESTAsync{},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "get, webhook interruptOnError, ok",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
|
request.Id = resp.GetDetails().GetId()
|
|
response.Target.Config.Name = name
|
|
response.Target.Details = resp.GetDetails()
|
|
return nil
|
|
},
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
want: &action.GetTargetResponse{
|
|
Target: &action.GetTarget{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestWebhook{
|
|
RestWebhook: &action.SetRESTWebhook{
|
|
InterruptOnError: true,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "get, call, ok",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
|
request.Id = resp.GetDetails().GetId()
|
|
response.Target.Config.Name = name
|
|
response.Target.Details = resp.GetDetails()
|
|
return nil
|
|
},
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
want: &action.GetTargetResponse{
|
|
Target: &action.GetTarget{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestCall{
|
|
RestCall: &action.SetRESTCall{
|
|
InterruptOnError: false,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "get, call interruptOnError, ok",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
|
request.Id = resp.GetDetails().GetId()
|
|
response.Target.Config.Name = name
|
|
response.Target.Details = resp.GetDetails()
|
|
return nil
|
|
},
|
|
req: &action.GetTargetRequest{},
|
|
},
|
|
want: &action.GetTargetResponse{
|
|
Target: &action.GetTarget{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestCall{
|
|
RestCall: &action.SetRESTCall{
|
|
InterruptOnError: true,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.args.dep != nil {
|
|
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
require.NoError(t, err)
|
|
}
|
|
got, getErr := Tester.Client.ActionV3Alpha.GetTarget(tt.args.ctx, tt.args.req)
|
|
if tt.wantErr {
|
|
assert.Error(t, getErr, "Error: "+getErr.Error())
|
|
} else {
|
|
assert.NoError(t, getErr)
|
|
wantTarget := tt.want.GetTarget()
|
|
gotTarget := got.GetTarget()
|
|
integration.AssertResourceDetails(t, wantTarget.GetDetails(), gotTarget.GetDetails())
|
|
assert.Equal(t, wantTarget.GetConfig(), gotTarget.GetConfig())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServer_ListTargets(t *testing.T) {
|
|
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
|
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
|
type args struct {
|
|
ctx context.Context
|
|
dep func(context.Context, *action.SearchTargetsRequest, *action.SearchTargetsResponse) error
|
|
req *action.SearchTargetsRequest
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *action.SearchTargetsResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "missing permission",
|
|
args: args{
|
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
req: &action.SearchTargetsRequest{},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "list, not found",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
req: &action.SearchTargetsRequest{
|
|
Filters: []*action.TargetSearchFilter{
|
|
{Filter: &action.TargetSearchFilter_InTargetIdsFilter{
|
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
|
TargetIds: []string{"notfound"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: &action.SearchTargetsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 0,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetTarget{},
|
|
},
|
|
},
|
|
{
|
|
name: "list single id",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
|
TargetIds: []string{resp.GetDetails().GetId()},
|
|
},
|
|
}
|
|
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
|
|
|
response.Result[0].Details = resp.GetDetails()
|
|
response.Result[0].Config.Name = name
|
|
return nil
|
|
},
|
|
req: &action.SearchTargetsRequest{
|
|
Filters: []*action.TargetSearchFilter{{}},
|
|
},
|
|
},
|
|
want: &action.SearchTargetsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 1,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetTarget{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestWebhook{
|
|
RestWebhook: &action.SetRESTWebhook{
|
|
InterruptOnError: false,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, {
|
|
name: "list single name",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
|
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{
|
|
TargetNameFilter: &action.TargetNameFilter{
|
|
TargetName: name,
|
|
},
|
|
}
|
|
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
|
|
|
response.Result[0].Details = resp.GetDetails()
|
|
response.Result[0].Config.Name = name
|
|
return nil
|
|
},
|
|
req: &action.SearchTargetsRequest{
|
|
Filters: []*action.TargetSearchFilter{{}},
|
|
},
|
|
},
|
|
want: &action.SearchTargetsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 1,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetTarget{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
Owner: &object.Owner{
|
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
|
Id: instanceID,
|
|
},
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestWebhook{
|
|
RestWebhook: &action.SetRESTWebhook{
|
|
InterruptOnError: false,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list multiple id",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchTargetsRequest, response *action.SearchTargetsResponse) error {
|
|
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
|
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
|
resp1 := Tester.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
|
resp2 := Tester.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
|
resp3 := Tester.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false)
|
|
request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
|
TargetIds: []string{resp1.GetDetails().GetId(), resp2.GetDetails().GetId(), resp3.GetDetails().GetId()},
|
|
},
|
|
}
|
|
response.Details.Timestamp = resp3.GetDetails().GetChanged()
|
|
|
|
response.Result[0].Details = resp1.GetDetails()
|
|
response.Result[0].Config.Name = name1
|
|
response.Result[1].Details = resp2.GetDetails()
|
|
response.Result[1].Config.Name = name2
|
|
response.Result[2].Details = resp3.GetDetails()
|
|
response.Result[2].Config.Name = name3
|
|
return nil
|
|
},
|
|
req: &action.SearchTargetsRequest{
|
|
Filters: []*action.TargetSearchFilter{{}},
|
|
},
|
|
},
|
|
want: &action.SearchTargetsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 3,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetTarget{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
Owner: &object.Owner{
|
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
|
Id: instanceID,
|
|
},
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestWebhook{
|
|
RestWebhook: &action.SetRESTWebhook{
|
|
InterruptOnError: false,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
Owner: &object.Owner{
|
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
|
Id: instanceID,
|
|
},
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestCall{
|
|
RestCall: &action.SetRESTCall{
|
|
InterruptOnError: true,
|
|
},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
Owner: &object.Owner{
|
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
|
Id: instanceID,
|
|
},
|
|
},
|
|
Config: &action.Target{
|
|
Endpoint: "https://example.com",
|
|
TargetType: &action.Target_RestAsync{
|
|
RestAsync: &action.SetRESTAsync{},
|
|
},
|
|
Timeout: durationpb.New(10 * time.Second),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.args.dep != nil {
|
|
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
retryDuration := 5 * time.Second
|
|
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
|
retryDuration = time.Until(ctxDeadline)
|
|
}
|
|
|
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
got, listErr := Tester.Client.ActionV3Alpha.SearchTargets(tt.args.ctx, tt.args.req)
|
|
if tt.wantErr {
|
|
assert.Error(ttt, listErr, "Error: "+listErr.Error())
|
|
} else {
|
|
assert.NoError(ttt, listErr)
|
|
}
|
|
if listErr != nil {
|
|
return
|
|
}
|
|
// always first check length, otherwise its failed anyway
|
|
assert.Len(ttt, got.Result, len(tt.want.Result))
|
|
for i := range tt.want.Result {
|
|
integration.AssertResourceDetails(t, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails())
|
|
assert.Equal(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig())
|
|
}
|
|
integration.AssertResourceListDetails(t, tt.want, got)
|
|
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServer_SearchExecutions(t *testing.T) {
|
|
_, instanceID, _, isolatedIAMOwnerCTX := Tester.UseIsolatedInstance(t, IAMOwnerCTX, SystemCTX)
|
|
ensureFeatureEnabled(t, isolatedIAMOwnerCTX)
|
|
targetResp := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
dep func(context.Context, *action.SearchExecutionsRequest, *action.SearchExecutionsResponse) error
|
|
req *action.SearchExecutionsRequest
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *action.SearchExecutionsResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "missing permission",
|
|
args: args{
|
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
req: &action.SearchExecutionsRequest{},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "list request single condition",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
|
cond := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
|
resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
|
|
|
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
|
// Set expected response with used values for SetExecution
|
|
response.Result[0].Details = resp.GetDetails()
|
|
response.Result[0].Condition = cond
|
|
return nil
|
|
},
|
|
req: &action.SearchExecutionsRequest{
|
|
Filters: []*action.ExecutionSearchFilter{{
|
|
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
|
InConditionsFilter: &action.InConditionsFilter{
|
|
Conditions: []*action.Condition{{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
want: &action.SearchExecutionsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 1,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetExecution{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Condition: &action.Condition{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Execution: &action.Execution{
|
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list request single target",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
|
target := Tester.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
|
// add target as Filter to the request
|
|
request.Filters[0] = &action.ExecutionSearchFilter{
|
|
Filter: &action.ExecutionSearchFilter_TargetFilter{
|
|
TargetFilter: &action.TargetFilter{
|
|
TargetId: target.GetDetails().GetId(),
|
|
},
|
|
},
|
|
}
|
|
cond := &action.Condition{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.management.v1.ManagementService/UpdateAction",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
targets := executionTargetsSingleTarget(target.GetDetails().GetId())
|
|
resp := Tester.SetExecution(ctx, t, cond, targets)
|
|
|
|
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
|
|
|
response.Result[0].Details = resp.GetDetails()
|
|
response.Result[0].Condition = cond
|
|
response.Result[0].Execution.Targets = targets
|
|
return nil
|
|
},
|
|
req: &action.SearchExecutionsRequest{
|
|
Filters: []*action.ExecutionSearchFilter{{}},
|
|
},
|
|
},
|
|
want: &action.SearchExecutionsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 1,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetExecution{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
Condition: &action.Condition{},
|
|
Execution: &action.Execution{
|
|
Targets: executionTargetsSingleTarget(""),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, {
|
|
name: "list request single include",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
|
cond := &action.Condition{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.management.v1.ManagementService/GetAction",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
|
request.Filters[0].GetIncludeFilter().Include = cond
|
|
|
|
includeCond := &action.Condition{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.management.v1.ManagementService/ListActions",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
includeTargets := executionTargetsSingleInclude(cond)
|
|
resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets)
|
|
|
|
response.Details.Timestamp = resp2.GetDetails().GetChanged()
|
|
|
|
response.Result[0].Details = resp2.GetDetails()
|
|
response.Result[0].Condition = includeCond
|
|
response.Result[0].Execution = &action.Execution{
|
|
Targets: includeTargets,
|
|
}
|
|
return nil
|
|
},
|
|
req: &action.SearchExecutionsRequest{
|
|
Filters: []*action.ExecutionSearchFilter{{
|
|
Filter: &action.ExecutionSearchFilter_IncludeFilter{
|
|
IncludeFilter: &action.IncludeFilter{},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
want: &action.SearchExecutionsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 1,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetExecution{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Created: timestamppb.Now(),
|
|
Changed: timestamppb.Now(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list multiple conditions",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
|
|
|
cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
|
targets1 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
|
resp1 := Tester.SetExecution(ctx, t, cond1, targets1)
|
|
response.Result[0].Details = resp1.GetDetails()
|
|
response.Result[0].Condition = cond1
|
|
response.Result[0].Execution = &action.Execution{
|
|
Targets: targets1,
|
|
}
|
|
|
|
cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1]
|
|
targets2 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
|
resp2 := Tester.SetExecution(ctx, t, cond2, targets2)
|
|
response.Result[1].Details = resp2.GetDetails()
|
|
response.Result[1].Condition = cond2
|
|
response.Result[1].Execution = &action.Execution{
|
|
Targets: targets2,
|
|
}
|
|
|
|
cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2]
|
|
targets3 := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
|
resp3 := Tester.SetExecution(ctx, t, cond3, targets3)
|
|
response.Result[2].Details = resp3.GetDetails()
|
|
response.Result[2].Condition = cond3
|
|
response.Result[2].Execution = &action.Execution{
|
|
Targets: targets3,
|
|
}
|
|
response.Details.Timestamp = resp3.GetDetails().GetChanged()
|
|
return nil
|
|
},
|
|
req: &action.SearchExecutionsRequest{
|
|
Filters: []*action.ExecutionSearchFilter{{
|
|
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
|
InConditionsFilter: &action.InConditionsFilter{
|
|
Conditions: []*action.Condition{
|
|
{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.session.v2.SessionService/CreateSession",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ConditionType: &action.Condition_Request{
|
|
Request: &action.RequestExecution{
|
|
Condition: &action.RequestExecution_Method{
|
|
Method: "/zitadel.session.v2.SessionService/SetSession",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
want: &action.SearchExecutionsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 3,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetExecution{
|
|
{
|
|
Details: &resource_object.Details{
|
|
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
|
},
|
|
}, {
|
|
Details: &resource_object.Details{
|
|
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
|
},
|
|
}, {
|
|
Details: &resource_object.Details{
|
|
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list multiple conditions all types",
|
|
args: args{
|
|
ctx: isolatedIAMOwnerCTX,
|
|
dep: func(ctx context.Context, request *action.SearchExecutionsRequest, response *action.SearchExecutionsResponse) error {
|
|
targets := executionTargetsSingleTarget(targetResp.GetDetails().GetId())
|
|
for i, cond := range request.Filters[0].GetInConditionsFilter().GetConditions() {
|
|
resp := Tester.SetExecution(ctx, t, cond, targets)
|
|
response.Result[i].Details = resp.GetDetails()
|
|
response.Result[i].Condition = cond
|
|
response.Result[i].Execution = &action.Execution{
|
|
Targets: targets,
|
|
}
|
|
// filled with info of last sequence
|
|
response.Details.Timestamp = resp.GetDetails().GetChanged()
|
|
}
|
|
|
|
return nil
|
|
},
|
|
req: &action.SearchExecutionsRequest{
|
|
Filters: []*action.ExecutionSearchFilter{{
|
|
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
|
InConditionsFilter: &action.InConditionsFilter{
|
|
Conditions: []*action.Condition{
|
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}},
|
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2.SessionService"}}}},
|
|
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}},
|
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}},
|
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2.SessionService"}}}},
|
|
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}},
|
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: "user.added"}}}},
|
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: "user"}}}},
|
|
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}},
|
|
{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"}}},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
want: &action.SearchExecutionsResponse{
|
|
Details: &resource_object.ListDetails{
|
|
TotalResult: 10,
|
|
AppliedLimit: 100,
|
|
},
|
|
Result: []*action.GetExecution{
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
{Details: &resource_object.Details{Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID}}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.args.dep != nil {
|
|
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
retryDuration := 5 * time.Second
|
|
if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok {
|
|
retryDuration = time.Until(ctxDeadline)
|
|
}
|
|
|
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
got, listErr := Tester.Client.ActionV3Alpha.SearchExecutions(tt.args.ctx, tt.args.req)
|
|
if tt.wantErr {
|
|
assert.Error(t, listErr, "Error: "+listErr.Error())
|
|
} else {
|
|
assert.NoError(t, listErr)
|
|
}
|
|
if listErr != nil {
|
|
return
|
|
}
|
|
// always first check length, otherwise its failed anyway
|
|
assert.Len(t, got.Result, len(tt.want.Result))
|
|
for i := range tt.want.Result {
|
|
// as not sorted, all elements have to be checked
|
|
// workaround as oneof elements can only be checked with assert.EqualExportedValues()
|
|
if j, found := containExecution(got.Result, tt.want.Result[i]); found {
|
|
assert.EqualExportedValues(t, tt.want.Result[i], got.Result[j])
|
|
}
|
|
}
|
|
integration.AssertResourceListDetails(t, tt.want, got)
|
|
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func containExecution(executionList []*action.GetExecution, execution *action.GetExecution) (int, bool) {
|
|
for i, exec := range executionList {
|
|
if reflect.DeepEqual(exec.Details, execution.Details) {
|
|
return i, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|