@@ -1115,7 +1115,13 @@ func TestGraphQL_UDGHeaders(t *testing.T) {
11151115 strings .Contains (string (b ), `{"name":"Context","value":"request-context"}` ) &&
11161116 strings .Contains (string (b ), `{"name":"Global-Static","value":"foobar"}` ) &&
11171117 strings .Contains (string (b ), `{"name":"Global-Context","value":"follow-up-request-global-context"}` ) &&
1118- strings .Contains (string (b ), `{"name":"Does-Exist-Already","value":"ds-does-exist-already"}` )
1118+ strings .Contains (string (b ), `{"name":"Does-Exist-Already","value":"ds-does-exist-already"}` ) &&
1119+ // A header with more than one value has to keep all of them. The round
1120+ // tripper that used to resolve variables on the way to the upstream read
1121+ // with Get and wrote with Set, so it collapsed this to gzip alone.
1122+ strings .Contains (string (b ), `{"name":"Accept-Encoding","value":"gzip"}` ) &&
1123+ strings .Contains (string (b ), `{"name":"Accept-Encoding","value":"deflate"}` ) &&
1124+ strings .Contains (string (b ), `{"name":"Accept-Encoding","value":"br"}` )
11191125 },
11201126 },
11211127 }... )
@@ -1194,6 +1200,107 @@ func TestGraphQL_ProxyOnlyHeaders(t *testing.T) {
11941200 })
11951201 assert .NoError (t , err )
11961202 })
1203+
1204+ t .Run ("test context variable request headers rewrite" , func (t * testing.T ) {
1205+ // request_headers_rewrite is applied by the engine transport, after the header
1206+ // modifier has already finalised the fetch headers, so it is resolved where the
1207+ // rules are built instead. See handleGraphQL.
1208+ spec := defaultSpec
1209+ spec .GraphQL .Proxy .RequestHeadersRewrite = map [string ]apidef.RequestHeadersRewriteConfig {
1210+ "X-Rewritten" : {Value : "$tyk_context.headers_Test_Header" },
1211+ }
1212+ spec .EnableContextVars = true
1213+ g .Gw .LoadAPI (spec )
1214+ g .AddDynamicHandler ("/dynamic" , func (writer http.ResponseWriter , r * http.Request ) {
1215+ if ! headerCheck ("X-Rewritten" , "test-value" , r .Header ) {
1216+ t .Errorf ("rewritten header not resolved, got %q" , r .Header .Get ("X-Rewritten" ))
1217+ }
1218+ })
1219+ _ , err := g .Run (t , test.TestCase {
1220+ Path : "/" ,
1221+ Headers : map [string ]string {
1222+ "Test-Header" : "test-value" ,
1223+ },
1224+ Method : http .MethodPost ,
1225+ Data : graphql.Request {
1226+ Query : gqlContinentQuery ,
1227+ },
1228+ })
1229+ assert .NoError (t , err )
1230+ })
1231+
1232+ t .Run ("the consumer's credential reaches the upstream once" , func (t * testing.T ) {
1233+ // Two writers put it there and neither knows about the other: with strip_auth_data
1234+ // off the engine adds the consumer's auth header to the fetch input through
1235+ // propagateAuthHeaders, and setProxyOnlyHeaders then forwards the consumer's
1236+ // headers again. The upstream used to receive the credential twice.
1237+ spec := defaultSpec
1238+ spec .GraphQL .Proxy .RequestHeadersRewrite = nil
1239+ spec .UseKeylessAccess = false
1240+ spec .UseStandardAuth = true
1241+ spec .StripAuthData = false
1242+ spec .AuthConfigs = map [string ]apidef.AuthConfig {
1243+ apidef .AuthTokenType : {AuthHeaderName : "X-API-KEY" },
1244+ }
1245+ g .Gw .LoadAPI (spec )
1246+
1247+ _ , authKey := g .CreateSession (func (s * user.SessionState ) {
1248+ s .AccessRights = map [string ]user.AccessDefinition {
1249+ spec .APIID : {APIName : spec .Name , APIID : spec .APIID , Versions : []string {"Default" }},
1250+ }
1251+ s .OrgID = spec .OrgID
1252+ })
1253+
1254+ g .AddDynamicHandler ("/dynamic" , func (writer http.ResponseWriter , r * http.Request ) {
1255+ values := r .Header .Values ("X-Api-Key" )
1256+ if len (values ) != 1 {
1257+ t .Errorf ("upstream received X-Api-Key %d times: %v" , len (values ), values )
1258+ return
1259+ }
1260+ if values [0 ] != authKey {
1261+ t .Errorf ("upstream received the wrong credential: %q" , values [0 ])
1262+ }
1263+ })
1264+ _ , err := g .Run (t , test.TestCase {
1265+ Path : "/" ,
1266+ Headers : map [string ]string {
1267+ "X-API-KEY" : authKey ,
1268+ },
1269+ Method : http .MethodPost ,
1270+ Data : graphql.Request {
1271+ Query : gqlContinentQuery ,
1272+ },
1273+ })
1274+ assert .NoError (t , err )
1275+ })
1276+
1277+ t .Run ("a variable inside a header the caller sent is not expanded" , func (t * testing.T ) {
1278+ // Only values that come from the API definition are resolved. A round tripper that
1279+ // walked every outgoing header used to sit on this path and expanded whatever the
1280+ // caller had put in one, which let a caller read the context of their own session
1281+ // back out of the upstream request. See handleGraphQL.
1282+ spec := defaultSpec
1283+ spec .GraphQL .Proxy .RequestHeadersRewrite = nil
1284+ spec .EnableContextVars = true
1285+ g .Gw .LoadAPI (spec )
1286+ g .AddDynamicHandler ("/dynamic" , func (writer http.ResponseWriter , r * http.Request ) {
1287+ if ! headerCheck ("X-Injection-Probe" , "$tyk_context.headers_Test_Header" , r .Header ) {
1288+ t .Errorf ("caller supplied variable was expanded, got %q" , r .Header .Get ("X-Injection-Probe" ))
1289+ }
1290+ })
1291+ _ , err := g .Run (t , test.TestCase {
1292+ Path : "/" ,
1293+ Headers : map [string ]string {
1294+ "Test-Header" : "test-value" ,
1295+ "X-Injection-Probe" : "$tyk_context.headers_Test_Header" ,
1296+ },
1297+ Method : http .MethodPost ,
1298+ Data : graphql.Request {
1299+ Query : gqlContinentQuery ,
1300+ },
1301+ })
1302+ assert .NoError (t , err )
1303+ })
11971304}
11981305
11991306func TestGraphQL_ProxyOnlyPassHeadersWithOTel (t * testing.T ) {
0 commit comments