Skip to content

Commit 40939e7

Browse files
ldebruijnldebruijn
andauthored
fix(field-suggestions): Fix block-field-suggestions rule throwing Bod… (#19)
Fix bug on Block Field suggestions rule. We found that the block field suggestion rule broke when the response body did not contain the valid JSON (i.e. when proxying GraphiQL). This MR makes sure to read the response body as plain bytes, try to determine whether it can and should be modified and if that fails, set the body back to the original bytes so that it can be read again later. Co-authored-by: ldebruijn <[email protected]>
1 parent 1d514d1 commit 40939e7

File tree

3 files changed

+158
-45
lines changed

3 files changed

+158
-45
lines changed

cmd/test_test.go

Lines changed: 0 additions & 40 deletions
This file was deleted.

internal/business/proxy/proxy.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,36 @@ func NewProxy(cfg Config, blockFieldSuggestions *block_field_suggestions.BlockFi
3232
KeepAlive: cfg.KeepAlive,
3333
}).DialContext,
3434
}
35-
proxy.ModifyResponse = func(res *http.Response) error {
35+
proxy.ModifyResponse = modifyResponse(blockFieldSuggestions)
36+
37+
return proxy, nil
38+
}
39+
40+
func modifyResponse(blockFieldSuggestions *block_field_suggestions.BlockFieldSuggestionsHandler) func(res *http.Response) error {
41+
return func(res *http.Response) error {
3642
if !blockFieldSuggestions.Enabled() {
3743
return nil
3844
}
3945

40-
decoder := json.NewDecoder(res.Body)
46+
// read raw response bytes
47+
bodyBytes, _ := io.ReadAll(res.Body)
4148
defer res.Body.Close()
4249

4350
var response map[string]interface{}
44-
err := decoder.Decode(&response)
51+
err := json.Unmarshal(bodyBytes, &response)
4552
if err != nil {
4653
// if we cannot decode just return
54+
// make sure to set body back to original bytes
55+
res.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
4756
return nil
4857
}
4958

5059
modified := blockFieldSuggestions.ProcessBody(response)
5160
bts, err := json.Marshal(modified)
5261
if err != nil {
5362
// if we cannot marshall just return
63+
// make sure to set body back to original bytes
64+
res.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
5465
return nil
5566
}
5667

@@ -61,6 +72,4 @@ func NewProxy(cfg Config, blockFieldSuggestions *block_field_suggestions.BlockFi
6172

6273
return nil
6374
}
64-
65-
return proxy, nil
6675
}

internal/business/proxy/proxy_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package proxy
2+
3+
import (
4+
"github.com/ldebruijn/go-graphql-armor/internal/business/block_field_suggestions"
5+
"github.com/stretchr/testify/assert"
6+
"io"
7+
"net/http"
8+
"strings"
9+
"testing"
10+
)
11+
12+
func Test_modifyResponse(t *testing.T) {
13+
type args struct {
14+
blockFieldSuggestions *block_field_suggestions.BlockFieldSuggestionsHandler
15+
response *http.Response
16+
}
17+
tests := []struct {
18+
name string
19+
args args
20+
want func(res *http.Response)
21+
}{
22+
{
23+
name: "nothing if disabled",
24+
args: args{
25+
blockFieldSuggestions: func() *block_field_suggestions.BlockFieldSuggestionsHandler {
26+
return block_field_suggestions.NewBlockFieldSuggestionsHandler(block_field_suggestions.Config{
27+
Enabled: false,
28+
})
29+
}(),
30+
response: func() *http.Response {
31+
return &http.Response{
32+
Status: "200",
33+
StatusCode: 200,
34+
Body: io.NopCloser(strings.NewReader("this is not valid json")),
35+
Proto: "HTTP/1.1",
36+
ProtoMajor: 1,
37+
ProtoMinor: 1,
38+
ContentLength: 0,
39+
Header: map[string][]string{},
40+
}
41+
}(),
42+
},
43+
want: func(res *http.Response) {
44+
body, _ := io.ReadAll(res.Body)
45+
assert.Equal(t, 200, res.StatusCode)
46+
assert.Equal(t, "200", res.Status)
47+
assert.Equal(t, "this is not valid json", string(body))
48+
},
49+
},
50+
{
51+
name: "handles non-json gracefully",
52+
args: args{
53+
blockFieldSuggestions: func() *block_field_suggestions.BlockFieldSuggestionsHandler {
54+
return block_field_suggestions.NewBlockFieldSuggestionsHandler(block_field_suggestions.Config{
55+
Enabled: true,
56+
})
57+
}(),
58+
response: func() *http.Response {
59+
return &http.Response{
60+
Status: "200",
61+
StatusCode: 200,
62+
Body: io.NopCloser(strings.NewReader("this is not valid json")),
63+
Proto: "HTTP/1.1",
64+
ProtoMajor: 1,
65+
ProtoMinor: 1,
66+
ContentLength: 0,
67+
Header: map[string][]string{},
68+
}
69+
}(),
70+
},
71+
want: func(res *http.Response) {
72+
body, _ := io.ReadAll(res.Body)
73+
assert.Equal(t, 200, res.StatusCode)
74+
assert.Equal(t, "200", res.Status)
75+
assert.Equal(t, "this is not valid json", string(body))
76+
},
77+
},
78+
{
79+
name: "handles invalid-json gracefully",
80+
args: args{
81+
blockFieldSuggestions: func() *block_field_suggestions.BlockFieldSuggestionsHandler {
82+
return block_field_suggestions.NewBlockFieldSuggestionsHandler(block_field_suggestions.Config{
83+
Enabled: true,
84+
})
85+
}(),
86+
response: func() *http.Response {
87+
return &http.Response{
88+
Status: "200",
89+
StatusCode: 200,
90+
Body: io.NopCloser(strings.NewReader("{ \"this\": \" is not valid json }")),
91+
Proto: "HTTP/1.1",
92+
ProtoMajor: 1,
93+
ProtoMinor: 1,
94+
ContentLength: 0,
95+
Header: map[string][]string{},
96+
}
97+
}(),
98+
},
99+
want: func(res *http.Response) {
100+
body, _ := io.ReadAll(res.Body)
101+
assert.Equal(t, 200, res.StatusCode)
102+
assert.Equal(t, "200", res.Status)
103+
assert.Equal(t, "{ \"this\": \" is not valid json }", string(body))
104+
},
105+
},
106+
{
107+
name: "handles json gracefully",
108+
args: args{
109+
blockFieldSuggestions: func() *block_field_suggestions.BlockFieldSuggestionsHandler {
110+
return block_field_suggestions.NewBlockFieldSuggestionsHandler(block_field_suggestions.Config{
111+
Enabled: true,
112+
Mask: "[masked]",
113+
})
114+
}(),
115+
response: func() *http.Response {
116+
return &http.Response{
117+
Status: "200",
118+
StatusCode: 200,
119+
Body: io.NopCloser(strings.NewReader("{ \"errors\": [{\"message\": \"Did you mean \"}] }")),
120+
Proto: "HTTP/1.1",
121+
ProtoMajor: 1,
122+
ProtoMinor: 1,
123+
ContentLength: 0,
124+
Header: map[string][]string{},
125+
}
126+
}(),
127+
},
128+
want: func(res *http.Response) {
129+
body, _ := io.ReadAll(res.Body)
130+
assert.Equal(t, 200, res.StatusCode)
131+
assert.Equal(t, "200", res.Status)
132+
assert.Equal(t, "{\"errors\":[{\"message\":\"[masked]\"}]}", string(body))
133+
},
134+
},
135+
}
136+
for _, tt := range tests {
137+
t.Run(tt.name, func(t *testing.T) {
138+
result := modifyResponse(tt.args.blockFieldSuggestions)
139+
140+
_ = result(tt.args.response)
141+
tt.want(tt.args.response)
142+
})
143+
}
144+
}

0 commit comments

Comments
 (0)