2021-04-28 20:14:21 +00:00
|
|
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
2020-10-06 14:11:57 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// Package discovery provides a handler for the OIDC discovery endpoint.
|
|
|
|
package discovery
|
|
|
|
|
|
|
|
import (
|
2021-03-03 18:37:43 +00:00
|
|
|
"bytes"
|
2020-10-06 14:11:57 +00:00
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
2021-04-28 20:14:21 +00:00
|
|
|
"sort"
|
2020-10-08 18:28:21 +00:00
|
|
|
|
|
|
|
"go.pinniped.dev/internal/oidc"
|
2020-10-06 14:11:57 +00:00
|
|
|
)
|
|
|
|
|
2021-04-28 20:14:21 +00:00
|
|
|
const (
|
|
|
|
idpDiscoveryTypeLDAP = "ldap"
|
|
|
|
idpDiscoveryTypeOIDC = "oidc"
|
|
|
|
)
|
|
|
|
|
2020-10-06 14:11:57 +00:00
|
|
|
// Metadata holds all fields (that we care about) from the OpenID Provider Metadata section in the
|
|
|
|
// OpenID Connect Discovery specification:
|
|
|
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3.
|
|
|
|
type Metadata struct {
|
2020-10-08 02:18:34 +00:00
|
|
|
// vvv Required vvv
|
2020-10-07 15:33:50 +00:00
|
|
|
|
2020-10-06 14:11:57 +00:00
|
|
|
Issuer string `json:"issuer"`
|
|
|
|
|
|
|
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
|
|
|
TokenEndpoint string `json:"token_endpoint"`
|
2020-10-07 15:33:50 +00:00
|
|
|
JWKSURI string `json:"jwks_uri"`
|
2020-10-06 14:11:57 +00:00
|
|
|
|
|
|
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
|
|
|
SubjectTypesSupported []string `json:"subject_types_supported"`
|
|
|
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
2020-10-07 15:33:50 +00:00
|
|
|
|
2020-10-08 02:18:34 +00:00
|
|
|
// ^^^ Required ^^^
|
2020-10-07 15:33:50 +00:00
|
|
|
|
2020-10-08 02:18:34 +00:00
|
|
|
// vvv Optional vvv
|
2020-10-07 15:33:50 +00:00
|
|
|
|
2020-12-07 22:15:31 +00:00
|
|
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
|
|
|
ScopesSupported []string `json:"scopes_supported"`
|
|
|
|
ClaimsSupported []string `json:"claims_supported"`
|
2020-10-07 15:33:50 +00:00
|
|
|
|
2020-10-08 02:18:34 +00:00
|
|
|
// ^^^ Optional ^^^
|
2020-10-06 14:11:57 +00:00
|
|
|
|
2021-04-28 20:14:21 +00:00
|
|
|
// vvv Custom vvv
|
2020-10-06 14:11:57 +00:00
|
|
|
|
2021-04-28 20:14:21 +00:00
|
|
|
IDPs []IdentityProviderMetadata `json:"pinniped_idps"`
|
|
|
|
|
|
|
|
// ^^^ Custom ^^^
|
|
|
|
}
|
2021-03-03 18:37:43 +00:00
|
|
|
|
2021-04-28 20:14:21 +00:00
|
|
|
type IdentityProviderMetadata struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewHandler returns an http.Handler that serves an OIDC discovery endpoint.
|
|
|
|
func NewHandler(issuerURL string, upstreamIDPs oidc.UpstreamIdentityProvidersLister) http.Handler {
|
2021-03-03 18:37:43 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2020-10-07 14:53:05 +00:00
|
|
|
if r.Method != http.MethodGet {
|
2020-10-17 00:51:40 +00:00
|
|
|
http.Error(w, `Method not allowed (try GET)`, http.StatusMethodNotAllowed)
|
2020-10-06 14:11:57 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-28 20:14:21 +00:00
|
|
|
encodedMetadata, encodeErr := metadata(issuerURL, upstreamIDPs)
|
2021-03-03 18:37:43 +00:00
|
|
|
if encodeErr != nil {
|
|
|
|
http.Error(w, encodeErr.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
2020-10-06 14:11:57 +00:00
|
|
|
}
|
2021-03-03 18:37:43 +00:00
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
if _, err := w.Write(encodedMetadata); err != nil {
|
2020-10-06 14:11:57 +00:00
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
2021-03-03 18:37:43 +00:00
|
|
|
return
|
2020-10-06 14:11:57 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2021-04-28 20:14:21 +00:00
|
|
|
|
|
|
|
func metadata(issuerURL string, upstreamIDPs oidc.UpstreamIdentityProvidersLister) ([]byte, error) {
|
|
|
|
oidcConfig := Metadata{
|
|
|
|
Issuer: issuerURL,
|
|
|
|
AuthorizationEndpoint: issuerURL + oidc.AuthorizationEndpointPath,
|
|
|
|
TokenEndpoint: issuerURL + oidc.TokenEndpointPath,
|
|
|
|
JWKSURI: issuerURL + oidc.JWKSEndpointPath,
|
|
|
|
ResponseTypesSupported: []string{"code"},
|
|
|
|
SubjectTypesSupported: []string{"public"},
|
|
|
|
IDTokenSigningAlgValuesSupported: []string{"ES256"},
|
|
|
|
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
|
|
|
|
ScopesSupported: []string{"openid", "offline"},
|
|
|
|
ClaimsSupported: []string{"groups"},
|
|
|
|
IDPs: []IdentityProviderMetadata{},
|
|
|
|
}
|
|
|
|
|
|
|
|
// The cache of IDPs could change at any time, so always recalculate the list.
|
|
|
|
for _, provider := range upstreamIDPs.GetLDAPIdentityProviders() {
|
|
|
|
oidcConfig.IDPs = append(oidcConfig.IDPs, IdentityProviderMetadata{Name: provider.GetName(), Type: idpDiscoveryTypeLDAP})
|
|
|
|
}
|
|
|
|
for _, provider := range upstreamIDPs.GetOIDCIdentityProviders() {
|
|
|
|
oidcConfig.IDPs = append(oidcConfig.IDPs, IdentityProviderMetadata{Name: provider.GetName(), Type: idpDiscoveryTypeOIDC})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nobody like an API that changes the results unnecessarily. :)
|
|
|
|
sort.SliceStable(oidcConfig.IDPs, func(i, j int) bool {
|
|
|
|
return oidcConfig.IDPs[i].Name < oidcConfig.IDPs[j].Name
|
|
|
|
})
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
encodeErr := json.NewEncoder(&b).Encode(&oidcConfig)
|
|
|
|
encodedMetadata := b.Bytes()
|
|
|
|
|
|
|
|
return encodedMetadata, encodeErr
|
|
|
|
}
|