ContainerImage.Pinniped/internal/oidc/provider/manager.go

116 lines
3.8 KiB
Go
Raw Normal View History

// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package provider
import (
"net/http"
"net/url"
"strings"
"sync"
"k8s.io/klog/v2"
"go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/discovery"
)
// Manager can manage multiple active OIDC providers. It acts as a request router for them.
//
// It is thread-safe.
type Manager struct {
mu sync.RWMutex
providerHandlers map[string]*providerHandler // map of issuer name to providerHandler
nextHandler http.Handler // the next handler in a chain, called when this manager didn't know how to handle a request
}
// New returns an empty Manager.
// nextHandler will be invoked for any requests that could not be handled by this manager's providers.
func NewManager(nextHandler http.Handler) *Manager {
return &Manager{providerHandlers: make(map[string]*providerHandler), nextHandler: nextHandler}
}
type providerHandler struct {
provider *OIDCProvider
discoveryHandler http.Handler
}
func (h *providerHandler) Issuer() *url.URL {
return h.provider.Issuer
}
// SetProviders adds or updates all the given providerHandlers using each provider's issuer string
// as the name of the provider to decide if it is an add or update operation.
//
// It also removes any providerHandlers that were previously added but were not passed in to
// the current invocation.
//
// This method assumes that all of the OIDCProvider arguments have already been validated
// by someone else before they are passed to this method.
func (c *Manager) SetProviders(oidcProviders ...*OIDCProvider) {
c.mu.Lock()
defer c.mu.Unlock()
// Add all of the incoming providers.
for _, incomingProvider := range oidcProviders {
issuerString := incomingProvider.Issuer.String()
c.providerHandlers[issuerString] = &providerHandler{
provider: incomingProvider,
discoveryHandler: discovery.New(issuerString),
}
klog.InfoS("oidc provider manager added or updated issuer", "issuer", issuerString)
}
// Remove any providers that we previously handled but no longer exist.
for issuerKey := range c.providerHandlers {
if !findIssuerInListOfProviders(issuerKey, oidcProviders) {
delete(c.providerHandlers, issuerKey)
klog.InfoS("oidc provider manager removed issuer", "issuer", issuerKey)
}
}
}
// ServeHTTP implements the http.Handler interface.
func (c *Manager) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
providerHandler := c.findProviderHandlerByIssuerURL(req.Host, req.URL.Path)
if providerHandler != nil {
if req.URL.Path == providerHandler.Issuer().Path+oidc.WellKnownURLPath {
providerHandler.discoveryHandler.ServeHTTP(resp, req)
return // handled!
}
klog.InfoS(
"oidc provider manager found issuer but could not handle request",
"method", req.Method,
"host", req.Host,
"path", req.URL.Path,
)
} else {
klog.InfoS(
"oidc provider manager could not find issuer to handle request",
"method", req.Method,
"host", req.Host,
"path", req.URL.Path,
)
}
// Didn't know how to handle this request, so send it along the chain for further processing.
c.nextHandler.ServeHTTP(resp, req)
}
func (c *Manager) findProviderHandlerByIssuerURL(host, path string) *providerHandler {
for _, providerHandler := range c.providerHandlers {
pi := providerHandler.Issuer()
// TODO do we need to compare scheme? not sure how to get it from the http.Request object
if host == pi.Host && strings.HasPrefix(path, pi.Path) { // TODO probably need better logic here? also maybe needs some of the logic from inside ServeMux
return providerHandler
}
}
return nil
}
func findIssuerInListOfProviders(issuer string, oidcProviders []*OIDCProvider) bool {
for _, provider := range oidcProviders {
if provider.Issuer.String() == issuer {
return true
}
}
return false
}