ContainerImage.Pinniped/internal/oidc/auth/auth_handler.go

112 lines
3.3 KiB
Go
Raw Normal View History

// 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"
"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,
generateState func() (state.State, error),
generatePKCE func() (pkce.Code, error),
generateNonce func() (nonce.Nonce, error),
) http.Handler {
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)
}
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
}