aboutsummaryrefslogtreecommitdiffstats
path: root/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go')
-rw-r--r--src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go846
1 files changed, 846 insertions, 0 deletions
diff --git a/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go
new file mode 100644
index 0000000..7a3cb40
--- /dev/null
+++ b/src/kube2msb/vendor/github.com/coreos/go-oidc/oidc/client.go
@@ -0,0 +1,846 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/mail"
+ "net/url"
+ "sync"
+ "time"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/go-oidc/key"
+ "github.com/coreos/go-oidc/oauth2"
+)
+
+const (
+ // amount of time that must pass after the last key sync
+ // completes before another attempt may begin
+ keySyncWindow = 5 * time.Second
+)
+
+var (
+ DefaultScope = []string{"openid", "email", "profile"}
+
+ supportedAuthMethods = map[string]struct{}{
+ oauth2.AuthMethodClientSecretBasic: struct{}{},
+ oauth2.AuthMethodClientSecretPost: struct{}{},
+ }
+)
+
+type ClientCredentials oauth2.ClientCredentials
+
+type ClientIdentity struct {
+ Credentials ClientCredentials
+ Metadata ClientMetadata
+}
+
+type JWAOptions struct {
+ // SigningAlg specifies an JWA alg for signing JWTs.
+ //
+ // Specifying this field implies different actions depending on the context. It may
+ // require objects be serialized and signed as a JWT instead of plain JSON, or
+ // require an existing JWT object use the specified alg.
+ //
+ // See: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
+ SigningAlg string
+ // EncryptionAlg, if provided, specifies that the returned or sent object be stored
+ // (or nested) within a JWT object and encrypted with the provided JWA alg.
+ EncryptionAlg string
+ // EncryptionEnc specifies the JWA enc algorithm to use with EncryptionAlg. If
+ // EncryptionAlg is provided and EncryptionEnc is omitted, this field defaults
+ // to A128CBC-HS256.
+ //
+ // If EncryptionEnc is provided EncryptionAlg must also be specified.
+ EncryptionEnc string
+}
+
+func (opt JWAOptions) valid() error {
+ if opt.EncryptionEnc != "" && opt.EncryptionAlg == "" {
+ return errors.New("encryption encoding provided with no encryption algorithm")
+ }
+ return nil
+}
+
+func (opt JWAOptions) defaults() JWAOptions {
+ if opt.EncryptionAlg != "" && opt.EncryptionEnc == "" {
+ opt.EncryptionEnc = jose.EncA128CBCHS256
+ }
+ return opt
+}
+
+var (
+ // Ensure ClientMetadata satisfies these interfaces.
+ _ json.Marshaler = &ClientMetadata{}
+ _ json.Unmarshaler = &ClientMetadata{}
+)
+
+// ClientMetadata holds metadata that the authorization server associates
+// with a client identifier. The fields range from human-facing display
+// strings such as client name, to items that impact the security of the
+// protocol, such as the list of valid redirect URIs.
+//
+// See http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
+//
+// TODO: support language specific claim representations
+// http://openid.net/specs/openid-connect-registration-1_0.html#LanguagesAndScripts
+type ClientMetadata struct {
+ RedirectURIs []url.URL // Required
+
+ // A list of OAuth 2.0 "response_type" values that the client wishes to restrict
+ // itself to. Either "code", "token", or another registered extension.
+ //
+ // If omitted, only "code" will be used.
+ ResponseTypes []string
+ // A list of OAuth 2.0 grant types the client wishes to restrict itself to.
+ // The grant type values used by OIDC are "authorization_code", "implicit",
+ // and "refresh_token".
+ //
+ // If ommitted, only "authorization_code" will be used.
+ GrantTypes []string
+ // "native" or "web". If omitted, "web".
+ ApplicationType string
+
+ // List of email addresses.
+ Contacts []mail.Address
+ // Name of client to be presented to the end-user.
+ ClientName string
+ // URL that references a logo for the Client application.
+ LogoURI *url.URL
+ // URL of the home page of the Client.
+ ClientURI *url.URL
+ // Profile data policies and terms of use to be provided to the end user.
+ PolicyURI *url.URL
+ TermsOfServiceURI *url.URL
+
+ // URL to or the value of the client's JSON Web Key Set document.
+ JWKSURI *url.URL
+ JWKS *jose.JWKSet
+
+ // URL referencing a flie with a single JSON array of redirect URIs.
+ SectorIdentifierURI *url.URL
+
+ SubjectType string
+
+ // Options to restrict the JWS alg and enc values used for server responses and requests.
+ IDTokenResponseOptions JWAOptions
+ UserInfoResponseOptions JWAOptions
+ RequestObjectOptions JWAOptions
+
+ // Client requested authorization method and signing options for the token endpoint.
+ //
+ // Defaults to "client_secret_basic"
+ TokenEndpointAuthMethod string
+ TokenEndpointAuthSigningAlg string
+
+ // DefaultMaxAge specifies the maximum amount of time in seconds before an authorized
+ // user must reauthroize.
+ //
+ // If 0, no limitation is placed on the maximum.
+ DefaultMaxAge int64
+ // RequireAuthTime specifies if the auth_time claim in the ID token is required.
+ RequireAuthTime bool
+
+ // Default Authentication Context Class Reference values for authentication requests.
+ DefaultACRValues []string
+
+ // URI that a third party can use to initiate a login by the relaying party.
+ //
+ // See: http://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
+ InitiateLoginURI *url.URL
+ // Pre-registered request_uri values that may be cached by the server.
+ RequestURIs []url.URL
+}
+
+// Defaults returns a shallow copy of ClientMetadata with default
+// values replacing omitted fields.
+func (m ClientMetadata) Defaults() ClientMetadata {
+ if len(m.ResponseTypes) == 0 {
+ m.ResponseTypes = []string{oauth2.ResponseTypeCode}
+ }
+ if len(m.GrantTypes) == 0 {
+ m.GrantTypes = []string{oauth2.GrantTypeAuthCode}
+ }
+ if m.ApplicationType == "" {
+ m.ApplicationType = "web"
+ }
+ if m.TokenEndpointAuthMethod == "" {
+ m.TokenEndpointAuthMethod = oauth2.AuthMethodClientSecretBasic
+ }
+ m.IDTokenResponseOptions = m.IDTokenResponseOptions.defaults()
+ m.UserInfoResponseOptions = m.UserInfoResponseOptions.defaults()
+ m.RequestObjectOptions = m.RequestObjectOptions.defaults()
+ return m
+}
+
+func (m *ClientMetadata) MarshalJSON() ([]byte, error) {
+ e := m.toEncodableStruct()
+ return json.Marshal(&e)
+}
+
+func (m *ClientMetadata) UnmarshalJSON(data []byte) error {
+ var e encodableClientMetadata
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ meta, err := e.toStruct()
+ if err != nil {
+ return err
+ }
+ if err := meta.Valid(); err != nil {
+ return err
+ }
+ *m = meta
+ return nil
+}
+
+type encodableClientMetadata struct {
+ RedirectURIs []string `json:"redirect_uris"` // Required
+ ResponseTypes []string `json:"response_types,omitempty"`
+ GrantTypes []string `json:"grant_types,omitempty"`
+ ApplicationType string `json:"application_type,omitempty"`
+ Contacts []string `json:"contacts,omitempty"`
+ ClientName string `json:"client_name,omitempty"`
+ LogoURI string `json:"logo_uri,omitempty"`
+ ClientURI string `json:"client_uri,omitempty"`
+ PolicyURI string `json:"policy_uri,omitempty"`
+ TermsOfServiceURI string `json:"tos_uri,omitempty"`
+ JWKSURI string `json:"jwks_uri,omitempty"`
+ JWKS *jose.JWKSet `json:"jwks,omitempty"`
+ SectorIdentifierURI string `json:"sector_identifier_uri,omitempty"`
+ SubjectType string `json:"subject_type,omitempty"`
+ IDTokenSignedResponseAlg string `json:"id_token_signed_response_alg,omitempty"`
+ IDTokenEncryptedResponseAlg string `json:"id_token_encrypted_response_alg,omitempty"`
+ IDTokenEncryptedResponseEnc string `json:"id_token_encrypted_response_enc,omitempty"`
+ UserInfoSignedResponseAlg string `json:"userinfo_signed_response_alg,omitempty"`
+ UserInfoEncryptedResponseAlg string `json:"userinfo_encrypted_response_alg,omitempty"`
+ UserInfoEncryptedResponseEnc string `json:"userinfo_encrypted_response_enc,omitempty"`
+ RequestObjectSigningAlg string `json:"request_object_signing_alg,omitempty"`
+ RequestObjectEncryptionAlg string `json:"request_object_encryption_alg,omitempty"`
+ RequestObjectEncryptionEnc string `json:"request_object_encryption_enc,omitempty"`
+ TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
+ TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
+ DefaultMaxAge int64 `json:"default_max_age,omitempty"`
+ RequireAuthTime bool `json:"require_auth_time,omitempty"`
+ DefaultACRValues []string `json:"default_acr_values,omitempty"`
+ InitiateLoginURI string `json:"initiate_login_uri,omitempty"`
+ RequestURIs []string `json:"request_uris,omitempty"`
+}
+
+func (c *encodableClientMetadata) toStruct() (ClientMetadata, error) {
+ p := stickyErrParser{}
+ m := ClientMetadata{
+ RedirectURIs: p.parseURIs(c.RedirectURIs, "redirect_uris"),
+ ResponseTypes: c.ResponseTypes,
+ GrantTypes: c.GrantTypes,
+ ApplicationType: c.ApplicationType,
+ Contacts: p.parseEmails(c.Contacts, "contacts"),
+ ClientName: c.ClientName,
+ LogoURI: p.parseURI(c.LogoURI, "logo_uri"),
+ ClientURI: p.parseURI(c.ClientURI, "client_uri"),
+ PolicyURI: p.parseURI(c.PolicyURI, "policy_uri"),
+ TermsOfServiceURI: p.parseURI(c.TermsOfServiceURI, "tos_uri"),
+ JWKSURI: p.parseURI(c.JWKSURI, "jwks_uri"),
+ JWKS: c.JWKS,
+ SectorIdentifierURI: p.parseURI(c.SectorIdentifierURI, "sector_identifier_uri"),
+ SubjectType: c.SubjectType,
+ TokenEndpointAuthMethod: c.TokenEndpointAuthMethod,
+ TokenEndpointAuthSigningAlg: c.TokenEndpointAuthSigningAlg,
+ DefaultMaxAge: c.DefaultMaxAge,
+ RequireAuthTime: c.RequireAuthTime,
+ DefaultACRValues: c.DefaultACRValues,
+ InitiateLoginURI: p.parseURI(c.InitiateLoginURI, "initiate_login_uri"),
+ RequestURIs: p.parseURIs(c.RequestURIs, "request_uris"),
+ IDTokenResponseOptions: JWAOptions{
+ c.IDTokenSignedResponseAlg,
+ c.IDTokenEncryptedResponseAlg,
+ c.IDTokenEncryptedResponseEnc,
+ },
+ UserInfoResponseOptions: JWAOptions{
+ c.UserInfoSignedResponseAlg,
+ c.UserInfoEncryptedResponseAlg,
+ c.UserInfoEncryptedResponseEnc,
+ },
+ RequestObjectOptions: JWAOptions{
+ c.RequestObjectSigningAlg,
+ c.RequestObjectEncryptionAlg,
+ c.RequestObjectEncryptionEnc,
+ },
+ }
+ if p.firstErr != nil {
+ return ClientMetadata{}, p.firstErr
+ }
+ return m, nil
+}
+
+// stickyErrParser parses URIs and email addresses. Once it encounters
+// a parse error, subsequent calls become no-op.
+type stickyErrParser struct {
+ firstErr error
+}
+
+func (p *stickyErrParser) parseURI(s, field string) *url.URL {
+ if p.firstErr != nil || s == "" {
+ return nil
+ }
+ u, err := url.Parse(s)
+ if err == nil {
+ if u.Host == "" {
+ err = errors.New("no host in URI")
+ } else if u.Scheme != "http" && u.Scheme != "https" {
+ err = errors.New("invalid URI scheme")
+ }
+ }
+ if err != nil {
+ p.firstErr = fmt.Errorf("failed to parse %s: %v", field, err)
+ return nil
+ }
+ return u
+}
+
+func (p *stickyErrParser) parseURIs(s []string, field string) []url.URL {
+ if p.firstErr != nil || len(s) == 0 {
+ return nil
+ }
+ uris := make([]url.URL, len(s))
+ for i, val := range s {
+ if val == "" {
+ p.firstErr = fmt.Errorf("invalid URI in field %s", field)
+ return nil
+ }
+ if u := p.parseURI(val, field); u != nil {
+ uris[i] = *u
+ }
+ }
+ return uris
+}
+
+func (p *stickyErrParser) parseEmails(s []string, field string) []mail.Address {
+ if p.firstErr != nil || len(s) == 0 {
+ return nil
+ }
+ addrs := make([]mail.Address, len(s))
+ for i, addr := range s {
+ if addr == "" {
+ p.firstErr = fmt.Errorf("invalid email in field %s", field)
+ return nil
+ }
+ a, err := mail.ParseAddress(addr)
+ if err != nil {
+ p.firstErr = fmt.Errorf("invalid email in field %s: %v", field, err)
+ return nil
+ }
+ addrs[i] = *a
+ }
+ return addrs
+}
+
+func (m *ClientMetadata) toEncodableStruct() encodableClientMetadata {
+ return encodableClientMetadata{
+ RedirectURIs: urisToStrings(m.RedirectURIs),
+ ResponseTypes: m.ResponseTypes,
+ GrantTypes: m.GrantTypes,
+ ApplicationType: m.ApplicationType,
+ Contacts: emailsToStrings(m.Contacts),
+ ClientName: m.ClientName,
+ LogoURI: uriToString(m.LogoURI),
+ ClientURI: uriToString(m.ClientURI),
+ PolicyURI: uriToString(m.PolicyURI),
+ TermsOfServiceURI: uriToString(m.TermsOfServiceURI),
+ JWKSURI: uriToString(m.JWKSURI),
+ JWKS: m.JWKS,
+ SectorIdentifierURI: uriToString(m.SectorIdentifierURI),
+ SubjectType: m.SubjectType,
+ IDTokenSignedResponseAlg: m.IDTokenResponseOptions.SigningAlg,
+ IDTokenEncryptedResponseAlg: m.IDTokenResponseOptions.EncryptionAlg,
+ IDTokenEncryptedResponseEnc: m.IDTokenResponseOptions.EncryptionEnc,
+ UserInfoSignedResponseAlg: m.UserInfoResponseOptions.SigningAlg,
+ UserInfoEncryptedResponseAlg: m.UserInfoResponseOptions.EncryptionAlg,
+ UserInfoEncryptedResponseEnc: m.UserInfoResponseOptions.EncryptionEnc,
+ RequestObjectSigningAlg: m.RequestObjectOptions.SigningAlg,
+ RequestObjectEncryptionAlg: m.RequestObjectOptions.EncryptionAlg,
+ RequestObjectEncryptionEnc: m.RequestObjectOptions.EncryptionEnc,
+ TokenEndpointAuthMethod: m.TokenEndpointAuthMethod,
+ TokenEndpointAuthSigningAlg: m.TokenEndpointAuthSigningAlg,
+ DefaultMaxAge: m.DefaultMaxAge,
+ RequireAuthTime: m.RequireAuthTime,
+ DefaultACRValues: m.DefaultACRValues,
+ InitiateLoginURI: uriToString(m.InitiateLoginURI),
+ RequestURIs: urisToStrings(m.RequestURIs),
+ }
+}
+
+func uriToString(u *url.URL) string {
+ if u == nil {
+ return ""
+ }
+ return u.String()
+}
+
+func urisToStrings(urls []url.URL) []string {
+ if len(urls) == 0 {
+ return nil
+ }
+ sli := make([]string, len(urls))
+ for i, u := range urls {
+ sli[i] = u.String()
+ }
+ return sli
+}
+
+func emailsToStrings(addrs []mail.Address) []string {
+ if len(addrs) == 0 {
+ return nil
+ }
+ sli := make([]string, len(addrs))
+ for i, addr := range addrs {
+ sli[i] = addr.String()
+ }
+ return sli
+}
+
+// Valid determines if a ClientMetadata conforms with the OIDC specification.
+//
+// Valid is called by UnmarshalJSON.
+//
+// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
+// URLs fields where the OIDC spec requires it. This may change in future releases
+// of this package. See: https://github.com/coreos/go-oidc/issues/34
+func (m *ClientMetadata) Valid() error {
+ if len(m.RedirectURIs) == 0 {
+ return errors.New("zero redirect URLs")
+ }
+
+ validURI := func(u *url.URL, fieldName string) error {
+ if u.Host == "" {
+ return fmt.Errorf("no host for uri field %s", fieldName)
+ }
+ if u.Scheme != "http" && u.Scheme != "https" {
+ return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
+ }
+ return nil
+ }
+
+ uris := []struct {
+ val *url.URL
+ name string
+ }{
+ {m.LogoURI, "logo_uri"},
+ {m.ClientURI, "client_uri"},
+ {m.PolicyURI, "policy_uri"},
+ {m.TermsOfServiceURI, "tos_uri"},
+ {m.JWKSURI, "jwks_uri"},
+ {m.SectorIdentifierURI, "sector_identifier_uri"},
+ {m.InitiateLoginURI, "initiate_login_uri"},
+ }
+
+ for _, uri := range uris {
+ if uri.val == nil {
+ continue
+ }
+ if err := validURI(uri.val, uri.name); err != nil {
+ return err
+ }
+ }
+
+ uriLists := []struct {
+ vals []url.URL
+ name string
+ }{
+ {m.RedirectURIs, "redirect_uris"},
+ {m.RequestURIs, "request_uris"},
+ }
+ for _, list := range uriLists {
+ for _, uri := range list.vals {
+ if err := validURI(&uri, list.name); err != nil {
+ return err
+ }
+ }
+ }
+
+ options := []struct {
+ option JWAOptions
+ name string
+ }{
+ {m.IDTokenResponseOptions, "id_token response"},
+ {m.UserInfoResponseOptions, "userinfo response"},
+ {m.RequestObjectOptions, "request_object"},
+ }
+ for _, option := range options {
+ if err := option.option.valid(); err != nil {
+ return fmt.Errorf("invalid JWA values for %s: %v", option.name, err)
+ }
+ }
+ return nil
+}
+
+type ClientRegistrationResponse struct {
+ ClientID string // Required
+ ClientSecret string
+ RegistrationAccessToken string
+ RegistrationClientURI string
+ // If IsZero is true, unspecified.
+ ClientIDIssuedAt time.Time
+ // Time at which the client_secret will expire.
+ // If IsZero is true, it will not expire.
+ ClientSecretExpiresAt time.Time
+
+ ClientMetadata
+}
+
+type encodableClientRegistrationResponse struct {
+ ClientID string `json:"client_id"` // Required
+ ClientSecret string `json:"client_secret,omitempty"`
+ RegistrationAccessToken string `json:"registration_access_token,omitempty"`
+ RegistrationClientURI string `json:"registration_client_uri,omitempty"`
+ ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
+ // Time at which the client_secret will expire, in seconds since the epoch.
+ // If 0 it will not expire.
+ ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` // Required
+
+ encodableClientMetadata
+}
+
+func unixToSec(t time.Time) int64 {
+ if t.IsZero() {
+ return 0
+ }
+ return t.Unix()
+}
+
+func (c *ClientRegistrationResponse) MarshalJSON() ([]byte, error) {
+ e := encodableClientRegistrationResponse{
+ ClientID: c.ClientID,
+ ClientSecret: c.ClientSecret,
+ RegistrationAccessToken: c.RegistrationAccessToken,
+ RegistrationClientURI: c.RegistrationClientURI,
+ ClientIDIssuedAt: unixToSec(c.ClientIDIssuedAt),
+ ClientSecretExpiresAt: unixToSec(c.ClientSecretExpiresAt),
+ encodableClientMetadata: c.ClientMetadata.toEncodableStruct(),
+ }
+ return json.Marshal(&e)
+}
+
+func secToUnix(sec int64) time.Time {
+ if sec == 0 {
+ return time.Time{}
+ }
+ return time.Unix(sec, 0)
+}
+
+func (c *ClientRegistrationResponse) UnmarshalJSON(data []byte) error {
+ var e encodableClientRegistrationResponse
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ if e.ClientID == "" {
+ return errors.New("no client_id in client registration response")
+ }
+ metadata, err := e.encodableClientMetadata.toStruct()
+ if err != nil {
+ return err
+ }
+ *c = ClientRegistrationResponse{
+ ClientID: e.ClientID,
+ ClientSecret: e.ClientSecret,
+ RegistrationAccessToken: e.RegistrationAccessToken,
+ RegistrationClientURI: e.RegistrationClientURI,
+ ClientIDIssuedAt: secToUnix(e.ClientIDIssuedAt),
+ ClientSecretExpiresAt: secToUnix(e.ClientSecretExpiresAt),
+ ClientMetadata: metadata,
+ }
+ return nil
+}
+
+type ClientConfig struct {
+ HTTPClient phttp.Client
+ Credentials ClientCredentials
+ Scope []string
+ RedirectURL string
+ ProviderConfig ProviderConfig
+ KeySet key.PublicKeySet
+}
+
+func NewClient(cfg ClientConfig) (*Client, error) {
+ // Allow empty redirect URL in the case where the client
+ // only needs to verify a given token.
+ ru, err := url.Parse(cfg.RedirectURL)
+ if err != nil {
+ return nil, fmt.Errorf("invalid redirect URL: %v", err)
+ }
+
+ c := Client{
+ credentials: cfg.Credentials,
+ httpClient: cfg.HTTPClient,
+ scope: cfg.Scope,
+ redirectURL: ru.String(),
+ providerConfig: newProviderConfigRepo(cfg.ProviderConfig),
+ keySet: cfg.KeySet,
+ }
+
+ if c.httpClient == nil {
+ c.httpClient = http.DefaultClient
+ }
+
+ if c.scope == nil {
+ c.scope = make([]string, len(DefaultScope))
+ copy(c.scope, DefaultScope)
+ }
+
+ return &c, nil
+}
+
+type Client struct {
+ httpClient phttp.Client
+ providerConfig *providerConfigRepo
+ credentials ClientCredentials
+ redirectURL string
+ scope []string
+ keySet key.PublicKeySet
+ providerSyncer *ProviderConfigSyncer
+
+ keySetSyncMutex sync.RWMutex
+ lastKeySetSync time.Time
+}
+
+func (c *Client) Healthy() error {
+ now := time.Now().UTC()
+
+ cfg := c.providerConfig.Get()
+
+ if cfg.Empty() {
+ return errors.New("oidc client provider config empty")
+ }
+
+ if !cfg.ExpiresAt.IsZero() && cfg.ExpiresAt.Before(now) {
+ return errors.New("oidc client provider config expired")
+ }
+
+ return nil
+}
+
+func (c *Client) OAuthClient() (*oauth2.Client, error) {
+ cfg := c.providerConfig.Get()
+ authMethod, err := chooseAuthMethod(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ ocfg := oauth2.Config{
+ Credentials: oauth2.ClientCredentials(c.credentials),
+ RedirectURL: c.redirectURL,
+ AuthURL: cfg.AuthEndpoint.String(),
+ TokenURL: cfg.TokenEndpoint.String(),
+ Scope: c.scope,
+ AuthMethod: authMethod,
+ }
+
+ return oauth2.NewClient(c.httpClient, ocfg)
+}
+
+func chooseAuthMethod(cfg ProviderConfig) (string, error) {
+ if len(cfg.TokenEndpointAuthMethodsSupported) == 0 {
+ return oauth2.AuthMethodClientSecretBasic, nil
+ }
+
+ for _, authMethod := range cfg.TokenEndpointAuthMethodsSupported {
+ if _, ok := supportedAuthMethods[authMethod]; ok {
+ return authMethod, nil
+ }
+ }
+
+ return "", errors.New("no supported auth methods")
+}
+
+// SyncProviderConfig starts the provider config syncer
+func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
+ r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
+ s := NewProviderConfigSyncer(r, c.providerConfig)
+ stop := s.Run()
+ s.WaitUntilInitialSync()
+ return stop
+}
+
+func (c *Client) maybeSyncKeys() error {
+ tooSoon := func() bool {
+ return time.Now().UTC().Before(c.lastKeySetSync.Add(keySyncWindow))
+ }
+
+ // ignore request to sync keys if a sync operation has been
+ // attempted too recently
+ if tooSoon() {
+ return nil
+ }
+
+ c.keySetSyncMutex.Lock()
+ defer c.keySetSyncMutex.Unlock()
+
+ // check again, as another goroutine may have been holding
+ // the lock while updating the keys
+ if tooSoon() {
+ return nil
+ }
+
+ cfg := c.providerConfig.Get()
+ r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
+ w := &clientKeyRepo{client: c}
+ _, err := key.Sync(r, w)
+ c.lastKeySetSync = time.Now().UTC()
+
+ return err
+}
+
+type clientKeyRepo struct {
+ client *Client
+}
+
+func (r *clientKeyRepo) Set(ks key.KeySet) error {
+ pks, ok := ks.(*key.PublicKeySet)
+ if !ok {
+ return errors.New("unable to cast to PublicKey")
+ }
+ r.client.keySet = *pks
+ return nil
+}
+
+func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) {
+ cfg := c.providerConfig.Get()
+
+ if !cfg.SupportsGrantType(oauth2.GrantTypeClientCreds) {
+ return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds)
+ }
+
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.ClientCredsToken(scope)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+// ExchangeAuthCode exchanges an OAuth2 auth code for an OIDC JWT ID token.
+func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) {
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token.
+func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) {
+ oac, err := c.OAuthClient()
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ jwt, err := jose.ParseJWT(t.IDToken)
+ if err != nil {
+ return jose.JWT{}, err
+ }
+
+ return jwt, c.VerifyJWT(jwt)
+}
+
+func (c *Client) VerifyJWT(jwt jose.JWT) error {
+ var keysFunc func() []key.PublicKey
+ if kID, ok := jwt.KeyID(); ok {
+ keysFunc = c.keysFuncWithID(kID)
+ } else {
+ keysFunc = c.keysFuncAll()
+ }
+
+ v := NewJWTVerifier(
+ c.providerConfig.Get().Issuer.String(),
+ c.credentials.ID,
+ c.maybeSyncKeys, keysFunc)
+
+ return v.Verify(jwt)
+}
+
+// keysFuncWithID returns a function that retrieves at most unexpired
+// public key from the Client that matches the provided ID
+func (c *Client) keysFuncWithID(kID string) func() []key.PublicKey {
+ return func() []key.PublicKey {
+ c.keySetSyncMutex.RLock()
+ defer c.keySetSyncMutex.RUnlock()
+
+ if c.keySet.ExpiresAt().Before(time.Now()) {
+ return []key.PublicKey{}
+ }
+
+ k := c.keySet.Key(kID)
+ if k == nil {
+ return []key.PublicKey{}
+ }
+
+ return []key.PublicKey{*k}
+ }
+}
+
+// keysFuncAll returns a function that retrieves all unexpired public
+// keys from the Client
+func (c *Client) keysFuncAll() func() []key.PublicKey {
+ return func() []key.PublicKey {
+ c.keySetSyncMutex.RLock()
+ defer c.keySetSyncMutex.RUnlock()
+
+ if c.keySet.ExpiresAt().Before(time.Now()) {
+ return []key.PublicKey{}
+ }
+
+ return c.keySet.Keys()
+ }
+}
+
+type providerConfigRepo struct {
+ mu sync.RWMutex
+ config ProviderConfig // do not access directly, use Get()
+}
+
+func newProviderConfigRepo(pc ProviderConfig) *providerConfigRepo {
+ return &providerConfigRepo{sync.RWMutex{}, pc}
+}
+
+// returns an error to implement ProviderConfigSetter
+func (r *providerConfigRepo) Set(cfg ProviderConfig) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ r.config = cfg
+ return nil
+}
+
+func (r *providerConfigRepo) Get() ProviderConfig {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+ return r.config
+}