Skip to content

Commit

Permalink
prevent body overwrite with path param
Browse files Browse the repository at this point in the history
  • Loading branch information
ljmsc committed Dec 21, 2022
1 parent 98a6467 commit ce75200
Show file tree
Hide file tree
Showing 12 changed files with 572 additions and 62 deletions.
3 changes: 0 additions & 3 deletions Makefile
Expand Up @@ -138,9 +138,6 @@ test: proto
go test -short -race ./...
go test -race ./examples/internal/integration -args -network=unix -endpoint=test.sock

test/integration:
go test -race ./examples/internal/integration -args -network=unix -endpoint=test.sock

clean:
find . -type f -name '*.pb.go' -delete
find . -type f -name '*.swagger.json' -delete
Expand Down
81 changes: 81 additions & 0 deletions examples/internal/clients/echo/api/swagger.yaml
Expand Up @@ -583,6 +583,87 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v1/example/echo_body2/{id}:
put:
tags:
- "EchoService"
summary: "EchoBody method receives a simple message and returns it."
operationId: "EchoService_EchoBody3"
parameters:
- name: "id"
in: "path"
description: "Id represents the message identifier."
required: true
type: "string"
x-exportParamName: "Id"
- in: "body"
name: "resourceId"
required: true
schema:
type: "string"
x-exportParamName: "ResourceId"
- name: "num"
in: "query"
required: false
type: "string"
format: "int64"
x-exportParamName: "Num"
x-optionalDataType: "String"
- name: "lineNum"
in: "query"
required: false
type: "string"
format: "int64"
x-exportParamName: "LineNum"
x-optionalDataType: "String"
- name: "lang"
in: "query"
required: false
type: "string"
x-exportParamName: "Lang"
x-optionalDataType: "String"
- name: "status.progress"
in: "query"
required: false
type: "string"
format: "int64"
x-exportParamName: "StatusProgress"
x-optionalDataType: "String"
- name: "status.note"
in: "query"
required: false
type: "string"
x-exportParamName: "StatusNote"
x-optionalDataType: "String"
- name: "en"
in: "query"
required: false
type: "string"
format: "int64"
x-exportParamName: "En"
x-optionalDataType: "String"
- name: "no.progress"
in: "query"
required: false
type: "string"
format: "int64"
x-exportParamName: "NoProgress"
x-optionalDataType: "String"
- name: "no.note"
in: "query"
required: false
type: "string"
x-exportParamName: "NoNote"
x-optionalDataType: "String"
responses:
200:
description: "A successful response."
schema:
$ref: "#/definitions/examplepbSimpleMessage"
default:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v1/example/echo_delete:
delete:
tags:
Expand Down
147 changes: 147 additions & 0 deletions examples/internal/clients/echo/api_echo_service.go
Expand Up @@ -1143,6 +1143,153 @@ func (a *EchoServiceApiService) EchoServiceEchoBody2(ctx context.Context, id str
return localVarReturnValue, localVarHttpResponse, nil
}

/*
EchoServiceApiService EchoBody method receives a simple message and returns it.
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
* @param id Id represents the message identifier.
* @param resourceId
* @param optional nil or *EchoServiceEchoBody3Opts - Optional Parameters:
* @param "Num" (optional.String) -
* @param "LineNum" (optional.String) -
* @param "Lang" (optional.String) -
* @param "StatusProgress" (optional.String) -
* @param "StatusNote" (optional.String) -
* @param "En" (optional.String) -
* @param "NoProgress" (optional.String) -
* @param "NoNote" (optional.String) -
@return ExamplepbSimpleMessage
*/

type EchoServiceEchoBody3Opts struct {
Num optional.String
LineNum optional.String
Lang optional.String
StatusProgress optional.String
StatusNote optional.String
En optional.String
NoProgress optional.String
NoNote optional.String
}

func (a *EchoServiceApiService) EchoServiceEchoBody3(ctx context.Context, id string, resourceId string, localVarOptionals *EchoServiceEchoBody3Opts) (ExamplepbSimpleMessage, *http.Response, error) {
var (
localVarHttpMethod = strings.ToUpper("Put")
localVarPostBody interface{}
localVarFileName string
localVarFileBytes []byte
localVarReturnValue ExamplepbSimpleMessage
)

// create path and map variables
localVarPath := a.client.cfg.BasePath + "/v1/example/echo_body2/{id}"
localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", fmt.Sprintf("%v", id), -1)

localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}

