From 51e12e224db81c70a8a63e0f9337816c386b25b4 Mon Sep 17 00:00:00 2001 From: Gayathri Vijayan <66356931+grvijayan@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:50:52 +0200 Subject: [PATCH] feat(actionsv2): Propagate request headers in actions v2 (#10632) # Which Problems Are Solved This PR adds functionality to propagate request headers in actions v2. # How the Problems Are Solved The new functionality is added to the`ExecutionHandler` interceptors, where the incoming request headers (from a list of allowed headers to be forwarded) are set in the payload of the request before calling the target. # Additional Changes This PR also contains minor fixes to the Actions V2 example docs. # Additional Context - Closes #9941 --------- Co-authored-by: Marco A. --- .../guides/integrate/actions/testing-event.md | 65 ++++++++------ .../actions/testing-request-manipulation.md | 50 +++++++---- .../actions/testing-request-signature.md | 78 +++++++++++----- .../integrate/actions/testing-request.md | 81 ++++++++++++----- .../integrate/actions/testing-response.md | 88 +++++++++++++------ .../integration_test/execution_target_test.go | 11 ++- .../integration_test/execution_target_test.go | 11 ++- .../execution_interceptor.go | 54 +++++++++--- .../execution_interceptor_test.go | 27 ++++++ .../middleware/execution_interceptor.go | 35 +++++--- internal/api/http/header.go | 1 + 11 files changed, 363 insertions(+), 138 deletions(-) diff --git a/docs/docs/guides/integrate/actions/testing-event.md b/docs/docs/guides/integrate/actions/testing-event.md index 7c8bdd8c2ba..bddaf9197fc 100644 --- a/docs/docs/guides/integrate/actions/testing-event.md +++ b/docs/docs/guides/integrate/actions/testing-event.md @@ -114,17 +114,27 @@ Now that you have set up the target and execution, you can test it by creating a by calling the ZITADEL API to create a human user. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "userId": { - "givenName": "Test", - "familyName": "User" - }, - "email": { - "email": "example@test.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }' ``` @@ -134,22 +144,25 @@ the [Sent information Event](./usage#sent-information-event) payload description ```json { - "aggregateID": "313014806065971608", + "aggregateID": "336494809936035843", "aggregateType": "user", - "resourceOwner": "312909075211944344", - "instanceID": "312909075211878808", + "resourceOwner": "336392597046099971", + "instanceID": "336392597046034435", "version": "v2", "sequence": 1, "event_type": "user.human.added", - "created_at": "2025-03-27T10:22:43.262665+01:00", - "userID": "312909075212468632", - "event_payload": { - "userName":"example@test.com", - "firstName":"Test", - "lastName":"User", - "displayName":"Test User", - "preferredLanguage":"und", - "email":"example@test.com" + "created_at": "2025-09-05T08:55:36.156333Z", + "userID": "336392597046755331", + "event_payload": + { + "email": "mini@mouse.com", + "gender": 1, + "lastName": "Mouse", + "nickName": "Mini", + "userName": "mini@mouse.com", + "firstName": "Minnie", + "displayName": "Minnie Mouse", + "preferredLanguage": "en" } } ``` @@ -158,12 +171,14 @@ The event_payload is base64 encoded and has the following content: ```json { - "userName": "example@test.com", - "firstName": "Test", - "lastName": "User", - "displayName": "Test User", - "preferredLanguage": "und", - "email": "example@test.com" + "email": "mini@mouse.com", + "gender": 1, + "lastName": "Mouse", + "nickName": "Mini", + "userName": "mini@mouse.com", + "firstName": "Minnie", + "displayName": "Minnie Mouse", + "preferredLanguage": "en" } ``` diff --git a/docs/docs/guides/integrate/actions/testing-request-manipulation.md b/docs/docs/guides/integrate/actions/testing-request-manipulation.md index b10c32d372d..49bc4d937f3 100644 --- a/docs/docs/guides/integrate/actions/testing-request-manipulation.md +++ b/docs/docs/guides/integrate/actions/testing-request-manipulation.md @@ -150,7 +150,7 @@ curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2beta/actions/executions' \ --data-raw '{ "condition": { "request": { - "method": "/zitadel.user.v2.UserService/AddHumanUser" + "method": "/zitadel.user.v2.UserService/CreateUser" } }, "targets": [ @@ -165,17 +165,27 @@ Now that you have set up the target and execution, you can test it by creating a by calling the ZITADEL API to create a human user. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "profile": { - "givenName": "Example_given", - "familyName": "Example_family" - }, - "email": { - "email": "example@example.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }' ``` @@ -184,17 +194,27 @@ Your server should now manipulate the request to something like the following. C the [Sent information Request](./usage#sent-information-request) payload description. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "profile": { - "givenName": "Example_given", - "familyName": "Example_family" - }, - "email": { - "email": "example@example.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } "metadata": [ {"key": "organization", "value": "Y29tcGFueQ=="} diff --git a/docs/docs/guides/integrate/actions/testing-request-signature.md b/docs/docs/guides/integrate/actions/testing-request-signature.md index b3a9f0fa5da..b39b75d7a87 100644 --- a/docs/docs/guides/integrate/actions/testing-request-signature.md +++ b/docs/docs/guides/integrate/actions/testing-request-signature.md @@ -110,7 +110,7 @@ curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2beta/actions/executions' \ --data-raw '{ "condition": { "request": { - "method": "/zitadel.user.v2.UserService/AddHumanUser" + "method": "/zitadel.user.v2.UserService/CreateUser" } }, "targets": [ @@ -125,17 +125,27 @@ Now that you have set up the target and execution, you can test it by creating a by calling the ZITADEL API to create a human user. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "profile": { - "givenName": "Example_given", - "familyName": "Example_family" - }, - "email": { - "email": "example@example.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }' ``` @@ -143,22 +153,48 @@ curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ Your server should now print out something like the following. Check out the [Sent information Request](./usage#sent-information-request) payload description. -```shell +```json { - "fullMethod": "/zitadel.user.v2.UserService/AddHumanUser", - "instanceID": "262851882718855632", - "orgID": "262851882718921168", - "projectID": "262851882719052240", - "userID": "262851882718986704", - "request": { - "profile": { - "given_name": "Example_given", - "family_name": "Example_family" + "fullMethod": "/zitadel.user.v2.UserService/CreateUser", + "instanceID": "336392597046034435", + "orgID": "336392597046099971", + "projectID": "336392597046165507", + "userID": "336392597046755331", + "request": + { + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini1@mouse.com" + } + } }, - "email": { - "email": "example@example.com" + "headers": + { + "Content-Type": + [ + "application/grpc" + ], + "Host": + [ + "localhost:8080" + ], + "X-Forwarded-Host": + [ + "localhost:8080" + ] } - } } ``` diff --git a/docs/docs/guides/integrate/actions/testing-request.md b/docs/docs/guides/integrate/actions/testing-request.md index c99e16cd2fb..6a3f780f09c 100644 --- a/docs/docs/guides/integrate/actions/testing-request.md +++ b/docs/docs/guides/integrate/actions/testing-request.md @@ -103,7 +103,7 @@ curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2beta/actions/executions' \ --data-raw '{ "condition": { "request": { - "method": "/zitadel.user.v2.UserService/AddHumanUser" + "method": "/zitadel.user.v2.UserService/CreateUser" } }, "targets": [ @@ -118,40 +118,77 @@ Now that you have set up the target and execution, you can test it by creating a by calling the ZITADEL API to create a human user. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "profile": { - "givenName": "Test", - "familyName": "User" - }, - "email": { - "email": "example@test.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }' ``` Your server should now print out something like the following. Check out -the [Sent information Request](./usage#sent-information-request) payload description. +the [Sent information Request](./usage#sent-information-request) payload description. +The incoming request headers to the Execution are propagated via the request payload to the target. -```shell +```json { - "fullMethod": "/zitadel.user.v2.UserService/AddHumanUser", - "instanceID": "262851882718855632", - "orgID": "262851882718921168", - "projectID": "262851882719052240", - "userID": "262851882718986704", - "request": { - "profile": { - "given_name": "Test", - "family_name": "User" + "fullMethod": "/zitadel.user.v2.UserService/CreateUser", + "instanceID": "336392597046034435", + "orgID": "336392597046099971", + "projectID": "336392597046165507", + "userID": "336392597046755331", + "request": + { + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini1@mouse.com" + } + } }, - "email": { - "email": "example@test.com" + "headers": + { + "Content-Type": + [ + "application/grpc" + ], + "Host": + [ + "localhost:8080" + ], + "X-Forwarded-Host": + [ + "localhost:8080" + ] } - } } ``` diff --git a/docs/docs/guides/integrate/actions/testing-response.md b/docs/docs/guides/integrate/actions/testing-response.md index aea6ac732ff..4114f9a3a84 100644 --- a/docs/docs/guides/integrate/actions/testing-response.md +++ b/docs/docs/guides/integrate/actions/testing-response.md @@ -103,7 +103,7 @@ curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2beta/actions/executions' \ --data-raw '{ "condition": { "response": { - "method": "/zitadel.user.v2.UserService/AddHumanUser" + "method": "/zitadel.user.v2.UserService/CreateUser" } }, "targets": [ @@ -118,47 +118,81 @@ Now that you have set up the target and execution, you can test it by creating a by calling the ZITADEL API to create a human user. ```shell -curl -L -X PUT 'https://$CUSTOM-DOMAIN/v2/users/human' \ +curl -L -X POST 'https://$CUSTOM-DOMAIN/v2/users/new' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' \ --data-raw '{ - "userId": { - "givenName": "Example_given", - "familyName": "Example_family" - }, - "email": { - "email": "example@example.com" + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }' ``` Your server should now print out something like the following. Check out the [Sent information Response](./usage#sent-information-response) payload description. +The incoming request headers to the Execution are propagated via the request payload to the target. ```json { - "fullMethod": "/zitadel.user.v2.UserService/AddHumanUser", - "instanceID": "262851882718855632", - "orgID": "262851882718921168", - "projectID": "262851882719052240", - "userID": "262851882718986704", - "request": { - "profile": { - "given_name": "Example_given", - "family_name": "Example_family" - }, - "email": { - "email": "example@example.com" + "fullMethod": "/zitadel.user.v2.UserService/CreateUser", + "instanceID": "336392597046034435", + "orgID": "336392597046099971", + "projectID": "336392597046165507", + "userID": "336392597046755331", + "request": + { + "organizationId": "336392597046099971", + "human": + { + "profile": + { + "givenName": "Minnie", + "familyName": "Mouse", + "nickName": "Mini", + "displayName": "Minnie Mouse", + "preferredLanguage": "en", + "gender": "GENDER_FEMALE" + }, + "email": + { + "email": "mini@mouse.com" + } } }, - "response": { - "user_id": "312918757460672920", - "details": { - "sequence": "2", - "change_date": "2025-03-26T17:28:33.856436Z", - "resource_owner": "312909075211944344", - } + "response": + { + "id": "336494809936035843", + "creationDate": "2025-09-05T08:55:36.156333Z" + }, + "headers": + { + "Content-Type": + [ + "application/grpc" + ], + "Host": + [ + "localhost:8080" + ], + "X-Forwarded-Host": + [ + "localhost:8080" + ] } } ``` diff --git a/internal/api/grpc/action/v2/integration_test/execution_target_test.go b/internal/api/grpc/action/v2/integration_test/execution_target_test.go index 36636563bfb..2acb7c2cc26 100644 --- a/internal/api/grpc/action/v2/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2/integration_test/execution_target_test.go @@ -77,7 +77,15 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // request received by target - wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} + wantRequest := &middleware.ContextInfoRequest{ + FullMethod: fullMethod, + InstanceID: instance.ID(), + OrgID: orgID, + ProjectID: projectID, + UserID: userID, + Request: middleware.Message{Message: request}, + Headers: map[string][]string{"Content-Type": {"application/grpc"}, "Host": {instance.Host()}}, + } changedRequest := &action.GetTargetRequest{Id: targetCreated.GetId()} // replace original request with different targetID urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusOK, changedRequest) @@ -145,6 +153,7 @@ func TestServer_ExecutionTarget(t *testing.T) { UserID: userID, Request: middleware.Message{Message: changedRequest}, Response: middleware.Message{Message: expectedResponse}, + Headers: map[string][]string{"Content-Type": {"application/grpc"}, "Host": {instance.Host()}}, } // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusOK, changedResponse) diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go index a749972c005..214fd6f4f42 100644 --- a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go @@ -77,7 +77,15 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // request received by target - wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} + wantRequest := &middleware.ContextInfoRequest{ + FullMethod: fullMethod, + InstanceID: instance.ID(), + OrgID: orgID, + ProjectID: projectID, + UserID: userID, + Request: middleware.Message{Message: request}, + Headers: map[string][]string{"Content-Type": {"application/grpc"}, "Host": {instance.Host()}}, + } changedRequest := &action.GetTargetRequest{Id: targetCreated.GetId()} // replace original request with different targetID urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusOK, changedRequest) @@ -145,6 +153,7 @@ func TestServer_ExecutionTarget(t *testing.T) { UserID: userID, Request: middleware.Message{Message: changedRequest}, Response: middleware.Message{Message: expectedResponse}, + Headers: map[string][]string{"Content-Type": {"application/grpc"}, "Host": {instance.Host()}}, } // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusOK, changedResponse) diff --git a/internal/api/grpc/server/connect_middleware/execution_interceptor.go b/internal/api/grpc/server/connect_middleware/execution_interceptor.go index 6402eabda26..e144e8a04ac 100644 --- a/internal/api/grpc/server/connect_middleware/execution_interceptor.go +++ b/internal/api/grpc/server/connect_middleware/execution_interceptor.go @@ -3,18 +3,29 @@ package connect_middleware import ( "context" "encoding/json" + "net/http" + "strings" "connectrpc.com/connect" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "github.com/zitadel/zitadel/internal/api/authz" + http_utils "github.com/zitadel/zitadel/internal/api/http" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/execution" target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) +var headersToForward = map[string]bool{ + strings.ToLower(http_utils.ContentType): true, + strings.ToLower(http_utils.ForwardedFor): true, + strings.ToLower(http_utils.ForwardedHost): true, + strings.ToLower(http_utils.Host): true, + strings.ToLower(http_utils.Origin): true, +} + func ExecutionHandler(alg crypto.EncryptionAlgorithm) connect.UnaryInterceptorFunc { return func(handler connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (_ connect.AnyResponse, err error) { @@ -53,6 +64,7 @@ func executeTargetsForRequest(ctx context.Context, targets []target_domain.Targe OrgID: ctxData.OrgID, UserID: ctxData.UserID, Request: Message{req.Any().(proto.Message)}, + Headers: SetRequestHeaders(req.Header()), } _, err = execution.CallTargets(ctx, targets, info, alg) @@ -80,6 +92,7 @@ func executeTargetsForResponse(ctx context.Context, targets []target_domain.Targ UserID: ctxData.UserID, Request: Message{req.Any().(proto.Message)}, Response: Message{resp.Any().(proto.Message)}, + Headers: SetRequestHeaders(req.Header()), } _, err = execution.CallTargets(ctx, targets, info, alg) @@ -92,12 +105,13 @@ func executeTargetsForResponse(ctx context.Context, targets []target_domain.Targ 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 Message `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"` + Headers http.Header `json:"headers,omitempty"` } type Message struct { @@ -135,13 +149,14 @@ func (c *ContextInfoRequest) GetContent() interface{} { 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 Message `json:"request,omitempty"` - Response Message `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"` + Headers http.Header `json:"headers,omitempty"` } func (c *ContextInfoResponse) GetHTTPRequestBody() []byte { @@ -159,3 +174,16 @@ func (c *ContextInfoResponse) SetHTTPResponseBody(resp []byte) error { func (c *ContextInfoResponse) GetContent() interface{} { return c.Response.Message } + +func SetRequestHeaders(reqHeaders map[string][]string) map[string][]string { + if len(reqHeaders) == 0 { + return nil + } + headers := make(map[string][]string) + for k, v := range reqHeaders { + if headersToForward[strings.ToLower(k)] { + headers[k] = v + } + } + return headers +} diff --git a/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go b/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go index d6a765afb1e..0b7a978000c 100644 --- a/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go +++ b/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go @@ -738,3 +738,30 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { }) } } + +func Test_setRequestHeaders(t *testing.T) { + t.Parallel() + tests := []struct { + name string + reqHeaders map[string][]string + want map[string][]string + }{ + { + name: "no headers", + reqHeaders: nil, + want: nil, + }, + { + name: "with headers", + reqHeaders: map[string][]string{"Authorization": {"Bearer XXX"}, "X-Random-Header": {"Random-Value"}, "X-Forwarded-For": {"1.2.3.4"}, "Host": {"localhost:8080"}}, + want: map[string][]string{"X-Forwarded-For": {"1.2.3.4"}, "Host": {"localhost:8080"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := SetRequestHeaders(tt.reqHeaders) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/api/grpc/server/middleware/execution_interceptor.go b/internal/api/grpc/server/middleware/execution_interceptor.go index c55f868d6a1..a8b7c21ca68 100644 --- a/internal/api/grpc/server/middleware/execution_interceptor.go +++ b/internal/api/grpc/server/middleware/execution_interceptor.go @@ -3,12 +3,15 @@ package middleware import ( "context" "encoding/json" + "net/http" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/api/grpc/server/connect_middleware" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/execution" target_domain "github.com/zitadel/zitadel/internal/execution/target" @@ -43,6 +46,7 @@ func executeTargetsForRequest(ctx context.Context, targets []target_domain.Targe return req, nil } + md, _ := metadata.FromIncomingContext(ctx) ctxData := authz.GetCtxData(ctx) info := &ContextInfoRequest{ FullMethod: fullMethod, @@ -51,6 +55,7 @@ func executeTargetsForRequest(ctx context.Context, targets []target_domain.Targe OrgID: ctxData.OrgID, UserID: ctxData.UserID, Request: Message{req.(proto.Message)}, + Headers: connect_middleware.SetRequestHeaders(md), } return execution.CallTargets(ctx, targets, info, alg) @@ -65,6 +70,7 @@ func executeTargetsForResponse(ctx context.Context, targets []target_domain.Targ return resp, nil } + md, _ := metadata.FromIncomingContext(ctx) ctxData := authz.GetCtxData(ctx) info := &ContextInfoResponse{ FullMethod: fullMethod, @@ -74,6 +80,7 @@ func executeTargetsForResponse(ctx context.Context, targets []target_domain.Targ UserID: ctxData.UserID, Request: Message{req.(proto.Message)}, Response: Message{resp.(proto.Message)}, + Headers: connect_middleware.SetRequestHeaders(md), } return execution.CallTargets(ctx, targets, info, alg) @@ -82,12 +89,13 @@ func executeTargetsForResponse(ctx context.Context, targets []target_domain.Targ 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 Message `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"` + Headers http.Header `json:"headers,omitempty"` } type Message struct { @@ -125,13 +133,14 @@ func (c *ContextInfoRequest) GetContent() interface{} { 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 Message `json:"request,omitempty"` - Response Message `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"` + Headers http.Header `json:"headers,omitempty"` } func (c *ContextInfoResponse) GetHTTPRequestBody() []byte { diff --git a/internal/api/http/header.go b/internal/api/http/header.go index fce5330df82..eec2616664d 100644 --- a/internal/api/http/header.go +++ b/internal/api/http/header.go @@ -26,6 +26,7 @@ const ( ForwardedHost = "x-forwarded-host" ForwardedProto = "x-forwarded-proto" Forwarded = "forwarded" + Host = "host" ZitadelForwarded = "x-zitadel-forwarded" XUserAgent = "x-user-agent" XGrpcWeb = "x-grpc-web"