Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oauth2 refinements #220

Merged
merged 6 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,9 @@ The CORS block configures the CORS (Cross-Origin Resource Sharing) behavior in C
| `token_endpoint` | <ul><li>&#9888; Mandatory.</li><li>URL of the token endpoint at the authorization server.</li></ul> |
| `client_id` | <ul><li>&#9888; Mandatory.</li><li>The client identifier.</li></ul> |
| `client_secret` | <ul><li>&#9888; Mandatory.</li><li>The client password.</li></ul> |
| `retries` | <ul><li>Optional.</li><li>The number of retries to get the token und resource, if the resource-request responses with `401 Forbidden` HTTP status code.</li><li>Default: `1`.</li></ul> |
| `retries` | <ul><li>Optional.</li><li>The number of retries to get the token and resource, if the resource-request responds with `401 Unauthorized` HTTP status code.</li><li>Default: `1`.</li></ul> |
| `token_endpoint_auth_method` | <ul><li>Optional.</li><li>Defines the method to authenticate the client at the token endpoint.</li><li>If set to `client_secret_post`, the client credentials are transported in the request body.</li><li>If set to `client_secret_basic`, the client credentials are transported via Basic Authentication.</li><li>Default: `client_secret_basic`.</li></ul> |
| `scope` | <ul><li>Optional.</li><li>A space separated list of requested scopes for the access token.</li></ul> |

### Modifier

Expand Down
108 changes: 85 additions & 23 deletions server/http_oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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()
}
}
25 changes: 25 additions & 0 deletions server/testdata/oauth2/01_couper.hcl
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
24 changes: 24 additions & 0 deletions server/testdata/oauth2/02_couper.hcl
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
23 changes: 23 additions & 0 deletions server/testdata/oauth2/03_couper.hcl
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
}
}
4 changes: 0 additions & 4 deletions server/testdata/oauth2/0_retries_couper.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Expand All @@ -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"
Expand Down
2 changes: 0 additions & 2 deletions server/testdata/oauth2/1_retries_couper.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ server "api" {
client_id = "user"
client_secret = "pass"
grant_type = "client_credentials"
token_endpoint_auth_method = "client_secret_basic"
}
}
}
Expand All @@ -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"
Expand Down