diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc181e..08f8454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Removed +## [0.11.0] + +### Added + +- Added ability to specify disallowed parameters: [#59](https://github.com/elastic/stream/pull/59) + ## [0.10.0] ### Added diff --git a/README.md b/README.md index 07be597..82cc384 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ The rules will be defined in order, and will only match if all criteria is true - `path`: the path to match. It can use [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux#pkg-overview) parameters patterns. - `methods`: a list of methods to match with the rule. - `user` and `password`: username and password for basic auth matching. -- `query_params`: Key-Value definitions of the query parameters to match. It can use [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux#Route.Queries) parameters patterns for the values. Web form params will also be added and compared against this for simplicity. +- `query_params`: Key-Value definitions of the query parameters to match. It can use [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux#Route.Queries) parameters patterns for the values. Web form params will also be added and compared against this for simplicity. If a key is given an empty value, requests with this parameter will not satisfy the rule. - `request_headers`: Key-Value definitions of the headers to match. Any headers outside of this list will be ignored. The matches can be defined [as regular expressions](https://pkg.go.dev/github.com/gorilla/mux#Route.HeadersRegexp). - `request_body`: a string defining the expected body to match for the request. If the string is quoted with slashes, the leading and trailing slash are stripped and the resulting string is interpreted as a regular expression. - `responses`: a list of zero or more responses to return on matches. If more than one are set, they will be returned in rolling sequence. @@ -127,4 +127,4 @@ The emulator does not require authentication. - `gcs-bucket`: The name of the GCS bucket that should be created, should not already exist. - `gcs-object`: The name of the GCS object that will be populated with the collected data, using the configured GCS bucket. -- `gcs-projectid`: The related projectID used when creating the bucket, this is required to be changed from the default value when not using an emulator. \ No newline at end of file +- `gcs-projectid`: The related projectID used when creating the bucket, this is required to be changed from the default value when not using an emulator. diff --git a/pkg/httpserver/httpserver.go b/pkg/httpserver/httpserver.go index 31762f2..02ec062 100644 --- a/pkg/httpserver/httpserver.go +++ b/pkg/httpserver/httpserver.go @@ -160,11 +160,24 @@ func newHandlerFromConfig(config *config, logger *zap.SugaredLogger) (http.Handl route.Methods(rule.Methods...) + exclude := make(map[string]bool) for key, vals := range rule.QueryParams { + if len(vals) == 0 { // Cannot use nil since ucfg interprets null as an empty slice instead of nil. + exclude[key] = true + continue + } for _, v := range vals { route.Queries(key, v) } } + route.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { + for key := range exclude { + if r.URL.Query().Has(key) { + return false + } + } + return true + }) for key, vals := range rule.RequestHeaders { for _, v := range vals { diff --git a/pkg/httpserver/httpserver_test.go b/pkg/httpserver/httpserver_test.go index 85d5dba..5f91121 100644 --- a/pkg/httpserver/httpserver_test.go +++ b/pkg/httpserver/httpserver_test.go @@ -30,6 +30,7 @@ func TestHTTPServer(t *testing.T) { password: passwd query_params: p1: ["v1"] + p2: null request_headers: accept: ["application/json"] @@ -124,4 +125,21 @@ func TestHTTPServer(t *testing.T) { assert.JSONEq(t, "2", string(body)) assert.Equal(t, "text/plain", resp.Header.Get("content-type")) }) + + t.Run("request has rejected parameter", func(t *testing.T) { + req, err := http.NewRequest("GET", "http://"+addr+"/path1/test?p1=v1&p2=bad", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + resp.Body.Close() + + assert.Equal(t, 404, resp.StatusCode) // must fail because p2 is present + + body, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + resp.Body.Close() + + assert.Equal(t, []byte{}, body) + }) }