diff --git a/config.go b/config.go index 626a8c3d0..cf584a62c 100644 --- a/config.go +++ b/config.go @@ -243,9 +243,10 @@ func (r *Config) isValid() error { } } } + // check: ensure each of the resource are valid for _, resource := range r.Resources { - if err := resource.valid(); err != nil { + if err := resource.valid(r.CustomHTTPMethods); err != nil { return err } } diff --git a/doc.go b/doc.go index c27bdc4b8..efe55dda7 100644 --- a/doc.go +++ b/doc.go @@ -188,6 +188,8 @@ type Config struct { RequestIDHeader string `json:"request-id-header" yaml:"request-id-header" usage:"the http header name for request id" env:"REQUEST_ID_HEADER"` // ResponseHeader is a map of response headers to add to the response ResponseHeaders map[string]string `json:"response-headers" yaml:"response-headers" usage:"custom headers to added to the http response key=value"` + // CustomHTTPMethods is a list of additional non-standard http methods. If additional method is required it has to explicitly allowed at resource allowed method definition. + CustomHTTPMethods []string `json:"custom-http-methods" yaml:"custom-http-methods" usage:"list of additional non-standard http methods" env:"CUSTOM_HTTP_METHODS"` // EnableSelfSignedTLS indicates we should create a self-signed ceritificate for the service EnabledSelfSignedTLS bool `json:"enable-self-signed-tls" yaml:"enable-self-signed-tls" usage:"create self signed certificates for the proxy" env:"ENABLE_SELF_SIGNED_TLS"` diff --git a/resource.go b/resource.go index f02ef8c00..2d4674b82 100644 --- a/resource.go +++ b/resource.go @@ -76,7 +76,7 @@ func (r *Resource) parse(resource string) (*Resource, error) { } // valid ensure the resource is valid -func (r *Resource) valid() error { +func (r *Resource) valid(customHTTPMethods []string) error { if r.Methods == nil { r.Methods = make([]string, 0) } @@ -96,7 +96,7 @@ func (r *Resource) valid() error { } // step: check the method is valid for _, m := range r.Methods { - if !isValidHTTPMethod(m) { + if !isValidHTTPMethod(m) && !isValidCustomHTTPMethod(m, customHTTPMethods) { return fmt.Errorf("invalid method %s", m) } } diff --git a/resource_test.go b/resource_test.go index 446b07ab3..74f5e2ac4 100644 --- a/resource_test.go +++ b/resource_test.go @@ -1,3 +1,4 @@ +//go:build !e2e // +build !e2e /* @@ -96,8 +97,9 @@ func TestResourceParseOk(t *testing.T) { func TestIsValid(t *testing.T) { testCases := []struct { - Resource *Resource - Ok bool + Resource *Resource + CustomHTTPMethods []string + Ok bool }{ { Resource: &Resource{URL: "/test"}, @@ -125,10 +127,18 @@ func TestIsValid(t *testing.T) { Methods: []string{"NO_SUCH_METHOD"}, }, }, + { + Resource: &Resource{ + URL: "/test", + Methods: []string{"PROPFIND"}, + }, + CustomHTTPMethods: []string{"PROPFIND"}, + Ok: true, + }, } for i, c := range testCases { - err := c.Resource.valid() + err := c.Resource.valid(c.CustomHTTPMethods) if err != nil && c.Ok { t.Errorf("case %d should not have failed, error: %s", i, err) } diff --git a/server.go b/server.go index 3fdc77fd1..9bb803192 100644 --- a/server.go +++ b/server.go @@ -285,6 +285,14 @@ func (r *oauthProxy) createReverseProxy() error { if err := r.createTemplates(); err != nil { return err } + + // step: add custom http methods + if r.config.CustomHTTPMethods != nil { + for _, customHTTPMethod := range r.config.CustomHTTPMethods { + chi.RegisterMethod(customHTTPMethod) + } + } + // step: provision in the protected resources enableDefaultDeny := r.config.EnableDefaultDeny for _, x := range r.config.Resources { diff --git a/server_test.go b/server_test.go index 530715b3b..1a5508037 100644 --- a/server_test.go +++ b/server_test.go @@ -1,3 +1,4 @@ +//go:build !e2e // +build !e2e /* @@ -1032,3 +1033,54 @@ func TestTLS(t *testing.T) { ) } } + +func TestCustomHTTPMethod(t *testing.T) { + config := newFakeKeycloakConfig() + config.EnableDefaultDeny = true + config.CustomHTTPMethods = []string{"PROPFIND"} // WebDav method + config.Resources = []*Resource{ + { + URL: "/public/*", + Methods: allHTTPMethods, + WhiteListed: true, + }, + { + URL: "/api/*", + Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut}, + }, + { + URL: "/webdav/*", + Methods: []string{"PROPFIND"}, + }, + } + requests := []fakeRequest{ + { + URI: "/public/allowed", + ExpectedProxy: true, + ExpectedCode: http.StatusOK, + }, + { + Method: "PROPFIND", + URI: "/public/allowed", + // FIXME: It should return 405, see https://github.com/gogatekeeper/gatekeeper/issues/142 + ExpectedCode: http.StatusUnauthorized, + }, + { + Method: "PROPFIND", + URI: "/api/test", + HasLogin: true, + Redirects: true, + // FIXME: It should return 405, see https://github.com/gogatekeeper/gatekeeper/issues/142 + ExpectedCode: http.StatusUnauthorized, + }, + { + Method: "PROPFIND", + URI: "/webdav/test", + HasLogin: true, + Redirects: true, + ExpectedProxy: true, + ExpectedCode: http.StatusOK, + }, + } + newFakeProxy(config, &fakeAuthConfig{}).RunTests(t, requests) +} diff --git a/utils.go b/utils.go index 3aad12134..cdb86b10e 100644 --- a/utils.go +++ b/utils.go @@ -231,6 +231,17 @@ func isValidHTTPMethod(method string) bool { return false } +// isValidCustomHTTPMethod ensure this is a valid custom http method type +func isValidCustomHTTPMethod(method string, customHTTPMethods []string) bool { + for _, x := range customHTTPMethods { + if method == x { + return true + } + } + + return false +} + // defaultTo returns the value of the default func defaultTo(v, d string) string { if v != "" {