Skip to content

Commit

Permalink
fix: sendOAuth2 introspection scope only when strategy is none (#379)
Browse files Browse the repository at this point in the history
This patch removes the `scope` key from the OAuth2 Introspection request body when a scope strategy other than `none` is set for the OAuth2 Introspection handler. If the scope strategy is `none`, the `scope` key is included in the body.

Closes #377
  • Loading branch information
tleef authored Mar 17, 2020
1 parent 0dfc608 commit 5e0c8dc
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 7 deletions.
10 changes: 8 additions & 2 deletions pipeline/authn/authenticator_oauth2_introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session
return errors.WithStack(ErrAuthenticatorNotResponsible)
}

body := url.Values{"token": {token}, "scope": {strings.Join(cf.Scopes, " ")}}
body := url.Values{"token": {token}}

ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.scope_strategy")
if ss == nil {
body.Add("scope", strings.Join(cf.Scopes, " "))
}

introspectReq, err := http.NewRequest(http.MethodPost, cf.IntrospectionURL, strings.NewReader(body.Encode()))
if err != nil {
return errors.WithStack(err)
Expand Down Expand Up @@ -129,7 +135,7 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session
}
}

if ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.scope_strategy"); ss != nil {
if ss != nil {
for _, scope := range cf.Scopes {
if !ss(strings.Split(i.Scope, " "), scope) {
return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope)))
Expand Down
141 changes: 136 additions & 5 deletions pipeline/authn/authenticator_oauth2_introspection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,80 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) {
expectErr: false,
},
{
d: "should pass because active and scope matching",
d: "should pass because no scope strategy",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "required_scope": ["scope-a", "scope-b"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "scope-a scope-b", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-z",
}))
})
},
expectErr: false,
},
{
d: "should pass because scope strategy is `none`",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "none", "required_scope": ["scope-a", "scope-b"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "scope-a scope-b", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-z",
}))
})
},
expectErr: false,
},
{
d: "should pass because active and scope matching exactly",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "exact", "required_scope": ["scope-a", "scope-b"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-a scope-b",
}))
})
},
expectErr: false,
},
{
d: "should fail because active but scope not matching exactly",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "exact", "required_scope": ["scope-a", "scope-b", "scope-c"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Expand All @@ -217,17 +283,83 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) {
}))
})
},
expectErr: true,
},
{
d: "should pass because active and scope matching hierarchically",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "hierarchic", "required_scope": ["scope-a", "scope-b.foo", "scope-c.bar"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-a scope-b scope-c.bar",
}))
})
},
expectErr: false,
},
{
d: "should fail because active but scope not matching hierarchically",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "hierarchic", "required_scope": ["scope-a", "scope-b.foo", "scope-c.bar"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-a scope-b",
}))
})
},
expectErr: true,
},
{
d: "should pass because active and scope matching wildcard",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "scope_strategy": "wildcard", "required_scope": ["scope-a", "scope-b.foo", "scope-c.bar"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Audience: []string{"audience"},
Issuer: "issuer",
Username: "username",
Extra: map[string]interface{}{"extra": "foo"},
Scope: "scope-a scope-b.* scope-c.bar",
}))
})
},
expectErr: false,
},
{
d: "should fail because active but scope not matching",
d: "should fail because active but scope not matching wildcard",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}},
config: []byte(`{ "required_scope": ["scope-a", "scope-b", "scope-c"] }`),
config: []byte(`{ "scope_strategy": "wildcard", "required_scope": ["scope-a", "scope-b.foo", "scope-c.bar"] }`),
setup: func(t *testing.T, m *httprouter.Router) {
m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
require.NoError(t, r.ParseForm())
require.Equal(t, "token", r.Form.Get("token"))
require.Equal(t, "scope-a scope-b scope-c", r.Form.Get("scope"))
require.Equal(t, "", r.Form.Get("scope"))
require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{
Active: true,
Subject: "subject",
Expand Down Expand Up @@ -331,7 +463,6 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) {
defer ts.Close()

tc.config, _ = sjson.SetBytes(tc.config, "introspection_url", ts.URL+"/oauth2/introspect")
tc.config, _ = sjson.SetBytes(tc.config, "scope_strategy", "exact")
sess := new(AuthenticationSession)
err := a.Authenticate(tc.r, sess, tc.config, nil)
if tc.expectErr {
Expand Down

0 comments on commit 5e0c8dc

Please sign in to comment.