2020-11-30 20:54:11 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// Package upstreamoidc implements an abstraction of upstream OIDC provider interactions.
|
|
|
|
package upstreamoidc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/coreos/go-oidc"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
|
|
|
"go.pinniped.dev/internal/httputil/httperr"
|
|
|
|
"go.pinniped.dev/internal/oidc/provider"
|
|
|
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
2020-11-30 23:02:03 +00:00
|
|
|
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
2020-11-30 20:54:11 +00:00
|
|
|
"go.pinniped.dev/pkg/oidcclient/pkce"
|
|
|
|
)
|
|
|
|
|
2020-11-30 23:14:57 +00:00
|
|
|
func New(config *oauth2.Config, provider *oidc.Provider) provider.UpstreamOIDCIdentityProviderI {
|
|
|
|
return &ProviderConfig{Config: config, Provider: provider}
|
|
|
|
}
|
|
|
|
|
2020-11-30 20:54:11 +00:00
|
|
|
// ProviderConfig holds the active configuration of an upstream OIDC provider.
|
|
|
|
type ProviderConfig struct {
|
|
|
|
Name string
|
|
|
|
UsernameClaim string
|
|
|
|
GroupsClaim string
|
|
|
|
Config *oauth2.Config
|
|
|
|
Provider interface {
|
|
|
|
Verifier(*oidc.Config) *oidc.IDTokenVerifier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetName() string {
|
|
|
|
return p.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetClientID() string {
|
|
|
|
return p.Config.ClientID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetAuthorizationURL() *url.URL {
|
|
|
|
result, _ := url.Parse(p.Config.Endpoint.AuthURL)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetScopes() []string {
|
|
|
|
return p.Config.Scopes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetUsernameClaim() string {
|
|
|
|
return p.UsernameClaim
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) GetGroupsClaim() string {
|
|
|
|
return p.GroupsClaim
|
|
|
|
}
|
|
|
|
|
2020-11-30 23:02:03 +00:00
|
|
|
func (p *ProviderConfig) ExchangeAuthcodeAndValidateTokens(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (oidctypes.Token, map[string]interface{}, error) {
|
2020-11-30 20:54:11 +00:00
|
|
|
tok, err := p.Config.Exchange(ctx, authcode, pkceCodeVerifier.Verifier())
|
|
|
|
if err != nil {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, err
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 23:08:27 +00:00
|
|
|
return p.ValidateToken(ctx, tok, expectedIDTokenNonce)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ProviderConfig) ValidateToken(ctx context.Context, tok *oauth2.Token, expectedIDTokenNonce nonce.Nonce) (oidctypes.Token, map[string]interface{}, error) {
|
2020-11-30 20:54:11 +00:00
|
|
|
idTok, hasIDTok := tok.Extra("id_token").(string)
|
|
|
|
if !hasIDTok {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, httperr.New(http.StatusBadRequest, "received response missing ID token")
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
validated, err := p.Provider.Verifier(&oidc.Config{ClientID: p.GetClientID()}).Verify(ctx, idTok)
|
|
|
|
if err != nil {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err)
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
if validated.AccessTokenHash != "" {
|
|
|
|
if err := validated.VerifyAccessToken(tok.AccessToken); err != nil {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err)
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if expectedIDTokenNonce != "" {
|
|
|
|
if err := expectedIDTokenNonce.Validate(validated); err != nil {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received ID token with invalid nonce", err)
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var validatedClaims map[string]interface{}
|
|
|
|
if err := validated.Claims(&validatedClaims); err != nil {
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{}, nil, httperr.Wrap(http.StatusInternalServerError, "could not unmarshal claims", err)
|
2020-11-30 20:54:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 23:02:03 +00:00
|
|
|
return oidctypes.Token{
|
|
|
|
AccessToken: &oidctypes.AccessToken{
|
2020-11-30 20:54:11 +00:00
|
|
|
Token: tok.AccessToken,
|
|
|
|
Type: tok.TokenType,
|
|
|
|
Expiry: metav1.NewTime(tok.Expiry),
|
|
|
|
},
|
2020-11-30 23:02:03 +00:00
|
|
|
RefreshToken: &oidctypes.RefreshToken{
|
2020-11-30 20:54:11 +00:00
|
|
|
Token: tok.RefreshToken,
|
|
|
|
},
|
2020-11-30 23:02:03 +00:00
|
|
|
IDToken: &oidctypes.IDToken{
|
2020-11-30 20:54:11 +00:00
|
|
|
Token: idTok,
|
|
|
|
Expiry: metav1.NewTime(validated.Expiry),
|
|
|
|
},
|
|
|
|
}, validatedClaims, nil
|
|
|
|
}
|