Test that the port of localhost redirect URI is ignored during validation
Also move definition of our oauth client and the general fosite configuration to a helper so we can use the same config to construct the handler for both test and production code. Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
parent
ba688f56aa
commit
a36f7c6c07
@ -10,7 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
|
|
||||||
"github.com/ory/fosite/compose"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
@ -28,39 +29,11 @@ type IDPListGetter interface {
|
|||||||
func NewHandler(
|
func NewHandler(
|
||||||
issuer string,
|
issuer string,
|
||||||
idpListGetter IDPListGetter,
|
idpListGetter IDPListGetter,
|
||||||
oauthStore interface{},
|
oauthHelper fosite.OAuth2Provider,
|
||||||
generateState func() (state.State, error),
|
generateState func() (state.State, error),
|
||||||
generatePKCE func() (pkce.Code, error),
|
generatePKCE func() (pkce.Code, error),
|
||||||
generateNonce func() (nonce.Nonce, error),
|
generateNonce func() (nonce.Nonce, error),
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
oauthConfig := &compose.Config{
|
|
||||||
EnforcePKCEForPublicClients: true,
|
|
||||||
}
|
|
||||||
secret := []byte("some-cool-secret-that-is-32bytes") // TODO use a real secret once we care about real authorization codes
|
|
||||||
oauthHelper := compose.Compose(
|
|
||||||
// Empty Config for right now since we aren't using anything in it. We may want to inject this
|
|
||||||
// in the future since it has some really nice configuration knobs like token lifetime.
|
|
||||||
oauthConfig,
|
|
||||||
|
|
||||||
// This is the thing that matters right now - the store is used to get information about the
|
|
||||||
// client in the authorization request.
|
|
||||||
oauthStore,
|
|
||||||
|
|
||||||
// Shouldn't need any of this filled in as of right now - we aren't doing auth code stuff,
|
|
||||||
// issuing ID tokens, or signing anything yet.
|
|
||||||
&compose.CommonStrategy{
|
|
||||||
CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, secret, nil),
|
|
||||||
},
|
|
||||||
|
|
||||||
// hasher, shouldn't need this right now - we aren't doing any client auth...yet?
|
|
||||||
nil,
|
|
||||||
|
|
||||||
// We will _probably_ want the below handlers somewhere in the code, but I'm not sure where yet,
|
|
||||||
// and we don't need them for the tests to pass currently, so they are commented out.
|
|
||||||
compose.OAuth2AuthorizeExplicitFactory,
|
|
||||||
// compose.OpenIDConnectExplicitFactory,
|
|
||||||
compose.OAuth2PKCEFactory,
|
|
||||||
)
|
|
||||||
return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
if r.Method != http.MethodPost && r.Method != http.MethodGet {
|
if r.Method != http.MethodPost && r.Method != http.MethodGet {
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||||
@ -69,21 +42,7 @@ func NewHandler(
|
|||||||
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
|
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(
|
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
||||||
r.Context(), // TODO: maybe another context here since this one will expire?
|
|
||||||
r,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform validations
|
|
||||||
_, err = oauthHelper.NewAuthorizeResponse(
|
|
||||||
r.Context(), // TODO: maybe another context here since this one will expire?
|
|
||||||
authorizeRequester,
|
|
||||||
&openid.DefaultSession{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
||||||
return nil
|
return nil
|
||||||
@ -94,6 +53,12 @@ func NewHandler(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{})
|
||||||
|
if err != nil {
|
||||||
|
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
stateValue, nonceValue, pkceValue, err := generateParams(generateState, generateNonce, generatePKCE)
|
stateValue, nonceValue, pkceValue, err := generateParams(generateState, generateNonce, generatePKCE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/here"
|
"go.pinniped.dev/internal/here"
|
||||||
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/oidcclient/nonce"
|
"go.pinniped.dev/internal/oidcclient/nonce"
|
||||||
"go.pinniped.dev/internal/oidcclient/pkce"
|
"go.pinniped.dev/internal/oidcclient/pkce"
|
||||||
@ -112,22 +113,15 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
issuer := "https://my-issuer.com/some-path"
|
issuer := "https://my-issuer.com/some-path"
|
||||||
|
|
||||||
|
// Configure fosite the same way that the production code would, except use in-memory storage.
|
||||||
oauthStore := &storage.MemoryStore{
|
oauthStore := &storage.MemoryStore{
|
||||||
Clients: map[string]fosite.Client{
|
Clients: map[string]fosite.Client{oidc.PinnipedCLIOIDCClient().ID: oidc.PinnipedCLIOIDCClient()},
|
||||||
"pinniped-cli": &fosite.DefaultOpenIDConnectClient{
|
|
||||||
DefaultClient: &fosite.DefaultClient{
|
|
||||||
ID: "pinniped-cli",
|
|
||||||
Public: true,
|
|
||||||
RedirectURIs: []string{downstreamRedirectURI},
|
|
||||||
ResponseTypes: []string{"code"},
|
|
||||||
GrantTypes: []string{"authorization_code"},
|
|
||||||
Scopes: []string{"openid", "profile", "email"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AuthorizeCodes: map[string]storage.StoreAuthorizeCode{},
|
AuthorizeCodes: map[string]storage.StoreAuthorizeCode{},
|
||||||
PKCES: map[string]fosite.Requester{},
|
PKCES: map[string]fosite.Requester{},
|
||||||
}
|
}
|
||||||
|
hmacSecret := []byte("some secret - must have at least 32 bytes")
|
||||||
|
require.GreaterOrEqual(t, len(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, hmacSecret)
|
||||||
|
|
||||||
happyStateGenerator := func() (state.State, error) { return "test-state", nil }
|
happyStateGenerator := func() (state.State, error) { return "test-state", nil }
|
||||||
happyPKCEGenerator := func() (pkce.Code, error) { return "test-pkce", nil }
|
happyPKCEGenerator := func() (pkce.Code, error) { return "test-pkce", nil }
|
||||||
@ -291,6 +285,25 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantContentType: "application/json; charset=utf-8",
|
wantContentType: "application/json; charset=utf-8",
|
||||||
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
wantBodyJSON: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "downstream redirect uri matches what is configured for client except for the port number",
|
||||||
|
issuer: issuer,
|
||||||
|
idpListGetter: newIDPListGetter(upstreamOIDCIdentityProvider),
|
||||||
|
generateState: happyStateGenerator,
|
||||||
|
generatePKCE: happyPKCEGenerator,
|
||||||
|
generateNonce: happyNonceGenerator,
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: modifiedHappyGetRequestPath(map[string]string{
|
||||||
|
"redirect_uri": "http://127.0.0.1:42/callback",
|
||||||
|
}),
|
||||||
|
wantStatus: http.StatusFound,
|
||||||
|
wantContentType: "text/html; charset=utf-8",
|
||||||
|
wantBodyString: fmt.Sprintf(`<a href="%s">Found</a>.%s`,
|
||||||
|
html.EscapeString(happyGetRequestExpectedRedirectLocation),
|
||||||
|
"\n\n",
|
||||||
|
),
|
||||||
|
wantLocationHeader: happyGetRequestExpectedRedirectLocation,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "response type is unsupported",
|
name: "response type is unsupported",
|
||||||
issuer: issuer,
|
issuer: issuer,
|
||||||
@ -534,7 +547,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
subject := NewHandler(test.issuer, test.idpListGetter, oauthStore, test.generateState, test.generatePKCE, test.generateNonce)
|
subject := NewHandler(test.issuer, test.idpListGetter, oauthHelper, test.generateState, test.generatePKCE, test.generateNonce)
|
||||||
runOneTestCase(t, test, subject)
|
runOneTestCase(t, test, subject)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -543,7 +556,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
test := tests[0]
|
test := tests[0]
|
||||||
require.Equal(t, "happy path using GET", test.name) // re-use the happy path test case
|
require.Equal(t, "happy path using GET", test.name) // re-use the happy path test case
|
||||||
|
|
||||||
subject := NewHandler(test.issuer, test.idpListGetter, oauthStore, test.generateState, test.generatePKCE, test.generateNonce)
|
subject := NewHandler(test.issuer, test.idpListGetter, oauthHelper, test.generateState, test.generatePKCE, test.generateNonce)
|
||||||
|
|
||||||
runOneTestCase(t, test, subject)
|
runOneTestCase(t, test, subject)
|
||||||
|
|
||||||
|
@ -4,9 +4,48 @@
|
|||||||
// Package oidc contains common OIDC functionality needed by Pinniped.
|
// Package oidc contains common OIDC functionality needed by Pinniped.
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/fosite/compose"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WellKnownEndpointPath = "/.well-known/openid-configuration"
|
WellKnownEndpointPath = "/.well-known/openid-configuration"
|
||||||
AuthorizationEndpointPath = "/oauth2/authorize"
|
AuthorizationEndpointPath = "/oauth2/authorize"
|
||||||
TokenEndpointPath = "/oauth2/token" //nolint:gosec // ignore lint warning that this is a credential
|
TokenEndpointPath = "/oauth2/token" //nolint:gosec // ignore lint warning that this is a credential
|
||||||
JWKSEndpointPath = "/jwks.json"
|
JWKSEndpointPath = "/jwks.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient {
|
||||||
|
return &fosite.DefaultOpenIDConnectClient{
|
||||||
|
DefaultClient: &fosite.DefaultClient{
|
||||||
|
ID: "pinniped-cli",
|
||||||
|
Public: true,
|
||||||
|
RedirectURIs: []string{"http://127.0.0.1/callback"},
|
||||||
|
ResponseTypes: []string{"code"},
|
||||||
|
GrantTypes: []string{"authorization_code"},
|
||||||
|
Scopes: []string{"openid", "profile", "email"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that Fosite requires the HMAC secret to be 32 bytes.
|
||||||
|
func FositeOauth2Helper(oauthStore interface{}, hmacSecretOfLength32 []byte) fosite.OAuth2Provider {
|
||||||
|
oauthConfig := &compose.Config{
|
||||||
|
EnforcePKCEForPublicClients: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return compose.Compose(
|
||||||
|
oauthConfig,
|
||||||
|
oauthStore,
|
||||||
|
&compose.CommonStrategy{
|
||||||
|
CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, hmacSecretOfLength32, nil),
|
||||||
|
},
|
||||||
|
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
||||||
|
compose.OAuth2AuthorizeExplicitFactory,
|
||||||
|
// compose.OAuth2RefreshTokenGrantFactory,
|
||||||
|
// compose.OpenIDConnectExplicitFactory,
|
||||||
|
// compose.OpenIDConnectRefreshFactory,
|
||||||
|
compose.OAuth2PKCEFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user