From 48e44effc831abb27f21c57e2ee37ecb204eedec Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 3 May 2021 14:11:02 +0200 Subject: [PATCH 1/3] Move options tests to own test-function --- server/http_oauth2_test.go | 108 +++++++++++++++----- server/testdata/oauth2/01_couper.hcl | 25 +++++ server/testdata/oauth2/02_couper.hcl | 24 +++++ server/testdata/oauth2/03_couper.hcl | 23 +++++ server/testdata/oauth2/0_retries_couper.hcl | 4 - server/testdata/oauth2/1_retries_couper.hcl | 2 - 6 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 server/testdata/oauth2/01_couper.hcl create mode 100644 server/testdata/oauth2/02_couper.hcl create mode 100644 server/testdata/oauth2/03_couper.hcl diff --git a/server/http_oauth2_test.go b/server/http_oauth2_test.go index d54ae9b0f..cf73f178b 100644 --- a/server/http_oauth2_test.go +++ b/server/http_oauth2_test.go @@ -21,29 +21,6 @@ func TestEndpoints_OAuth2(t *testing.T) { oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.URL.Path == "/oauth2" { - reqBody, _ := ioutil.ReadAll(req.Body) - authorization := req.Header.Get("Authorization") - - if i == 0 { - exp := `client_id=user&client_secret=pass+word&grant_type=client_credentials&scope=scope1+scope2` - if exp != string(reqBody) { - t.Errorf("want\n%s\ngot\n%s", exp, reqBody) - } - exp = "" - if exp != authorization { - t.Errorf("want\n%s\ngot\n%s", exp, authorization) - } - } else { - exp := `grant_type=client_credentials` - if exp != string(reqBody) { - t.Errorf("want\n%s\ngot\n%s", exp, reqBody) - } - exp = "Basic dXNlcjpwYXNz" - if exp != authorization { - t.Errorf("want\n%s\ngot\n%s", exp, authorization) - } - } - rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) @@ -131,3 +108,88 @@ func TestEndpoints_OAuth2(t *testing.T) { shutdown() } } + +func TestEndpoints_OAuth2_Options(t *testing.T) { + helper := test.New(t) + + type testCase struct { + configFile string + expBody string + expAuth string + } + + for _, tc := range []testCase{ + { + "01_couper.hcl", + `client_id=user&client_secret=pass+word&grant_type=client_credentials&scope=scope1+scope2`, + "", + }, + { + "02_couper.hcl", + `grant_type=client_credentials`, + "Basic dXNlcjpwYXNz", + }, + { + "03_couper.hcl", + `grant_type=client_credentials`, + "Basic dXNlcjpwYXNz", + }, + } { + var tokenSeenCh chan struct{} + + oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/options" { + reqBody, _ := ioutil.ReadAll(req.Body) + authorization := req.Header.Get("Authorization") + + if tc.expBody != string(reqBody) { + t.Errorf("want\n%s\ngot\n%s", tc.expBody, reqBody) + } + if tc.expAuth != authorization { + t.Errorf("want\n%s\ngot\n%s", tc.expAuth, authorization) + } + + rw.WriteHeader(http.StatusNoContent) + + close(tokenSeenCh) + return + } + rw.WriteHeader(http.StatusBadRequest) + })) + defer oauthOrigin.Close() + + confPath := fmt.Sprintf("testdata/oauth2/%s", tc.configFile) + shutdown, hook := newCouper(confPath, test.New(t)) + defer func() { + if t.Failed() { + for _, e := range hook.Entries { + println(e.String()) + } + } + shutdown() + }() + + req, err := http.NewRequest(http.MethodGet, "http://anyserver:8080/", nil) + helper.Must(err) + + req.Header.Set("X-Token-Endpoint", oauthOrigin.URL) + + hook.Reset() + + tokenSeenCh = make(chan struct{}) + + req.URL.Path = "/" + _, err = newClient().Do(req) + helper.Must(err) + + timer := time.NewTimer(time.Second * 2) + select { + case <-timer.C: + t.Error("OAuth2 request failed") + case <-tokenSeenCh: + } + + oauthOrigin.Close() + shutdown() + } +} diff --git a/server/testdata/oauth2/01_couper.hcl b/server/testdata/oauth2/01_couper.hcl new file mode 100644 index 000000000..7bc2d4e69 --- /dev/null +++ b/server/testdata/oauth2/01_couper.hcl @@ -0,0 +1,25 @@ +server "oauth2-options" { + error_file = "./../integration/server_error.html" + + api { + error_file = "./../integration/api_error.json" + + endpoint "/" { + proxy { + backend { + url = "https://example.com/" + + oauth2 { + token_endpoint = "${request.headers.x-token-endpoint}/options" + retries = 0 + client_id = "user" + client_secret = "pass word" + grant_type = "client_credentials" + scope = "scope1 scope2" + token_endpoint_auth_method = "client_secret_post" + } + } + } + } + } +} diff --git a/server/testdata/oauth2/02_couper.hcl b/server/testdata/oauth2/02_couper.hcl new file mode 100644 index 000000000..1f4cbcc24 --- /dev/null +++ b/server/testdata/oauth2/02_couper.hcl @@ -0,0 +1,24 @@ +server "oauth2-options" { + error_file = "./../integration/server_error.html" + + api { + error_file = "./../integration/api_error.json" + + endpoint "/" { + proxy { + backend { + url = "https://example.com/" + + oauth2 { + token_endpoint = "${request.headers.x-token-endpoint}/options" + retries = 0 + client_id = "user" + client_secret = "pass" + grant_type = "client_credentials" + token_endpoint_auth_method = "client_secret_basic" + } + } + } + } + } +} diff --git a/server/testdata/oauth2/03_couper.hcl b/server/testdata/oauth2/03_couper.hcl new file mode 100644 index 000000000..6d31920d8 --- /dev/null +++ b/server/testdata/oauth2/03_couper.hcl @@ -0,0 +1,23 @@ +server "oauth2-options" { + error_file = "./../integration/server_error.html" + + api { + error_file = "./../integration/api_error.json" + + endpoint "/" { + proxy { + backend { + url = "https://example.com/" + + oauth2 { + token_endpoint = "${request.headers.x-token-endpoint}/options" + retries = 0 + client_id = "user" + client_secret = "pass" + grant_type = "client_credentials" + } + } + } + } + } +} diff --git a/server/testdata/oauth2/0_retries_couper.hcl b/server/testdata/oauth2/0_retries_couper.hcl index 9e8ec49e8..ee0df23a3 100644 --- a/server/testdata/oauth2/0_retries_couper.hcl +++ b/server/testdata/oauth2/0_retries_couper.hcl @@ -16,8 +16,6 @@ server "api" { client_secret = "pass word" grant_type = "client_credentials" retries = 0 - scope = "scope1 scope2" - token_endpoint_auth_method = "client_secret_post" } } } @@ -34,8 +32,6 @@ server "api" { client_secret = "pass word" grant_type = "client_credentials" retries = 0 - scope = "scope1 scope2" - token_endpoint_auth_method = "client_secret_post" backend { origin = "${request.headers.x-token-endpoint}" path = "/oauth2" diff --git a/server/testdata/oauth2/1_retries_couper.hcl b/server/testdata/oauth2/1_retries_couper.hcl index 630b96a06..1a229b100 100644 --- a/server/testdata/oauth2/1_retries_couper.hcl +++ b/server/testdata/oauth2/1_retries_couper.hcl @@ -15,7 +15,6 @@ server "api" { client_id = "user" client_secret = "pass" grant_type = "client_credentials" - token_endpoint_auth_method = "client_secret_basic" } } } @@ -31,7 +30,6 @@ server "api" { client_id = "user" client_secret = "pass" grant_type = "client_credentials" - token_endpoint_auth_method = "client_secret_basic" backend { origin = "${request.headers.x-token-endpoint}" path = "/oauth2" From 8b319b3ce51dcc41a26d77e4e09ce8851f42c38d Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 3 May 2021 14:24:38 +0200 Subject: [PATCH 2/3] Docu --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 46b46c61f..a5606c6c9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -659,6 +659,8 @@ The CORS block configures the CORS (Cross-Origin Resource Sharing) behavior in C | `client_id` | | | `client_secret` | | | `retries` | | +| `token_endpoint_auth_method` | | +| `scope` | | ### Modifier From be39fd74db84fa441e50fe5a332d45e02bc36f9a Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 10 May 2021 09:41:28 +0200 Subject: [PATCH 3/3] Typos and wording change --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index a5606c6c9..8fe0ff258 100644 --- a/docs/README.md +++ b/docs/README.md @@ -658,9 +658,9 @@ The CORS block configures the CORS (Cross-Origin Resource Sharing) behavior in C | `token_endpoint` |
  • ⚠ Mandatory.
  • URL of the token endpoint at the authorization server.
| | `client_id` |
  • ⚠ Mandatory.
  • The client identifier.
| | `client_secret` |
  • ⚠ Mandatory.
  • The client password.
| -| `retries` |
  • Optional.
  • The number of retries to get the token und resource, if the resource-request responses with `401 Forbidden` HTTP status code.
  • Default: `1`.
| +| `retries` |
  • Optional.
  • The number of retries to get the token and resource, if the resource-request responds with `401 Unauthorized` HTTP status code.
  • Default: `1`.
| | `token_endpoint_auth_method` |
  • Optional.
  • Defines the method to authenticate the client at the token endpoint.
  • If set to `client_secret_post`, the client credentials are transported in the request body.
  • If set to `client_secret_basic`, the client credentials are transported via Basic Authentication.
  • Default: `client_secret_basic`.
| -| `scope` |
  • Optional.
  • A space separated list of scopes of the access request.
| +| `scope` |
  • Optional.
  • A space separated list of requested scopes for the access token.
| ### Modifier