mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-28 09:23:41 +00:00
feat(v3alpha): write actions (#8225)
# Which Problems Are Solved The current v3alpha actions APIs don't exactly adhere to the [new resources API design](https://zitadel.com/docs/apis/v3#standard-resources). # How the Problems Are Solved - **Breaking**: The current v3alpha actions APIs are removed. This is breaking. - **Resource Namespace**: New v3alpha actions APIs for targets and executions are added under the namespace /resources. - **Feature Flag**: New v3alpha actions APIs still have to be activated using the actions feature flag - **Reduced Executions Overhead**: Executions are managed similar to settings according to the new API design: an empty list of targets basically makes an execution a Noop. So a single method, SetExecution is enough to cover all use cases. Noop executions are not returned in future search requests. - **Compatibility**: The executions created with previous v3alpha APIs are still available to be managed with the new executions API. # Additional Changes - Removed integration tests which test executions but rely on readable targets. They are added again with #8169 # Additional Context Closes #8168
This commit is contained in:
parent
a1d24353db
commit
cc3ec1e2a7
@ -1041,12 +1041,9 @@ InternalAuthZ:
|
|||||||
- "events.read"
|
- "events.read"
|
||||||
- "milestones.read"
|
- "milestones.read"
|
||||||
- "session.delete"
|
- "session.delete"
|
||||||
- "execution.target.read"
|
- "action.target.write"
|
||||||
- "execution.target.write"
|
- "action.target.delete"
|
||||||
- "execution.target.delete"
|
- "action.execution.write"
|
||||||
- "execution.read"
|
|
||||||
- "execution.write"
|
|
||||||
- "execution.delete"
|
|
||||||
- "userschema.read"
|
- "userschema.read"
|
||||||
- "userschema.write"
|
- "userschema.write"
|
||||||
- "userschema.delete"
|
- "userschema.delete"
|
||||||
|
@ -34,7 +34,6 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api"
|
"github.com/zitadel/zitadel/internal/api"
|
||||||
"github.com/zitadel/zitadel/internal/api/assets"
|
"github.com/zitadel/zitadel/internal/api/assets"
|
||||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||||
action_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/action/v3alpha"
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||||
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
||||||
@ -44,6 +43,7 @@ import (
|
|||||||
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
|
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
|
||||||
org_v2 "github.com/zitadel/zitadel/internal/api/grpc/org/v2"
|
org_v2 "github.com/zitadel/zitadel/internal/api/grpc/org/v2"
|
||||||
org_v2beta "github.com/zitadel/zitadel/internal/api/grpc/org/v2beta"
|
org_v2beta "github.com/zitadel/zitadel/internal/api/grpc/org/v2beta"
|
||||||
|
action_v3_alpha "github.com/zitadel/zitadel/internal/api/grpc/resources/action/v3alpha"
|
||||||
session_v2 "github.com/zitadel/zitadel/internal/api/grpc/session/v2"
|
session_v2 "github.com/zitadel/zitadel/internal/api/grpc/session/v2"
|
||||||
session_v2beta "github.com/zitadel/zitadel/internal/api/grpc/session/v2beta"
|
session_v2beta "github.com/zitadel/zitadel/internal/api/grpc/session/v2beta"
|
||||||
settings_v2 "github.com/zitadel/zitadel/internal/api/grpc/settings/v2"
|
settings_v2 "github.com/zitadel/zitadel/internal/api/grpc/settings/v2"
|
||||||
|
@ -333,7 +333,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
action_v3: {
|
action_v3: {
|
||||||
specPath: ".artifacts/openapi/zitadel/action/v3alpha/action_service.swagger.json",
|
specPath: ".artifacts/openapi/zitadel/resources/action/v3alpha/action_service.swagger.json",
|
||||||
outputDir: "docs/apis/resources/action_service_v3",
|
outputDir: "docs/apis/resources/action_service_v3",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: "tag",
|
groupPathsBy: "tag",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,323 +0,0 @@
|
|||||||
//go:build integration
|
|
||||||
|
|
||||||
package action_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServer_ExecutionTarget(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
|
|
||||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
ctx context.Context
|
|
||||||
dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) (func(), error)
|
|
||||||
clean func(context.Context)
|
|
||||||
req *action.GetTargetByIDRequest
|
|
||||||
want *action.GetTargetByIDResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "GetTargetByID, request and response, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
|
||||||
|
|
||||||
instanceID := Tester.Instance.InstanceID()
|
|
||||||
orgID := Tester.Organisation.ID
|
|
||||||
projectID := ""
|
|
||||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
|
||||||
|
|
||||||
// create target for target changes
|
|
||||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
|
||||||
targetCreatedURL := "https://nonexistent"
|
|
||||||
|
|
||||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
|
||||||
|
|
||||||
// request received by target
|
|
||||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
|
||||||
changedRequest := &action.GetTargetByIDRequest{TargetId: targetCreated.GetId()}
|
|
||||||
// replace original request with different targetID
|
|
||||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
|
||||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, false)
|
|
||||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
|
||||||
// GetTargetByID with used target
|
|
||||||
request.TargetId = targetRequest.GetId()
|
|
||||||
|
|
||||||
// expected response from the GetTargetByID
|
|
||||||
expectedResponse := &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
TargetId: targetCreated.GetId(),
|
|
||||||
Details: targetCreated.GetDetails(),
|
|
||||||
Name: targetCreatedName,
|
|
||||||
Endpoint: targetCreatedURL,
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// has to be set separately because of the pointers
|
|
||||||
response.Target = &action.Target{
|
|
||||||
TargetId: targetCreated.GetId(),
|
|
||||||
Details: targetCreated.GetDetails(),
|
|
||||||
Name: targetCreatedName,
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
}
|
|
||||||
|
|
||||||
// content for partial update
|
|
||||||
changedResponse := &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
TargetId: "changed",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// change partial updated content on returned response
|
|
||||||
response.Target.TargetId = changedResponse.Target.TargetId
|
|
||||||
|
|
||||||
// response received by target
|
|
||||||
wantResponse := &middleware.ContextInfoResponse{
|
|
||||||
FullMethod: fullMethod,
|
|
||||||
InstanceID: instanceID,
|
|
||||||
OrgID: orgID,
|
|
||||||
ProjectID: projectID,
|
|
||||||
UserID: userID,
|
|
||||||
Request: changedRequest,
|
|
||||||
Response: expectedResponse,
|
|
||||||
}
|
|
||||||
// after request with different targetID, return changed response
|
|
||||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
|
||||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, false)
|
|
||||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
closeRequest()
|
|
||||||
closeResponse()
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
clean: func(ctx context.Context) {
|
|
||||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
|
||||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
want: &action.GetTargetByIDResponse{},
|
|
||||||
},
|
|
||||||
/*{
|
|
||||||
name: "GetTargetByID, request, interrupt",
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
|
||||||
|
|
||||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
|
||||||
instanceID := Tester.Instance.InstanceID()
|
|
||||||
orgID := Tester.Organisation.ID
|
|
||||||
projectID := ""
|
|
||||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
|
||||||
|
|
||||||
// request received by target
|
|
||||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instanceID, OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
|
||||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetByIDRequest{TargetId: "notchanged"})
|
|
||||||
|
|
||||||
targetRequest := Tester.CreateTarget(ctx, t, "", urlRequest, domain.TargetTypeCall, true)
|
|
||||||
Tester.SetExecution(ctx, t, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
|
||||||
// GetTargetByID with used target
|
|
||||||
request.TargetId = targetRequest.GetId()
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
closeRequest()
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
clean: func(ctx context.Context) {
|
|
||||||
Tester.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GetTargetByID, response, interrupt",
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) (func(), error) {
|
|
||||||
|
|
||||||
fullMethod := "/zitadel.action.v3alpha.ActionService/GetTargetByID"
|
|
||||||
instanceID := Tester.Instance.InstanceID()
|
|
||||||
orgID := Tester.Organisation.ID
|
|
||||||
projectID := ""
|
|
||||||
userID := Tester.Users.Get(integration.FirstInstanceUsersKey, integration.IAMOwner).ID
|
|
||||||
|
|
||||||
// create target for target changes
|
|
||||||
targetCreatedName := fmt.Sprint("GetTargetByID", time.Now().UnixNano()+1)
|
|
||||||
targetCreatedURL := "https://nonexistent"
|
|
||||||
|
|
||||||
targetCreated := Tester.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
|
||||||
|
|
||||||
// GetTargetByID with used target
|
|
||||||
request.TargetId = targetCreated.GetId()
|
|
||||||
|
|
||||||
// expected response from the GetTargetByID
|
|
||||||
expectedResponse := &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
TargetId: targetCreated.GetId(),
|
|
||||||
Details: targetCreated.GetDetails(),
|
|
||||||
Name: targetCreatedName,
|
|
||||||
Endpoint: targetCreatedURL,
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// content for partial update
|
|
||||||
changedResponse := &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
TargetId: "changed",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// response received by target
|
|
||||||
wantResponse := &middleware.ContextInfoResponse{
|
|
||||||
FullMethod: fullMethod,
|
|
||||||
InstanceID: instanceID,
|
|
||||||
OrgID: orgID,
|
|
||||||
ProjectID: projectID,
|
|
||||||
UserID: userID,
|
|
||||||
Request: request,
|
|
||||||
Response: expectedResponse,
|
|
||||||
}
|
|
||||||
// after request with different targetID, return changed response
|
|
||||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
|
||||||
targetResponse := Tester.CreateTarget(ctx, t, "", targetResponseURL, domain.TargetTypeCall, true)
|
|
||||||
Tester.SetExecution(ctx, t, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
|
||||||
|
|
||||||
return func() {
|
|
||||||
closeResponse()
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
clean: func(ctx context.Context) {
|
|
||||||
Tester.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
wantErr: true,
|
|
||||||
},*/
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if tt.dep != nil {
|
|
||||||
close, err := tt.dep(tt.ctx, tt.req, tt.want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer close()
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := Client.GetTargetByID(tt.ctx, tt.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
|
||||||
|
|
||||||
assert.Equal(t, tt.want.Target.TargetId, got.Target.TargetId)
|
|
||||||
|
|
||||||
if tt.clean != nil {
|
|
||||||
tt.clean(tt.ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func conditionRequestFullMethod(fullMethod string) *action.Condition {
|
|
||||||
return &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: fullMethod,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func conditionResponseFullMethod(fullMethod string) *action.Condition {
|
|
||||||
return &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Response{
|
|
||||||
Response: &action.ResponseExecution{
|
|
||||||
Condition: &action.ResponseExecution_Method{
|
|
||||||
Method: fullMethod,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testServerCall(
|
|
||||||
reqBody interface{},
|
|
||||||
sleep time.Duration,
|
|
||||||
statusCode int,
|
|
||||||
respBody interface{},
|
|
||||||
) (string, func()) {
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sentBody, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(data, sentBody) {
|
|
||||||
http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if statusCode != http.StatusOK {
|
|
||||||
http.Error(w, "error, statusCode", statusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(sleep)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
resp, err := json.Marshal(respBody)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := io.WriteString(w, string(resp)); err != nil {
|
|
||||||
http.Error(w, "error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
|
||||||
|
|
||||||
return server.URL, server.Close
|
|
||||||
}
|
|
@ -1,364 +0,0 @@
|
|||||||
package action
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
|
||||||
"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/v3alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) ListTargets(ctx context.Context, req *action.ListTargetsRequest) (*action.ListTargetsResponse, error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queries, err := listTargetsRequestToModel(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := s.query.SearchTargets(ctx, queries)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.ListTargetsResponse{
|
|
||||||
Result: targetsToPb(resp.Targets),
|
|
||||||
Details: object.ToListDetails(resp.SearchResponse),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listTargetsRequestToModel(req *action.ListTargetsRequest) (*query.TargetSearchQueries, error) {
|
|
||||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
|
||||||
queries, err := targetQueriesToQuery(req.Queries)
|
|
||||||
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 targetFieldNameToSortingColumn(field action.TargetFieldName) query.Column {
|
|
||||||
switch field {
|
|
||||||
case action.TargetFieldName_FIELD_NAME_UNSPECIFIED:
|
|
||||||
return query.TargetColumnID
|
|
||||||
case action.TargetFieldName_FIELD_NAME_ID:
|
|
||||||
return query.TargetColumnID
|
|
||||||
case action.TargetFieldName_FIELD_NAME_CREATION_DATE:
|
|
||||||
return query.TargetColumnCreationDate
|
|
||||||
case action.TargetFieldName_FIELD_NAME_CHANGE_DATE:
|
|
||||||
return query.TargetColumnChangeDate
|
|
||||||
case action.TargetFieldName_FIELD_NAME_NAME:
|
|
||||||
return query.TargetColumnName
|
|
||||||
case action.TargetFieldName_FIELD_NAME_TARGET_TYPE:
|
|
||||||
return query.TargetColumnTargetType
|
|
||||||
case action.TargetFieldName_FIELD_NAME_URL:
|
|
||||||
return query.TargetColumnURL
|
|
||||||
case action.TargetFieldName_FIELD_NAME_TIMEOUT:
|
|
||||||
return query.TargetColumnTimeout
|
|
||||||
case action.TargetFieldName_FIELD_NAME_INTERRUPT_ON_ERROR:
|
|
||||||
return query.TargetColumnInterruptOnError
|
|
||||||
default:
|
|
||||||
return query.TargetColumnID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetQueriesToQuery(queries []*action.TargetSearchQuery) (_ []query.SearchQuery, err error) {
|
|
||||||
q := make([]query.SearchQuery, len(queries))
|
|
||||||
for i, query := range queries {
|
|
||||||
q[i], err = targetQueryToQuery(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetQueryToQuery(query *action.TargetSearchQuery) (query.SearchQuery, error) {
|
|
||||||
switch q := query.Query.(type) {
|
|
||||||
case *action.TargetSearchQuery_TargetNameQuery:
|
|
||||||
return targetNameQueryToQuery(q.TargetNameQuery)
|
|
||||||
case *action.TargetSearchQuery_InTargetIdsQuery:
|
|
||||||
return targetInTargetIdsQueryToQuery(q.InTargetIdsQuery)
|
|
||||||
default:
|
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetNameQueryToQuery(q *action.TargetNameQuery) (query.SearchQuery, error) {
|
|
||||||
return query.NewTargetNameSearchQuery(object.TextMethodToQuery(q.Method), q.GetTargetName())
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetInTargetIdsQueryToQuery(q *action.InTargetIDsQuery) (query.SearchQuery, error) {
|
|
||||||
return query.NewTargetInIDsSearchQuery(q.GetTargetIds())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) GetTargetByID(ctx context.Context, req *action.GetTargetByIDRequest) (_ *action.GetTargetByIDResponse, err error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.query.GetTargetByID(ctx, req.GetTargetId())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.GetTargetByIDResponse{
|
|
||||||
Target: targetToPb(resp),
|
|
||||||
}, 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{
|
|
||||||
Details: object.DomainToDetailsPb(&t.ObjectDetails),
|
|
||||||
TargetId: t.ID,
|
|
||||||
Name: t.Name,
|
|
||||||
Timeout: durationpb.New(t.Timeout),
|
|
||||||
Endpoint: t.Endpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t.TargetType {
|
|
||||||
case domain.TargetTypeWebhook:
|
|
||||||
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
|
||||||
case domain.TargetTypeCall:
|
|
||||||
target.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
|
||||||
case domain.TargetTypeAsync:
|
|
||||||
target.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
|
||||||
default:
|
|
||||||
target.TargetType = nil
|
|
||||||
}
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ListExecutions(ctx context.Context, req *action.ListExecutionsRequest) (*action.ListExecutionsResponse, error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queries, err := listExecutionsRequestToModel(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := s.query.SearchExecutions(ctx, queries)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.ListExecutionsResponse{
|
|
||||||
Result: executionsToPb(resp.Executions),
|
|
||||||
Details: object.ToListDetails(resp.SearchResponse),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listExecutionsRequestToModel(req *action.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
|
||||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
|
||||||
queries, err := executionQueriesToQuery(req.Queries)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &query.ExecutionSearchQueries{
|
|
||||||
SearchRequest: query.SearchRequest{
|
|
||||||
Offset: offset,
|
|
||||||
Limit: limit,
|
|
||||||
Asc: asc,
|
|
||||||
},
|
|
||||||
Queries: queries,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func executionQueriesToQuery(queries []*action.SearchQuery) (_ []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.SearchQuery) (query.SearchQuery, error) {
|
|
||||||
switch q := searchQuery.Query.(type) {
|
|
||||||
case *action.SearchQuery_InConditionsQuery:
|
|
||||||
return inConditionsQueryToQuery(q.InConditionsQuery)
|
|
||||||
case *action.SearchQuery_ExecutionTypeQuery:
|
|
||||||
return executionTypeToQuery(q.ExecutionTypeQuery)
|
|
||||||
case *action.SearchQuery_IncludeQuery:
|
|
||||||
include, err := conditionToInclude(q.IncludeQuery.GetInclude())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return query.NewIncludeSearchQuery(include)
|
|
||||||
case *action.SearchQuery_TargetQuery:
|
|
||||||
return query.NewTargetSearchQuery(q.TargetQuery.GetTargetId())
|
|
||||||
default:
|
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func executionTypeToQuery(q *action.ExecutionTypeQuery) (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.InConditionsQuery) (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([]*action.ExecutionTargetType, len(e.Targets))
|
|
||||||
for i := range e.Targets {
|
|
||||||
switch e.Targets[i].Type {
|
|
||||||
case domain.ExecutionTargetTypeInclude:
|
|
||||||
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Include{Include: executionIDToCondition(e.Targets[i].Target)}}
|
|
||||||
case domain.ExecutionTargetTypeTarget:
|
|
||||||
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Target{Target: e.Targets[i].Target}}
|
|
||||||
case domain.ExecutionTargetTypeUnspecified:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &action.Execution{
|
|
||||||
Details: object.DomainToDetailsPb(&e.ObjectDetails),
|
|
||||||
Condition: executionIDToCondition(e.ID),
|
|
||||||
Targets: targets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 2:
|
|
||||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
|
||||||
case 1:
|
|
||||||
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
|
||||||
case 0:
|
|
||||||
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 2:
|
|
||||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
|
||||||
case 1:
|
|
||||||
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
|
||||||
case 0:
|
|
||||||
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 1:
|
|
||||||
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 0:
|
|
||||||
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, "/")}}}
|
|
||||||
}
|
|
@ -1,877 +0,0 @@
|
|||||||
//go:build integration
|
|
||||||
|
|
||||||
package action_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServer_GetTargetByID(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
dep func(context.Context, *action.GetTargetByIDRequest, *action.GetTargetByIDResponse) error
|
|
||||||
req *action.GetTargetByIDRequest
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *action.GetTargetByIDResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
args: args{
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not found",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.GetTargetByIDRequest{TargetId: "notexisting"},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get, ok",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
request.TargetId = resp.GetId()
|
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
|
||||||
response.Target.Name = name
|
|
||||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
|
||||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
want: &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get, async, ok",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false)
|
|
||||||
request.TargetId = resp.GetId()
|
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
|
||||||
response.Target.Name = name
|
|
||||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
|
||||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
want: &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestAsync{
|
|
||||||
RestAsync: &action.SetRESTAsync{},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get, webhook interruptOnError, ok",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true)
|
|
||||||
request.TargetId = resp.GetId()
|
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
|
||||||
response.Target.Name = name
|
|
||||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
|
||||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
want: &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get, call, ok",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false)
|
|
||||||
request.TargetId = resp.GetId()
|
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
|
||||||
response.Target.Name = name
|
|
||||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
|
||||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
want: &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "get, call interruptOnError, ok",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.GetTargetByIDRequest, response *action.GetTargetByIDResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true)
|
|
||||||
request.TargetId = resp.GetId()
|
|
||||||
|
|
||||||
response.Target.TargetId = resp.GetId()
|
|
||||||
response.Target.Name = name
|
|
||||||
response.Target.Details.ResourceOwner = resp.GetDetails().GetResourceOwner()
|
|
||||||
response.Target.Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Target.Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.GetTargetByIDRequest{},
|
|
||||||
},
|
|
||||||
want: &action.GetTargetByIDResponse{
|
|
||||||
Target: &action.Target{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if tt.args.dep != nil {
|
|
||||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retryDuration := 5 * time.Second
|
|
||||||
if ctxDeadline, ok := CTX.Deadline(); ok {
|
|
||||||
retryDuration = time.Until(ctxDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
||||||
got, getErr := Client.GetTargetByID(tt.args.ctx, tt.args.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(ttt, getErr, "Error: "+getErr.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(ttt, getErr)
|
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want.GetTarget(), got.GetTarget())
|
|
||||||
|
|
||||||
assert.Equal(t, tt.want.Target, got.Target)
|
|
||||||
}
|
|
||||||
|
|
||||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServer_ListTargets(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
dep func(context.Context, *action.ListTargetsRequest, *action.ListTargetsResponse) error
|
|
||||||
req *action.ListTargetsRequest
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *action.ListTargetsResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
args: args{
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.ListTargetsRequest{},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list, not found",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.ListTargetsRequest{
|
|
||||||
Queries: []*action.TargetSearchQuery{
|
|
||||||
{Query: &action.TargetSearchQuery_InTargetIdsQuery{
|
|
||||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
|
||||||
TargetIds: []string{"notfound"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListTargetsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 0,
|
|
||||||
},
|
|
||||||
Result: []*action.Target{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list single id",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
|
||||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
|
||||||
TargetIds: []string{resp.GetId()},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
|
||||||
//response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
response.Result[0].TargetId = resp.GetId()
|
|
||||||
response.Result[0].Name = name
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListTargetsRequest{
|
|
||||||
Queries: []*action.TargetSearchQuery{{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListTargetsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 1,
|
|
||||||
},
|
|
||||||
Result: []*action.Target{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "list single name",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
|
||||||
name := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
resp := Tester.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_TargetNameQuery{
|
|
||||||
TargetNameQuery: &action.TargetNameQuery{
|
|
||||||
TargetName: name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
response.Result[0].TargetId = resp.GetId()
|
|
||||||
response.Result[0].Name = name
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListTargetsRequest{
|
|
||||||
Queries: []*action.TargetSearchQuery{{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListTargetsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 1,
|
|
||||||
},
|
|
||||||
Result: []*action.Target{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list multiple id",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) error {
|
|
||||||
name1 := fmt.Sprint(time.Now().UnixNano() + 1)
|
|
||||||
name2 := fmt.Sprint(time.Now().UnixNano() + 3)
|
|
||||||
name3 := fmt.Sprint(time.Now().UnixNano() + 5)
|
|
||||||
resp1 := Tester.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
resp2 := Tester.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true)
|
|
||||||
resp3 := Tester.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false)
|
|
||||||
request.Queries[0].Query = &action.TargetSearchQuery_InTargetIdsQuery{
|
|
||||||
InTargetIdsQuery: &action.InTargetIDsQuery{
|
|
||||||
TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
|
|
||||||
response.Result[0].TargetId = resp1.GetId()
|
|
||||||
response.Result[0].Name = name1
|
|
||||||
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
|
||||||
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
|
|
||||||
response.Result[1].TargetId = resp2.GetId()
|
|
||||||
response.Result[1].Name = name2
|
|
||||||
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
|
|
||||||
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
|
|
||||||
response.Result[2].TargetId = resp3.GetId()
|
|
||||||
response.Result[2].Name = name3
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListTargetsRequest{
|
|
||||||
Queries: []*action.TargetSearchQuery{{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListTargetsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 3,
|
|
||||||
},
|
|
||||||
Result: []*action.Target{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.Target_RestAsync{
|
|
||||||
RestAsync: &action.SetRESTAsync{},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if tt.args.dep != nil {
|
|
||||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retryDuration := 5 * time.Second
|
|
||||||
if ctxDeadline, ok := CTX.Deadline(); ok {
|
|
||||||
retryDuration = time.Until(ctxDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
||||||
got, listErr := Client.ListTargets(tt.args.ctx, tt.args.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(ttt, listErr, "Error: "+listErr.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(ttt, listErr)
|
|
||||||
}
|
|
||||||
if listErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// always first check length, otherwise its failed anyway
|
|
||||||
assert.Len(ttt, got.Result, len(tt.want.Result))
|
|
||||||
for i := range tt.want.Result {
|
|
||||||
assert.Contains(ttt, got.Result, tt.want.Result[i])
|
|
||||||
}
|
|
||||||
integration.AssertListDetails(t, tt.want, got)
|
|
||||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServer_ListExecutions(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
targetResp := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
dep func(context.Context, *action.ListExecutionsRequest, *action.ListExecutionsResponse) error
|
|
||||||
req *action.ListExecutionsRequest
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *action.ListExecutionsResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
args: args{
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.ListExecutionsRequest{},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list request single condition",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
|
||||||
cond := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
|
||||||
resp := Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
|
||||||
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
|
||||||
// response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
// Set expected response with used values for SetExecution
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
response.Result[0].Condition = cond
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListExecutionsRequest{
|
|
||||||
Queries: []*action.SearchQuery{{
|
|
||||||
Query: &action.SearchQuery_InConditionsQuery{
|
|
||||||
InConditionsQuery: &action.InConditionsQuery{
|
|
||||||
Conditions: []*action.Condition{{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListExecutionsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 1,
|
|
||||||
},
|
|
||||||
Result: []*action.Execution{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Condition: &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list request single target",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
|
||||||
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
// add target as query to the request
|
|
||||||
request.Queries[0] = &action.SearchQuery{
|
|
||||||
Query: &action.SearchQuery_TargetQuery{
|
|
||||||
TargetQuery: &action.TargetQuery{
|
|
||||||
TargetId: target.GetId(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cond := &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.management.v1.ManagementService/UpdateAction",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
targets := executionTargetsSingleTarget(target.GetId())
|
|
||||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
|
||||||
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
response.Result[0].Condition = cond
|
|
||||||
response.Result[0].Targets = targets
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListExecutionsRequest{
|
|
||||||
Queries: []*action.SearchQuery{{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListExecutionsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 1,
|
|
||||||
},
|
|
||||||
Result: []*action.Execution{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
Condition: &action.Condition{},
|
|
||||||
Targets: executionTargetsSingleTarget(""),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
name: "list request single include",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
|
||||||
cond := &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.management.v1.ManagementService/GetAction",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Tester.SetExecution(ctx, t, cond, executionTargetsSingleTarget(targetResp.GetId()))
|
|
||||||
request.Queries[0].GetIncludeQuery().Include = cond
|
|
||||||
|
|
||||||
includeCond := &action.Condition{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.management.v1.ManagementService/ListActions",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
includeTargets := executionTargetsSingleInclude(cond)
|
|
||||||
resp2 := Tester.SetExecution(ctx, t, includeCond, includeTargets)
|
|
||||||
|
|
||||||
response.Details.Timestamp = resp2.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp2.GetDetails().GetSequence()
|
|
||||||
|
|
||||||
response.Result[0].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp2.GetDetails().GetSequence()
|
|
||||||
response.Result[0].Condition = includeCond
|
|
||||||
response.Result[0].Targets = includeTargets
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListExecutionsRequest{
|
|
||||||
Queries: []*action.SearchQuery{{
|
|
||||||
Query: &action.SearchQuery_IncludeQuery{
|
|
||||||
IncludeQuery: &action.IncludeQuery{},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListExecutionsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 1,
|
|
||||||
},
|
|
||||||
Result: []*action.Execution{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list multiple conditions",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
|
||||||
|
|
||||||
cond1 := request.Queries[0].GetInConditionsQuery().GetConditions()[0]
|
|
||||||
targets1 := executionTargetsSingleTarget(targetResp.GetId())
|
|
||||||
resp1 := Tester.SetExecution(ctx, t, cond1, targets1)
|
|
||||||
response.Result[0].Details.ChangeDate = resp1.GetDetails().GetChangeDate()
|
|
||||||
response.Result[0].Details.Sequence = resp1.GetDetails().GetSequence()
|
|
||||||
response.Result[0].Condition = cond1
|
|
||||||
response.Result[0].Targets = targets1
|
|
||||||
|
|
||||||
cond2 := request.Queries[0].GetInConditionsQuery().GetConditions()[1]
|
|
||||||
targets2 := executionTargetsSingleTarget(targetResp.GetId())
|
|
||||||
resp2 := Tester.SetExecution(ctx, t, cond2, targets2)
|
|
||||||
response.Result[1].Details.ChangeDate = resp2.GetDetails().GetChangeDate()
|
|
||||||
response.Result[1].Details.Sequence = resp2.GetDetails().GetSequence()
|
|
||||||
response.Result[1].Condition = cond2
|
|
||||||
response.Result[1].Targets = targets2
|
|
||||||
|
|
||||||
cond3 := request.Queries[0].GetInConditionsQuery().GetConditions()[2]
|
|
||||||
targets3 := executionTargetsSingleTarget(targetResp.GetId())
|
|
||||||
resp3 := Tester.SetExecution(ctx, t, cond3, targets3)
|
|
||||||
response.Result[2].Details.ChangeDate = resp3.GetDetails().GetChangeDate()
|
|
||||||
response.Result[2].Details.Sequence = resp3.GetDetails().GetSequence()
|
|
||||||
response.Result[2].Condition = cond3
|
|
||||||
response.Result[2].Targets = targets3
|
|
||||||
|
|
||||||
response.Details.Timestamp = resp3.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp3.GetDetails().GetSequence()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListExecutionsRequest{
|
|
||||||
Queries: []*action.SearchQuery{{
|
|
||||||
Query: &action.SearchQuery_InConditionsQuery{
|
|
||||||
InConditionsQuery: &action.InConditionsQuery{
|
|
||||||
Conditions: []*action.Condition{
|
|
||||||
{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.session.v2.SessionService/GetSession",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.session.v2.SessionService/CreateSession",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ConditionType: &action.Condition_Request{
|
|
||||||
Request: &action.RequestExecution{
|
|
||||||
Condition: &action.RequestExecution_Method{
|
|
||||||
Method: "/zitadel.session.v2.SessionService/SetSession",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListExecutionsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 3,
|
|
||||||
},
|
|
||||||
Result: []*action.Execution{
|
|
||||||
{
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Details: &object.Details{
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list multiple conditions all types",
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) error {
|
|
||||||
targets := executionTargetsSingleTarget(targetResp.GetId())
|
|
||||||
for i, cond := range request.Queries[0].GetInConditionsQuery().GetConditions() {
|
|
||||||
resp := Tester.SetExecution(ctx, t, cond, targets)
|
|
||||||
response.Result[i].Details.ChangeDate = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Result[i].Details.Sequence = resp.GetDetails().GetSequence()
|
|
||||||
response.Result[i].Condition = cond
|
|
||||||
response.Result[i].Targets = targets
|
|
||||||
|
|
||||||
// filled with info of last sequence
|
|
||||||
response.Details.Timestamp = resp.GetDetails().GetChangeDate()
|
|
||||||
response.Details.ProcessedSequence = resp.GetDetails().GetSequence()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
req: &action.ListExecutionsRequest{
|
|
||||||
Queries: []*action.SearchQuery{{
|
|
||||||
Query: &action.SearchQuery_InConditionsQuery{
|
|
||||||
InConditionsQuery: &action.InConditionsQuery{
|
|
||||||
Conditions: []*action.Condition{
|
|
||||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}},
|
|
||||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: "zitadel.session.v2.SessionService"}}}},
|
|
||||||
{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}},
|
|
||||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: "/zitadel.session.v2.SessionService/GetSession"}}}},
|
|
||||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: "zitadel.session.v2.SessionService"}}}},
|
|
||||||
{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}},
|
|
||||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: "user.added"}}}},
|
|
||||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: "user"}}}},
|
|
||||||
{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}},
|
|
||||||
{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"}}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.ListExecutionsResponse{
|
|
||||||
Details: &object.ListDetails{
|
|
||||||
TotalResult: 10,
|
|
||||||
},
|
|
||||||
Result: []*action.Execution{
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
{Details: &object.Details{ResourceOwner: Tester.Instance.InstanceID()}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if tt.args.dep != nil {
|
|
||||||
err := tt.args.dep(tt.args.ctx, tt.args.req, tt.want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retryDuration := 5 * time.Second
|
|
||||||
if ctxDeadline, ok := CTX.Deadline(); ok {
|
|
||||||
retryDuration = time.Until(ctxDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
||||||
got, listErr := Client.ListExecutions(tt.args.ctx, tt.args.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, listErr, "Error: "+listErr.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, listErr)
|
|
||||||
}
|
|
||||||
if listErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// always first check length, otherwise its failed anyway
|
|
||||||
assert.Len(t, got.Result, len(tt.want.Result))
|
|
||||||
for i := range tt.want.Result {
|
|
||||||
// as not sorted, all elements have to be checked
|
|
||||||
// workaround as oneof elements can only be checked with assert.EqualExportedValues()
|
|
||||||
if j, found := containExecution(got.Result, tt.want.Result[i]); found {
|
|
||||||
assert.EqualExportedValues(t, tt.want.Result[i], got.Result[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
integration.AssertListDetails(t, tt.want, got)
|
|
||||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containExecution(executionList []*action.Execution, execution *action.Execution) (int, bool) {
|
|
||||||
for i, exec := range executionList {
|
|
||||||
if reflect.DeepEqual(exec.Details, execution.Details) {
|
|
||||||
return i, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
package action
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/muhlemmer/gu"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
|
||||||
"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/v3alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
add := createTargetToCommand(req)
|
|
||||||
details, err := s.command.AddTarget(ctx, add, authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.CreateTargetResponse{
|
|
||||||
Id: add.AggregateID,
|
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) UpdateTarget(ctx context.Context, req *action.UpdateTargetRequest) (*action.UpdateTargetResponse, error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
details, err := s.command.ChangeTarget(ctx, updateTargetToCommand(req), authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.UpdateTargetResponse{
|
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) {
|
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
details, err := s.command.DeleteTarget(ctx, req.GetTargetId(), authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &action.DeleteTargetResponse{
|
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
}, 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 {
|
|
||||||
if req == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
target := &command.ChangeTarget{
|
|
||||||
ObjectRoot: models.ObjectRoot{
|
|
||||||
AggregateID: req.GetTargetId(),
|
|
||||||
},
|
|
||||||
Name: req.Name,
|
|
||||||
Endpoint: req.Endpoint,
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,423 +0,0 @@
|
|||||||
//go:build integration
|
|
||||||
|
|
||||||
package action_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/muhlemmer/gu"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServer_CreateTarget(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
ctx context.Context
|
|
||||||
req *action.CreateTargetRequest
|
|
||||||
want *action.CreateTargetResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty name",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: "",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty type",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
TargetType: nil,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty webhook url",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty request response url",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
TargetType: &action.CreateTargetRequest_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty timeout",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{},
|
|
||||||
},
|
|
||||||
Timeout: nil,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "async, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
|
||||||
RestAsync: &action.SetRESTAsync{},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
want: &action.CreateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "webhook, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
want: &action.CreateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "webhook, interrupt on error, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
want: &action.CreateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "call, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
want: &action.CreateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "call, interruptOnError, ok",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.CreateTargetRequest{
|
|
||||||
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
|
||||||
Endpoint: "https://example.com",
|
|
||||||
TargetType: &action.CreateTargetRequest_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
|
||||||
},
|
|
||||||
want: &action.CreateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := Client.CreateTarget(tt.ctx, tt.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
|
||||||
assert.NotEmpty(t, got.GetId())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServer_UpdateTarget(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
req *action.UpdateTargetRequest
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
prepare func(request *action.UpdateTargetRequest) error
|
|
||||||
args args
|
|
||||||
want *action.UpdateTargetResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not existing",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
request.TargetId = "notexisting"
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "change name, ok",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.UpdateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "change type, ok",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
|
||||||
RestCall: &action.SetRESTCall{
|
|
||||||
InterruptOnError: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.UpdateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "change url, ok",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.UpdateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "change timeout, ok",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
Timeout: durationpb.New(20 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.UpdateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "change type async, ok",
|
|
||||||
prepare: func(request *action.UpdateTargetRequest) error {
|
|
||||||
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId()
|
|
||||||
request.TargetId = targetID
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.UpdateTargetRequest{
|
|
||||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
|
||||||
RestAsync: &action.SetRESTAsync{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &action.UpdateTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := tt.prepare(tt.args.req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := Client.UpdateTarget(tt.args.ctx, tt.args.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServer_DeleteTarget(t *testing.T) {
|
|
||||||
ensureFeatureEnabled(t)
|
|
||||||
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
ctx context.Context
|
|
||||||
req *action.DeleteTargetRequest
|
|
||||||
want *action.DeleteTargetResponse
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing permission",
|
|
||||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
|
||||||
req: &action.DeleteTargetRequest{
|
|
||||||
TargetId: target.GetId(),
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty id",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.DeleteTargetRequest{
|
|
||||||
TargetId: "",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "delete target",
|
|
||||||
ctx: CTX,
|
|
||||||
req: &action.DeleteTargetRequest{
|
|
||||||
TargetId: target.GetId(),
|
|
||||||
},
|
|
||||||
want: &action.DeleteTargetResponse{
|
|
||||||
Details: &object.Details{
|
|
||||||
ChangeDate: timestamppb.Now(),
|
|
||||||
ResourceOwner: Tester.Instance.InstanceID(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := Client.DeleteTarget(tt.ctx, tt.req)
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
integration.AssertDetails(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,38 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
settings_object "github.com/zitadel/zitadel/internal/api/grpc/settings/object/v3alpha"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) ListExecutionFunctions(_ context.Context, _ *action.ListExecutionFunctionsRequest) (*action.ListExecutionFunctionsResponse, error) {
|
|
||||||
return &action.ListExecutionFunctionsResponse{
|
|
||||||
Functions: s.ListActionFunctions(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ListExecutionMethods(_ context.Context, _ *action.ListExecutionMethodsRequest) (*action.ListExecutionMethodsResponse, error) {
|
|
||||||
return &action.ListExecutionMethodsResponse{
|
|
||||||
Methods: s.ListGRPCMethods(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ListExecutionServices(_ context.Context, _ *action.ListExecutionServicesRequest) (*action.ListExecutionServicesResponse, error) {
|
|
||||||
return &action.ListExecutionServicesResponse{
|
|
||||||
Services: s.ListGRPCServices(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) {
|
func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) {
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
if err := checkExecutionEnabled(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
reqTargets := req.GetExecution().GetTargets()
|
||||||
targets := make([]*execution.Target, len(req.Targets))
|
targets := make([]*execution.Target, len(reqTargets))
|
||||||
for i, target := range req.Targets {
|
for i, target := range reqTargets {
|
||||||
switch t := target.GetType().(type) {
|
switch t := target.GetType().(type) {
|
||||||
case *action.ExecutionTargetType_Include:
|
case *action.ExecutionTargetType_Include:
|
||||||
include, err := conditionToInclude(t.Include)
|
include, err := conditionToInclude(t.Include)
|
||||||
@ -50,36 +34,32 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
|||||||
set := &command.SetExecution{
|
set := &command.SetExecution{
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
}
|
}
|
||||||
|
owner := &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: authz.GetInstance(ctx).InstanceID(),
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
var details *domain.ObjectDetails
|
var details *domain.ObjectDetails
|
||||||
switch t := req.GetCondition().GetConditionType().(type) {
|
switch t := req.GetCondition().GetConditionType().(type) {
|
||||||
case *action.Condition_Request:
|
case *action.Condition_Request:
|
||||||
cond := executionConditionFromRequest(t.Request)
|
cond := executionConditionFromRequest(t.Request)
|
||||||
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionRequest(ctx, cond, set, owner.Id)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *action.Condition_Response:
|
case *action.Condition_Response:
|
||||||
cond := executionConditionFromResponse(t.Response)
|
cond := executionConditionFromResponse(t.Response)
|
||||||
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionResponse(ctx, cond, set, owner.Id)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *action.Condition_Event:
|
case *action.Condition_Event:
|
||||||
cond := executionConditionFromEvent(t.Event)
|
cond := executionConditionFromEvent(t.Event)
|
||||||
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionEvent(ctx, cond, set, owner.Id)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *action.Condition_Function:
|
case *action.Condition_Function:
|
||||||
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, authz.GetInstance(ctx).InstanceID())
|
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), set, owner.Id)
|
||||||
|
default:
|
||||||
|
err = zerrors.ThrowInvalidArgument(nil, "ACTION-5r5Ju", "Errors.Execution.ConditionInvalid")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return &action.SetExecutionResponse{
|
return &action.SetExecutionResponse{
|
||||||
Details: object.DomainToDetailsPb(details),
|
Details: settings_object.DomainToDetailsPb(details, owner),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,44 +89,26 @@ func conditionToInclude(cond *action.Condition) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cond.ID(), nil
|
return cond.ID(), nil
|
||||||
|
default:
|
||||||
|
return "", zerrors.ThrowInvalidArgument(nil, "ACTION-9BBob", "Errors.Execution.ConditionInvalid")
|
||||||
}
|
}
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteExecution(ctx context.Context, req *action.DeleteExecutionRequest) (*action.DeleteExecutionResponse, error) {
|
func (s *Server) ListExecutionFunctions(_ context.Context, _ *action.ListExecutionFunctionsRequest) (*action.ListExecutionFunctionsResponse, error) {
|
||||||
if err := checkExecutionEnabled(ctx); err != nil {
|
return &action.ListExecutionFunctionsResponse{
|
||||||
return nil, err
|
Functions: s.ListActionFunctions(),
|
||||||
}
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
func (s *Server) ListExecutionMethods(_ context.Context, _ *action.ListExecutionMethodsRequest) (*action.ListExecutionMethodsResponse, error) {
|
||||||
var details *domain.ObjectDetails
|
return &action.ListExecutionMethodsResponse{
|
||||||
switch t := req.GetCondition().GetConditionType().(type) {
|
Methods: s.ListGRPCMethods(),
|
||||||
case *action.Condition_Request:
|
}, nil
|
||||||
cond := executionConditionFromRequest(t.Request)
|
}
|
||||||
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
func (s *Server) ListExecutionServices(_ context.Context, _ *action.ListExecutionServicesRequest) (*action.ListExecutionServicesResponse, error) {
|
||||||
return nil, err
|
return &action.ListExecutionServicesResponse{
|
||||||
}
|
Services: s.ListGRPCServices(),
|
||||||
case *action.Condition_Response:
|
|
||||||
cond := executionConditionFromResponse(t.Response)
|
|
||||||
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *action.Condition_Event:
|
|
||||||
cond := executionConditionFromEvent(t.Event)
|
|
||||||
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case *action.Condition_Function:
|
|
||||||
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function.GetName()), authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &action.DeleteExecutionResponse{
|
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,805 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
|
settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||||
|
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Target{Target: id}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executionTargetsSingleInclude(include *action.Condition) []*action.ExecutionTargetType {
|
||||||
|
return []*action.ExecutionTargetType{{Type: &action.ExecutionTargetType_Include{Include: include}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Request(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
want *action.SetExecutionResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
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: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "NotExistingService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
executionCond := &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Tester.SetExecution(CTX, t,
|
||||||
|
executionCond,
|
||||||
|
executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
)
|
||||||
|
|
||||||
|
circularExecutionService := &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Tester.SetExecution(CTX, t,
|
||||||
|
circularExecutionService,
|
||||||
|
executionTargetsSingleInclude(executionCond),
|
||||||
|
)
|
||||||
|
circularExecutionMethod := &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Tester.SetExecution(CTX, t,
|
||||||
|
circularExecutionMethod,
|
||||||
|
executionTargetsSingleInclude(circularExecutionService),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
want *action.SetExecutionResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "method, circular error",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: circularExecutionService,
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleInclude(circularExecutionMethod),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
|
||||||
|
Targets: executionTargetsSingleInclude(executionCond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Request{
|
||||||
|
Request: &action.RequestExecution{
|
||||||
|
Condition: &action.RequestExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
|
||||||
|
Targets: executionTargetsSingleInclude(executionCond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Response(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
want *action.SetExecutionResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
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: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.NotExistingService/List",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Method{
|
||||||
|
Method: "/zitadel.session.v2beta.SessionService/ListSessions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Service{
|
||||||
|
Service: "NotExistingService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_Service{
|
||||||
|
Service: "zitadel.session.v2beta.SessionService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{
|
||||||
|
Condition: &action.ResponseExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Event(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
want *action.SetExecutionResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
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: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
//TODO event existing check
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "event, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Event{
|
||||||
|
Event: "xxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
name: "event, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Event{
|
||||||
|
Event: "xxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
// TODO:
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "group, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Group{
|
||||||
|
Group: "xxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: []string{targetResp.GetId()},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
name: "group, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_Group{
|
||||||
|
Group: "xxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Event{
|
||||||
|
Event: &action.EventExecution{
|
||||||
|
Condition: &action.EventExecution_All{
|
||||||
|
All: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_SetExecution_Function(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
targetResp := Tester.CreateTarget(CTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.SetExecutionRequest
|
||||||
|
want *action.SetExecutionResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
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: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Response{
|
||||||
|
Response: &action.ResponseExecution{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function, not existing",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Function{
|
||||||
|
Function: &action.FunctionExecution{Name: "xxx"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "function, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.SetExecutionRequest{
|
||||||
|
Condition: &action.Condition{
|
||||||
|
ConditionType: &action.Condition_Function{
|
||||||
|
Function: &action.FunctionExecution{Name: "Action.Flow.Type.ExternalAuthentication.Action.TriggerType.PostAuthentication"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &action.SetExecutionResponse{
|
||||||
|
Details: &settings_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
got, err := Client.SetExecution(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertSettingsDetails(t, tt.want.Details, got.Details)
|
||||||
|
|
||||||
|
// cleanup to not impact other requests
|
||||||
|
Tester.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,13 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ action.ActionServiceServer = (*Server)(nil)
|
var _ action.ZITADELActionsServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
action.UnimplementedActionServiceServer
|
action.UnimplementedZITADELActionsServer
|
||||||
command *command.Commands
|
command *command.Commands
|
||||||
query *query.Queries
|
query *query.Queries
|
||||||
ListActionFunctions func() []string
|
ListActionFunctions func() []string
|
||||||
@ -43,23 +43,23 @@ func CreateServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||||
action.RegisterActionServiceServer(grpcServer, s)
|
action.RegisterZITADELActionsServer(grpcServer, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AppName() string {
|
func (s *Server) AppName() string {
|
||||||
return action.ActionService_ServiceDesc.ServiceName
|
return action.ZITADELActions_ServiceDesc.ServiceName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) MethodPrefix() string {
|
func (s *Server) MethodPrefix() string {
|
||||||
return action.ActionService_ServiceDesc.ServiceName
|
return action.ZITADELActions_ServiceDesc.ServiceName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||||
return action.ActionService_AuthMethods
|
return action.ZITADELActions_AuthMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||||
return action.RegisterActionServiceHandler
|
return action.RegisterZITADELActionsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkExecutionEnabled(ctx context.Context) error {
|
func checkExecutionEnabled(ctx context.Context) error {
|
@ -13,14 +13,14 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CTX context.Context
|
CTX context.Context
|
||||||
Tester *integration.Tester
|
Tester *integration.Tester
|
||||||
Client action.ActionServiceClient
|
Client action.ZITADELActionsClient
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
121
internal/api/grpc/resources/action/v3alpha/target.go
Normal file
121
internal/api/grpc/resources/action/v3alpha/target.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) {
|
||||||
|
if err := checkExecutionEnabled(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
add := createTargetToCommand(req)
|
||||||
|
instance := targetOwnerInstance(ctx)
|
||||||
|
details, err := s.command.AddTarget(ctx, add, instance.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &action.CreateTargetResponse{
|
||||||
|
Details: resource_object.DomainToDetailsPb(details, instance, add.AggregateID),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest) (*action.PatchTargetResponse, error) {
|
||||||
|
if err := checkExecutionEnabled(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
instance := targetOwnerInstance(ctx)
|
||||||
|
details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instance.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &action.PatchTargetResponse{
|
||||||
|
Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetRequest) (*action.DeleteTargetResponse, error) {
|
||||||
|
if err := checkExecutionEnabled(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
instance := targetOwnerInstance(ctx)
|
||||||
|
details, err := s.command.DeleteTarget(ctx, req.GetId(), instance.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &action.DeleteTargetResponse{
|
||||||
|
Details: resource_object.DomainToDetailsPb(details, instance, req.GetId()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
||||||
|
reqTarget := req.GetTarget()
|
||||||
|
var (
|
||||||
|
targetType domain.TargetType
|
||||||
|
interruptOnError bool
|
||||||
|
)
|
||||||
|
switch t := reqTarget.GetTargetType().(type) {
|
||||||
|
case *action.Target_RestWebhook:
|
||||||
|
targetType = domain.TargetTypeWebhook
|
||||||
|
interruptOnError = t.RestWebhook.InterruptOnError
|
||||||
|
case *action.Target_RestCall:
|
||||||
|
targetType = domain.TargetTypeCall
|
||||||
|
interruptOnError = t.RestCall.InterruptOnError
|
||||||
|
case *action.Target_RestAsync:
|
||||||
|
targetType = domain.TargetTypeAsync
|
||||||
|
}
|
||||||
|
return &command.AddTarget{
|
||||||
|
Name: reqTarget.GetName(),
|
||||||
|
TargetType: targetType,
|
||||||
|
Endpoint: reqTarget.GetEndpoint(),
|
||||||
|
Timeout: reqTarget.GetTimeout().AsDuration(),
|
||||||
|
InterruptOnError: interruptOnError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget {
|
||||||
|
reqTarget := req.GetTarget()
|
||||||
|
if reqTarget == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := &command.ChangeTarget{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: req.GetId(),
|
||||||
|
},
|
||||||
|
Name: reqTarget.Name,
|
||||||
|
Endpoint: reqTarget.Endpoint,
|
||||||
|
}
|
||||||
|
if reqTarget.TargetType != nil {
|
||||||
|
switch t := reqTarget.GetTargetType().(type) {
|
||||||
|
case *action.PatchTarget_RestWebhook:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||||
|
target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError)
|
||||||
|
case *action.PatchTarget_RestCall:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeCall)
|
||||||
|
target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError)
|
||||||
|
case *action.PatchTarget_RestAsync:
|
||||||
|
target.TargetType = gu.Ptr(domain.TargetTypeAsync)
|
||||||
|
target.InterruptOnError = gu.Ptr(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reqTarget.Timeout != nil {
|
||||||
|
target.Timeout = gu.Ptr(reqTarget.GetTimeout().AsDuration())
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func targetOwnerInstance(ctx context.Context) *object.Owner {
|
||||||
|
return &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: authz.GetInstance(ctx).InstanceID(),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,447 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package action_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
|
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_CreateTarget(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.Target
|
||||||
|
want *resource_object.Details
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty name",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty type",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty webhook url",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty request response url",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty timeout",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
|
},
|
||||||
|
Timeout: nil,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "async, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestAsync{
|
||||||
|
RestAsync: &action.SetRESTAsync{},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webhook, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webhook, interrupt on error, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestWebhook{
|
||||||
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "call, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "call, interruptOnError, ok",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.Target{
|
||||||
|
Name: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||||
|
Endpoint: "https://example.com",
|
||||||
|
TargetType: &action.Target_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Client.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_PatchTarget(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
req *action.PatchTargetRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
prepare func(request *action.PatchTargetRequest) error
|
||||||
|
args args
|
||||||
|
want *resource_object.Details
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not existing",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
request.Id = "notexisting"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change name, ok",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
Name: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change type, ok",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
TargetType: &action.PatchTarget_RestCall{
|
||||||
|
RestCall: &action.SetRESTCall{
|
||||||
|
InterruptOnError: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change url, ok",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change timeout, ok",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
Timeout: durationpb.New(20 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change type async, ok",
|
||||||
|
prepare: func(request *action.PatchTargetRequest) error {
|
||||||
|
targetID := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId()
|
||||||
|
request.Id = targetID
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.PatchTargetRequest{
|
||||||
|
Target: &action.PatchTarget{
|
||||||
|
TargetType: &action.PatchTarget_RestAsync{
|
||||||
|
RestAsync: &action.SetRESTAsync{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.prepare(tt.args.req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// We want to have the same response no matter how often we call the function
|
||||||
|
Client.PatchTarget(tt.args.ctx, tt.args.req)
|
||||||
|
got, err := Client.PatchTarget(tt.args.ctx, tt.args.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_DeleteTarget(t *testing.T) {
|
||||||
|
ensureFeatureEnabled(t)
|
||||||
|
target := Tester.CreateTarget(CTX, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
req *action.DeleteTargetRequest
|
||||||
|
want *resource_object.Details
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: target.GetDetails().GetId(),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty id",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: "",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete target",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &action.DeleteTargetRequest{
|
||||||
|
Id: target.GetDetails().GetId(),
|
||||||
|
},
|
||||||
|
want: &resource_object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
Owner: &object.Owner{
|
||||||
|
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||||
|
Id: Tester.Instance.InstanceID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Client.DeleteTarget(tt.ctx, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_createTargetToCommand(t *testing.T) {
|
func Test_createTargetToCommand(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
req *action.CreateTargetRequest
|
req *action.Target
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -34,10 +34,10 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (webhook)",
|
name: "all fields (webhook)",
|
||||||
args: args{&action.CreateTargetRequest{
|
args: args{&action.Target{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
Endpoint: "https://example.com/hooks/1",
|
Endpoint: "https://example.com/hooks/1",
|
||||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
TargetType: &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{},
|
RestWebhook: &action.SetRESTWebhook{},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -52,10 +52,10 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (async)",
|
name: "all fields (async)",
|
||||||
args: args{&action.CreateTargetRequest{
|
args: args{&action.Target{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
Endpoint: "https://example.com/hooks/1",
|
Endpoint: "https://example.com/hooks/1",
|
||||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
TargetType: &action.Target_RestAsync{
|
||||||
RestAsync: &action.SetRESTAsync{},
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -70,10 +70,10 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (interrupting response)",
|
name: "all fields (interrupting response)",
|
||||||
args: args{&action.CreateTargetRequest{
|
args: args{&action.Target{
|
||||||
Name: "target 1",
|
Name: "target 1",
|
||||||
Endpoint: "https://example.com/hooks/1",
|
Endpoint: "https://example.com/hooks/1",
|
||||||
TargetType: &action.CreateTargetRequest_RestCall{
|
TargetType: &action.Target_RestCall{
|
||||||
RestCall: &action.SetRESTCall{
|
RestCall: &action.SetRESTCall{
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
@ -91,7 +91,7 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := createTargetToCommand(tt.args.req)
|
got := createTargetToCommand(&action.CreateTargetRequest{Target: tt.args.req})
|
||||||
assert.Equal(t, tt.want, got)
|
assert.Equal(t, tt.want, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ func Test_createTargetToCommand(t *testing.T) {
|
|||||||
|
|
||||||
func Test_updateTargetToCommand(t *testing.T) {
|
func Test_updateTargetToCommand(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
req *action.UpdateTargetRequest
|
req *action.PatchTarget
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -113,7 +113,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields nil",
|
name: "all fields nil",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: nil,
|
Name: nil,
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
Timeout: nil,
|
Timeout: nil,
|
||||||
@ -128,7 +128,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields empty",
|
name: "all fields empty",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: gu.Ptr(""),
|
Name: gu.Ptr(""),
|
||||||
TargetType: nil,
|
TargetType: nil,
|
||||||
Timeout: durationpb.New(0),
|
Timeout: durationpb.New(0),
|
||||||
@ -143,10 +143,10 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (webhook)",
|
name: "all fields (webhook)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
TargetType: &action.PatchTarget_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
InterruptOnError: false,
|
InterruptOnError: false,
|
||||||
},
|
},
|
||||||
@ -163,10 +163,10 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (webhook interrupt)",
|
name: "all fields (webhook interrupt)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
TargetType: &action.PatchTarget_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
@ -183,10 +183,10 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (async)",
|
name: "all fields (async)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
TargetType: &action.PatchTarget_RestAsync{
|
||||||
RestAsync: &action.SetRESTAsync{},
|
RestAsync: &action.SetRESTAsync{},
|
||||||
},
|
},
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
@ -201,10 +201,10 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all fields (interrupting response)",
|
name: "all fields (interrupting response)",
|
||||||
args: args{&action.UpdateTargetRequest{
|
args: args{&action.PatchTarget{
|
||||||
Name: gu.Ptr("target 1"),
|
Name: gu.Ptr("target 1"),
|
||||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
TargetType: &action.PatchTarget_RestCall{
|
||||||
RestCall: &action.SetRESTCall{
|
RestCall: &action.SetRESTCall{
|
||||||
InterruptOnError: true,
|
InterruptOnError: true,
|
||||||
},
|
},
|
||||||
@ -222,7 +222,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := updateTargetToCommand(tt.args.req)
|
got := patchTargetToCommand(&action.PatchTargetRequest{Target: tt.args.req})
|
||||||
assert.Equal(t, tt.want, got)
|
assert.Equal(t, tt.want, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
21
internal/api/grpc/resources/object/v3alpha/converter.go
Normal file
21
internal/api/grpc/resources/object/v3alpha/converter.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
resources_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DomainToDetailsPb(objectDetail *domain.ObjectDetails, owner *object.Owner, id string) *resources_object.Details {
|
||||||
|
details := &resources_object.Details{
|
||||||
|
Id: id,
|
||||||
|
Sequence: objectDetail.Sequence,
|
||||||
|
Owner: owner,
|
||||||
|
}
|
||||||
|
if !objectDetail.EventDate.IsZero() {
|
||||||
|
details.ChangeDate = timestamppb.New(objectDetail.EventDate)
|
||||||
|
}
|
||||||
|
return details
|
||||||
|
}
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
exec_repo "github.com/zitadel/zitadel/internal/repository/execution"
|
exec_repo "github.com/zitadel/zitadel/internal/repository/execution"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor {
|
func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor {
|
||||||
@ -143,6 +144,9 @@ func (c *ContextInfoRequest) GetHTTPRequestBody() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContextInfoRequest) SetHTTPResponseBody(resp []byte) error {
|
func (c *ContextInfoRequest) SetHTTPResponseBody(resp []byte) error {
|
||||||
|
if !json.Valid(resp) {
|
||||||
|
return zerrors.ThrowPreconditionFailed(nil, "ACTION-4m9s2", "Errors.Execution.ResponseIsNotValidJSON")
|
||||||
|
}
|
||||||
return json.Unmarshal(resp, c.Request)
|
return json.Unmarshal(resp, c.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
internal/api/grpc/settings/object/v3alpha/converter.go
Normal file
20
internal/api/grpc/settings/object/v3alpha/converter.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||||
|
settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DomainToDetailsPb(objectDetail *domain.ObjectDetails, owner *object.Owner) *settings_object.Details {
|
||||||
|
details := &settings_object.Details{
|
||||||
|
Sequence: objectDetail.Sequence,
|
||||||
|
Owner: owner,
|
||||||
|
}
|
||||||
|
if !objectDetail.EventDate.IsZero() {
|
||||||
|
details.ChangeDate = timestamppb.New(objectDetail.EventDate)
|
||||||
|
}
|
||||||
|
return details
|
||||||
|
}
|
@ -60,6 +60,11 @@ func (c *Commands) SetExecutionRequest(ctx context.Context, cond *ExecutionAPICo
|
|||||||
if err := cond.IsValid(); err != nil {
|
if err := cond.IsValid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, target := range set.Targets {
|
||||||
|
if err = target.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := cond.Existing(c); err != nil {
|
if err := cond.Existing(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -73,6 +78,11 @@ func (c *Commands) SetExecutionResponse(ctx context.Context, cond *ExecutionAPIC
|
|||||||
if err := cond.IsValid(); err != nil {
|
if err := cond.IsValid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, target := range set.Targets {
|
||||||
|
if err = target.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := cond.Existing(c); err != nil {
|
if err := cond.Existing(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -106,9 +116,19 @@ func (c *Commands) SetExecutionFunction(ctx context.Context, cond ExecutionFunct
|
|||||||
if err := cond.IsValid(); err != nil {
|
if err := cond.IsValid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, target := range set.Targets {
|
||||||
|
if err = target.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := cond.Existing(c); err != nil {
|
if err := cond.Existing(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, target := range set.Targets {
|
||||||
|
if err = target.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if set.AggregateID == "" {
|
if set.AggregateID == "" {
|
||||||
set.AggregateID = cond.ID()
|
set.AggregateID = cond.ID()
|
||||||
}
|
}
|
||||||
@ -165,6 +185,11 @@ func (c *Commands) SetExecutionEvent(ctx context.Context, cond *ExecutionEventCo
|
|||||||
if err := cond.IsValid(); err != nil {
|
if err := cond.IsValid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, target := range set.Targets {
|
||||||
|
if err = target.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := cond.Existing(c); err != nil {
|
if err := cond.Existing(c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -200,13 +225,6 @@ func (t SetExecution) GetTargets() []string {
|
|||||||
return targets
|
return targets
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SetExecution) IsValid() error {
|
|
||||||
if len(e.Targets) == 0 {
|
|
||||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
|
||||||
targets := e.GetTargets()
|
targets := e.GetTargets()
|
||||||
if len(targets) > 0 && !c.existsTargetsByIDs(ctx, targets, resourceOwner) {
|
if len(targets) > 0 && !c.existsTargetsByIDs(ctx, targets, resourceOwner) {
|
||||||
@ -225,16 +243,17 @@ func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resource
|
|||||||
if resourceOwner == "" || set.AggregateID == "" {
|
if resourceOwner == "" || set.AggregateID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-gg3a6ol4om", "Errors.IDMissing")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-gg3a6ol4om", "Errors.IDMissing")
|
||||||
}
|
}
|
||||||
if err := set.IsValid(); err != nil {
|
wm, err := c.getExecutionWriteModelByID(ctx, set.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
wm := NewExecutionWriteModel(set.AggregateID, resourceOwner)
|
|
||||||
// Check if targets and includes for execution are existing
|
// Check if targets and includes for execution are existing
|
||||||
|
if wm.ExecutionTargetsEqual(set.Targets) {
|
||||||
|
return writeModelToObjectDetails(&wm.WriteModel), err
|
||||||
|
}
|
||||||
if err := set.Existing(c, ctx, resourceOwner); err != nil {
|
if err := set.Existing(c, ctx, resourceOwner); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEventV2(
|
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEventV2(
|
||||||
ctx,
|
ctx,
|
||||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
||||||
@ -245,55 +264,6 @@ func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resource
|
|||||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) DeleteExecutionRequest(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
|
||||||
if err := cond.IsValid(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeRequest), resourceOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) DeleteExecutionResponse(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
|
||||||
if err := cond.IsValid(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeResponse), resourceOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) DeleteExecutionFunction(ctx context.Context, cond ExecutionFunctionCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
|
||||||
if err := cond.IsValid(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) DeleteExecutionEvent(ctx context.Context, cond *ExecutionEventCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
|
||||||
if err := cond.IsValid(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) deleteExecution(ctx context.Context, aggID string, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
|
||||||
if resourceOwner == "" || aggID == "" {
|
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-cnic97c0g3", "Errors.IDMissing")
|
|
||||||
}
|
|
||||||
|
|
||||||
wm, err := c.getExecutionWriteModelByID(ctx, aggID, resourceOwner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !wm.Exists() {
|
|
||||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-suq2upd3rt", "Errors.Execution.NotFound")
|
|
||||||
}
|
|
||||||
if err := c.pushAppendAndReduce(ctx, wm, execution.NewRemovedEvent(
|
|
||||||
ctx,
|
|
||||||
ExecutionAggregateFromWriteModel(&wm.WriteModel),
|
|
||||||
)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) existsExecutionsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
|
func (c *Commands) existsExecutionsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
|
||||||
wm := NewExecutionsExistWriteModel(ids, resourceOwner)
|
wm := NewExecutionsExistWriteModel(ids, resourceOwner)
|
||||||
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
err := c.eventstore.FilterToQueryReducer(ctx, wm)
|
||||||
|
@ -16,6 +16,18 @@ type ExecutionWriteModel struct {
|
|||||||
ExecutionTargets []*execution.Target
|
ExecutionTargets []*execution.Target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ExecutionWriteModel) ExecutionTargetsEqual(targets []*execution.Target) bool {
|
||||||
|
if len(e.ExecutionTargets) != len(targets) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range e.ExecutionTargets {
|
||||||
|
if e.ExecutionTargets[i].Type != targets[i].Type || e.ExecutionTargets[i].Target != targets[i].Target {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ExecutionWriteModel) IncludeList() []string {
|
func (e *ExecutionWriteModel) IncludeList() []string {
|
||||||
includes := make([]string, 0)
|
includes := make([]string, 0)
|
||||||
for i := range e.ExecutionTargets {
|
for i := range e.ExecutionTargets {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ func CallTargets(
|
|||||||
}
|
}
|
||||||
if len(resp) > 0 {
|
if len(resp) > 0 {
|
||||||
// error in unmarshalling
|
// error in unmarshalling
|
||||||
if err := info.SetHTTPResponseBody(resp); err != nil {
|
if err := info.SetHTTPResponseBody(resp); err != nil && target.IsInterruptOnError() {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,10 +73,10 @@ func CallTarget(
|
|||||||
return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||||
// get request, return response and error
|
// get request, return response and error
|
||||||
case domain.TargetTypeCall:
|
case domain.TargetTypeCall:
|
||||||
return call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
return Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody())
|
||||||
case domain.TargetTypeAsync:
|
case domain.TargetTypeAsync:
|
||||||
go func(target Target, info ContextInfoRequest) {
|
go func(target Target, info ContextInfoRequest) {
|
||||||
if _, err := call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()); err != nil {
|
if _, err := Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody()); err != nil {
|
||||||
logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err)
|
logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err)
|
||||||
}
|
}
|
||||||
}(target, info)
|
}(target, info)
|
||||||
@ -88,12 +88,12 @@ func CallTarget(
|
|||||||
|
|
||||||
// webhook call a webhook, ignore the response but return the errror
|
// webhook call a webhook, ignore the response but return the errror
|
||||||
func webhook(ctx context.Context, url string, timeout time.Duration, body []byte) error {
|
func webhook(ctx context.Context, url string, timeout time.Duration, body []byte) error {
|
||||||
_, err := call(ctx, url, timeout, body)
|
_, err := Call(ctx, url, timeout, body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// call function to do a post HTTP request to a desired url with timeout
|
// Call function to do a post HTTP request to a desired url with timeout
|
||||||
func call(ctx context.Context, url string, timeout time.Duration, body []byte) (_ []byte, err error) {
|
func Call(ctx context.Context, url string, timeout time.Duration, body []byte) (_ []byte, err error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package execution
|
package execution_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -12,37 +12,11 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/execution"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Target = &mockTarget{}
|
|
||||||
|
|
||||||
type mockTarget struct {
|
|
||||||
InstanceID string
|
|
||||||
ExecutionID string
|
|
||||||
TargetID string
|
|
||||||
TargetType domain.TargetType
|
|
||||||
Endpoint string
|
|
||||||
Timeout time.Duration
|
|
||||||
InterruptOnError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *mockTarget) GetTargetID() string {
|
|
||||||
return e.TargetID
|
|
||||||
}
|
|
||||||
func (e *mockTarget) IsInterruptOnError() bool {
|
|
||||||
return e.InterruptOnError
|
|
||||||
}
|
|
||||||
func (e *mockTarget) GetEndpoint() string {
|
|
||||||
return e.Endpoint
|
|
||||||
}
|
|
||||||
func (e *mockTarget) GetTargetType() domain.TargetType {
|
|
||||||
return e.TargetType
|
|
||||||
}
|
|
||||||
func (e *mockTarget) GetTimeout() time.Duration {
|
|
||||||
return e.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Call(t *testing.T) {
|
func Test_Call(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -110,12 +84,14 @@ func Test_Call(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
respBody, err := testServerCall(t,
|
respBody, err := testServer(t,
|
||||||
tt.args.method,
|
&callTestServer{
|
||||||
tt.args.body,
|
method: tt.args.method,
|
||||||
tt.args.sleep,
|
expectBody: tt.args.body,
|
||||||
tt.args.statusCode,
|
timeout: tt.args.sleep,
|
||||||
tt.args.respBody,
|
statusCode: tt.args.statusCode,
|
||||||
|
respondBody: tt.args.respBody,
|
||||||
|
},
|
||||||
testCall(tt.args.ctx, tt.args.timeout, tt.args.body),
|
testCall(tt.args.ctx, tt.args.timeout, tt.args.body),
|
||||||
)
|
)
|
||||||
if tt.res.wantErr {
|
if tt.res.wantErr {
|
||||||
@ -129,98 +105,12 @@ func Test_Call(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCall(ctx context.Context, timeout time.Duration, body []byte) func(string) ([]byte, error) {
|
|
||||||
return func(url string) ([]byte, error) {
|
|
||||||
return call(ctx, url, timeout, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCallTarget(ctx context.Context,
|
|
||||||
target *mockTarget,
|
|
||||||
info ContextInfoRequest,
|
|
||||||
) func(string) ([]byte, error) {
|
|
||||||
return func(url string) (r []byte, err error) {
|
|
||||||
target.Endpoint = url
|
|
||||||
return CallTarget(ctx, target, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testServerCall(
|
|
||||||
t *testing.T,
|
|
||||||
method string,
|
|
||||||
body []byte,
|
|
||||||
timeout time.Duration,
|
|
||||||
statusCode int,
|
|
||||||
respBody []byte,
|
|
||||||
call func(string) ([]byte, error),
|
|
||||||
) ([]byte, error) {
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
checkRequest(t, r, method, body)
|
|
||||||
|
|
||||||
if statusCode != http.StatusOK {
|
|
||||||
http.Error(w, "error", statusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(timeout)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
if _, err := io.WriteString(w, string(respBody)); err != nil {
|
|
||||||
http.Error(w, "error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
return call(server.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte) {
|
|
||||||
sentBody, err := io.ReadAll(sent.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedBody, sentBody)
|
|
||||||
require.Equal(t, method, sent.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ContextInfoRequest = &mockContextInfoRequest{}
|
|
||||||
|
|
||||||
type request struct {
|
|
||||||
Request string `json:"request"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockContextInfoRequest struct {
|
|
||||||
Request *request `json:"request"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockContextInfoRequest(s string) *mockContextInfoRequest {
|
|
||||||
return &mockContextInfoRequest{&request{s}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockContextInfoRequest) GetHTTPRequestBody() []byte {
|
|
||||||
data, _ := json.Marshal(c)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *mockContextInfoRequest) GetContent() []byte {
|
|
||||||
data, _ := json.Marshal(c.Request)
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_CallTarget(t *testing.T) {
|
func Test_CallTarget(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
info *middleware.ContextInfoRequest
|
||||||
|
server *callTestServer
|
||||||
target *mockTarget
|
target *mockTarget
|
||||||
sleep time.Duration
|
|
||||||
|
|
||||||
info ContextInfoRequest
|
|
||||||
|
|
||||||
method string
|
|
||||||
body []byte
|
|
||||||
|
|
||||||
respBody []byte
|
|
||||||
statusCode int
|
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
body []byte
|
body []byte
|
||||||
@ -235,15 +125,17 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
"unknown targettype, error",
|
"unknown targettype, error",
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
sleep: time.Second,
|
info: requestContextInfo1,
|
||||||
|
server: &callTestServer{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
info: newMockContextInfoRequest("content1"),
|
expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respondBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
timeout: time.Second,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
target: &mockTarget{
|
target: &mockTarget{
|
||||||
TargetType: 4,
|
TargetType: 4,
|
||||||
},
|
},
|
||||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
|
||||||
respBody: []byte("{\"request\":\"content2\"}"),
|
|
||||||
statusCode: http.StatusInternalServerError,
|
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -253,16 +145,18 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
"webhook, error",
|
"webhook, error",
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
sleep: time.Second,
|
info: requestContextInfo1,
|
||||||
|
server: &callTestServer{
|
||||||
|
timeout: time.Second,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
info: newMockContextInfoRequest("content1"),
|
expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respondBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
target: &mockTarget{
|
target: &mockTarget{
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: time.Minute,
|
Timeout: time.Minute,
|
||||||
},
|
},
|
||||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
|
||||||
respBody: []byte("{\"request\":\"content2\"}"),
|
|
||||||
statusCode: http.StatusInternalServerError,
|
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -272,16 +166,18 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
"webhook, ok",
|
"webhook, ok",
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
sleep: time.Second,
|
info: requestContextInfo1,
|
||||||
|
server: &callTestServer{
|
||||||
|
timeout: time.Second,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
info: newMockContextInfoRequest("content1"),
|
expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respondBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
target: &mockTarget{
|
target: &mockTarget{
|
||||||
TargetType: domain.TargetTypeWebhook,
|
TargetType: domain.TargetTypeWebhook,
|
||||||
Timeout: time.Minute,
|
Timeout: time.Minute,
|
||||||
},
|
},
|
||||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
|
||||||
respBody: []byte("{\"request\":\"content2\"}"),
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
body: nil,
|
body: nil,
|
||||||
@ -291,16 +187,18 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
"request response, error",
|
"request response, error",
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
sleep: time.Second,
|
info: requestContextInfo1,
|
||||||
|
server: &callTestServer{
|
||||||
|
timeout: time.Second,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
info: newMockContextInfoRequest("content1"),
|
expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respondBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
target: &mockTarget{
|
target: &mockTarget{
|
||||||
TargetType: domain.TargetTypeCall,
|
TargetType: domain.TargetTypeCall,
|
||||||
Timeout: time.Minute,
|
Timeout: time.Minute,
|
||||||
},
|
},
|
||||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
|
||||||
respBody: []byte("{\"request\":\"content2\"}"),
|
|
||||||
statusCode: http.StatusInternalServerError,
|
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -310,16 +208,18 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
"request response, ok",
|
"request response, ok",
|
||||||
args{
|
args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
sleep: time.Second,
|
info: requestContextInfo1,
|
||||||
|
server: &callTestServer{
|
||||||
|
timeout: time.Second,
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
info: newMockContextInfoRequest("content1"),
|
expectBody: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
||||||
|
respondBody: []byte("{\"request\":\"content2\"}"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
},
|
||||||
target: &mockTarget{
|
target: &mockTarget{
|
||||||
TargetType: domain.TargetTypeCall,
|
TargetType: domain.TargetTypeCall,
|
||||||
Timeout: time.Minute,
|
Timeout: time.Minute,
|
||||||
},
|
},
|
||||||
body: []byte("{\"request\":{\"request\":\"content1\"}}"),
|
|
||||||
respBody: []byte("{\"request\":\"content2\"}"),
|
|
||||||
statusCode: http.StatusOK,
|
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
body: []byte("{\"request\":\"content2\"}"),
|
body: []byte("{\"request\":\"content2\"}"),
|
||||||
@ -328,14 +228,7 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
respBody, err := testServerCall(t,
|
respBody, err := testServer(t, tt.args.server, testCallTarget(tt.args.ctx, tt.args.info, tt.args.target))
|
||||||
tt.args.method,
|
|
||||||
tt.args.body,
|
|
||||||
tt.args.sleep,
|
|
||||||
tt.args.statusCode,
|
|
||||||
tt.args.respBody,
|
|
||||||
testCallTarget(tt.args.ctx, tt.args.target, tt.args.info),
|
|
||||||
)
|
|
||||||
if tt.res.wantErr {
|
if tt.res.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
@ -345,3 +238,278 @@ func Test_CallTarget(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_CallTargets(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
info *middleware.ContextInfoRequest
|
||||||
|
servers []*callTestServer
|
||||||
|
targets []*mockTarget
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
ret interface{}
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"interrupt on status",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
info: requestContextInfo1,
|
||||||
|
servers: []*callTestServer{{
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
}, {
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
}},
|
||||||
|
targets: []*mockTarget{
|
||||||
|
{InterruptOnError: false},
|
||||||
|
{InterruptOnError: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"continue on status",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
info: requestContextInfo1,
|
||||||
|
servers: []*callTestServer{{
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
}, {
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
}},
|
||||||
|
targets: []*mockTarget{
|
||||||
|
{InterruptOnError: false},
|
||||||
|
{InterruptOnError: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
ret: requestContextInfo1.GetContent(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interrupt on json error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
info: requestContextInfo1,
|
||||||
|
servers: []*callTestServer{{
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}, {
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: []byte("just a string, not json"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}},
|
||||||
|
targets: []*mockTarget{
|
||||||
|
{InterruptOnError: false},
|
||||||
|
{InterruptOnError: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"continue on json error",
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
info: requestContextInfo1,
|
||||||
|
servers: []*callTestServer{{
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: requestContextInfoBody2,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}, {
|
||||||
|
timeout: time.Second,
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectBody: requestContextInfoBody1,
|
||||||
|
respondBody: []byte("just a string, not json"),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}},
|
||||||
|
targets: []*mockTarget{
|
||||||
|
{InterruptOnError: false},
|
||||||
|
{InterruptOnError: false},
|
||||||
|
}},
|
||||||
|
res{
|
||||||
|
ret: requestContextInfo1.GetContent(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
respBody, err := testServers(t,
|
||||||
|
tt.args.servers,
|
||||||
|
testCallTargets(tt.args.ctx, tt.args.info, tt.args.targets),
|
||||||
|
)
|
||||||
|
if tt.res.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
fmt.Println(respBody)
|
||||||
|
assert.Equal(t, tt.res.ret, respBody)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ execution.Target = &mockTarget{}
|
||||||
|
|
||||||
|
type mockTarget struct {
|
||||||
|
InstanceID string
|
||||||
|
ExecutionID string
|
||||||
|
TargetID string
|
||||||
|
TargetType domain.TargetType
|
||||||
|
Endpoint string
|
||||||
|
Timeout time.Duration
|
||||||
|
InterruptOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *mockTarget) GetTargetID() string {
|
||||||
|
return e.TargetID
|
||||||
|
}
|
||||||
|
func (e *mockTarget) IsInterruptOnError() bool {
|
||||||
|
return e.InterruptOnError
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetEndpoint() string {
|
||||||
|
return e.Endpoint
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetTargetType() domain.TargetType {
|
||||||
|
return e.TargetType
|
||||||
|
}
|
||||||
|
func (e *mockTarget) GetTimeout() time.Duration {
|
||||||
|
return e.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
type callTestServer struct {
|
||||||
|
method string
|
||||||
|
expectBody []byte
|
||||||
|
timeout time.Duration
|
||||||
|
statusCode int
|
||||||
|
respondBody []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func testServers(
|
||||||
|
t *testing.T,
|
||||||
|
c []*callTestServer,
|
||||||
|
call func([]string) (interface{}, error),
|
||||||
|
) (interface{}, error) {
|
||||||
|
urls := make([]string, len(c))
|
||||||
|
for i := range c {
|
||||||
|
url, close := listen(t, c[i])
|
||||||
|
defer close()
|
||||||
|
urls[i] = url
|
||||||
|
}
|
||||||
|
return call(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testServer(
|
||||||
|
t *testing.T,
|
||||||
|
c *callTestServer,
|
||||||
|
call func(string) ([]byte, error),
|
||||||
|
) ([]byte, error) {
|
||||||
|
url, close := listen(t, c)
|
||||||
|
defer close()
|
||||||
|
return call(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listen(
|
||||||
|
t *testing.T,
|
||||||
|
c *callTestServer,
|
||||||
|
) (url string, close func()) {
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
checkRequest(t, r, c.method, c.expectBody)
|
||||||
|
|
||||||
|
if c.statusCode != http.StatusOK {
|
||||||
|
http.Error(w, "error", c.statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(c.timeout)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if _, err := io.WriteString(w, string(c.respondBody)); err != nil {
|
||||||
|
http.Error(w, "error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
return server.URL, server.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequest(t *testing.T, sent *http.Request, method string, expectedBody []byte) {
|
||||||
|
sentBody, err := io.ReadAll(sent.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedBody, sentBody)
|
||||||
|
require.Equal(t, method, sent.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCall(ctx context.Context, timeout time.Duration, body []byte) func(string) ([]byte, error) {
|
||||||
|
return func(url string) ([]byte, error) {
|
||||||
|
return execution.Call(ctx, url, timeout, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallTarget(ctx context.Context,
|
||||||
|
info *middleware.ContextInfoRequest,
|
||||||
|
target *mockTarget,
|
||||||
|
) func(string) ([]byte, error) {
|
||||||
|
return func(url string) (r []byte, err error) {
|
||||||
|
target.Endpoint = url
|
||||||
|
return execution.CallTarget(ctx, target, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCallTargets(ctx context.Context,
|
||||||
|
info *middleware.ContextInfoRequest,
|
||||||
|
target []*mockTarget,
|
||||||
|
) func([]string) (interface{}, error) {
|
||||||
|
return func(urls []string) (interface{}, error) {
|
||||||
|
targets := make([]execution.Target, len(target))
|
||||||
|
for i, t := range target {
|
||||||
|
t.Endpoint = urls[i]
|
||||||
|
targets[i] = t
|
||||||
|
}
|
||||||
|
return execution.CallTargets(ctx, targets, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestContextInfo1 = &middleware.ContextInfoRequest{
|
||||||
|
Request: &request{
|
||||||
|
Request: "content1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestContextInfoBody1 = []byte("{\"request\":{\"request\":\"content1\"}}")
|
||||||
|
var requestContextInfoBody2 = []byte("{\"request\":{\"request\":\"content2\"}}")
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Request string `json:"request"`
|
||||||
|
}
|
||||||
|
@ -9,6 +9,9 @@ import (
|
|||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
resources_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||||
|
settings_object "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Details is the interface that covers both v1 and v2 proto generated object details.
|
// Details is the interface that covers both v1 and v2 proto generated object details.
|
||||||
@ -63,6 +66,31 @@ func AssertDetails[D Details, M DetailsMsg[D]](t testing.TB, expected, actual M)
|
|||||||
assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner())
|
assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AssertResourceDetails(t testing.TB, expected *resources_object.Details, actual *resources_object.Details) {
|
||||||
|
assert.NotZero(t, actual.GetSequence())
|
||||||
|
|
||||||
|
if expected.GetChangeDate() != nil {
|
||||||
|
wantChangeDate := time.Now()
|
||||||
|
gotChangeDate := actual.GetChangeDate().AsTime()
|
||||||
|
assert.WithinRange(t, gotChangeDate, wantChangeDate.Add(-time.Minute), wantChangeDate.Add(time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected.GetOwner(), actual.GetOwner())
|
||||||
|
assert.NotEmpty(t, actual.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertSettingsDetails(t testing.TB, expected *settings_object.Details, actual *settings_object.Details) {
|
||||||
|
assert.NotZero(t, actual.GetSequence())
|
||||||
|
|
||||||
|
if expected.GetChangeDate() != nil {
|
||||||
|
wantChangeDate := time.Now()
|
||||||
|
gotChangeDate := actual.GetChangeDate().AsTime()
|
||||||
|
assert.WithinRange(t, gotChangeDate, wantChangeDate.Add(-time.Minute), wantChangeDate.Add(time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected.GetOwner(), actual.GetOwner())
|
||||||
|
}
|
||||||
|
|
||||||
func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expected, actual D) {
|
func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expected, actual D) {
|
||||||
wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails()
|
wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails()
|
||||||
var nilDetails L
|
var nilDetails L
|
||||||
|
@ -25,21 +25,20 @@ import (
|
|||||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha"
|
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||||
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
||||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||||
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
org "github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||||
organisation "github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
|
||||||
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||||
|
session "github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||||
session_v2beta "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
session_v2beta "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||||
settings_v2beta "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
|
settings_v2beta "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||||
@ -62,9 +61,9 @@ type Client struct {
|
|||||||
OIDCv2beta oidc_pb_v2beta.OIDCServiceClient
|
OIDCv2beta oidc_pb_v2beta.OIDCServiceClient
|
||||||
OIDCv2 oidc_pb.OIDCServiceClient
|
OIDCv2 oidc_pb.OIDCServiceClient
|
||||||
OrgV2beta org_v2beta.OrganizationServiceClient
|
OrgV2beta org_v2beta.OrganizationServiceClient
|
||||||
OrgV2 organisation.OrganizationServiceClient
|
OrgV2 org.OrganizationServiceClient
|
||||||
System system.SystemServiceClient
|
System system.SystemServiceClient
|
||||||
ActionV3 action.ActionServiceClient
|
ActionV3 action.ZITADELActionsClient
|
||||||
FeatureV2beta feature_v2beta.FeatureServiceClient
|
FeatureV2beta feature_v2beta.FeatureServiceClient
|
||||||
FeatureV2 feature.FeatureServiceClient
|
FeatureV2 feature.FeatureServiceClient
|
||||||
UserSchemaV3 schema.UserSchemaServiceClient
|
UserSchemaV3 schema.UserSchemaServiceClient
|
||||||
@ -85,9 +84,9 @@ func newClient(cc *grpc.ClientConn) Client {
|
|||||||
OIDCv2beta: oidc_pb_v2beta.NewOIDCServiceClient(cc),
|
OIDCv2beta: oidc_pb_v2beta.NewOIDCServiceClient(cc),
|
||||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||||
OrgV2beta: org_v2beta.NewOrganizationServiceClient(cc),
|
OrgV2beta: org_v2beta.NewOrganizationServiceClient(cc),
|
||||||
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
OrgV2: org.NewOrganizationServiceClient(cc),
|
||||||
System: system.NewSystemServiceClient(cc),
|
System: system.NewSystemServiceClient(cc),
|
||||||
ActionV3: action.NewActionServiceClient(cc),
|
ActionV3: action.NewZITADELActionsClient(cc),
|
||||||
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
||||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||||
UserSchemaV3: schema.NewUserSchemaServiceClient(cc),
|
UserSchemaV3: schema.NewUserSchemaServiceClient(cc),
|
||||||
@ -627,50 +626,52 @@ func (s *Tester) CreateTarget(ctx context.Context, t *testing.T, name, endpoint
|
|||||||
if name != "" {
|
if name != "" {
|
||||||
nameSet = name
|
nameSet = name
|
||||||
}
|
}
|
||||||
req := &action.CreateTargetRequest{
|
reqTarget := &action.Target{
|
||||||
Name: nameSet,
|
Name: nameSet,
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
Timeout: durationpb.New(10 * time.Second),
|
Timeout: durationpb.New(10 * time.Second),
|
||||||
}
|
}
|
||||||
switch ty {
|
switch ty {
|
||||||
case domain.TargetTypeWebhook:
|
case domain.TargetTypeWebhook:
|
||||||
req.TargetType = &action.CreateTargetRequest_RestWebhook{
|
reqTarget.TargetType = &action.Target_RestWebhook{
|
||||||
RestWebhook: &action.SetRESTWebhook{
|
RestWebhook: &action.SetRESTWebhook{
|
||||||
InterruptOnError: interrupt,
|
InterruptOnError: interrupt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case domain.TargetTypeCall:
|
case domain.TargetTypeCall:
|
||||||
req.TargetType = &action.CreateTargetRequest_RestCall{
|
reqTarget.TargetType = &action.Target_RestCall{
|
||||||
RestCall: &action.SetRESTCall{
|
RestCall: &action.SetRESTCall{
|
||||||
InterruptOnError: interrupt,
|
InterruptOnError: interrupt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case domain.TargetTypeAsync:
|
case domain.TargetTypeAsync:
|
||||||
req.TargetType = &action.CreateTargetRequest_RestAsync{
|
reqTarget.TargetType = &action.Target_RestAsync{
|
||||||
RestAsync: &action.SetRESTAsync{},
|
RestAsync: &action.SetRESTAsync{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target, err := s.Client.ActionV3.CreateTarget(ctx, req)
|
target, err := s.Client.ActionV3.CreateTarget(ctx, &action.CreateTargetRequest{Target: reqTarget})
|
||||||
require.NoError(t, err)
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
|
||||||
target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
|
||||||
Condition: cond,
|
|
||||||
Targets: targets,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Tester) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
func (s *Tester) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
||||||
_, err := s.Client.ActionV3.DeleteExecution(ctx, &action.DeleteExecutionRequest{
|
_, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
||||||
Condition: cond,
|
Condition: cond,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
||||||
|
target, err := s.Client.ActionV3.SetExecution(ctx, &action.SetExecutionRequest{
|
||||||
|
Condition: cond,
|
||||||
|
Execution: &action.Execution{
|
||||||
|
Targets: targets,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
||||||
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -56,6 +57,13 @@ type Target struct {
|
|||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Target) Validate() error {
|
||||||
|
if t.Type == domain.ExecutionTargetTypeUnspecified || t.Target == "" {
|
||||||
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-hdm4zl1hmd", "Errors.Execution.Invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewSetEventV2(
|
func NewSetEventV2(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
|
@ -580,6 +580,7 @@ Errors:
|
|||||||
NotFound: Изпълнението не е намерено
|
NotFound: Изпълнението не е намерено
|
||||||
IncludeNotFound: Включването не е намерено
|
IncludeNotFound: Включването не е намерено
|
||||||
NoTargets: Няма определени цели
|
NoTargets: Няма определени цели
|
||||||
|
ResponseIsNotValidJSON: Отговорът не е валиден JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Функцията „Потребителска схема“ не е активирана
|
NotEnabled: Функцията „Потребителска схема“ не е активирана
|
||||||
Type:
|
Type:
|
||||||
|
@ -561,6 +561,7 @@ Errors:
|
|||||||
NotFound: Provedení nenalezeno
|
NotFound: Provedení nenalezeno
|
||||||
IncludeNotFound: Zahrnout nenalezeno
|
IncludeNotFound: Zahrnout nenalezeno
|
||||||
NoTargets: Nejsou definovány žádné cíle
|
NoTargets: Nejsou definovány žádné cíle
|
||||||
|
ResponseIsNotValidJSON: Odpověď není platný JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Funkce "Uživatelské schéma" není povolena
|
NotEnabled: Funkce "Uživatelské schéma" není povolena
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Ausführung nicht gefunden
|
NotFound: Ausführung nicht gefunden
|
||||||
IncludeNotFound: Einschließen nicht gefunden
|
IncludeNotFound: Einschließen nicht gefunden
|
||||||
NoTargets: Keine Ziele definiert
|
NoTargets: Keine Ziele definiert
|
||||||
|
ResponseIsNotValidJSON: Antwort ist kein gültiges JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Funktion Benutzerschema ist nicht aktiviert
|
NotEnabled: Funktion Benutzerschema ist nicht aktiviert
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Execution not found
|
NotFound: Execution not found
|
||||||
IncludeNotFound: Include not found
|
IncludeNotFound: Include not found
|
||||||
NoTargets: No targets defined
|
NoTargets: No targets defined
|
||||||
|
ResponseIsNotValidJSON: Response is not valid JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Feature "User Schema" is not enabled
|
NotEnabled: Feature "User Schema" is not enabled
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Ejecución no encontrada
|
NotFound: Ejecución no encontrada
|
||||||
IncludeNotFound: Incluir no encontrado
|
IncludeNotFound: Incluir no encontrado
|
||||||
NoTargets: No hay objetivos definidos
|
NoTargets: No hay objetivos definidos
|
||||||
|
ResponseIsNotValidJSON: La respuesta no es un JSON válido
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: La función "Esquema de usuario" no está habilitada
|
NotEnabled: La función "Esquema de usuario" no está habilitada
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Exécution introuvable
|
NotFound: Exécution introuvable
|
||||||
IncludeNotFound: Inclure introuvable
|
IncludeNotFound: Inclure introuvable
|
||||||
NoTargets: Aucune cible définie
|
NoTargets: Aucune cible définie
|
||||||
|
ResponseIsNotValidJSON: La réponse n'est pas un JSON valide
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: La fonctionnalité "Schéma utilisateur" n'est pas activée
|
NotEnabled: La fonctionnalité "Schéma utilisateur" n'est pas activée
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Esecuzione non trovata
|
NotFound: Esecuzione non trovata
|
||||||
IncludeNotFound: Includi non trovato
|
IncludeNotFound: Includi non trovato
|
||||||
NoTargets: Nessun obiettivo definito
|
NoTargets: Nessun obiettivo definito
|
||||||
|
ResponseIsNotValidJSON: La risposta non è un JSON valido
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: La funzionalità "Schema utente" non è abilitata
|
NotEnabled: La funzionalità "Schema utente" non è abilitata
|
||||||
Type:
|
Type:
|
||||||
|
@ -552,6 +552,7 @@ Errors:
|
|||||||
NotFound: 実行が見つかりませんでした
|
NotFound: 実行が見つかりませんでした
|
||||||
IncludeNotFound: 見つからないものを含める
|
IncludeNotFound: 見つからないものを含める
|
||||||
NoTargets: ターゲットが定義されていません
|
NoTargets: ターゲットが定義されていません
|
||||||
|
ResponseIsNotValidJSON: 応答は有効な JSON ではありません
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: 機能「ユーザースキーマ」が有効になっていません
|
NotEnabled: 機能「ユーザースキーマ」が有効になっていません
|
||||||
Type:
|
Type:
|
||||||
|
@ -562,6 +562,7 @@ Errors:
|
|||||||
NotFound: Извршувањето не е пронајдено
|
NotFound: Извршувањето не е пронајдено
|
||||||
IncludeNotFound: Вклучете не е пронајден
|
IncludeNotFound: Вклучете не е пронајден
|
||||||
NoTargets: Не се дефинирани цели
|
NoTargets: Не се дефинирани цели
|
||||||
|
ResponseIsNotValidJSON: Одговорот не е валиден JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Функцијата „Корисничка шема“ не е овозможена
|
NotEnabled: Функцијата „Корисничка шема“ не е овозможена
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Uitvoering niet gevonden
|
NotFound: Uitvoering niet gevonden
|
||||||
IncludeNotFound: Inclusief niet gevonden
|
IncludeNotFound: Inclusief niet gevonden
|
||||||
NoTargets: Geen doelstellingen gedefinieerd
|
NoTargets: Geen doelstellingen gedefinieerd
|
||||||
|
ResponseIsNotValidJSON: Reactie is geen geldige JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Functie "Gebruikersschema" is niet ingeschakeld
|
NotEnabled: Functie "Gebruikersschema" is niet ingeschakeld
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Nie znaleziono wykonania
|
NotFound: Nie znaleziono wykonania
|
||||||
IncludeNotFound: Nie znaleziono uwzględnienia
|
IncludeNotFound: Nie znaleziono uwzględnienia
|
||||||
NoTargets: Nie zdefiniowano celów
|
NoTargets: Nie zdefiniowano celów
|
||||||
|
ResponseIsNotValidJSON: Odpowiedź nie jest prawidłowym JSON-em
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Funkcja „Schemat użytkownika” nie jest włączona
|
NotEnabled: Funkcja „Schemat użytkownika” nie jest włączona
|
||||||
Type:
|
Type:
|
||||||
|
@ -558,6 +558,7 @@ Errors:
|
|||||||
NotFound: Execução não encontrada
|
NotFound: Execução não encontrada
|
||||||
IncludeNotFound: Incluir não encontrado
|
IncludeNotFound: Incluir não encontrado
|
||||||
NoTargets: Nenhuma meta definida
|
NoTargets: Nenhuma meta definida
|
||||||
|
ResponseIsNotValidJSON: A resposta não é um JSON válido
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: O recurso "Esquema do usuário" não está habilitado
|
NotEnabled: O recurso "Esquema do usuário" não está habilitado
|
||||||
Type:
|
Type:
|
||||||
|
@ -552,6 +552,7 @@ Errors:
|
|||||||
NotFound: Исполнение не найдено
|
NotFound: Исполнение не найдено
|
||||||
IncludeNotFound: Включить не найдено
|
IncludeNotFound: Включить не найдено
|
||||||
NoTargets: Цели не определены
|
NoTargets: Цели не определены
|
||||||
|
ResponseIsNotValidJSON: Ответ не является допустимым JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Функция «Пользовательская схема» не включена
|
NotEnabled: Функция «Пользовательская схема» не включена
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: Exekveringen hittades inte
|
NotFound: Exekveringen hittades inte
|
||||||
IncludeNotFound: Inkluderingen hittades inte
|
IncludeNotFound: Inkluderingen hittades inte
|
||||||
NoTargets: Inga mål definierade
|
NoTargets: Inga mål definierade
|
||||||
|
ResponseIsNotValidJSON: Svaret är inte giltigt JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: Funktionen "Användarschema" är inte aktiverad
|
NotEnabled: Funktionen "Användarschema" är inte aktiverad
|
||||||
Type:
|
Type:
|
||||||
|
@ -563,6 +563,7 @@ Errors:
|
|||||||
NotFound: 未找到执行
|
NotFound: 未找到执行
|
||||||
IncludeNotFound: 包括未找到的内容
|
IncludeNotFound: 包括未找到的内容
|
||||||
NoTargets: 没有定义目标
|
NoTargets: 没有定义目标
|
||||||
|
ResponseIsNotValidJSON: 响应不是有效的 JSON
|
||||||
UserSchema:
|
UserSchema:
|
||||||
NotEnabled: 未启用“用户架构”功能
|
NotEnabled: 未启用“用户架构”功能
|
||||||
Type:
|
Type:
|
||||||
|
@ -1,612 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package zitadel.action.v3alpha;
|
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
|
||||||
import "google/api/field_behavior.proto";
|
|
||||||
import "google/protobuf/duration.proto";
|
|
||||||
import "google/protobuf/struct.proto";
|
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
|
||||||
import "validate/validate.proto";
|
|
||||||
import "zitadel/action/v3alpha/target.proto";
|
|
||||||
import "zitadel/action/v3alpha/execution.proto";
|
|
||||||
import "zitadel/action/v3alpha/query.proto";
|
|
||||||
import "zitadel/object/v2/object.proto";
|
|
||||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
|
||||||
info: {
|
|
||||||
title: "Action Service";
|
|
||||||
version: "3.0-preview";
|
|
||||||
description: "This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance. This project is in preview state. It can AND will continue breaking until the services provide the same functionality as the current actions.";
|
|
||||||
contact:{
|
|
||||||
name: "ZITADEL"
|
|
||||||
url: "https://zitadel.com"
|
|
||||||
email: "hi@zitadel.com"
|
|
||||||
}
|
|
||||||
license: {
|
|
||||||
name: "Apache 2.0",
|
|
||||||
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
schemes: HTTPS;
|
|
||||||
schemes: HTTP;
|
|
||||||
|
|
||||||
consumes: "application/json";
|
|
||||||
consumes: "application/grpc";
|
|
||||||
|
|
||||||
produces: "application/json";
|
|
||||||
produces: "application/grpc";
|
|
||||||
|
|
||||||
consumes: "application/grpc-web+proto";
|
|
||||||
produces: "application/grpc-web+proto";
|
|
||||||
|
|
||||||
host: "$CUSTOM-DOMAIN";
|
|
||||||
base_path: "/";
|
|
||||||
|
|
||||||
external_docs: {
|
|
||||||
description: "Detailed information about ZITADEL",
|
|
||||||
url: "https://zitadel.com/docs"
|
|
||||||
}
|
|
||||||
security_definitions: {
|
|
||||||
security: {
|
|
||||||
key: "OAuth2";
|
|
||||||
value: {
|
|
||||||
type: TYPE_OAUTH2;
|
|
||||||
flow: FLOW_ACCESS_CODE;
|
|
||||||
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
|
|
||||||
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
|
|
||||||
scopes: {
|
|
||||||
scope: {
|
|
||||||
key: "openid";
|
|
||||||
value: "openid";
|
|
||||||
}
|
|
||||||
scope: {
|
|
||||||
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
|
||||||
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
security: {
|
|
||||||
security_requirement: {
|
|
||||||
key: "OAuth2";
|
|
||||||
value: {
|
|
||||||
scope: "openid";
|
|
||||||
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responses: {
|
|
||||||
key: "403";
|
|
||||||
value: {
|
|
||||||
description: "Returned when the user does not have permission to access the resource.";
|
|
||||||
schema: {
|
|
||||||
json_schema: {
|
|
||||||
ref: "#/definitions/rpcStatus";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responses: {
|
|
||||||
key: "404";
|
|
||||||
value: {
|
|
||||||
description: "Returned when the resource does not exist.";
|
|
||||||
schema: {
|
|
||||||
json_schema: {
|
|
||||||
ref: "#/definitions/rpcStatus";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
service ActionService {
|
|
||||||
|
|
||||||
// Create a target
|
|
||||||
//
|
|
||||||
// Create a new target, which can be used in executions.
|
|
||||||
rpc CreateTarget (CreateTargetRequest) returns (CreateTargetResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
post: "/v3alpha/targets"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.target.write"
|
|
||||||
}
|
|
||||||
http_response: {
|
|
||||||
success_code: 201
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "201";
|
|
||||||
value: {
|
|
||||||
description: "Target successfully created";
|
|
||||||
schema: {
|
|
||||||
json_schema: {
|
|
||||||
ref: "#/definitions/v3alphaCreateTargetResponse";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update a target
|
|
||||||
//
|
|
||||||
// Update an existing target.
|
|
||||||
rpc UpdateTarget (UpdateTargetRequest) returns (UpdateTargetResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
put: "/v3alpha/targets/{target_id}"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.target.write"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "Target successfully updated";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a target
|
|
||||||
//
|
|
||||||
// Delete an existing target. This will remove it from any configured execution as well.
|
|
||||||
rpc DeleteTarget (DeleteTargetRequest) returns (DeleteTargetResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
delete: "/v3alpha/targets/{target_id}"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.target.delete"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "Target successfully deleted";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// List targets
|
|
||||||
//
|
|
||||||
// List all matching targets. By default, we will return all targets of your instance.
|
|
||||||
// Make sure to include a limit and sorting for pagination.
|
|
||||||
rpc ListTargets (ListTargetsRequest) returns (ListTargetsResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
post: "/v3alpha/targets/search"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.target.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "A list of all targets matching the query";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
key: "400";
|
|
||||||
value: {
|
|
||||||
description: "invalid list query";
|
|
||||||
schema: {
|
|
||||||
json_schema: {
|
|
||||||
ref: "#/definitions/rpcStatus";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target by ID
|
|
||||||
//
|
|
||||||
// Returns the target identified by the requested ID.
|
|
||||||
rpc GetTargetByID (GetTargetByIDRequest) returns (GetTargetByIDResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
get: "/v3alpha/targets/{target_id}"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.target.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200"
|
|
||||||
value: {
|
|
||||||
description: "Target successfully retrieved";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set an execution
|
|
||||||
//
|
|
||||||
// Set an execution to call a previously defined target or include the targets of a previously defined execution.
|
|
||||||
rpc SetExecution (SetExecutionRequest) returns (SetExecutionResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
put: "/v3alpha/executions"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.write"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "Execution successfully set";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete an execution
|
|
||||||
//
|
|
||||||
// Delete an existing execution.
|
|
||||||
rpc DeleteExecution (DeleteExecutionRequest) returns (DeleteExecutionResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
delete: "/v3alpha/executions"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.delete"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "Execution successfully deleted";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// List executions
|
|
||||||
//
|
|
||||||
// List all matching executions. By default, we will return all executions of your instance.
|
|
||||||
// Make sure to include a limit and sorting for pagination.
|
|
||||||
rpc ListExecutions (ListExecutionsRequest) returns (ListExecutionsResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
post: "/v3alpha/executions/search"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "A list of all executions matching the query";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
key: "400";
|
|
||||||
value: {
|
|
||||||
description: "invalid list query";
|
|
||||||
schema: {
|
|
||||||
json_schema: {
|
|
||||||
ref: "#/definitions/rpcStatus";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all available functions
|
|
||||||
//
|
|
||||||
// List all available functions which can be used as condition for executions.
|
|
||||||
rpc ListExecutionFunctions (ListExecutionFunctionsRequest) returns (ListExecutionFunctionsResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
get: "/v3alpha/executions/functions"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "List all functions successfully";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// List all available methods
|
|
||||||
//
|
|
||||||
// List all available methods which can be used as condition for executions.
|
|
||||||
rpc ListExecutionMethods (ListExecutionMethodsRequest) returns (ListExecutionMethodsResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
get: "/v3alpha/executions/methods"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "List all methods successfully";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// List all available service
|
|
||||||
//
|
|
||||||
// List all available services which can be used as condition for executions.
|
|
||||||
rpc ListExecutionServices (ListExecutionServicesRequest) returns (ListExecutionServicesResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
get: "/v3alpha/executions/services"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
|
||||||
auth_option: {
|
|
||||||
permission: "execution.read"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
responses: {
|
|
||||||
key: "200";
|
|
||||||
value: {
|
|
||||||
description: "List all services successfully";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message CreateTargetRequest {
|
|
||||||
// Unique name of the target.
|
|
||||||
string name = 1 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 200,
|
|
||||||
example: "\"ip_allow_list\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Defines the target type and how the response of the target is treated.
|
|
||||||
oneof target_type {
|
|
||||||
option (validate.required) = true;
|
|
||||||
|
|
||||||
SetRESTWebhook rest_webhook = 2;
|
|
||||||
SetRESTCall rest_call = 3;
|
|
||||||
SetRESTAsync rest_async = 4;
|
|
||||||
}
|
|
||||||
// Timeout defines the duration until ZITADEL cancels the execution.
|
|
||||||
google.protobuf.Duration timeout = 5 [
|
|
||||||
(validate.rules).duration = {gt: {seconds: 0}, required: true},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"10s\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
string endpoint = 6 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000, uri: true},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 1000,
|
|
||||||
example: "\"https://example.com/hooks/ip_check\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message CreateTargetResponse {
|
|
||||||
// ID is the read-only unique identifier of the target.
|
|
||||||
string id = 1;
|
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
|
||||||
zitadel.object.v2.Details details = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateTargetRequest {
|
|
||||||
// unique identifier of the target.
|
|
||||||
string target_id = 1 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 200,
|
|
||||||
example: "\"69629026806489455\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Optionally change the unique name of the target.
|
|
||||||
optional string name = 2 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 200,
|
|
||||||
example: "\"ip_allow_list\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Optionally change the target type and how the response of the target is treated,
|
|
||||||
// or its target URL.
|
|
||||||
oneof target_type {
|
|
||||||
SetRESTWebhook rest_webhook = 3;
|
|
||||||
SetRESTCall rest_call = 4;
|
|
||||||
SetRESTAsync rest_async = 5;
|
|
||||||
}
|
|
||||||
// Optionally change the timeout, which defines the duration until ZITADEL cancels the execution.
|
|
||||||
optional google.protobuf.Duration timeout = 6 [
|
|
||||||
(validate.rules).duration = {gt: {seconds: 0}},
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"10s\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
optional string endpoint = 7 [
|
|
||||||
(validate.rules).string = {max_len: 1000, uri: true},
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
max_length: 1000,
|
|
||||||
example: "\"https://example.com/hooks/ip_check\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateTargetResponse {
|
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
|
||||||
zitadel.object.v2.Details details = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteTargetRequest {
|
|
||||||
// unique identifier of the target.
|
|
||||||
string target_id = 1 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 200,
|
|
||||||
example: "\"69629026806489455\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteTargetResponse {
|
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
|
||||||
zitadel.object.v2.Details details = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListTargetsRequest {
|
|
||||||
// list limitations and ordering.
|
|
||||||
zitadel.object.v2.ListQuery query = 1;
|
|
||||||
// the field the result is sorted.
|
|
||||||
zitadel.action.v3alpha.TargetFieldName sorting_column = 2 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"FIELD_NAME_SCHEMA_TYPE\""
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Define the criteria to query for.
|
|
||||||
repeated zitadel.action.v3alpha.TargetSearchQuery queries = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListTargetsResponse {
|
|
||||||
// Details provides information about the returned result including total amount found.
|
|
||||||
zitadel.object.v2.ListDetails details = 1;
|
|
||||||
// States by which field the results are sorted.
|
|
||||||
zitadel.action.v3alpha.TargetFieldName sorting_column = 2;
|
|
||||||
// The result contains the user schemas, which matched the queries.
|
|
||||||
repeated zitadel.action.v3alpha.Target result = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetTargetByIDRequest {
|
|
||||||
// unique identifier of the target.
|
|
||||||
string target_id = 1 [
|
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
|
||||||
(google.api.field_behavior) = REQUIRED,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
min_length: 1,
|
|
||||||
max_length: 200,
|
|
||||||
example: "\"69629026806489455\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetTargetByIDResponse {
|
|
||||||
zitadel.action.v3alpha.Target target = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetExecutionRequest {
|
|
||||||
// Defines the condition type and content of the condition for execution.
|
|
||||||
Condition condition = 1;
|
|
||||||
// Ordered list of targets/includes called during the execution.
|
|
||||||
repeated zitadel.action.v3alpha.ExecutionTargetType targets = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetExecutionResponse {
|
|
||||||
// Details provide some base information (such as the last change date) of the execution.
|
|
||||||
zitadel.object.v2.Details details = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteExecutionRequest {
|
|
||||||
// Unique identifier of the execution.
|
|
||||||
Condition condition = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteExecutionResponse {
|
|
||||||
// Details provide some base information (such as the last change date) of the execution.
|
|
||||||
zitadel.object.v2.Details details = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListExecutionsRequest {
|
|
||||||
// list limitations and ordering.
|
|
||||||
zitadel.object.v2.ListQuery query = 1;
|
|
||||||
// Define the criteria to query for.
|
|
||||||
repeated zitadel.action.v3alpha.SearchQuery queries = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListExecutionsResponse {
|
|
||||||
// Details provides information about the returned result including total amount found.
|
|
||||||
zitadel.object.v2.ListDetails details = 1;
|
|
||||||
// The result contains the executions, which matched the queries.
|
|
||||||
repeated zitadel.action.v3alpha.Execution result = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListExecutionFunctionsRequest{}
|
|
||||||
message ListExecutionFunctionsResponse{
|
|
||||||
// All available methods
|
|
||||||
repeated string functions = 1;
|
|
||||||
}
|
|
||||||
message ListExecutionMethodsRequest{}
|
|
||||||
message ListExecutionMethodsResponse{
|
|
||||||
// All available methods
|
|
||||||
repeated string methods = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListExecutionServicesRequest{}
|
|
||||||
message ListExecutionServicesResponse{
|
|
||||||
// All available methods
|
|
||||||
repeated string services = 1;
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package zitadel.action.v3alpha;
|
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
|
||||||
|
|
||||||
import "google/api/field_behavior.proto";
|
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
|
||||||
import "validate/validate.proto";
|
|
||||||
import "zitadel/object/v2/object.proto";
|
|
||||||
import "zitadel/action/v3alpha/execution.proto";
|
|
||||||
|
|
||||||
message SearchQuery {
|
|
||||||
oneof query {
|
|
||||||
option (validate.required) = true;
|
|
||||||
|
|
||||||
InConditionsQuery in_conditions_query = 1;
|
|
||||||
ExecutionTypeQuery execution_type_query = 2;
|
|
||||||
TargetQuery target_query = 3;
|
|
||||||
IncludeQuery include_query = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message InConditionsQuery {
|
|
||||||
// Defines the conditions to query for.
|
|
||||||
repeated Condition conditions = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ExecutionTypeQuery {
|
|
||||||
// Defines the type to query for.
|
|
||||||
ExecutionType execution_type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TargetQuery {
|
|
||||||
// Defines the id to query for.
|
|
||||||
string target_id = 1 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
description: "the id of the targets to include"
|
|
||||||
example: "\"69629023906488334\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message IncludeQuery {
|
|
||||||
// Defines the include to query for.
|
|
||||||
Condition include = 1 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
description: "the id of the include"
|
|
||||||
example: "\"request.zitadel.session.v2.SessionService\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message TargetSearchQuery {
|
|
||||||
oneof query {
|
|
||||||
option (validate.required) = true;
|
|
||||||
|
|
||||||
TargetNameQuery target_name_query = 1;
|
|
||||||
InTargetIDsQuery in_target_ids_query = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message TargetNameQuery {
|
|
||||||
// Defines the name of the target to query for.
|
|
||||||
string target_name = 1 [
|
|
||||||
(validate.rules).string = {max_len: 200},
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
max_length: 200;
|
|
||||||
example: "\"ip_allow_list\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Defines which text comparison method used for the name query.
|
|
||||||
zitadel.object.v2.TextQueryMethod method = 2 [
|
|
||||||
(validate.rules).enum.defined_only = true,
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
description: "defines which text equality method is used";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
message InTargetIDsQuery {
|
|
||||||
// Defines the ids to query for.
|
|
||||||
repeated string target_ids = 1 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
description: "the ids of the targets to include"
|
|
||||||
example: "[\"69629023906488334\",\"69622366012355662\"]";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ExecutionType {
|
|
||||||
EXECUTION_TYPE_UNSPECIFIED = 0;
|
|
||||||
EXECUTION_TYPE_REQUEST = 1;
|
|
||||||
EXECUTION_TYPE_RESPONSE = 2;
|
|
||||||
EXECUTION_TYPE_EVENT = 3;
|
|
||||||
EXECUTION_TYPE_FUNCTION = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TargetFieldName {
|
|
||||||
FIELD_NAME_UNSPECIFIED = 0;
|
|
||||||
FIELD_NAME_ID = 1;
|
|
||||||
FIELD_NAME_CREATION_DATE = 2;
|
|
||||||
FIELD_NAME_CHANGE_DATE = 3;
|
|
||||||
FIELD_NAME_NAME = 4;
|
|
||||||
FIELD_NAME_TARGET_TYPE = 5;
|
|
||||||
FIELD_NAME_URL = 6;
|
|
||||||
FIELD_NAME_TIMEOUT = 7;
|
|
||||||
FIELD_NAME_ASYNC = 8;
|
|
||||||
FIELD_NAME_INTERRUPT_ON_ERROR = 9;
|
|
||||||
}
|
|
22
proto/zitadel/object/v3alpha/object.proto
Normal file
22
proto/zitadel/object/v3alpha/object.proto
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.object.v3alpha;
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha;object";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
|
||||||
|
enum OwnerType {
|
||||||
|
OWNER_TYPE_UNSPECIFIED = 0;
|
||||||
|
OWNER_TYPE_SYSTEM = 1;
|
||||||
|
OWNER_TYPE_INSTANCE = 2;
|
||||||
|
OWNER_TYPE_ORG = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Owner {
|
||||||
|
OwnerType type = 1;
|
||||||
|
string id = 2;
|
||||||
|
}
|
||||||
|
|
361
proto/zitadel/resources/action/v3alpha/action_service.proto
Normal file
361
proto/zitadel/resources/action/v3alpha/action_service.proto
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.resources.action.v3alpha;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
|
||||||
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
import "zitadel/resources/action/v3alpha/target.proto";
|
||||||
|
import "zitadel/resources/action/v3alpha/execution.proto";
|
||||||
|
import "zitadel/resources/object/v3alpha/object.proto";
|
||||||
|
import "zitadel/settings/object/v3alpha/object.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||||
|
info: {
|
||||||
|
title: "Action Service";
|
||||||
|
version: "3.0-alpha";
|
||||||
|
description: "This API is intended to manage custom executions (previously known as actions) in a ZITADEL instance. It will continue breaking as long as it is in alpha state.";
|
||||||
|
contact:{
|
||||||
|
name: "ZITADEL"
|
||||||
|
url: "https://zitadel.com"
|
||||||
|
email: "hi@zitadel.com"
|
||||||
|
}
|
||||||
|
license: {
|
||||||
|
name: "Apache 2.0",
|
||||||
|
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
schemes: HTTPS;
|
||||||
|
schemes: HTTP;
|
||||||
|
|
||||||
|
consumes: "application/json";
|
||||||
|
consumes: "application/grpc";
|
||||||
|
|
||||||
|
produces: "application/json";
|
||||||
|
produces: "application/grpc";
|
||||||
|
|
||||||
|
consumes: "application/grpc-web+proto";
|
||||||
|
produces: "application/grpc-web+proto";
|
||||||
|
|
||||||
|
host: "$ZITADEL_DOMAIN";
|
||||||
|
base_path: "/resources/v3alpha/actions";
|
||||||
|
|
||||||
|
external_docs: {
|
||||||
|
description: "Detailed information about ZITADEL",
|
||||||
|
url: "https://zitadel.com/docs"
|
||||||
|
}
|
||||||
|
security_definitions: {
|
||||||
|
security: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
type: TYPE_OAUTH2;
|
||||||
|
flow: FLOW_ACCESS_CODE;
|
||||||
|
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
|
||||||
|
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
|
||||||
|
scopes: {
|
||||||
|
scope: {
|
||||||
|
key: "openid";
|
||||||
|
value: "openid";
|
||||||
|
}
|
||||||
|
scope: {
|
||||||
|
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
security: {
|
||||||
|
security_requirement: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
scope: "openid";
|
||||||
|
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "403";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the user does not have permission to access the resource.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "404";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the resource does not exist.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
service ZITADELActions {
|
||||||
|
|
||||||
|
// Create a target
|
||||||
|
//
|
||||||
|
// Create a new target, which can be used in executions.
|
||||||
|
rpc CreateTarget (CreateTargetRequest) returns (CreateTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/targets"
|
||||||
|
body: "target"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "action.target.write"
|
||||||
|
}
|
||||||
|
http_response: {
|
||||||
|
success_code: 201
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "201";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully created";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/CreateTargetResponse";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch a target
|
||||||
|
//
|
||||||
|
// Patch an existing target.
|
||||||
|
rpc PatchTarget (PatchTargetRequest) returns (PatchTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
patch: "/targets/{id}"
|
||||||
|
body: "target"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "action.target.write"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully updated or left unchanged";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a target
|
||||||
|
//
|
||||||
|
// Delete an existing target. This will remove it from any configured execution as well.
|
||||||
|
rpc DeleteTarget (DeleteTargetRequest) returns (DeleteTargetResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/targets/{id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "action.target.delete"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "Target successfully deleted";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets an execution to call a target or include the targets of another execution.
|
||||||
|
//
|
||||||
|
// Setting an empty list of targets will remove all targets from the execution, making it a noop.
|
||||||
|
rpc SetExecution (SetExecutionRequest) returns (SetExecutionResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
put: "/executions"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "action.execution.write"
|
||||||
|
}
|
||||||
|
http_response: {
|
||||||
|
success_code: 201
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "Execution successfully updated or left unchanged";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/SetExecutionResponse";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all available functions
|
||||||
|
//
|
||||||
|
// List all available functions which can be used as condition for executions.
|
||||||
|
rpc ListExecutionFunctions (ListExecutionFunctionsRequest) returns (ListExecutionFunctionsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/executions/functions"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "List all functions successfully";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// List all available methods
|
||||||
|
//
|
||||||
|
// List all available methods which can be used as condition for executions.
|
||||||
|
rpc ListExecutionMethods (ListExecutionMethodsRequest) returns (ListExecutionMethodsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/executions/methods"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "List all methods successfully";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// List all available service
|
||||||
|
//
|
||||||
|
// List all available services which can be used as condition for executions.
|
||||||
|
rpc ListExecutionServices (ListExecutionServicesRequest) returns (ListExecutionServicesResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/executions/services"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "List all services successfully";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateTargetRequest {
|
||||||
|
Target target = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateTargetResponse {
|
||||||
|
zitadel.resources.object.v3alpha.Details details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PatchTargetRequest {
|
||||||
|
string id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1,
|
||||||
|
max_length: 200,
|
||||||
|
example: "\"69629026806489455\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
PatchTarget target = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PatchTargetResponse {
|
||||||
|
zitadel.resources.object.v3alpha.Details details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteTargetRequest {
|
||||||
|
string id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1,
|
||||||
|
max_length: 200,
|
||||||
|
example: "\"69629026806489455\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteTargetResponse {
|
||||||
|
zitadel.resources.object.v3alpha.Details details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetExecutionRequest {
|
||||||
|
Condition condition = 1;
|
||||||
|
Execution execution = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetExecutionResponse {
|
||||||
|
zitadel.settings.object.v3alpha.Details details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListExecutionFunctionsRequest{}
|
||||||
|
message ListExecutionFunctionsResponse{
|
||||||
|
// All available methods
|
||||||
|
repeated string functions = 1;
|
||||||
|
}
|
||||||
|
message ListExecutionMethodsRequest{}
|
||||||
|
message ListExecutionMethodsResponse{
|
||||||
|
// All available methods
|
||||||
|
repeated string methods = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListExecutionServicesRequest{}
|
||||||
|
message ListExecutionServicesResponse{
|
||||||
|
// All available methods
|
||||||
|
repeated string services = 1;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package zitadel.action.v3alpha;
|
package zitadel.resources.action.v3alpha;
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/api/field_behavior.proto";
|
import "google/api/field_behavior.proto";
|
||||||
@ -8,21 +8,22 @@ import "google/protobuf/duration.proto";
|
|||||||
import "google/protobuf/struct.proto";
|
import "google/protobuf/struct.proto";
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
import "zitadel/object/v2/object.proto";
|
|
||||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
import "zitadel/resources/object/v3alpha/object.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zitadel/object/v3alpha/object.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
|
||||||
|
|
||||||
message Execution {
|
message Execution {
|
||||||
Condition Condition = 1;
|
// Ordered list of targets/includes called during the execution.
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
repeated ExecutionTargetType targets = 1;
|
||||||
zitadel.object.v2.Details details = 2;
|
|
||||||
// List of ordered list of targets/includes called during the execution.
|
|
||||||
repeated ExecutionTargetType targets = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ExecutionTargetType {
|
message ExecutionTargetType {
|
||||||
oneof type {
|
oneof type {
|
||||||
|
option (validate.required) = true;
|
||||||
// Unique identifier of existing target to call.
|
// Unique identifier of existing target to call.
|
||||||
string target = 1;
|
string target = 1;
|
||||||
// Unique identifier of existing execution to include targets of.
|
// Unique identifier of existing execution to include targets of.
|
||||||
@ -47,8 +48,9 @@ message Condition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message RequestExecution {
|
message RequestExecution {
|
||||||
// Condition for the request execution, only one possible.
|
// Condition for the request execution. Only one is possible.
|
||||||
oneof condition{
|
oneof condition{
|
||||||
|
option (validate.required) = true;
|
||||||
// GRPC-method as condition.
|
// GRPC-method as condition.
|
||||||
string method = 1 [
|
string method = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||||
@ -67,14 +69,15 @@ message RequestExecution {
|
|||||||
example: "\"zitadel.session.v2.SessionService\"";
|
example: "\"zitadel.session.v2.SessionService\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
// All calls to any available service and endpoint as condition.
|
// All calls to any available services and methods as condition.
|
||||||
bool all = 3;
|
bool all = 3 [(validate.rules).bool = {const: true}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResponseExecution {
|
message ResponseExecution {
|
||||||
// Condition for the response execution, only one possible.
|
// Condition for the response execution. Only one is possible.
|
||||||
oneof condition{
|
oneof condition{
|
||||||
|
option (validate.required) = true;
|
||||||
// GRPC-method as condition.
|
// GRPC-method as condition.
|
||||||
string method = 1 [
|
string method = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||||
@ -93,8 +96,8 @@ message ResponseExecution {
|
|||||||
example: "\"zitadel.session.v2.SessionService\"";
|
example: "\"zitadel.session.v2.SessionService\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
// All calls to any available service and endpoint as condition.
|
// All calls to any available services and methods as condition.
|
||||||
bool all = 3;
|
bool all = 3 [(validate.rules).bool = {const: true}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,9 +106,10 @@ message FunctionExecution {
|
|||||||
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 1000}];
|
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 1000}];
|
||||||
}
|
}
|
||||||
|
|
||||||
message EventExecution{
|
message EventExecution {
|
||||||
// Condition for the event execution, only one possible.
|
// Condition for the event execution. Only one is possible.
|
||||||
oneof condition{
|
oneof condition{
|
||||||
|
option (validate.required) = true;
|
||||||
// Event name as condition.
|
// Event name as condition.
|
||||||
string event = 1 [
|
string event = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 1000},
|
(validate.rules).string = {min_len: 1, max_len: 1000},
|
||||||
@ -125,7 +129,6 @@ message EventExecution{
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
// all events as condition.
|
// all events as condition.
|
||||||
bool all = 3;
|
bool all = 3 [(validate.rules).bool = {const: true}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package zitadel.action.v3alpha;
|
package zitadel.resources.action.v3alpha;
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/api/field_behavior.proto";
|
import "google/api/field_behavior.proto";
|
||||||
@ -8,10 +8,61 @@ import "google/protobuf/duration.proto";
|
|||||||
import "google/protobuf/struct.proto";
|
import "google/protobuf/struct.proto";
|
||||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
import "zitadel/object/v2/object.proto";
|
|
||||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
|
||||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v3alpha;action";
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha;action";
|
||||||
|
|
||||||
|
message Target {
|
||||||
|
string name = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"ip_allow_list\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Defines the target type and how the response of the target is treated.
|
||||||
|
oneof target_type {
|
||||||
|
option (validate.required) = true;
|
||||||
|
SetRESTWebhook rest_webhook = 2;
|
||||||
|
SetRESTCall rest_call = 3;
|
||||||
|
SetRESTAsync rest_async = 4;
|
||||||
|
}
|
||||||
|
// Timeout defines the duration until ZITADEL cancels the execution.
|
||||||
|
google.protobuf.Duration timeout = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"10s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string endpoint = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message PatchTarget {
|
||||||
|
optional string name = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"ip_allow_list\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Defines the target type and how the response of the target is treated.
|
||||||
|
oneof target_type {
|
||||||
|
SetRESTWebhook rest_webhook = 2;
|
||||||
|
SetRESTCall rest_call = 3;
|
||||||
|
SetRESTAsync rest_async = 4;
|
||||||
|
}
|
||||||
|
// Timeout defines the duration until ZITADEL cancels the execution.
|
||||||
|
optional google.protobuf.Duration timeout = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"10s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional string endpoint = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"https://example.com/hooks/ip_check\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Wait for response but response body is ignored, status is checked, call is sent as post.
|
// Wait for response but response body is ignored, status is checked, call is sent as post.
|
||||||
message SetRESTWebhook {
|
message SetRESTWebhook {
|
||||||
@ -27,39 +78,3 @@ message SetRESTCall {
|
|||||||
|
|
||||||
// Call is executed in parallel to others, ZITADEL does not wait until the call is finished. The state is ignored, call is sent as post.
|
// Call is executed in parallel to others, ZITADEL does not wait until the call is finished. The state is ignored, call is sent as post.
|
||||||
message SetRESTAsync {}
|
message SetRESTAsync {}
|
||||||
|
|
||||||
message Target {
|
|
||||||
// ID is the read-only unique identifier of the target.
|
|
||||||
string target_id = 1 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"69629012906488334\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Details provide some base information (such as the last change date) of the target.
|
|
||||||
zitadel.object.v2.Details details = 2;
|
|
||||||
|
|
||||||
// Unique name of the target.
|
|
||||||
string name = 3 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"ip_allow_list\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Defines the target type and how the response of the target is treated.
|
|
||||||
oneof target_type {
|
|
||||||
SetRESTWebhook rest_webhook = 4;
|
|
||||||
SetRESTCall rest_call = 5;
|
|
||||||
SetRESTAsync rest_async = 6;
|
|
||||||
}
|
|
||||||
// Timeout defines the duration until ZITADEL cancels the execution.
|
|
||||||
google.protobuf.Duration timeout = 7 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"10s\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
string endpoint = 8 [
|
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
||||||
example: "\"https://example.com/hooks/ip_check\"";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
43
proto/zitadel/resources/object/v3alpha/object.proto
Normal file
43
proto/zitadel/resources/object/v3alpha/object.proto
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.resources.object.v3alpha;
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha;object";
|
||||||
|
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
|
||||||
|
import "zitadel/object/v3alpha/object.proto";
|
||||||
|
|
||||||
|
message Details {
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629012906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
//sequence represents the order of events. It's always counting
|
||||||
|
//
|
||||||
|
// on read: the sequence of the last event reduced by the projection
|
||||||
|
//
|
||||||
|
// on manipulation: the timestamp of the event(s) added by the manipulation
|
||||||
|
uint64 sequence = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
//change_date is the timestamp when the object was changed
|
||||||
|
//
|
||||||
|
// on read: the timestamp of the last event reduced by the projection
|
||||||
|
//
|
||||||
|
// on manipulation: the timestamp of the event(s) added by the manipulation
|
||||||
|
google.protobuf.Timestamp change_date = 3;
|
||||||
|
//resource_owner represents the context an object belongs to
|
||||||
|
zitadel.object.v3alpha.Owner owner = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
38
proto/zitadel/settings/object/v3alpha/object.proto
Normal file
38
proto/zitadel/settings/object/v3alpha/object.proto
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.settings.object.v3alpha;
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/object/v3alpha;object";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
import "zitadel/object/v3alpha/object.proto";
|
||||||
|
|
||||||
|
message Details {
|
||||||
|
//sequence represents the order of events. It's always counting
|
||||||
|
//
|
||||||
|
// on read: the sequence of the last event reduced by the projection
|
||||||
|
//
|
||||||
|
// on manipulation: the timestamp of the event(s) added by the manipulation
|
||||||
|
uint64 sequence = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
//change_date is the timestamp when the object was changed
|
||||||
|
//
|
||||||
|
// on read: the timestamp of the last event reduced by the projection
|
||||||
|
//
|
||||||
|
// on manipulation: the timestamp of the event(s) added by the manipulation
|
||||||
|
google.protobuf.Timestamp change_date = 2;
|
||||||
|
//resource_owner represents the context an object belongs to
|
||||||
|
zitadel.object.v3alpha.Owner owner = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user