fix: remove action feature flag and include execution (#9727)

# Which Problems Are Solved

Actions v2 is not a feature flag anymore, include functionality on
executions is not used and json tags of proto messages are handled
incorrectly.

# How the Problems Are Solved

- Remove actions from the feature flags on system and instance level
- Remove include type on executions, only in the API, later maybe in the
handling logic as well
- Use protojson in request and response handling of actions v2

# Additional Changes

- Correct integration tests for request and response handling
- Use json.RawMessage for events, so that the event payload is not
base64 encoded
- Added separate context for async webhook calls, that executions are
not cancelled when called async

# Additional Context

Related to #9759 
Closes #9710

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2025-04-28 11:24:50 +02:00
committed by GitHub
parent 84628671bd
commit b8ba7bd5ba
55 changed files with 427 additions and 799 deletions

View File

@@ -5,12 +5,13 @@ import (
"encoding/json"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/execution"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor {
@@ -48,7 +49,7 @@ func executeTargetsForRequest(ctx context.Context, targets []execution.Target, f
ProjectID: ctxData.ProjectID,
OrgID: ctxData.OrgID,
UserID: ctxData.UserID,
Request: req,
Request: Message{req.(proto.Message)},
}
return execution.CallTargets(ctx, targets, info)
@@ -70,8 +71,8 @@ func executeTargetsForResponse(ctx context.Context, targets []execution.Target,
ProjectID: ctxData.ProjectID,
OrgID: ctxData.OrgID,
UserID: ctxData.UserID,
Request: req,
Response: resp,
Request: Message{req.(proto.Message)},
Response: Message{resp.(proto.Message)},
}
return execution.CallTargets(ctx, targets, info)
@@ -80,12 +81,28 @@ func executeTargetsForResponse(ctx context.Context, targets []execution.Target,
var _ execution.ContextInfo = &ContextInfoRequest{}
type ContextInfoRequest struct {
FullMethod string `json:"fullMethod,omitempty"`
InstanceID string `json:"instanceID,omitempty"`
OrgID string `json:"orgID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
UserID string `json:"userID,omitempty"`
Request interface{} `json:"request,omitempty"`
FullMethod string `json:"fullMethod,omitempty"`
InstanceID string `json:"instanceID,omitempty"`
OrgID string `json:"orgID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
UserID string `json:"userID,omitempty"`
Request Message `json:"request,omitempty"`
}
type Message struct {
proto.Message
}
func (r *Message) MarshalJSON() ([]byte, error) {
data, err := protojson.Marshal(r.Message)
if err != nil {
return nil, err
}
return data, nil
}
func (r *Message) UnmarshalJSON(data []byte) error {
return protojson.Unmarshal(data, r.Message)
}
func (c *ContextInfoRequest) GetHTTPRequestBody() []byte {
@@ -97,26 +114,23 @@ func (c *ContextInfoRequest) GetHTTPRequestBody() []byte {
}
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)
}
func (c *ContextInfoRequest) GetContent() interface{} {
return c.Request
return c.Request.Message
}
var _ execution.ContextInfo = &ContextInfoResponse{}
type ContextInfoResponse struct {
FullMethod string `json:"fullMethod,omitempty"`
InstanceID string `json:"instanceID,omitempty"`
OrgID string `json:"orgID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
UserID string `json:"userID,omitempty"`
Request interface{} `json:"request,omitempty"`
Response interface{} `json:"response,omitempty"`
FullMethod string `json:"fullMethod,omitempty"`
InstanceID string `json:"instanceID,omitempty"`
OrgID string `json:"orgID,omitempty"`
ProjectID string `json:"projectID,omitempty"`
UserID string `json:"userID,omitempty"`
Request Message `json:"request,omitempty"`
Response Message `json:"response,omitempty"`
}
func (c *ContextInfoResponse) GetHTTPRequestBody() []byte {
@@ -128,9 +142,9 @@ func (c *ContextInfoResponse) GetHTTPRequestBody() []byte {
}
func (c *ContextInfoResponse) SetHTTPResponseBody(resp []byte) error {
return json.Unmarshal(resp, c.Response)
return json.Unmarshal(resp, &c.Response)
}
func (c *ContextInfoResponse) GetContent() interface{} {
return c.Response
return c.Response.Message
}

View File

@@ -11,6 +11,9 @@ import (
"time"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/execution"
@@ -54,28 +57,28 @@ func (e *mockExecutionTarget) GetSigningKey() string {
return e.SigningKey
}
type mockContentRequest struct {
Content string
}
func newMockContentRequest(content string) *mockContentRequest {
return &mockContentRequest{
Content: content,
func newMockContentRequest(content string) proto.Message {
return &structpb.Struct{
Fields: map[string]*structpb.Value{
"content": {
Kind: &structpb.Value_StringValue{StringValue: content},
},
},
}
}
func newMockContextInfoRequest(fullMethod, request string) *ContextInfoRequest {
return &ContextInfoRequest{
FullMethod: fullMethod,
Request: newMockContentRequest(request),
Request: Message{Message: newMockContentRequest(request)},
}
}
func newMockContextInfoResponse(fullMethod, request, response string) *ContextInfoResponse {
return &ContextInfoResponse{
FullMethod: fullMethod,
Request: newMockContentRequest(request),
Response: newMockContentRequest(response),
Request: Message{Message: newMockContentRequest(request)},
Response: Message{Message: newMockContentRequest(response)},
}
}
@@ -591,7 +594,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.res.want, resp)
assert.EqualExportedValues(t, tt.res.want, resp)
for _, closeF := range closeFuncs {
closeF()
@@ -632,7 +635,7 @@ func testServerCall(
time.Sleep(sleep)
w.Header().Set("Content-Type", "application/json")
resp, err := json.Marshal(respBody)
resp, err := protojson.Marshal(respBody.(proto.Message))
if err != nil {
http.Error(w, "error", http.StatusInternalServerError)
return
@@ -723,7 +726,8 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
statusCode: http.StatusOK,
},
},
req: []byte{},
req: newMockContentRequest(""),
resp: newMockContentRequest(""),
},
res{
wantErr: true,
@@ -790,7 +794,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.res.want, resp)
assert.EqualExportedValues(t, tt.res.want, resp)
for _, closeF := range closeFuncs {
closeF()