2020-11-04 00:17:38 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// Package auth provides a handler for the OIDC authorization endpoint.
|
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
2020-11-04 15:35:26 +00:00
|
|
|
"github.com/ory/fosite/compose"
|
2020-11-04 00:17:38 +00:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
"k8s.io/klog/v2"
|
|
|
|
|
|
|
|
"go.pinniped.dev/internal/httputil/httperr"
|
|
|
|
"go.pinniped.dev/internal/oidc/provider"
|
|
|
|
"go.pinniped.dev/internal/oidcclient/nonce"
|
|
|
|
"go.pinniped.dev/internal/oidcclient/pkce"
|
|
|
|
"go.pinniped.dev/internal/oidcclient/state"
|
|
|
|
)
|
|
|
|
|
|
|
|
type IDPListGetter interface {
|
|
|
|
GetIDPList() []provider.UpstreamOIDCIdentityProvider
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewHandler(
|
|
|
|
issuer string,
|
|
|
|
idpListGetter IDPListGetter,
|
2020-11-04 15:35:26 +00:00
|
|
|
oauthStore interface{},
|
2020-11-04 00:17:38 +00:00
|
|
|
generateState func() (state.State, error),
|
|
|
|
generatePKCE func() (pkce.Code, error),
|
|
|
|
generateNonce func() (nonce.Nonce, error),
|
|
|
|
) http.Handler {
|
2020-11-04 15:35:26 +00:00
|
|
|
oauthHelper := compose.Compose(
|
2020-11-04 16:20:03 +00:00
|
|
|
// 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.
|
2020-11-04 15:35:26 +00:00
|
|
|
&compose.Config{},
|
2020-11-04 16:20:03 +00:00
|
|
|
|
|
|
|
// This is the thing that matters right now - the store is used to get information about the
|
|
|
|
// client in the authorization request.
|
2020-11-04 15:35:26 +00:00
|
|
|
oauthStore,
|
2020-11-04 16:20:03 +00:00
|
|
|
|
|
|
|
// 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{},
|
|
|
|
|
|
|
|
// 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,
|
2020-11-04 15:35:26 +00:00
|
|
|
)
|
2020-11-04 00:17:38 +00:00
|
|
|
return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
if r.Method != http.MethodPost && r.Method != http.MethodGet {
|
|
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
|
|
|
// Authorization Servers MUST support the use of the HTTP GET and POST methods defined in
|
|
|
|
// RFC 2616 [RFC2616] at the Authorization Endpoint.
|
|
|
|
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
|
|
|
|
}
|
|
|
|
|
2020-11-04 15:15:19 +00:00
|
|
|
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(
|
|
|
|
r.Context(), // TODO: maybe another context here since this one will expire?
|
|
|
|
r,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
oauthHelper.WriteAuthorizeError(w, authorizeRequester, err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-04 00:17:38 +00:00
|
|
|
upstreamIDP, err := chooseUpstreamIDP(idpListGetter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stateValue, nonceValue, pkceValue, err := generateParams(generateState, generateNonce, generatePKCE)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
upstreamOAuthConfig := oauth2.Config{
|
|
|
|
ClientID: upstreamIDP.ClientID,
|
|
|
|
Endpoint: oauth2.Endpoint{
|
|
|
|
AuthURL: upstreamIDP.AuthorizationURL.String(),
|
|
|
|
},
|
|
|
|
RedirectURL: fmt.Sprintf("%s/callback/%s", issuer, upstreamIDP.Name),
|
|
|
|
Scopes: upstreamIDP.Scopes,
|
|
|
|
}
|
|
|
|
|
|
|
|
http.Redirect(w, r,
|
|
|
|
upstreamOAuthConfig.AuthCodeURL(
|
|
|
|
stateValue.String(),
|
|
|
|
oauth2.AccessTypeOffline,
|
|
|
|
nonceValue.Param(),
|
|
|
|
pkceValue.Challenge(),
|
|
|
|
pkceValue.Method(),
|
|
|
|
),
|
|
|
|
302,
|
|
|
|
)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func chooseUpstreamIDP(idpListGetter IDPListGetter) (*provider.UpstreamOIDCIdentityProvider, error) {
|
|
|
|
allUpstreamIDPs := idpListGetter.GetIDPList()
|
|
|
|
if len(allUpstreamIDPs) == 0 {
|
|
|
|
return nil, httperr.New(
|
|
|
|
http.StatusUnprocessableEntity,
|
|
|
|
"No upstream providers are configured",
|
|
|
|
)
|
|
|
|
} else if len(allUpstreamIDPs) > 1 {
|
|
|
|
return nil, httperr.New(
|
|
|
|
http.StatusUnprocessableEntity,
|
|
|
|
"Too many upstream providers are configured (support for multiple upstreams is not yet implemented)",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return &allUpstreamIDPs[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateParams(
|
|
|
|
generateState func() (state.State, error),
|
|
|
|
generateNonce func() (nonce.Nonce, error),
|
|
|
|
generatePKCE func() (pkce.Code, error),
|
|
|
|
) (state.State, nonce.Nonce, pkce.Code, error) {
|
|
|
|
stateValue, err := generateState()
|
|
|
|
if err != nil {
|
|
|
|
klog.InfoS("error generating state param", "err", err)
|
|
|
|
return "", "", "", httperr.Wrap(http.StatusInternalServerError, "error generating state param", err)
|
|
|
|
}
|
|
|
|
nonceValue, err := generateNonce()
|
|
|
|
if err != nil {
|
|
|
|
klog.InfoS("error generating nonce param", "err", err)
|
|
|
|
return "", "", "", httperr.Wrap(http.StatusInternalServerError, "error generating nonce param", err)
|
|
|
|
}
|
|
|
|
pkceValue, err := generatePKCE()
|
|
|
|
if err != nil {
|
|
|
|
klog.InfoS("error generating PKCE param", "err", err)
|
|
|
|
return "", "", "", httperr.Wrap(http.StatusInternalServerError, "error generating PKCE param", err)
|
|
|
|
}
|
|
|
|
return stateValue, nonceValue, pkceValue, nil
|
|
|
|
}
|