aboutsummaryrefslogtreecommitdiffstats
path: root/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc
diff options
context:
space:
mode:
authorHuabingZhao <zhao.huabing@zte.com.cn>2017-08-31 11:59:47 +0800
committerHuabingZhao <zhao.huabing@zte.com.cn>2017-08-31 12:00:09 +0800
commitc0f3b093c704da85252044b3a177dbabab63c49a (patch)
treef6cb5d85315c307bcf4984ac04e76e84b9abeac6 /kube2msb/src/vendor/github.com/coreos/go-oidc/oidc
parentc1737d2abac61511e00f388538779d67464b8a98 (diff)
add vendor package
Issue-Id: OOM-61 Change-Id: I251336e3b711b14f8ae9a8b0bf6055011a1d9bc8 Signed-off-by: HuabingZhao <zhao.huabing@zte.com.cn>
Diffstat (limited to 'kube2msb/src/vendor/github.com/coreos/go-oidc/oidc')
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/client.go846
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/identity.go44
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/interface.go3
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/key.go67
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/provider.go688
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/transport.go88
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/util.go109
-rw-r--r--kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/verification.go188
8 files changed, 2033 insertions, 0 deletions
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/client.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/client.go
new file mode 100644
index 0000000..7a3cb40
--- /dev/null
+++ b/kube2msb/src/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
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/identity.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/identity.go
new file mode 100644
index 0000000..9bfa8e3
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/identity.go
@@ -0,0 +1,44 @@
+package oidc
+
+import (
+ "errors"
+ "time"
+
+ "github.com/coreos/go-oidc/jose"
+)
+
+type Identity struct {
+ ID string
+ Name string
+ Email string
+ ExpiresAt time.Time
+}
+
+func IdentityFromClaims(claims jose.Claims) (*Identity, error) {
+ if claims == nil {
+ return nil, errors.New("nil claim set")
+ }
+
+ var ident Identity
+ var err error
+ var ok bool
+
+ if ident.ID, ok, err = claims.StringClaim("sub"); err != nil {
+ return nil, err
+ } else if !ok {
+ return nil, errors.New("missing required claim: sub")
+ }
+
+ if ident.Email, _, err = claims.StringClaim("email"); err != nil {
+ return nil, err
+ }
+
+ exp, ok, err := claims.TimeClaim("exp")
+ if err != nil {
+ return nil, err
+ } else if ok {
+ ident.ExpiresAt = exp
+ }
+
+ return &ident, nil
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/interface.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/interface.go
new file mode 100644
index 0000000..248cac0
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/interface.go
@@ -0,0 +1,3 @@
+package oidc
+
+type LoginFunc func(ident Identity, sessionKey string) (redirectURL string, err error)
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/key.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/key.go
new file mode 100644
index 0000000..82a0f56
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/key.go
@@ -0,0 +1,67 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/go-oidc/key"
+)
+
+// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
+// Cache-Control header is provided by the JWK Set document endpoint.
+const DefaultPublicKeySetTTL = 24 * time.Hour
+
+// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
+func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
+ return &remotePublicKeyRepo{hc: hc, ep: ep}
+}
+
+type remotePublicKeyRepo struct {
+ hc phttp.Client
+ ep string
+}
+
+// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
+// is set on the Key Set to avoid it having to be re-retrieved for every
+// encryption event. This TTL is typically controlled by the endpoint returning
+// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
+// is found.
+func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
+ req, err := http.NewRequest("GET", r.ep, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := r.hc.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var d struct {
+ Keys []jose.JWK `json:"keys"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
+ return nil, err
+ }
+
+ if len(d.Keys) == 0 {
+ return nil, errors.New("zero keys in response")
+ }
+
+ ttl, ok, err := phttp.Cacheable(resp.Header)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ ttl = DefaultPublicKeySetTTL
+ }
+
+ exp := time.Now().UTC().Add(ttl)
+ ks := key.NewPublicKeySet(d.Keys, exp)
+ return ks, nil
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/provider.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/provider.go
new file mode 100644
index 0000000..1235890
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/provider.go
@@ -0,0 +1,688 @@
+package oidc
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/coreos/pkg/capnslog"
+ "github.com/coreos/pkg/timeutil"
+ "github.com/jonboulle/clockwork"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/oauth2"
+)
+
+var (
+ log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
+)
+
+const (
+ // Subject Identifier types defined by the OIDC spec. Specifies if the provider
+ // should provide the same sub claim value to all clients (public) or a unique
+ // value for each client (pairwise).
+ //
+ // See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
+ SubjectTypePublic = "public"
+ SubjectTypePairwise = "pairwise"
+)
+
+var (
+ // Default values for omitted provider config fields.
+ //
+ // Use ProviderConfig's Defaults method to fill a provider config with these values.
+ DefaultGrantTypesSupported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
+ DefaultResponseModesSupported = []string{"query", "fragment"}
+ DefaultTokenEndpointAuthMethodsSupported = []string{oauth2.AuthMethodClientSecretBasic}
+ DefaultClaimTypesSupported = []string{"normal"}
+)
+
+const (
+ MaximumProviderConfigSyncInterval = 24 * time.Hour
+ MinimumProviderConfigSyncInterval = time.Minute
+
+ discoveryConfigPath = "/.well-known/openid-configuration"
+)
+
+// internally configurable for tests
+var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
+
+var (
+ // Ensure ProviderConfig satisfies these interfaces.
+ _ json.Marshaler = &ProviderConfig{}
+ _ json.Unmarshaler = &ProviderConfig{}
+)
+
+// ProviderConfig represents the OpenID Provider Metadata specifying what
+// configurations a provider supports.
+//
+// See: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
+type ProviderConfig struct {
+ Issuer *url.URL // Required
+ AuthEndpoint *url.URL // Required
+ TokenEndpoint *url.URL // Required if grant types other than "implicit" are supported
+ UserInfoEndpoint *url.URL
+ KeysEndpoint *url.URL // Required
+ RegistrationEndpoint *url.URL
+
+ // Servers MAY choose not to advertise some supported scope values even when this
+ // parameter is used, although those defined in OpenID Core SHOULD be listed, if supported.
+ ScopesSupported []string
+ // OAuth2.0 response types supported.
+ ResponseTypesSupported []string // Required
+ // OAuth2.0 response modes supported.
+ //
+ // If omitted, defaults to DefaultResponseModesSupported.
+ ResponseModesSupported []string
+ // OAuth2.0 grant types supported.
+ //
+ // If omitted, defaults to DefaultGrantTypesSupported.
+ GrantTypesSupported []string
+ ACRValuesSupported []string
+ // SubjectTypesSupported specifies strategies for providing values for the sub claim.
+ SubjectTypesSupported []string // Required
+
+ // JWA signing and encryption algorith values supported for ID tokens.
+ IDTokenSigningAlgValues []string // Required
+ IDTokenEncryptionAlgValues []string
+ IDTokenEncryptionEncValues []string
+
+ // JWA signing and encryption algorith values supported for user info responses.
+ UserInfoSigningAlgValues []string
+ UserInfoEncryptionAlgValues []string
+ UserInfoEncryptionEncValues []string
+
+ // JWA signing and encryption algorith values supported for request objects.
+ ReqObjSigningAlgValues []string
+ ReqObjEncryptionAlgValues []string
+ ReqObjEncryptionEncValues []string
+
+ TokenEndpointAuthMethodsSupported []string
+ TokenEndpointAuthSigningAlgValuesSupported []string
+ DisplayValuesSupported []string
+ ClaimTypesSupported []string
+ ClaimsSupported []string
+ ServiceDocs *url.URL
+ ClaimsLocalsSupported []string
+ UILocalsSupported []string
+ ClaimsParameterSupported bool
+ RequestParameterSupported bool
+ RequestURIParamaterSupported bool
+ RequireRequestURIRegistration bool
+
+ Policy *url.URL
+ TermsOfService *url.URL
+
+ // Not part of the OpenID Provider Metadata
+ ExpiresAt time.Time
+}
+
+// Defaults returns a shallow copy of ProviderConfig with default
+// values replacing omitted fields.
+//
+// var cfg oidc.ProviderConfig
+// // Fill provider config with default values for omitted fields.
+// cfg = cfg.Defaults()
+//
+func (p ProviderConfig) Defaults() ProviderConfig {
+ setDefault := func(val *[]string, defaultVal []string) {
+ if len(*val) == 0 {
+ *val = defaultVal
+ }
+ }
+ setDefault(&p.GrantTypesSupported, DefaultGrantTypesSupported)
+ setDefault(&p.ResponseModesSupported, DefaultResponseModesSupported)
+ setDefault(&p.TokenEndpointAuthMethodsSupported, DefaultTokenEndpointAuthMethodsSupported)
+ setDefault(&p.ClaimTypesSupported, DefaultClaimTypesSupported)
+ return p
+}
+
+func (p *ProviderConfig) MarshalJSON() ([]byte, error) {
+ e := p.toEncodableStruct()
+ return json.Marshal(&e)
+}
+
+func (p *ProviderConfig) UnmarshalJSON(data []byte) error {
+ var e encodableProviderConfig
+ if err := json.Unmarshal(data, &e); err != nil {
+ return err
+ }
+ conf, err := e.toStruct()
+ if err != nil {
+ return err
+ }
+ if err := conf.Valid(); err != nil {
+ return err
+ }
+ *p = conf
+ return nil
+}
+
+type encodableProviderConfig struct {
+ Issuer string `json:"issuer"`
+ AuthEndpoint string `json:"authorization_endpoint"`
+ TokenEndpoint string `json:"token_endpoint"`
+ UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
+ KeysEndpoint string `json:"jwks_uri"`
+ RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
+
+ // Use 'omitempty' for all slices as per OIDC spec:
+ // "Claims that return multiple values are represented as JSON arrays.
+ // Claims with zero elements MUST be omitted from the response."
+ // http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
+
+ ScopesSupported []string `json:"scopes_supported,omitempty"`
+ ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
+ ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
+ GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
+ ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
+ SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
+
+ IDTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported,omitempty"`
+ IDTokenEncryptionAlgValues []string `json:"id_token_encryption_alg_values_supported,omitempty"`
+ IDTokenEncryptionEncValues []string `json:"id_token_encryption_enc_values_supported,omitempty"`
+ UserInfoSigningAlgValues []string `json:"userinfo_signing_alg_values_supported,omitempty"`
+ UserInfoEncryptionAlgValues []string `json:"userinfo_encryption_alg_values_supported,omitempty"`
+ UserInfoEncryptionEncValues []string `json:"userinfo_encryption_enc_values_supported,omitempty"`
+ ReqObjSigningAlgValues []string `json:"request_object_signing_alg_values_supported,omitempty"`
+ ReqObjEncryptionAlgValues []string `json:"request_object_encryption_alg_values_supported,omitempty"`
+ ReqObjEncryptionEncValues []string `json:"request_object_encryption_enc_values_supported,omitempty"`
+
+ TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
+ TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
+
+ DisplayValuesSupported []string `json:"display_values_supported,omitempty"`
+ ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
+ ClaimsSupported []string `json:"claims_supported,omitempty"`
+ ServiceDocs string `json:"service_documentation,omitempty"`
+ ClaimsLocalsSupported []string `json:"claims_locales_supported,omitempty"`
+ UILocalsSupported []string `json:"ui_locales_supported,omitempty"`
+ ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
+ RequestParameterSupported bool `json:"request_parameter_supported,omitempty"`
+ RequestURIParamaterSupported bool `json:"request_uri_parameter_supported,omitempty"`
+ RequireRequestURIRegistration bool `json:"require_request_uri_registration,omitempty"`
+
+ Policy string `json:"op_policy_uri,omitempty"`
+ TermsOfService string `json:"op_tos_uri,omitempty"`
+}
+
+func (cfg ProviderConfig) toEncodableStruct() encodableProviderConfig {
+ return encodableProviderConfig{
+ Issuer: uriToString(cfg.Issuer),
+ AuthEndpoint: uriToString(cfg.AuthEndpoint),
+ TokenEndpoint: uriToString(cfg.TokenEndpoint),
+ UserInfoEndpoint: uriToString(cfg.UserInfoEndpoint),
+ KeysEndpoint: uriToString(cfg.KeysEndpoint),
+ RegistrationEndpoint: uriToString(cfg.RegistrationEndpoint),
+ ScopesSupported: cfg.ScopesSupported,
+ ResponseTypesSupported: cfg.ResponseTypesSupported,
+ ResponseModesSupported: cfg.ResponseModesSupported,
+ GrantTypesSupported: cfg.GrantTypesSupported,
+ ACRValuesSupported: cfg.ACRValuesSupported,
+ SubjectTypesSupported: cfg.SubjectTypesSupported,
+ IDTokenSigningAlgValues: cfg.IDTokenSigningAlgValues,
+ IDTokenEncryptionAlgValues: cfg.IDTokenEncryptionAlgValues,
+ IDTokenEncryptionEncValues: cfg.IDTokenEncryptionEncValues,
+ UserInfoSigningAlgValues: cfg.UserInfoSigningAlgValues,
+ UserInfoEncryptionAlgValues: cfg.UserInfoEncryptionAlgValues,
+ UserInfoEncryptionEncValues: cfg.UserInfoEncryptionEncValues,
+ ReqObjSigningAlgValues: cfg.ReqObjSigningAlgValues,
+ ReqObjEncryptionAlgValues: cfg.ReqObjEncryptionAlgValues,
+ ReqObjEncryptionEncValues: cfg.ReqObjEncryptionEncValues,
+ TokenEndpointAuthMethodsSupported: cfg.TokenEndpointAuthMethodsSupported,
+ TokenEndpointAuthSigningAlgValuesSupported: cfg.TokenEndpointAuthSigningAlgValuesSupported,
+ DisplayValuesSupported: cfg.DisplayValuesSupported,
+ ClaimTypesSupported: cfg.ClaimTypesSupported,
+ ClaimsSupported: cfg.ClaimsSupported,
+ ServiceDocs: uriToString(cfg.ServiceDocs),
+ ClaimsLocalsSupported: cfg.ClaimsLocalsSupported,
+ UILocalsSupported: cfg.UILocalsSupported,
+ ClaimsParameterSupported: cfg.ClaimsParameterSupported,
+ RequestParameterSupported: cfg.RequestParameterSupported,
+ RequestURIParamaterSupported: cfg.RequestURIParamaterSupported,
+ RequireRequestURIRegistration: cfg.RequireRequestURIRegistration,
+ Policy: uriToString(cfg.Policy),
+ TermsOfService: uriToString(cfg.TermsOfService),
+ }
+}
+
+func (e encodableProviderConfig) toStruct() (ProviderConfig, error) {
+ p := stickyErrParser{}
+ conf := ProviderConfig{
+ Issuer: p.parseURI(e.Issuer, "issuer"),
+ AuthEndpoint: p.parseURI(e.AuthEndpoint, "authorization_endpoint"),
+ TokenEndpoint: p.parseURI(e.TokenEndpoint, "token_endpoint"),
+ UserInfoEndpoint: p.parseURI(e.UserInfoEndpoint, "userinfo_endpoint"),
+ KeysEndpoint: p.parseURI(e.KeysEndpoint, "jwks_uri"),
+ RegistrationEndpoint: p.parseURI(e.RegistrationEndpoint, "registration_endpoint"),
+ ScopesSupported: e.ScopesSupported,
+ ResponseTypesSupported: e.ResponseTypesSupported,
+ ResponseModesSupported: e.ResponseModesSupported,
+ GrantTypesSupported: e.GrantTypesSupported,
+ ACRValuesSupported: e.ACRValuesSupported,
+ SubjectTypesSupported: e.SubjectTypesSupported,
+ IDTokenSigningAlgValues: e.IDTokenSigningAlgValues,
+ IDTokenEncryptionAlgValues: e.IDTokenEncryptionAlgValues,
+ IDTokenEncryptionEncValues: e.IDTokenEncryptionEncValues,
+ UserInfoSigningAlgValues: e.UserInfoSigningAlgValues,
+ UserInfoEncryptionAlgValues: e.UserInfoEncryptionAlgValues,
+ UserInfoEncryptionEncValues: e.UserInfoEncryptionEncValues,
+ ReqObjSigningAlgValues: e.ReqObjSigningAlgValues,
+ ReqObjEncryptionAlgValues: e.ReqObjEncryptionAlgValues,
+ ReqObjEncryptionEncValues: e.ReqObjEncryptionEncValues,
+ TokenEndpointAuthMethodsSupported: e.TokenEndpointAuthMethodsSupported,
+ TokenEndpointAuthSigningAlgValuesSupported: e.TokenEndpointAuthSigningAlgValuesSupported,
+ DisplayValuesSupported: e.DisplayValuesSupported,
+ ClaimTypesSupported: e.ClaimTypesSupported,
+ ClaimsSupported: e.ClaimsSupported,
+ ServiceDocs: p.parseURI(e.ServiceDocs, "service_documentation"),
+ ClaimsLocalsSupported: e.ClaimsLocalsSupported,
+ UILocalsSupported: e.UILocalsSupported,
+ ClaimsParameterSupported: e.ClaimsParameterSupported,
+ RequestParameterSupported: e.RequestParameterSupported,
+ RequestURIParamaterSupported: e.RequestURIParamaterSupported,
+ RequireRequestURIRegistration: e.RequireRequestURIRegistration,
+ Policy: p.parseURI(e.Policy, "op_policy-uri"),
+ TermsOfService: p.parseURI(e.TermsOfService, "op_tos_uri"),
+ }
+ if p.firstErr != nil {
+ return ProviderConfig{}, p.firstErr
+ }
+ return conf, nil
+}
+
+// Empty returns if a ProviderConfig holds no information.
+//
+// This case generally indicates a ProviderConfigGetter has experienced an error
+// and has nothing to report.
+func (p ProviderConfig) Empty() bool {
+ return p.Issuer == nil
+}
+
+func contains(sli []string, ele string) bool {
+ for _, s := range sli {
+ if s == ele {
+ return true
+ }
+ }
+ return false
+}
+
+// Valid determines if a ProviderConfig conforms with the OIDC specification.
+// If Valid returns successfully it guarantees required field are non-nil and
+// URLs are well formed.
+//
+// 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 (p ProviderConfig) Valid() error {
+ grantTypes := p.GrantTypesSupported
+ if len(grantTypes) == 0 {
+ grantTypes = DefaultGrantTypesSupported
+ }
+ implicitOnly := true
+ for _, grantType := range grantTypes {
+ if grantType != oauth2.GrantTypeImplicit {
+ implicitOnly = false
+ break
+ }
+ }
+
+ if len(p.SubjectTypesSupported) == 0 {
+ return errors.New("missing required field subject_types_supported")
+ }
+ if len(p.IDTokenSigningAlgValues) == 0 {
+ return errors.New("missing required field id_token_signing_alg_values_supported")
+ }
+
+ if len(p.ScopesSupported) != 0 && !contains(p.ScopesSupported, "openid") {
+ return errors.New("scoped_supported must be unspecified or include 'openid'")
+ }
+
+ if !contains(p.IDTokenSigningAlgValues, "RS256") {
+ return errors.New("id_token_signing_alg_values_supported must include 'RS256'")
+ }
+ if contains(p.TokenEndpointAuthMethodsSupported, "none") {
+ return errors.New("token_endpoint_auth_signing_alg_values_supported cannot include 'none'")
+ }
+
+ uris := []struct {
+ val *url.URL
+ name string
+ required bool
+ }{
+ {p.Issuer, "issuer", true},
+ {p.AuthEndpoint, "authorization_endpoint", true},
+ {p.TokenEndpoint, "token_endpoint", !implicitOnly},
+ {p.UserInfoEndpoint, "userinfo_endpoint", false},
+ {p.KeysEndpoint, "jwks_uri", true},
+ {p.RegistrationEndpoint, "registration_endpoint", false},
+ {p.ServiceDocs, "service_documentation", false},
+ {p.Policy, "op_policy_uri", false},
+ {p.TermsOfService, "op_tos_uri", false},
+ }
+
+ for _, uri := range uris {
+ if uri.val == nil {
+ if !uri.required {
+ continue
+ }
+ return fmt.Errorf("empty value for required uri field %s", uri.name)
+ }
+ if uri.val.Host == "" {
+ return fmt.Errorf("no host for uri field %s", uri.name)
+ }
+ if uri.val.Scheme != "http" && uri.val.Scheme != "https" {
+ return fmt.Errorf("uri field %s schemeis not http or https", uri.name)
+ }
+ }
+ return nil
+}
+
+// Supports determines if provider supports a client given their respective metadata.
+func (p ProviderConfig) Supports(c ClientMetadata) error {
+ if err := p.Valid(); err != nil {
+ return fmt.Errorf("invalid provider config: %v", err)
+ }
+ if err := c.Valid(); err != nil {
+ return fmt.Errorf("invalid client config: %v", err)
+ }
+
+ // Fill default values for omitted fields
+ c = c.Defaults()
+ p = p.Defaults()
+
+ // Do the supported values list the requested one?
+ supports := []struct {
+ supported []string
+ requested string
+ name string
+ }{
+ {p.IDTokenSigningAlgValues, c.IDTokenResponseOptions.SigningAlg, "id_token_signed_response_alg"},
+ {p.IDTokenEncryptionAlgValues, c.IDTokenResponseOptions.EncryptionAlg, "id_token_encryption_response_alg"},
+ {p.IDTokenEncryptionEncValues, c.IDTokenResponseOptions.EncryptionEnc, "id_token_encryption_response_enc"},
+ {p.UserInfoSigningAlgValues, c.UserInfoResponseOptions.SigningAlg, "userinfo_signed_response_alg"},
+ {p.UserInfoEncryptionAlgValues, c.UserInfoResponseOptions.EncryptionAlg, "userinfo_encryption_response_alg"},
+ {p.UserInfoEncryptionEncValues, c.UserInfoResponseOptions.EncryptionEnc, "userinfo_encryption_response_enc"},
+ {p.ReqObjSigningAlgValues, c.RequestObjectOptions.SigningAlg, "request_object_signing_alg"},
+ {p.ReqObjEncryptionAlgValues, c.RequestObjectOptions.EncryptionAlg, "request_object_encryption_alg"},
+ {p.ReqObjEncryptionEncValues, c.RequestObjectOptions.EncryptionEnc, "request_object_encryption_enc"},
+ }
+ for _, field := range supports {
+ if field.requested == "" {
+ continue
+ }
+ if !contains(field.supported, field.requested) {
+ return fmt.Errorf("provider does not support requested value for field %s", field.name)
+ }
+ }
+
+ stringsEqual := func(s1, s2 string) bool { return s1 == s2 }
+
+ // For lists, are the list of requested values a subset of the supported ones?
+ supportsAll := []struct {
+ supported []string
+ requested []string
+ name string
+ // OAuth2.0 response_type can be space separated lists where order doesn't matter.
+ // For example "id_token token" is the same as "token id_token"
+ // Support a custom compare method.
+ comp func(s1, s2 string) bool
+ }{
+ {p.GrantTypesSupported, c.GrantTypes, "grant_types", stringsEqual},
+ {p.ResponseTypesSupported, c.ResponseTypes, "response_type", oauth2.ResponseTypesEqual},
+ }
+ for _, field := range supportsAll {
+ requestLoop:
+ for _, req := range field.requested {
+ for _, sup := range field.supported {
+ if field.comp(req, sup) {
+ continue requestLoop
+ }
+ }
+ return fmt.Errorf("provider does not support requested value for field %s", field.name)
+ }
+ }
+
+ // TODO(ericchiang): Are there more checks we feel comfortable with begin strict about?
+
+ return nil
+}
+
+func (p ProviderConfig) SupportsGrantType(grantType string) bool {
+ var supported []string
+ if len(p.GrantTypesSupported) == 0 {
+ supported = DefaultGrantTypesSupported
+ } else {
+ supported = p.GrantTypesSupported
+ }
+
+ for _, t := range supported {
+ if t == grantType {
+ return true
+ }
+ }
+ return false
+}
+
+type ProviderConfigGetter interface {
+ Get() (ProviderConfig, error)
+}
+
+type ProviderConfigSetter interface {
+ Set(ProviderConfig) error
+}
+
+type ProviderConfigSyncer struct {
+ from ProviderConfigGetter
+ to ProviderConfigSetter
+ clock clockwork.Clock
+
+ initialSyncDone bool
+ initialSyncWait sync.WaitGroup
+}
+
+func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
+ return &ProviderConfigSyncer{
+ from: from,
+ to: to,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+func (s *ProviderConfigSyncer) Run() chan struct{} {
+ stop := make(chan struct{})
+
+ var next pcsStepper
+ next = &pcsStepNext{aft: time.Duration(0)}
+
+ s.initialSyncWait.Add(1)
+ go func() {
+ for {
+ select {
+ case <-s.clock.After(next.after()):
+ next = next.step(s.sync)
+ case <-stop:
+ return
+ }
+ }
+ }()
+
+ return stop
+}
+
+func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
+ s.initialSyncWait.Wait()
+}
+
+func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
+ cfg, err := s.from.Get()
+ if err != nil {
+ return 0, err
+ }
+
+ if err = s.to.Set(cfg); err != nil {
+ return 0, fmt.Errorf("error setting provider config: %v", err)
+ }
+
+ if !s.initialSyncDone {
+ s.initialSyncWait.Done()
+ s.initialSyncDone = true
+ }
+
+ log.Infof("Updating provider config: config=%#v", cfg)
+
+ return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
+}
+
+type pcsStepFunc func() (time.Duration, error)
+
+type pcsStepper interface {
+ after() time.Duration
+ step(pcsStepFunc) pcsStepper
+}
+
+type pcsStepNext struct {
+ aft time.Duration
+}
+
+func (n *pcsStepNext) after() time.Duration {
+ return n.aft
+}
+
+func (n *pcsStepNext) step(fn pcsStepFunc) (next pcsStepper) {
+ ttl, err := fn()
+ if err == nil {
+ next = &pcsStepNext{aft: ttl}
+ log.Debugf("Synced provider config, next attempt in %v", next.after())
+ } else {
+ next = &pcsStepRetry{aft: time.Second}
+ log.Errorf("Provider config sync failed, retrying in %v: %v", next.after(), err)
+ }
+ return
+}
+
+type pcsStepRetry struct {
+ aft time.Duration
+}
+
+func (r *pcsStepRetry) after() time.Duration {
+ return r.aft
+}
+
+func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) {
+ ttl, err := fn()
+ if err == nil {
+ next = &pcsStepNext{aft: ttl}
+ log.Infof("Provider config sync no longer failing")
+ } else {
+ next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)}
+ log.Errorf("Provider config sync still failing, retrying in %v: %v", next.after(), err)
+ }
+ return
+}
+
+func nextSyncAfter(exp time.Time, clock clockwork.Clock) time.Duration {
+ if exp.IsZero() {
+ return MaximumProviderConfigSyncInterval
+ }
+
+ t := exp.Sub(clock.Now()) / 2
+ if t > MaximumProviderConfigSyncInterval {
+ t = MaximumProviderConfigSyncInterval
+ } else if t < minimumProviderConfigSyncInterval {
+ t = minimumProviderConfigSyncInterval
+ }
+
+ return t
+}
+
+type httpProviderConfigGetter struct {
+ hc phttp.Client
+ issuerURL string
+ clock clockwork.Clock
+}
+
+func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProviderConfigGetter {
+ return &httpProviderConfigGetter{
+ hc: hc,
+ issuerURL: issuerURL,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
+ // If the Issuer value contains a path component, any terminating / MUST be removed before
+ // appending /.well-known/openid-configuration.
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
+ discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
+ req, err := http.NewRequest("GET", discoveryURL, nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := r.hc.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if err = json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
+ return
+ }
+
+ var ttl time.Duration
+ var ok bool
+ ttl, ok, err = phttp.Cacheable(resp.Header)
+ if err != nil {
+ return
+ } else if ok {
+ cfg.ExpiresAt = r.clock.Now().UTC().Add(ttl)
+ }
+
+ // The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
+ // http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
+ if !urlEqual(cfg.Issuer.String(), r.issuerURL) {
+ err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
+ return
+ }
+
+ return
+}
+
+func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, error) {
+ if hc == nil {
+ hc = http.DefaultClient
+ }
+
+ g := NewHTTPProviderConfigGetter(hc, issuerURL)
+ return g.Get()
+}
+
+func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
+ return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
+}
+
+func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
+ var sleep time.Duration
+ var err error
+ for {
+ pcfg, err = FetchProviderConfig(hc, issuerURL)
+ if err == nil {
+ break
+ }
+
+ sleep = timeutil.ExpBackoff(sleep, time.Minute)
+ fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err)
+ time.Sleep(sleep)
+ }
+
+ return
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/transport.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/transport.go
new file mode 100644
index 0000000..61c926d
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/transport.go
@@ -0,0 +1,88 @@
+package oidc
+
+import (
+ "fmt"
+ "net/http"
+ "sync"
+
+ phttp "github.com/coreos/go-oidc/http"
+ "github.com/coreos/go-oidc/jose"
+)
+
+type TokenRefresher interface {
+ // Verify checks if the provided token is currently valid or not.
+ Verify(jose.JWT) error
+
+ // Refresh attempts to authenticate and retrieve a new token.
+ Refresh() (jose.JWT, error)
+}
+
+type ClientCredsTokenRefresher struct {
+ Issuer string
+ OIDCClient *Client
+}
+
+func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
+ _, err = VerifyClientClaims(jwt, c.Issuer)
+ return
+}
+
+func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
+ if err = c.OIDCClient.Healthy(); err != nil {
+ err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
+ return
+ }
+
+ jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
+ if err != nil {
+ err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
+ return
+ }
+
+ return
+}
+
+type AuthenticatedTransport struct {
+ TokenRefresher
+ http.RoundTripper
+
+ mu sync.Mutex
+ jwt jose.JWT
+}
+
+func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ if t.TokenRefresher.Verify(t.jwt) == nil {
+ return t.jwt, nil
+ }
+
+ jwt, err := t.TokenRefresher.Refresh()
+ if err != nil {
+ return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
+ }
+
+ t.jwt = jwt
+ return t.jwt, nil
+}
+
+// SetJWT sets the JWT held by the Transport.
+// This is useful for cases in which you want to set an initial JWT.
+func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ t.jwt = jwt
+}
+
+func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
+ jwt, err := t.verifiedJWT()
+ if err != nil {
+ return nil, err
+ }
+
+ req := phttp.CopyRequest(r)
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
+ return t.RoundTripper.RoundTrip(req)
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/util.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/util.go
new file mode 100644
index 0000000..f2a5a19
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/util.go
@@ -0,0 +1,109 @@
+package oidc
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/coreos/go-oidc/jose"
+)
+
+// RequestTokenExtractor funcs extract a raw encoded token from a request.
+type RequestTokenExtractor func(r *http.Request) (string, error)
+
+// ExtractBearerToken is a RequestTokenExtractor which extracts a bearer token from a request's
+// Authorization header.
+func ExtractBearerToken(r *http.Request) (string, error) {
+ ah := r.Header.Get("Authorization")
+ if ah == "" {
+ return "", errors.New("missing Authorization header")
+ }
+
+ if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" {
+ return "", errors.New("should be a bearer token")
+ }
+
+ val := ah[7:]
+ if len(val) == 0 {
+ return "", errors.New("bearer token is empty")
+ }
+
+ return val, nil
+}
+
+// CookieTokenExtractor returns a RequestTokenExtractor which extracts a token from the named cookie in a request.
+func CookieTokenExtractor(cookieName string) RequestTokenExtractor {
+ return func(r *http.Request) (string, error) {
+ ck, err := r.Cookie(cookieName)
+ if err != nil {
+ return "", fmt.Errorf("token cookie not found in request: %v", err)
+ }
+
+ if ck.Value == "" {
+ return "", errors.New("token cookie found but is empty")
+ }
+
+ return ck.Value, nil
+ }
+}
+
+func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims {
+ return jose.Claims{
+ // required
+ "iss": iss,
+ "sub": sub,
+ "aud": aud,
+ "iat": iat.Unix(),
+ "exp": exp.Unix(),
+ }
+}
+
+func GenClientID(hostport string) (string, error) {
+ b, err := randBytes(32)
+ if err != nil {
+ return "", err
+ }
+
+ var host string
+ if strings.Contains(hostport, ":") {
+ host, _, err = net.SplitHostPort(hostport)
+ if err != nil {
+ return "", err
+ }
+ } else {
+ host = hostport
+ }
+
+ return fmt.Sprintf("%s@%s", base64.URLEncoding.EncodeToString(b), host), nil
+}
+
+func randBytes(n int) ([]byte, error) {
+ b := make([]byte, n)
+ got, err := rand.Read(b)
+ if err != nil {
+ return nil, err
+ } else if n != got {
+ return nil, errors.New("unable to generate enough random data")
+ }
+ return b, nil
+}
+
+// urlEqual checks two urls for equality using only the host and path portions.
+func urlEqual(url1, url2 string) bool {
+ u1, err := url.Parse(url1)
+ if err != nil {
+ return false
+ }
+ u2, err := url.Parse(url2)
+ if err != nil {
+ return false
+ }
+
+ return strings.ToLower(u1.Host+u1.Path) == strings.ToLower(u2.Host+u2.Path)
+}
diff --git a/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/verification.go b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/verification.go
new file mode 100644
index 0000000..0024130
--- /dev/null
+++ b/kube2msb/src/vendor/github.com/coreos/go-oidc/oidc/verification.go
@@ -0,0 +1,188 @@
+package oidc
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/jonboulle/clockwork"
+
+ "github.com/coreos/go-oidc/jose"
+ "github.com/coreos/go-oidc/key"
+)
+
+func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) {
+ jwtBytes := []byte(jwt.Data())
+ for _, k := range keys {
+ v, err := k.Verifier()
+ if err != nil {
+ return false, err
+ }
+ if v.Verify(jwt.Signature, jwtBytes) == nil {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+// containsString returns true if the given string(needle) is found
+// in the string array(haystack).
+func containsString(needle string, haystack []string) bool {
+ for _, v := range haystack {
+ if v == needle {
+ return true
+ }
+ }
+ return false
+}
+
+// Verify claims in accordance with OIDC spec
+// http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
+func VerifyClaims(jwt jose.JWT, issuer, clientID string) error {
+ now := time.Now().UTC()
+
+ claims, err := jwt.Claims()
+ if err != nil {
+ return err
+ }
+
+ ident, err := IdentityFromClaims(claims)
+ if err != nil {
+ return err
+ }
+
+ if ident.ExpiresAt.Before(now) {
+ return errors.New("token is expired")
+ }
+
+ // iss REQUIRED. Issuer Identifier for the Issuer of the response.
+ // The iss value is a case sensitive URL using the https scheme that contains scheme,
+ // host, and optionally, port number and path components and no query or fragment components.
+ if iss, exists := claims["iss"].(string); exists {
+ if !urlEqual(iss, issuer) {
+ return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss)
+ }
+ } else {
+ return errors.New("missing claim: 'iss'")
+ }
+
+ // iat REQUIRED. Time at which the JWT was issued.
+ // Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
+ // as measured in UTC until the date/time.
+ if _, exists := claims["iat"].(float64); !exists {
+ return errors.New("missing claim: 'iat'")
+ }
+
+ // aud REQUIRED. Audience(s) that this ID Token is intended for.
+ // It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
+ // It MAY also contain identifiers for other audiences. In the general case, the aud
+ // value is an array of case sensitive strings. In the common special case when there
+ // is one audience, the aud value MAY be a single case sensitive string.
+ if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
+ if aud != clientID {
+ return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID)
+ }
+ } else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
+ if !containsString(clientID, aud) {
+ return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID)
+ }
+ } else {
+ return errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
+ }
+
+ return nil
+}
+
+// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT.
+// Returns the client ID if valid, or an error if invalid.
+func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) {
+ claims, err := jwt.Claims()
+ if err != nil {
+ return "", fmt.Errorf("failed to parse JWT claims: %v", err)
+ }
+
+ iss, ok, err := claims.StringClaim("iss")
+ if err != nil {
+ return "", fmt.Errorf("failed to parse 'iss' claim: %v", err)
+ } else if !ok {
+ return "", errors.New("missing required 'iss' claim")
+ } else if !urlEqual(iss, issuer) {
+ return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss)
+ }
+
+ sub, ok, err := claims.StringClaim("sub")
+ if err != nil {
+ return "", fmt.Errorf("failed to parse 'sub' claim: %v", err)
+ } else if !ok {
+ return "", errors.New("missing required 'sub' claim")
+ }
+
+ if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
+ if aud != sub {
+ return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub)
+ }
+ } else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
+ if !containsString(sub, aud) {
+ return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub)
+ }
+ } else {
+ return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
+ }
+
+ now := time.Now().UTC()
+ exp, ok, err := claims.TimeClaim("exp")
+ if err != nil {
+ return "", fmt.Errorf("failed to parse 'exp' claim: %v", err)
+ } else if !ok {
+ return "", errors.New("missing required 'exp' claim")
+ } else if exp.Before(now) {
+ return "", fmt.Errorf("token already expired at: %v", exp)
+ }
+
+ return sub, nil
+}
+
+type JWTVerifier struct {
+ issuer string
+ clientID string
+ syncFunc func() error
+ keysFunc func() []key.PublicKey
+ clock clockwork.Clock
+}
+
+func NewJWTVerifier(issuer, clientID string, syncFunc func() error, keysFunc func() []key.PublicKey) JWTVerifier {
+ return JWTVerifier{
+ issuer: issuer,
+ clientID: clientID,
+ syncFunc: syncFunc,
+ keysFunc: keysFunc,
+ clock: clockwork.NewRealClock(),
+ }
+}
+
+func (v *JWTVerifier) Verify(jwt jose.JWT) error {
+ ok, err := VerifySignature(jwt, v.keysFunc())
+ if ok {
+ goto SignatureVerified
+ } else if err != nil {
+ return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
+ }
+
+ if err = v.syncFunc(); err != nil {
+ return fmt.Errorf("oidc: failed syncing KeySet: %v", err)
+ }
+
+ ok, err = VerifySignature(jwt, v.keysFunc())
+ if err != nil {
+ return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
+ } else if !ok {
+ return errors.New("oidc: unable to verify JWT signature: no matching keys")
+ }
+
+SignatureVerified:
+ if err := VerifyClaims(jwt, v.issuer, v.clientID); err != nil {
+ return fmt.Errorf("oidc: JWT claims invalid: %v", err)
+ }
+
+ return nil
+}