mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 10:37:44 +00:00
fix go build
This commit is contained in:
92
apps/api/internal/api/grpc/action/v2beta/execution.go
Normal file
92
apps/api/internal/api/grpc/action/v2beta/execution.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) SetExecution(ctx context.Context, req *connect.Request[action.SetExecutionRequest]) (*connect.Response[action.SetExecutionResponse], error) {
|
||||||
|
reqTargets := req.Msg.GetTargets()
|
||||||
|
targets := make([]*execution.Target, len(reqTargets))
|
||||||
|
for i, target := range reqTargets {
|
||||||
|
targets[i] = &execution.Target{Type: domain.ExecutionTargetTypeTarget, Target: target}
|
||||||
|
}
|
||||||
|
set := &command.SetExecution{
|
||||||
|
Targets: targets,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var details *domain.ObjectDetails
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
switch t := req.Msg.GetCondition().GetConditionType().(type) {
|
||||||
|
case *action.Condition_Request:
|
||||||
|
cond := executionConditionFromRequest(t.Request)
|
||||||
|
details, err = s.command.SetExecutionRequest(ctx, cond, set, instanceID)
|
||||||
|
case *action.Condition_Response:
|
||||||
|
cond := executionConditionFromResponse(t.Response)
|
||||||
|
details, err = s.command.SetExecutionResponse(ctx, cond, set, instanceID)
|
||||||
|
case *action.Condition_Event:
|
||||||
|
cond := executionConditionFromEvent(t.Event)
|
||||||
|
details, err = s.command.SetExecutionEvent(ctx, cond, set, instanceID)
|
||||||
|
case *action.Condition_Function:
|
||||||
|
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, instanceID)
|
||||||
|
default:
|
||||||
|
err = zerrors.ThrowInvalidArgument(nil, "ACTION-5r5Ju", "Errors.Execution.ConditionInvalid")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.SetExecutionResponse{
|
||||||
|
SetDate: timestamppb.New(details.EventDate),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListExecutionFunctions(ctx context.Context, _ *connect.Request[action.ListExecutionFunctionsRequest]) (*connect.Response[action.ListExecutionFunctionsResponse], error) {
|
||||||
|
return connect.NewResponse(&action.ListExecutionFunctionsResponse{
|
||||||
|
Functions: s.ListActionFunctions(),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListExecutionMethods(ctx context.Context, _ *connect.Request[action.ListExecutionMethodsRequest]) (*connect.Response[action.ListExecutionMethodsResponse], error) {
|
||||||
|
return connect.NewResponse(&action.ListExecutionMethodsResponse{
|
||||||
|
Methods: s.ListGRPCMethods(),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListExecutionServices(ctx context.Context, _ *connect.Request[action.ListExecutionServicesRequest]) (*connect.Response[action.ListExecutionServicesResponse], error) {
|
||||||
|
return connect.NewResponse(&action.ListExecutionServicesResponse{
|
||||||
|
Services: s.ListGRPCServices(),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionConditionFromRequest(request *action.RequestExecution) *command.ExecutionAPICondition {
|
||||||
|
return &command.ExecutionAPICondition{
|
||||||
|
Method: request.GetMethod(),
|
||||||
|
Service: request.GetService(),
|
||||||
|
All: request.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionConditionFromResponse(response *action.ResponseExecution) *command.ExecutionAPICondition {
|
||||||
|
return &command.ExecutionAPICondition{
|
||||||
|
Method: response.GetMethod(),
|
||||||
|
Service: response.GetService(),
|
||||||
|
All: response.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionConditionFromEvent(event *action.EventExecution) *command.ExecutionEventCondition {
|
||||||
|
return &command.ExecutionEventCondition{
|
||||||
|
Event: event.GetEvent(),
|
||||||
|
Group: event.GetGroup(),
|
||||||
|
All: event.GetAll(),
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,565 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Request(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
wantSetDate bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_All{All: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no condition, error",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "NotExistingService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// We want to have the same response no matter how often we call the function
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||||
|
setDate := time.Now().UTC()
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
deleteExecution(tt.ctx, t, instance, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSetExecutionResponse(t *testing.T, creationDate, setDate time.Time, expectedSetDate bool, actualResp *action.SetExecutionResponse) {
|
||||||
|
if expectedSetDate {
|
||||||
|
if !setDate.IsZero() {
|
||||||
|
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, setDate)
|
||||||
|
} else {
|
||||||
|
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.SetDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Response(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
wantSetDate bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_All{All: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no condition, error",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Service{
|
||||||
|
Service: "NotExistingService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||||
|
setDate := time.Now().UTC()
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
deleteExecution(tt.ctx, t, instance, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Event(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
wantSetDate bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no condition, error",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Event{
|
||||||
|
Event: "user.human.notexisting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Event{
|
||||||
|
Event: "user.human.added",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "group, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Group{
|
||||||
|
Group: "user.notexisting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "group, level 1, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Group{
|
||||||
|
Group: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "group, level 2, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Group{
|
||||||
|
Group: "user.human",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||||
|
setDate := time.Now().UTC()
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
deleteExecution(tt.ctx, t, instance, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Function(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
wantSetDate bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_All{All: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no condition, error",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function, not existing",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Function{
|
||||||
|
Function: &action.FunctionExecution{Name: "xxx"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Function{
|
||||||
|
Function: &action.FunctionExecution{Name: "presamlresponse"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantSetDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||||
|
setDate := time.Now().UTC()
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
deleteExecution(tt.ctx, t, instance, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,784 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_GetTarget(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
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: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
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 := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
request.Id = resp.GetId()
|
||||||
|
response.Target.Id = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.CreationDate = resp.GetCreationDate()
|
||||||
|
response.Target.ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Target.SigningKey = resp.GetSigningKey()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, async, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
||||||
|
request.Id = resp.GetId()
|
||||||
|
response.Target.Id = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.CreationDate = resp.GetCreationDate()
|
||||||
|
response.Target.ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Target.SigningKey = resp.GetSigningKey()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, webhook interruptOnError, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
||||||
|
request.Id = resp.GetId()
|
||||||
|
response.Target.Id = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.CreationDate = resp.GetCreationDate()
|
||||||
|
response.Target.ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Target.SigningKey = resp.GetSigningKey()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, call, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
||||||
|
request.Id = resp.GetId()
|
||||||
|
response.Target.Id = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.CreationDate = resp.GetCreationDate()
|
||||||
|
response.Target.ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Target.SigningKey = resp.GetSigningKey()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get, call interruptOnError, ok",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
||||||
|
request.Id = resp.GetId()
|
||||||
|
response.Target.Id = resp.GetId()
|
||||||
|
response.Target.Name = name
|
||||||
|
response.Target.CreationDate = resp.GetCreationDate()
|
||||||
|
response.Target.ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Target.SigningKey = resp.GetSigningKey()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
req: &action.GetTargetRequest{},
|
||||||
|
},
|
||||||
|
want: &action.GetTargetResponse{
|
||||||
|
Target: &action.Target{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * 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, tick := integration.WaitForAndTickWithMaxDuration(isolatedIAMOwnerCTX, 2*time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
got, err := instance.Client.ActionV2beta.GetTarget(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(ttt, err, "Error: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(ttt, err)
|
||||||
|
assert.EqualExportedValues(ttt, tt.want, got)
|
||||||
|
}, retryDuration, tick, "timeout waiting for expected target Executions")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_ListTargets(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
dep func(context.Context, *action.ListTargetsRequest, *action.ListTargetsResponse)
|
||||||
|
req *action.ListTargetsRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *action.ListTargetsResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
args: args{
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.ListTargetsRequest{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list, not found",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.ListTargetsRequest{
|
||||||
|
Filters: []*action.TargetSearchFilter{
|
||||||
|
{Filter: &action.TargetSearchFilter_InTargetIdsFilter{
|
||||||
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||||
|
TargetIds: []string{"notfound"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListTargetsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 0,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Targets: []*action.Target{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list single id",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
||||||
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||||
|
TargetIds: []string{resp.GetId()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Targets[0].Id = resp.GetId()
|
||||||
|
response.Targets[0].Name = name
|
||||||
|
response.Targets[0].CreationDate = resp.GetCreationDate()
|
||||||
|
response.Targets[0].ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Targets[0].SigningKey = resp.GetSigningKey()
|
||||||
|
},
|
||||||
|
req: &action.ListTargetsRequest{
|
||||||
|
Filters: []*action.TargetSearchFilter{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListTargetsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 1,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Targets: []*action.Target{
|
||||||
|
{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "list single name",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) {
|
||||||
|
name := gofakeit.Name()
|
||||||
|
resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{
|
||||||
|
TargetNameFilter: &action.TargetNameFilter{
|
||||||
|
TargetName: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Targets[0].Id = resp.GetId()
|
||||||
|
response.Targets[0].Name = name
|
||||||
|
response.Targets[0].CreationDate = resp.GetCreationDate()
|
||||||
|
response.Targets[0].ChangeDate = resp.GetCreationDate()
|
||||||
|
response.Targets[0].SigningKey = resp.GetSigningKey()
|
||||||
|
},
|
||||||
|
req: &action.ListTargetsRequest{
|
||||||
|
Filters: []*action.TargetSearchFilter{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListTargetsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 1,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Targets: []*action.Target{
|
||||||
|
{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple id",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) {
|
||||||
|
name1 := gofakeit.Name()
|
||||||
|
name2 := gofakeit.Name()
|
||||||
|
name3 := gofakeit.Name()
|
||||||
|
resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
||||||
|
resp3 := instance.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false)
|
||||||
|
request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{
|
||||||
|
InTargetIdsFilter: &action.InTargetIDsFilter{
|
||||||
|
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Targets[2].Id = resp1.GetId()
|
||||||
|
response.Targets[2].Name = name1
|
||||||
|
response.Targets[2].CreationDate = resp1.GetCreationDate()
|
||||||
|
response.Targets[2].ChangeDate = resp1.GetCreationDate()
|
||||||
|
response.Targets[2].SigningKey = resp1.GetSigningKey()
|
||||||
|
|
||||||
|
response.Targets[1].Id = resp2.GetId()
|
||||||
|
response.Targets[1].Name = name2
|
||||||
|
response.Targets[1].CreationDate = resp2.GetCreationDate()
|
||||||
|
response.Targets[1].ChangeDate = resp2.GetCreationDate()
|
||||||
|
response.Targets[1].SigningKey = resp2.GetSigningKey()
|
||||||
|
|
||||||
|
response.Targets[0].Id = resp3.GetId()
|
||||||
|
response.Targets[0].Name = name3
|
||||||
|
response.Targets[0].CreationDate = resp3.GetCreationDate()
|
||||||
|
response.Targets[0].ChangeDate = resp3.GetCreationDate()
|
||||||
|
response.Targets[0].SigningKey = resp3.GetSigningKey()
|
||||||
|
},
|
||||||
|
req: &action.ListTargetsRequest{
|
||||||
|
Filters: []*action.TargetSearchFilter{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListTargetsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 3,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Targets: []*action.Target{
|
||||||
|
{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(5 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.dep != nil {
|
||||||
|
tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(isolatedIAMOwnerCTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
got, listErr := instance.Client.ActionV2beta.ListTargets(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(ttt, listErr, "Error: "+listErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(ttt, listErr)
|
||||||
|
|
||||||
|
// always first check length, otherwise its failed anyway
|
||||||
|
if assert.Len(ttt, got.Targets, len(tt.want.Targets)) {
|
||||||
|
for i := range tt.want.Targets {
|
||||||
|
assert.EqualExportedValues(ttt, tt.want.Targets[i], got.Targets[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||||
|
}, retryDuration, tick, "timeout waiting for expected execution Executions")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationResponse, actual *filter.PaginationResponse) {
|
||||||
|
assert.Equal(t, expected.AppliedLimit, actual.AppliedLimit)
|
||||||
|
assert.Equal(t, expected.TotalResult, actual.TotalResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_ListExecutions(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
dep func(context.Context, *action.ListExecutionsRequest, *action.ListExecutionsResponse)
|
||||||
|
req *action.ListExecutionsRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *action.ListExecutionsResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
args: args{
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.ListExecutionsRequest{},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list request single condition",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) {
|
||||||
|
cond := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
||||||
|
resp := setExecution(ctx, t, instance, cond, []string{targetResp.GetId()})
|
||||||
|
|
||||||
|
// Set expected response with used values for SetExecution
|
||||||
|
response.Executions[0].CreationDate = resp.GetSetDate()
|
||||||
|
response.Executions[0].ChangeDate = resp.GetSetDate()
|
||||||
|
response.Executions[0].Condition = cond
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
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.ListExecutionsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 1,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Executions: []*action.Execution{
|
||||||
|
{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2.SessionService/GetSession",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list request single target",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) {
|
||||||
|
target := instance.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.GetId(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cond := &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.management.v1.ManagementService/UpdateAction",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp := setExecution(ctx, t, instance, cond, []string{target.GetId()})
|
||||||
|
|
||||||
|
response.Executions[0].CreationDate = resp.GetSetDate()
|
||||||
|
response.Executions[0].ChangeDate = resp.GetSetDate()
|
||||||
|
response.Executions[0].Condition = cond
|
||||||
|
response.Executions[0].Targets = []string{target.GetId()}
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
Filters: []*action.ExecutionSearchFilter{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListExecutionsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 1,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Executions: []*action.Execution{
|
||||||
|
{
|
||||||
|
Condition: &action.Condition{},
|
||||||
|
Targets: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple conditions",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) {
|
||||||
|
|
||||||
|
request.Filters[0] = &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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
|
||||||
|
resp1 := setExecution(ctx, t, instance, cond1, []string{targetResp.GetId()})
|
||||||
|
response.Executions[2] = &action.Execution{
|
||||||
|
CreationDate: resp1.GetSetDate(),
|
||||||
|
ChangeDate: resp1.GetSetDate(),
|
||||||
|
Condition: cond1,
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
}
|
||||||
|
|
||||||
|
cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1]
|
||||||
|
resp2 := setExecution(ctx, t, instance, cond2, []string{targetResp.GetId()})
|
||||||
|
response.Executions[1] = &action.Execution{
|
||||||
|
CreationDate: resp2.GetSetDate(),
|
||||||
|
ChangeDate: resp2.GetSetDate(),
|
||||||
|
Condition: cond2,
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
}
|
||||||
|
|
||||||
|
cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2]
|
||||||
|
resp3 := setExecution(ctx, t, instance, cond3, []string{targetResp.GetId()})
|
||||||
|
response.Executions[0] = &action.Execution{
|
||||||
|
CreationDate: resp3.GetSetDate(),
|
||||||
|
ChangeDate: resp3.GetSetDate(),
|
||||||
|
Condition: cond3,
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
Filters: []*action.ExecutionSearchFilter{
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListExecutionsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 3,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Executions: []*action.Execution{
|
||||||
|
{}, {}, {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple conditions all types",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) {
|
||||||
|
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
|
||||||
|
for i, cond := range conditions {
|
||||||
|
resp := setExecution(ctx, t, instance, cond, []string{targetResp.GetId()})
|
||||||
|
response.Executions[(len(conditions)-1)-i] = &action.Execution{
|
||||||
|
CreationDate: resp.GetSetDate(),
|
||||||
|
ChangeDate: resp.GetSetDate(),
|
||||||
|
Condition: cond,
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
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: "presamlresponse"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListExecutionsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 10,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Executions: []*action.Execution{
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple conditions all types, sort id",
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) {
|
||||||
|
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
|
||||||
|
for i, cond := range conditions {
|
||||||
|
resp := setExecution(ctx, t, instance, cond, []string{targetResp.GetId()})
|
||||||
|
response.Executions[i] = &action.Execution{
|
||||||
|
CreationDate: resp.GetSetDate(),
|
||||||
|
ChangeDate: resp.GetSetDate(),
|
||||||
|
Condition: cond,
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
req: &action.ListExecutionsRequest{
|
||||||
|
SortingColumn: gu.Ptr(action.ExecutionFieldName_EXECUTION_FIELD_NAME_ID),
|
||||||
|
Filters: []*action.ExecutionSearchFilter{{
|
||||||
|
Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
||||||
|
InConditionsFilter: &action.InConditionsFilter{
|
||||||
|
Conditions: []*action.Condition{
|
||||||
|
{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_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_Function{Function: &action.FunctionExecution{Name: "presamlresponse"}}},
|
||||||
|
{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}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.ListExecutionsResponse{
|
||||||
|
Pagination: &filter.PaginationResponse{
|
||||||
|
TotalResult: 10,
|
||||||
|
AppliedLimit: 100,
|
||||||
|
},
|
||||||
|
Executions: []*action.Execution{
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.dep != nil {
|
||||||
|
tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(isolatedIAMOwnerCTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
got, listErr := instance.Client.ActionV2beta.ListExecutions(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(ttt, listErr, "Error: "+listErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(ttt, listErr)
|
||||||
|
// always first check length, otherwise its failed anyway
|
||||||
|
if assert.Len(ttt, got.Executions, len(tt.want.Executions)) {
|
||||||
|
assert.EqualExportedValues(ttt, got.Executions, tt.want.Executions)
|
||||||
|
}
|
||||||
|
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
|
||||||
|
}, retryDuration, tick, "timeout waiting for expected execution Executions")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CTX context.Context
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(func() int {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
CTX = ctx
|
||||||
|
return m.Run()
|
||||||
|
}())
|
||||||
|
}
|
@@ -0,0 +1,549 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_CreateTarget(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
type want struct {
|
||||||
|
id bool
|
||||||
|
creationDate bool
|
||||||
|
signingKey bool
|
||||||
|
}
|
||||||
|
alreadyExistingTargetName := gofakeit.AppName()
|
||||||
|
instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", domain.TargetTypeAsync, false)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.CreateTargetRequest
|
||||||
|
want
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty name",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty type",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
TargetType: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty webhook url",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty request response url",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty timeout",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{},
|
||||||
|
},
|
||||||
|
Timeout: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "async, already existing, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: alreadyExistingTargetName,
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "async, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
id: true,
|
||||||
|
creationDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webhook, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
id: true,
|
||||||
|
creationDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webhook, interrupt on error, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
id: true,
|
||||||
|
creationDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "call, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
id: true,
|
||||||
|
creationDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "call, interruptOnError, ok",
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.CreateTargetRequest{
|
||||||
|
Name: gofakeit.Name(),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
id: true,
|
||||||
|
creationDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
got, err := instance.Client.ActionV2beta.CreateTarget(tt.ctx, tt.req)
|
||||||
|
changeDate := time.Now().UTC()
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertCreateTargetResponse(t, creationDate, changeDate, tt.want.creationDate, tt.want.id, tt.want.signingKey, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCreateTargetResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate, expectedID, expectedSigningKey bool, actualResp *action.CreateTargetResponse) {
|
||||||
|
if expectedCreationDate {
|
||||||
|
if !changeDate.IsZero() {
|
||||||
|
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, changeDate)
|
||||||
|
} else {
|
||||||
|
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, time.Now().UTC())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.CreationDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedID {
|
||||||
|
assert.NotEmpty(t, actualResp.GetId())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedSigningKey {
|
||||||
|
assert.NotEmpty(t, actualResp.GetSigningKey())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.SigningKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_UpdateTarget(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
req *action.UpdateTargetRequest
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
change bool
|
||||||
|
changeDate bool
|
||||||
|
signingKey bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
prepare func(request *action.UpdateTargetRequest)
|
||||||
|
args args
|
||||||
|
want want
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(gofakeit.Name()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not existing",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
request.Id = "notexisting"
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(gofakeit.Name()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no change, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Endpoint: gu.Ptr("https://example.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: false,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change name, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(gofakeit.Name()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "regenerate signingkey, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
ExpirationSigningKey: durationpb.New(0 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change type, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change url, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change timeout, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
Timeout: durationpb.New(20 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change type async, ok",
|
||||||
|
prepare: func(request *action.UpdateTargetRequest) {
|
||||||
|
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: isolatedIAMOwnerCTX,
|
||||||
|
req: &action.UpdateTargetRequest{
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
change: true,
|
||||||
|
changeDate: true,
|
||||||
|
signingKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
tt.prepare(tt.args.req)
|
||||||
|
|
||||||
|
got, err := instance.Client.ActionV2beta.UpdateTarget(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
changeDate := time.Time{}
|
||||||
|
if tt.want.change {
|
||||||
|
changeDate = time.Now().UTC()
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertUpdateTargetResponse(t, creationDate, changeDate, tt.want.changeDate, tt.want.signingKey, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertUpdateTargetResponse(t *testing.T, creationDate, changeDate time.Time, expectedChangeDate, expectedSigningKey bool, actualResp *action.UpdateTargetResponse) {
|
||||||
|
if expectedChangeDate {
|
||||||
|
if !changeDate.IsZero() {
|
||||||
|
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, changeDate)
|
||||||
|
} else {
|
||||||
|
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, time.Now().UTC())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.ChangeDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedSigningKey {
|
||||||
|
assert.NotEmpty(t, actualResp.GetSigningKey())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.SigningKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_DeleteTarget(t *testing.T) {
|
||||||
|
instance := integration.NewInstance(CTX)
|
||||||
|
iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
prepare func(request *action.DeleteTargetRequest) (time.Time, time.Time)
|
||||||
|
req *action.DeleteTargetRequest
|
||||||
|
wantDeletionDate bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: instance.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: "notexisting",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty id",
|
||||||
|
ctx: iamOwnerCtx,
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete target, not existing",
|
||||||
|
ctx: iamOwnerCtx,
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: "notexisting",
|
||||||
|
},
|
||||||
|
wantDeletionDate: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete target",
|
||||||
|
ctx: iamOwnerCtx,
|
||||||
|
prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return creationDate, time.Time{}
|
||||||
|
},
|
||||||
|
req: &action.DeleteTargetRequest{},
|
||||||
|
wantDeletionDate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete target, already removed",
|
||||||
|
ctx: iamOwnerCtx,
|
||||||
|
prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) {
|
||||||
|
creationDate := time.Now().UTC()
|
||||||
|
targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
instance.DeleteTarget(iamOwnerCtx, t, targetID)
|
||||||
|
return creationDate, time.Now().UTC()
|
||||||
|
},
|
||||||
|
req: &action.DeleteTargetRequest{},
|
||||||
|
wantDeletionDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var creationDate, deletionDate time.Time
|
||||||
|
if tt.prepare != nil {
|
||||||
|
creationDate, deletionDate = tt.prepare(tt.req)
|
||||||
|
}
|
||||||
|
got, err := instance.Client.ActionV2beta.DeleteTarget(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertDeleteTargetResponse(t, creationDate, deletionDate, tt.wantDeletionDate, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertDeleteTargetResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *action.DeleteTargetResponse) {
|
||||||
|
if expectedDeletionDate {
|
||||||
|
if !deletionDate.IsZero() {
|
||||||
|
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
|
||||||
|
} else {
|
||||||
|
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, actualResp.DeletionDate)
|
||||||
|
}
|
||||||
|
}
|
404
apps/api/internal/api/grpc/action/v2beta/query.go
Normal file
404
apps/api/internal/api/grpc/action/v2beta/query.go
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
conditionIDAllSegmentCount = 0
|
||||||
|
conditionIDRequestResponseServiceSegmentCount = 1
|
||||||
|
conditionIDRequestResponseMethodSegmentCount = 2
|
||||||
|
conditionIDEventGroupSegmentCount = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) GetTarget(ctx context.Context, req *connect.Request[action.GetTargetRequest]) (*connect.Response[action.GetTargetResponse], error) {
|
||||||
|
resp, err := s.query.GetTargetByID(ctx, req.Msg.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.GetTargetResponse{
|
||||||
|
Target: targetToPb(resp),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstanceContext interface {
|
||||||
|
GetInstanceId() string
|
||||||
|
GetInstanceDomain() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context interface {
|
||||||
|
GetOwner() InstanceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListTargets(ctx context.Context, req *connect.Request[action.ListTargetsRequest]) (*connect.Response[action.ListTargetsResponse], error) {
|
||||||
|
queries, err := s.ListTargetsRequestToModel(req.Msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := s.query.SearchTargets(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.ListTargetsResponse{
|
||||||
|
Targets: targetsToPb(resp.Targets),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListExecutions(ctx context.Context, req *connect.Request[action.ListExecutionsRequest]) (*connect.Response[action.ListExecutionsResponse], error) {
|
||||||
|
queries, err := s.ListExecutionsRequestToModel(req.Msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := s.query.SearchExecutions(ctx, queries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.ListExecutionsResponse{
|
||||||
|
Executions: executionsToPb(resp.Executions),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetsToPb(targets []*query.Target) []*action.Target {
|
||||||
|
t := make([]*action.Target, len(targets))
|
||||||
|
for i, target := range targets {
|
||||||
|
t[i] = targetToPb(target)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetToPb(t *query.Target) *action.Target {
|
||||||
|
target := &action.Target{
|
||||||
|
Id: t.ID,
|
||||||
|
Name: t.Name,
|
||||||
|
Timeout: durationpb.New(t.Timeout),
|
||||||
|
Endpoint: t.Endpoint,
|
||||||
|
SigningKey: t.SigningKey,
|
||||||
|
}
|
||||||
|
switch t.TargetType {
|
||||||
|
case domain.TargetTypeWebhook:
|
||||||
|
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.RESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||||
|
case domain.TargetTypeCall:
|
||||||
|
target.TargetType = &action.Target_RestCall{RestCall: &action.RESTCall{InterruptOnError: t.InterruptOnError}}
|
||||||
|
case domain.TargetTypeAsync:
|
||||||
|
target.TargetType = &action.Target_RestAsync{RestAsync: &action.RESTAsync{}}
|
||||||
|
default:
|
||||||
|
target.TargetType = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !t.EventDate.IsZero() {
|
||||||
|
target.ChangeDate = timestamppb.New(t.EventDate)
|
||||||
|
}
|
||||||
|
if !t.CreationDate.IsZero() {
|
||||||
|
target.CreationDate = timestamppb.New(t.CreationDate)
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListTargetsRequestToModel(req *action.ListTargetsRequest) (*query.TargetSearchQueries, error) {
|
||||||
|
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries, err := targetQueriesToQuery(req.Filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &query.TargetSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: targetFieldNameToSortingColumn(req.SortingColumn),
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetQueriesToQuery(queries []*action.TargetSearchFilter) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(queries))
|
||||||
|
for i, qry := range queries {
|
||||||
|
q[i], err = targetQueryToQuery(qry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetQueryToQuery(filter *action.TargetSearchFilter) (query.SearchQuery, error) {
|
||||||
|
switch q := filter.Filter.(type) {
|
||||||
|
case *action.TargetSearchFilter_TargetNameFilter:
|
||||||
|
return targetNameQueryToQuery(q.TargetNameFilter)
|
||||||
|
case *action.TargetSearchFilter_InTargetIdsFilter:
|
||||||
|
return targetInTargetIdsQueryToQuery(q.InTargetIdsFilter)
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetNameQueryToQuery(q *action.TargetNameFilter) (query.SearchQuery, error) {
|
||||||
|
return query.NewTargetNameSearchQuery(filter.TextMethodPbToQuery(q.Method), q.GetTargetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetInTargetIdsQueryToQuery(q *action.InTargetIDsFilter) (query.SearchQuery, error) {
|
||||||
|
return query.NewTargetInIDsSearchQuery(q.GetTargetIds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// targetFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||||
|
func targetFieldNameToSortingColumn(field *action.TargetFieldName) query.Column {
|
||||||
|
if field == nil {
|
||||||
|
return query.TargetColumnCreationDate
|
||||||
|
}
|
||||||
|
switch *field {
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_UNSPECIFIED:
|
||||||
|
return query.TargetColumnCreationDate
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_ID:
|
||||||
|
return query.TargetColumnID
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_CREATED_DATE:
|
||||||
|
return query.TargetColumnCreationDate
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_CHANGED_DATE:
|
||||||
|
return query.TargetColumnChangeDate
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_NAME:
|
||||||
|
return query.TargetColumnName
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_TARGET_TYPE:
|
||||||
|
return query.TargetColumnTargetType
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_URL:
|
||||||
|
return query.TargetColumnURL
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_TIMEOUT:
|
||||||
|
return query.TargetColumnTimeout
|
||||||
|
case action.TargetFieldName_TARGET_FIELD_NAME_INTERRUPT_ON_ERROR:
|
||||||
|
return query.TargetColumnInterruptOnError
|
||||||
|
default:
|
||||||
|
return query.TargetColumnCreationDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executionFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||||
|
func executionFieldNameToSortingColumn(field *action.ExecutionFieldName) query.Column {
|
||||||
|
if field == nil {
|
||||||
|
return query.ExecutionColumnCreationDate
|
||||||
|
}
|
||||||
|
switch *field {
|
||||||
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_UNSPECIFIED:
|
||||||
|
return query.ExecutionColumnCreationDate
|
||||||
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_ID:
|
||||||
|
return query.ExecutionColumnID
|
||||||
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CREATED_DATE:
|
||||||
|
return query.ExecutionColumnCreationDate
|
||||||
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CHANGED_DATE:
|
||||||
|
return query.ExecutionColumnChangeDate
|
||||||
|
default:
|
||||||
|
return query.ExecutionColumnCreationDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListExecutionsRequestToModel(req *action.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
||||||
|
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries, err := executionQueriesToQuery(req.Filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &query.ExecutionSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: executionFieldNameToSortingColumn(req.SortingColumn),
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionQueriesToQuery(queries []*action.ExecutionSearchFilter) (_ []query.SearchQuery, err error) {
|
||||||
|
q := make([]query.SearchQuery, len(queries))
|
||||||
|
for i, query := range queries {
|
||||||
|
q[i], err = executionQueryToQuery(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionQueryToQuery(searchQuery *action.ExecutionSearchFilter) (query.SearchQuery, error) {
|
||||||
|
switch q := searchQuery.Filter.(type) {
|
||||||
|
case *action.ExecutionSearchFilter_InConditionsFilter:
|
||||||
|
return inConditionsQueryToQuery(q.InConditionsFilter)
|
||||||
|
case *action.ExecutionSearchFilter_ExecutionTypeFilter:
|
||||||
|
return executionTypeToQuery(q.ExecutionTypeFilter)
|
||||||
|
case *action.ExecutionSearchFilter_TargetFilter:
|
||||||
|
return query.NewTargetSearchQuery(q.TargetFilter.GetTargetId())
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionTypeToQuery(q *action.ExecutionTypeFilter) (query.SearchQuery, error) {
|
||||||
|
switch q.ExecutionType {
|
||||||
|
case action.ExecutionType_EXECUTION_TYPE_UNSPECIFIED:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
||||||
|
case action.ExecutionType_EXECUTION_TYPE_REQUEST:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest)
|
||||||
|
case action.ExecutionType_EXECUTION_TYPE_RESPONSE:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse)
|
||||||
|
case action.ExecutionType_EXECUTION_TYPE_EVENT:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent)
|
||||||
|
case action.ExecutionType_EXECUTION_TYPE_FUNCTION:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction)
|
||||||
|
default:
|
||||||
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inConditionsQueryToQuery(q *action.InConditionsFilter) (query.SearchQuery, error) {
|
||||||
|
values := make([]string, len(q.GetConditions()))
|
||||||
|
for i, condition := range q.GetConditions() {
|
||||||
|
id, err := conditionToID(condition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values[i] = id
|
||||||
|
}
|
||||||
|
return query.NewExecutionInIDsSearchQuery(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionToID(q *action.Condition) (string, error) {
|
||||||
|
switch t := q.GetConditionType().(type) {
|
||||||
|
case *action.Condition_Request:
|
||||||
|
cond := &command.ExecutionAPICondition{
|
||||||
|
Method: t.Request.GetMethod(),
|
||||||
|
Service: t.Request.GetService(),
|
||||||
|
All: t.Request.GetAll(),
|
||||||
|
}
|
||||||
|
return cond.ID(domain.ExecutionTypeRequest), nil
|
||||||
|
case *action.Condition_Response:
|
||||||
|
cond := &command.ExecutionAPICondition{
|
||||||
|
Method: t.Response.GetMethod(),
|
||||||
|
Service: t.Response.GetService(),
|
||||||
|
All: t.Response.GetAll(),
|
||||||
|
}
|
||||||
|
return cond.ID(domain.ExecutionTypeResponse), nil
|
||||||
|
case *action.Condition_Event:
|
||||||
|
cond := &command.ExecutionEventCondition{
|
||||||
|
Event: t.Event.GetEvent(),
|
||||||
|
Group: t.Event.GetGroup(),
|
||||||
|
All: t.Event.GetAll(),
|
||||||
|
}
|
||||||
|
return cond.ID(), nil
|
||||||
|
case *action.Condition_Function:
|
||||||
|
return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil
|
||||||
|
default:
|
||||||
|
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionsToPb(executions []*query.Execution) []*action.Execution {
|
||||||
|
e := make([]*action.Execution, len(executions))
|
||||||
|
for i, execution := range executions {
|
||||||
|
e[i] = executionToPb(execution)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionToPb(e *query.Execution) *action.Execution {
|
||||||
|
targets := make([]string, len(e.Targets))
|
||||||
|
for i := range e.Targets {
|
||||||
|
switch e.Targets[i].Type {
|
||||||
|
case domain.ExecutionTargetTypeTarget:
|
||||||
|
targets[i] = e.Targets[i].Target
|
||||||
|
case domain.ExecutionTargetTypeInclude, domain.ExecutionTargetTypeUnspecified:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec := &action.Execution{
|
||||||
|
Condition: executionIDToCondition(e.ID),
|
||||||
|
Targets: targets,
|
||||||
|
}
|
||||||
|
if !e.EventDate.IsZero() {
|
||||||
|
exec.ChangeDate = timestamppb.New(e.EventDate)
|
||||||
|
}
|
||||||
|
if !e.CreationDate.IsZero() {
|
||||||
|
exec.CreationDate = timestamppb.New(e.CreationDate)
|
||||||
|
}
|
||||||
|
return exec
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionIDToCondition(include string) *action.Condition {
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeRequest.String()) {
|
||||||
|
return includeRequestToCondition(strings.TrimPrefix(include, domain.ExecutionTypeRequest.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeResponse.String()) {
|
||||||
|
return includeResponseToCondition(strings.TrimPrefix(include, domain.ExecutionTypeResponse.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeEvent.String()) {
|
||||||
|
return includeEventToCondition(strings.TrimPrefix(include, domain.ExecutionTypeEvent.String()))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(include, domain.ExecutionTypeFunction.String()) {
|
||||||
|
return includeFunctionToCondition(strings.TrimPrefix(include, domain.ExecutionTypeFunction.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeRequestToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case conditionIDRequestResponseMethodSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
||||||
|
case conditionIDRequestResponseServiceSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
case conditionIDAllSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func includeResponseToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case conditionIDRequestResponseMethodSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
||||||
|
case conditionIDRequestResponseServiceSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
case conditionIDAllSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeEventToCondition(id string) *action.Condition {
|
||||||
|
switch strings.Count(id, "/") {
|
||||||
|
case conditionIDEventGroupSegmentCount:
|
||||||
|
if strings.HasSuffix(id, command.EventGroupSuffix) {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: strings.TrimSuffix(strings.TrimPrefix(id, "/"), command.EventGroupSuffix)}}}}
|
||||||
|
} else {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: strings.TrimPrefix(id, "/")}}}}
|
||||||
|
}
|
||||||
|
case conditionIDAllSegmentCount:
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeFunctionToCondition(id string) *action.Condition {
|
||||||
|
return &action.Condition{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: strings.TrimPrefix(id, "/")}}}
|
||||||
|
}
|
71
apps/api/internal/api/grpc/action/v2beta/server.go
Normal file
71
apps/api/internal/api/grpc/action/v2beta/server.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/action/v2beta/actionconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ actionconnect.ActionServiceHandler = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
command *command.Commands
|
||||||
|
query *query.Queries
|
||||||
|
ListActionFunctions func() []string
|
||||||
|
ListGRPCMethods func() []string
|
||||||
|
ListGRPCServices func() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
func CreateServer(
|
||||||
|
systemDefaults systemdefaults.SystemDefaults,
|
||||||
|
command *command.Commands,
|
||||||
|
query *query.Queries,
|
||||||
|
listActionFunctions func() []string,
|
||||||
|
listGRPCMethods func() []string,
|
||||||
|
listGRPCServices func() []string,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
systemDefaults: systemDefaults,
|
||||||
|
command: command,
|
||||||
|
query: query,
|
||||||
|
ListActionFunctions: listActionFunctions,
|
||||||
|
ListGRPCMethods: listGRPCMethods,
|
||||||
|
ListGRPCServices: listGRPCServices,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||||
|
return actionconnect.NewActionServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||||
|
return action.File_zitadel_action_v2beta_action_service_proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AppName() string {
|
||||||
|
return action.ActionService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) MethodPrefix() string {
|
||||||
|
return action.ActionService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||||
|
return action.ActionService_AuthMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||||
|
return action.RegisterActionServiceHandler
|
||||||
|
}
|
126
apps/api/internal/api/grpc/action/v2beta/target.go
Normal file
126
apps/api/internal/api/grpc/action/v2beta/target.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) CreateTarget(ctx context.Context, req *connect.Request[action.CreateTargetRequest]) (*connect.Response[action.CreateTargetResponse], error) {
|
||||||
|
add := createTargetToCommand(req.Msg)
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
createdAt, err := s.command.AddTarget(ctx, add, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var creationDate *timestamppb.Timestamp
|
||||||
|
if !createdAt.IsZero() {
|
||||||
|
creationDate = timestamppb.New(createdAt)
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.CreateTargetResponse{
|
||||||
|
Id: add.AggregateID,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
SigningKey: add.SigningKey,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateTarget(ctx context.Context, req *connect.Request[action.UpdateTargetRequest]) (*connect.Response[action.UpdateTargetResponse], error) {
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
update := updateTargetToCommand(req.Msg)
|
||||||
|
changedAt, err := s.command.ChangeTarget(ctx, update, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var changeDate *timestamppb.Timestamp
|
||||||
|
if !changedAt.IsZero() {
|
||||||
|
changeDate = timestamppb.New(changedAt)
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.UpdateTargetResponse{
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
SigningKey: update.SigningKey,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteTarget(ctx context.Context, req *connect.Request[action.DeleteTargetRequest]) (*connect.Response[action.DeleteTargetResponse], error) {
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
deletedAt, err := s.command.DeleteTarget(ctx, req.Msg.GetId(), instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var deletionDate *timestamppb.Timestamp
|
||||||
|
if !deletedAt.IsZero() {
|
||||||
|
deletionDate = timestamppb.New(deletedAt)
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&action.DeleteTargetResponse{
|
||||||
|
DeletionDate: deletionDate,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
||||||
|
var (
|
||||||
|
targetType domain.TargetType
|
||||||
|
interruptOnError bool
|
||||||
|
)
|
||||||
|
switch t := req.GetTargetType().(type) {
|
||||||
|
case *action.CreateTargetRequest_RestWebhook:
|
||||||
|
targetType = domain.TargetTypeWebhook
|
||||||
|
interruptOnError = t.RestWebhook.InterruptOnError
|
||||||
|
case *action.CreateTargetRequest_RestCall:
|
||||||
|
targetType = domain.TargetTypeCall
|
||||||
|
interruptOnError = t.RestCall.InterruptOnError
|
||||||
|
case *action.CreateTargetRequest_RestAsync:
|
||||||
|
targetType = domain.TargetTypeAsync
|
||||||
|
}
|
||||||
|
return &command.AddTarget{
|
||||||
|
Name: req.GetName(),
|
||||||
|
TargetType: targetType,
|
||||||
|
Endpoint: req.GetEndpoint(),
|
||||||
|
Timeout: req.GetTimeout().AsDuration(),
|
||||||
|
InterruptOnError: interruptOnError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarget {
|
||||||
|
expirationSigningKey := false
|
||||||
|
// TODO handle expiration, currently only immediate expiration is supported
|
||||||
|
if req.GetExpirationSigningKey() != nil {
|
||||||
|
expirationSigningKey = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := &command.ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: req.GetId(),
|
||||||
|
},
|
||||||
|
Name: req.Name,
|
||||||
|
Endpoint: req.Endpoint,
|
||||||
|
ExpirationSigningKey: expirationSigningKey,
|
||||||
|
}
|
||||||
|
if req.TargetType != nil {
|
||||||
|
switch t := req.GetTargetType().(type) {
|
||||||
|
case *action.UpdateTargetRequest_RestWebhook:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||||
|
target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError)
|
||||||
|
case *action.UpdateTargetRequest_RestCall:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeCall)
|
||||||
|
target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError)
|
||||||
|
case *action.UpdateTargetRequest_RestAsync:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeAsync)
|
||||||
|
target.InterruptOnError = gu.Ptr(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Timeout != nil {
|
||||||
|
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
229
apps/api/internal/api/grpc/action/v2beta/target_test.go
Normal file
229
apps/api/internal/api/grpc/action/v2beta/target_test.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_createTargetToCommand(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
req *action.CreateTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *command.AddTarget
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
args: args{nil},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "",
|
||||||
|
Endpoint: "",
|
||||||
|
Timeout: 0,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (webhook)",
|
||||||
|
args: args{&action.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeWebhook,
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async)",
|
||||||
|
args: args{&action.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeAsync,
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (interrupting response)",
|
||||||
|
args: args{&action.CreateTargetRequest{
|
||||||
|
Name: "target 1",
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
TargetType: &action.CreateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.AddTarget{
|
||||||
|
Name: "target 1",
|
||||||
|
TargetType: domain.TargetTypeCall,
|
||||||
|
Endpoint: "https://example.com/hooks/1",
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := createTargetToCommand(tt.args.req)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_updateTargetToCommand(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
req *action.UpdateTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *command.ChangeTarget
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
args: args{nil},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields nil",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: nil,
|
||||||
|
TargetType: nil,
|
||||||
|
Timeout: nil,
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: nil,
|
||||||
|
TargetType: nil,
|
||||||
|
Endpoint: nil,
|
||||||
|
Timeout: nil,
|
||||||
|
InterruptOnError: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields empty",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr(""),
|
||||||
|
TargetType: nil,
|
||||||
|
Timeout: durationpb.New(0),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr(""),
|
||||||
|
TargetType: nil,
|
||||||
|
Endpoint: nil,
|
||||||
|
Timeout: gu.Ptr(0 * time.Second),
|
||||||
|
InterruptOnError: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (webhook)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (webhook interrupt)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||||
|
RestWebhook: &action.RESTWebhook{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeWebhook),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (async)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||||
|
RestAsync: &action.RESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeAsync),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all fields (interrupting response)",
|
||||||
|
args: args{&action.UpdateTargetRequest{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||||
|
RestCall: &action.RESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
}},
|
||||||
|
want: &command.ChangeTarget{
|
||||||
|
Name: gu.Ptr("target 1"),
|
||||||
|
TargetType: gu.Ptr(domain.TargetTypeCall),
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
|
Timeout: gu.Ptr(10 * time.Second),
|
||||||
|
InterruptOnError: gu.Ptr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := updateTargetToCommand(tt.args.req)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user