Add check for grant type in tokenexchangehandler,
- also started writing a test for the tokenexchangehandler, skipping for now Signed-off-by: Ryan Richard <rrichard@vmware.com>
This commit is contained in:
parent
ef3f837800
commit
f103c02408
@ -27,7 +27,7 @@ func TestNullStorage_GetClient(t *testing.T) {
|
|||||||
Public: true,
|
Public: true,
|
||||||
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
||||||
ResponseTypes: []string{"code"},
|
ResponseTypes: []string{"code"},
|
||||||
GrantTypes: []string{"authorization_code", "refresh_token"},
|
GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"},
|
||||||
Scopes: []string{"openid", "offline_access", "profile", "email"},
|
Scopes: []string{"openid", "offline_access", "profile", "email"},
|
||||||
},
|
},
|
||||||
TokenEndpointAuthMethod: "none",
|
TokenEndpointAuthMethod: "none",
|
||||||
|
@ -84,8 +84,8 @@ func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient {
|
|||||||
Public: true,
|
Public: true,
|
||||||
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
||||||
ResponseTypes: []string{"code"},
|
ResponseTypes: []string{"code"},
|
||||||
GrantTypes: []string{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange"},
|
GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"},
|
||||||
Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email"},
|
Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email", "pinniped.sts.unrestricted"},
|
||||||
},
|
},
|
||||||
TokenEndpointAuthMethod: "none",
|
TokenEndpointAuthMethod: "none",
|
||||||
}
|
}
|
||||||
@ -126,12 +126,12 @@ func FositeOauth2Helper(
|
|||||||
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
||||||
},
|
},
|
||||||
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
||||||
TokenExchangeFactory,
|
|
||||||
compose.OAuth2AuthorizeExplicitFactory,
|
compose.OAuth2AuthorizeExplicitFactory,
|
||||||
// compose.OAuth2RefreshTokenGrantFactory,
|
// compose.OAuth2RefreshTokenGrantFactory,
|
||||||
compose.OpenIDConnectExplicitFactory,
|
compose.OpenIDConnectExplicitFactory,
|
||||||
// compose.OpenIDConnectRefreshFactory,
|
// compose.OpenIDConnectRefreshFactory,
|
||||||
compose.OAuth2PKCEFactory,
|
compose.OAuth2PKCEFactory,
|
||||||
|
TokenExchangeFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +206,19 @@ var (
|
|||||||
"redirect_uri": {goodRedirectURI},
|
"redirect_uri": {goodRedirectURI},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
happyTokenExchangeRequest = func(audience string, subjectToken string) *http.Request {
|
||||||
|
return &http.Request{
|
||||||
|
Form: url.Values{
|
||||||
|
"grant_type": {"urn:ietf:params:oauth:grant-type:token-exchange"},
|
||||||
|
"audience": {audience},
|
||||||
|
"subject_token": {subjectToken},
|
||||||
|
"subject_token_type": {"urn:ietf:params:oauth:token-type:access_token"},
|
||||||
|
"requested_token_type": {"urn:ietf:params:oauth:token-type:jwt"},
|
||||||
|
"client_id": {goodClient},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type authcodeExchangeInputs struct {
|
type authcodeExchangeInputs struct {
|
||||||
@ -550,6 +563,55 @@ func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenExchange(t *testing.T) {
|
||||||
|
// TODO write this test
|
||||||
|
t.Skip()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
authcodeExchange authcodeExchangeInputs
|
||||||
|
wantStatus int
|
||||||
|
requestedAudience string
|
||||||
|
modifyTokenExchangeRequest func(r *http.Request)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "token exchange happy path",
|
||||||
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
|
modifyAuthRequest: func(authRequest *http.Request) {
|
||||||
|
authRequest.Form.Set("scope", "openid pinniped.sts.unrestricted")
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantBodyFields: []string{"id_token", "access_token", "token_type", "expires_in", "scope"},
|
||||||
|
wantRequestedScopes: []string{"openid", "pinniped.sts.unrestricted"},
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
subject, rsp, _, _, _ := exchangeAuthcodeForTokens(t, test.authcodeExchange)
|
||||||
|
var parsedResponseBody map[string]interface{}
|
||||||
|
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedResponseBody))
|
||||||
|
|
||||||
|
request := happyTokenExchangeRequest("foo-cluster", parsedResponseBody["access_token"].(string))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("POST", "/path/shouldn't/matter", body(request.Form).ReadCloser())
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if test.modifyTokenExchangeRequest != nil {
|
||||||
|
test.modifyTokenExchangeRequest(req)
|
||||||
|
}
|
||||||
|
rsp = httptest.NewRecorder()
|
||||||
|
|
||||||
|
subject.ServeHTTP(rsp, req)
|
||||||
|
t.Logf("response: %#v", rsp)
|
||||||
|
t.Logf("response body: %q", rsp.Body.String())
|
||||||
|
|
||||||
|
require.Equal(t, test.wantStatus, rsp.Code)
|
||||||
|
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), "application/json")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
|
func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
|
||||||
subject http.Handler,
|
subject http.Handler,
|
||||||
rsp *httptest.ResponseRecorder,
|
rsp *httptest.ResponseRecorder,
|
||||||
@ -758,6 +820,9 @@ func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Reques
|
|||||||
if strings.Contains(authRequest.Form.Get("scope"), "offline_access") {
|
if strings.Contains(authRequest.Form.Get("scope"), "offline_access") {
|
||||||
authRequester.GrantScope("offline_access")
|
authRequester.GrantScope("offline_access")
|
||||||
}
|
}
|
||||||
|
if strings.Contains(authRequest.Form.Get("scope"), "pinniped.sts.unrestricted") {
|
||||||
|
authRequester.GrantScope("pinniped.sts.unrestricted")
|
||||||
|
}
|
||||||
authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session)
|
authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return authResponder
|
return authResponder
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -35,6 +38,9 @@ func (t *TokenExchangeHandler) HandleTokenEndpointRequest(ctx context.Context, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
|
func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
|
||||||
|
if !(requester.GetGrantTypes().ExactOne("urn:ietf:params:oauth:grant-type:token-exchange")) {
|
||||||
|
return errors.WithStack(fosite.ErrUnknownRequest)
|
||||||
|
}
|
||||||
params := requester.GetRequestForm()
|
params := requester.GetRequestForm()
|
||||||
accessToken := params.Get("subject_token")
|
accessToken := params.Get("subject_token")
|
||||||
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, requester, accessToken); err != nil {
|
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, requester, accessToken); err != nil {
|
||||||
@ -49,7 +55,7 @@ func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context
|
|||||||
return errors.WithStack(fosite.ErrScopeNotGranted)
|
return errors.WithStack(fosite.ErrScopeNotGranted)
|
||||||
}
|
}
|
||||||
// TODO check the other requester fields
|
// TODO check the other requester fields
|
||||||
scopedDownRequester := fosite.NewAccessRequest(requester.GetSession())
|
scopedDownRequester := fosite.NewAccessRequest(accessTokenSession.GetSession())
|
||||||
scopedDownRequester.GrantedAudience = []string{params.Get("audience")}
|
scopedDownRequester.GrantedAudience = []string{params.Get("audience")}
|
||||||
newToken, err := t.idTokenStrategy.GenerateIDToken(ctx, scopedDownRequester)
|
newToken, err := t.idTokenStrategy.GenerateIDToken(ctx, scopedDownRequester)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user