1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-01-24 05:26:55 +02:00
oauth2-proxy/providers/provider_data.go

219 lines
6.3 KiB
Go

package providers
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/util"
"golang.org/x/oauth2"
)
const (
// This is not exported as it's not currently user configurable
oidcUserClaim = "sub"
)
// ProviderData contains information required to configure all implementations
// of OAuth2 providers
type ProviderData struct {
ProviderName string
LoginURL *url.URL
RedeemURL *url.URL
ProfileURL *url.URL
ProtectedResource *url.URL
ValidateURL *url.URL
// Auth request params & related, see
//https://openid.net/specs/openid-connect-basic-1_0.html#rfc.section.2.1.1.1
AcrValues string
ApprovalPrompt string // NOTE: Renamed to "prompt" in OAuth2
ClientID string
ClientSecret string
ClientSecretFile string
Scope string
Prompt string
// Common OIDC options for any OIDC-based providers to consume
AllowUnverifiedEmail bool
UserClaim string
EmailClaim string
GroupsClaim string
Verifier *internaloidc.IDTokenVerifier
// Universal Group authorization data structure
// any provider can set to consume
AllowedGroups map[string]struct{}
getAuthorizationHeaderFunc func(string) http.Header
}
// Data returns the ProviderData
func (p *ProviderData) Data() *ProviderData { return p }
func (p *ProviderData) GetClientSecret() (clientSecret string, err error) {
if p.ClientSecret != "" || p.ClientSecretFile == "" {
return p.ClientSecret, nil
}
// Getting ClientSecret can fail in runtime so we need to report it without returning the file name to the user
fileClientSecret, err := ioutil.ReadFile(p.ClientSecretFile)
if err != nil {
logger.Errorf("error reading client secret file %s: %s", p.ClientSecretFile, err)
return "", errors.New("could not read client secret file")
}
return string(fileClientSecret), nil
}
// setAllowedGroups organizes a group list into the AllowedGroups map
// to be consumed by Authorize implementations
func (p *ProviderData) setAllowedGroups(groups []string) {
p.AllowedGroups = make(map[string]struct{}, len(groups))
for _, group := range groups {
p.AllowedGroups[group] = struct{}{}
}
}
type providerDefaults struct {
name string
loginURL *url.URL
redeemURL *url.URL
profileURL *url.URL
validateURL *url.URL
scope string
}
func (p *ProviderData) setProviderDefaults(defaults providerDefaults) {
p.ProviderName = defaults.name
p.LoginURL = defaultURL(p.LoginURL, defaults.loginURL)
p.RedeemURL = defaultURL(p.RedeemURL, defaults.redeemURL)
p.ProfileURL = defaultURL(p.ProfileURL, defaults.profileURL)
p.ValidateURL = defaultURL(p.ValidateURL, defaults.validateURL)
if p.Scope == "" {
p.Scope = defaults.scope
}
if p.UserClaim == "" {
p.UserClaim = oidcUserClaim
}
}
// defaultURL will set return a default value if the given value is not set.
func defaultURL(u *url.URL, d *url.URL) *url.URL {
if u != nil && u.String() != "" {
// The value is already set
return u
}
// If the default is given, return that
if d != nil {
return d
}
return &url.URL{}
}
// ****************************************************************************
// These private OIDC helper methods are available to any providers that are
// OIDC compliant
// ****************************************************************************
func (p *ProviderData) verifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
rawIDToken := getIDToken(token)
if strings.TrimSpace(rawIDToken) == "" {
return nil, ErrMissingIDToken
}
if p.Verifier == nil {
return nil, ErrMissingOIDCVerifier
}
return p.Verifier.Verify(ctx, rawIDToken)
}
// buildSessionFromClaims uses IDToken claims to populate a fresh SessionState
// with non-Token related fields.
func (p *ProviderData) buildSessionFromClaims(rawIDToken, accessToken string) (*sessions.SessionState, error) {
ss := &sessions.SessionState{}
if rawIDToken == "" {
return ss, nil
}
extractor, err := p.getClaimExtractor(rawIDToken, accessToken)
if err != nil {
return nil, err
}
// Use a slice of a struct (vs map) here in case the same claim is used twice
for _, c := range []struct {
claim string
dst interface{}
}{
{p.UserClaim, &ss.User},
{p.EmailClaim, &ss.Email},
{p.GroupsClaim, &ss.Groups},
// TODO (@NickMeves) Deprecate for dynamic claim to session mapping
{"preferred_username", &ss.PreferredUsername},
} {
if _, err := extractor.GetClaimInto(c.claim, c.dst); err != nil {
return nil, err
}
}
// `email_verified` must be present and explicitly set to `false` to be
// considered unverified.
verifyEmail := (p.EmailClaim == options.OIDCEmailClaim) && !p.AllowUnverifiedEmail
var verified bool
exists, err := extractor.GetClaimInto("email_verified", &verified)
if err != nil {
return nil, err
}
if verifyEmail && exists && !verified {
return nil, fmt.Errorf("email in id_token (%s) isn't verified", ss.Email)
}
return ss, nil
}
func (p *ProviderData) getClaimExtractor(rawIDToken, accessToken string) (util.ClaimExtractor, error) {
extractor, err := util.NewClaimExtractor(context.TODO(), rawIDToken, p.ProfileURL, p.getAuthorizationHeader(accessToken))
if err != nil {
return nil, fmt.Errorf("could not initialise claim extractor: %v", err)
}
return extractor, nil
}
// checkNonce compares the session's nonce with the IDToken's nonce claim
func (p *ProviderData) checkNonce(s *sessions.SessionState) error {
extractor, err := p.getClaimExtractor(s.IDToken, "")
if err != nil {
return fmt.Errorf("id_token claims extraction failed: %v", err)
}
var nonce string
if _, err := extractor.GetClaimInto("nonce", &nonce); err != nil {
return fmt.Errorf("could not extract nonce from ID Token: %v", err)
}
if !s.CheckNonce(nonce) {
return errors.New("id_token nonce claim does not match the session nonce")
}
return nil
}
func (p *ProviderData) getAuthorizationHeader(accessToken string) http.Header {
if p.getAuthorizationHeaderFunc != nil && accessToken != "" {
return p.getAuthorizationHeaderFunc(accessToken)
}
return nil
}