if localVarOptionals != nil && localVarOptionals.Num.IsSet() {
localVarQueryParams.Add("num", parameterToString(localVarOptionals.Num.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.LineNum.IsSet() {
localVarQueryParams.Add("lineNum", parameterToString(localVarOptionals.LineNum.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.Lang.IsSet() {
localVarQueryParams.Add("lang", parameterToString(localVarOptionals.Lang.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.StatusProgress.IsSet() {
localVarQueryParams.Add("status.progress", parameterToString(localVarOptionals.StatusProgress.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.StatusNote.IsSet() {
localVarQueryParams.Add("status.note", parameterToString(localVarOptionals.StatusNote.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.En.IsSet() {
localVarQueryParams.Add("en", parameterToString(localVarOptionals.En.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.NoProgress.IsSet() {
localVarQueryParams.Add("no.progress", parameterToString(localVarOptionals.NoProgress.Value(), ""))
}
if localVarOptionals != nil && localVarOptionals.NoNote.IsSet() {
localVarQueryParams.Add("no.note", parameterToString(localVarOptionals.NoNote.Value(), ""))
}
// to determine the Content-Type header
localVarHttpContentTypes := []string{"application/json"}

// set Content-Type header
localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes)
if localVarHttpContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHttpContentType
}

// to determine the Accept header
localVarHttpHeaderAccepts := []string{"application/json"}

// set Accept header
localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts)
if localVarHttpHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHttpHeaderAccept
}
// body params
localVarPostBody = &resourceId
r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes)
if err != nil {
return localVarReturnValue, nil, err
}

localVarHttpResponse, err := a.client.callAPI(r)
if err != nil || localVarHttpResponse == nil {
return localVarReturnValue, localVarHttpResponse, err
}

localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body)
localVarHttpResponse.Body.Close()
if err != nil {
return localVarReturnValue, localVarHttpResponse, err
}

if localVarHttpResponse.StatusCode < 300 {
// If we succeed, return the data, otherwise pass on to decode error.
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type"));
if err == nil {
return localVarReturnValue, localVarHttpResponse, err
}
}

if localVarHttpResponse.StatusCode >= 300 {
newErr := GenericSwaggerError{
body: localVarBody,
error: localVarHttpResponse.Status,
}

if localVarHttpResponse.StatusCode == 200 {
var v ExamplepbSimpleMessage
err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type"));
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHttpResponse, newErr
}
newErr.model = v
return localVarReturnValue, localVarHttpResponse, newErr
}

if localVarHttpResponse.StatusCode == 0 {
var v RpcStatus
err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type"));
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHttpResponse, newErr
}
newErr.model = v
return localVarReturnValue, localVarHttpResponse, newErr
}

return localVarReturnValue, localVarHttpResponse, newErr
}

return localVarReturnValue, localVarHttpResponse, nil
}

/*
EchoServiceApiService EchoDelete method receives a simple message and returns it.
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
Expand Down
49 changes: 46 additions & 3 deletions examples/internal/integration/integration_test.go
Expand Up @@ -46,12 +46,13 @@ func TestEcho(t *testing.T) {
testEchoOneof(t, 8088, apiPrefix, "application/json")
testEchoOneof1(t, 8088, apiPrefix, "application/json")
testEchoOneof2(t, 8088, apiPrefix, "application/json")
testEchoResource(t, 8088)
testEchoPathParamOverwrite(t, 8088)
testEchoBody(t, 8088, apiPrefix, true)
testEchoBody(t, 8088, apiPrefix, false)
// Use SendHeader/SetTrailer without gRPC server https://github.com/grpc-ecosystem/grpc-gateway/issues/517#issuecomment-684625645
testEchoBody(t, 8089, apiPrefix, true)
testEchoBody(t, 8089, apiPrefix, false)
testEchoBodyParamOverwrite(t, 8088)
})
}
}
Expand Down Expand Up @@ -348,7 +349,7 @@ func testEchoOneof2(t *testing.T, port int, apiPrefix string, contentType string
}
}

func testEchoResource(t *testing.T, port int) {
func testEchoPathParamOverwrite(t *testing.T, port int) {
apiURL := fmt.Sprintf("http://localhost:%d/v1/example/echo/resource/my_resource_id?resourceId=bad_resource_id", port)
resp, err := http.Get(apiURL)
if err != nil {
Expand Down Expand Up @@ -378,7 +379,7 @@ func testEchoResource(t *testing.T, port int) {
}

func testEchoBody(t *testing.T, port int, apiPrefix string, useTrailers bool) {
sent := examplepb.UnannotatedSimpleMessage{Id: "example"}
sent := examplepb.UnannotatedSimpleMessage{Id: "example", ResourceId: "my_resource_id"}
payload, err := marshaler.Marshal(&sent)
if err != nil {
t.Fatalf("marshaler.Marshal(%#v) failed with %v; want success", payload, err)
Expand Down Expand Up @@ -443,6 +444,48 @@ func testEchoBody(t *testing.T, port int, apiPrefix string, useTrailers bool) {
}
}

func testEchoBodyParamOverwrite(t *testing.T, port int) {
sent := "my_resource_id"
payload, err := marshaler.Marshal(&sent)
if err != nil {
t.Fatalf("marshaler.Marshal(%#v) failed with %v; want success", payload, err)
}

apiURL := fmt.Sprintf("http://localhost:%d/v1/example/echo_body2/%s?resourceId=bad_resource_id", port, "my_id")

req, err := http.NewRequest("PUT", apiURL, bytes.NewReader(payload))
if err != nil {
t.Errorf("http.NewRequest() failed with %v; want success", err)
return
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("client.Do(%v) failed with %v; want success", req, err)
return
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
t.Errorf("io.ReadAll(resp.Body) failed with %v; want success", err)
return
}

if got, want := resp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
}

var received examplepb.UnannotatedSimpleMessage
if err := marshaler.Unmarshal(buf, &received); err != nil {
t.Errorf("marshaler.Unmarshal(%s, msg) failed with %v; want success", buf, err)
return
}
if diff := cmp.Diff(&received.ResourceId, &sent, protocmp.Transform()); diff != "" {
t.Errorf(diff)
}
}

func TestABE(t *testing.T) {
if testing.Short() {
t.Skip()
Expand Down
10 changes: 5 additions & 5 deletions examples/internal/proto/examplepb/a_bit_of_everything.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ce75200

Please sign in to comment.