From e14c2574b8af69f5b8d4205c6a2dbb0fec47fe00 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 7 Apr 2015 23:33:10 -0400 Subject: [PATCH 1/2] Refactor OAuth config --- pkg/cmd/server/api/helpers.go | 18 ++++---- pkg/cmd/server/api/register.go | 10 ++--- pkg/cmd/server/api/types.go | 14 +++--- pkg/cmd/server/api/v1/register.go | 10 ++--- pkg/cmd/server/api/v1/types.go | 11 ++--- pkg/cmd/server/api/validation/oauth.go | 34 +++++++++----- pkg/cmd/server/origin/auth.go | 61 ++++++++++++++------------ 7 files changed, 84 insertions(+), 74 deletions(-) diff --git a/pkg/cmd/server/api/helpers.go b/pkg/cmd/server/api/helpers.go index 75cad15e7035..e5430f7025f8 100644 --- a/pkg/cmd/server/api/helpers.go +++ b/pkg/cmd/server/api/helpers.go @@ -244,10 +244,7 @@ func GetKubeletClientConfig(options MasterConfig) *kclient.KubeletConfig { } func IsPasswordAuthenticator(provider IdentityProvider) bool { - return IsPasswordAuthenticatorProviderType(provider.Provider) -} -func IsPasswordAuthenticatorProviderType(provider runtime.EmbeddedObject) bool { - switch provider.Object.(type) { + switch provider.Provider.Object.(type) { case (*BasicAuthPasswordIdentityProvider), (*AllowAllPasswordIdentityProvider), @@ -264,11 +261,12 @@ func IsIdentityProviderType(provider runtime.EmbeddedObject) bool { switch provider.Object.(type) { case (*RequestHeaderIdentityProvider), - (*OAuthRedirectingIdentityProvider), (*BasicAuthPasswordIdentityProvider), (*AllowAllPasswordIdentityProvider), (*DenyAllPasswordIdentityProvider), - (*HTPasswdPasswordIdentityProvider): + (*HTPasswdPasswordIdentityProvider), + (*GitHubIdentityProvider), + (*GoogleIdentityProvider): return true } @@ -276,11 +274,11 @@ func IsIdentityProviderType(provider runtime.EmbeddedObject) bool { return false } -func IsOAuthProviderType(provider runtime.EmbeddedObject) bool { - switch provider.Object.(type) { +func IsOAuthIdentityProvider(provider IdentityProvider) bool { + switch provider.Provider.Object.(type) { case - (*GoogleOAuthProvider), - (*GitHubOAuthProvider): + (*GitHubIdentityProvider), + (*GoogleIdentityProvider): return true } diff --git a/pkg/cmd/server/api/register.go b/pkg/cmd/server/api/register.go index b54805587403..f635a77b8e2c 100644 --- a/pkg/cmd/server/api/register.go +++ b/pkg/cmd/server/api/register.go @@ -18,10 +18,9 @@ func init() { &DenyAllPasswordIdentityProvider{}, &HTPasswdPasswordIdentityProvider{}, &RequestHeaderIdentityProvider{}, - &OAuthRedirectingIdentityProvider{}, + &GitHubIdentityProvider{}, + &GoogleIdentityProvider{}, &GrantConfig{}, - &GoogleOAuthProvider{}, - &GitHubOAuthProvider{}, ) } @@ -31,10 +30,9 @@ func (*AllowAllPasswordIdentityProvider) IsAnAPIObject() {} func (*DenyAllPasswordIdentityProvider) IsAnAPIObject() {} func (*HTPasswdPasswordIdentityProvider) IsAnAPIObject() {} func (*RequestHeaderIdentityProvider) IsAnAPIObject() {} -func (*OAuthRedirectingIdentityProvider) IsAnAPIObject() {} +func (*GitHubIdentityProvider) IsAnAPIObject() {} +func (*GoogleIdentityProvider) IsAnAPIObject() {} func (*GrantConfig) IsAnAPIObject() {} -func (*GoogleOAuthProvider) IsAnAPIObject() {} -func (*GitHubOAuthProvider) IsAnAPIObject() {} func (*MasterConfig) IsAnAPIObject() {} func (*NodeConfig) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/types.go b/pkg/cmd/server/api/types.go index 9b81a5daa45f..d499d65b4919 100644 --- a/pkg/cmd/server/api/types.go +++ b/pkg/cmd/server/api/types.go @@ -252,22 +252,24 @@ type RequestHeaderIdentityProvider struct { Headers []string } -type OAuthRedirectingIdentityProvider struct { +type GitHubIdentityProvider struct { api.TypeMeta // ClientID is the oauth client ID ClientID string // ClientSecret is the oauth client secret ClientSecret string - - // Provider contains the information about exactly which kind of oauth you're identifying with - Provider runtime.EmbeddedObject } -type GoogleOAuthProvider struct { +type GoogleIdentityProvider struct { api.TypeMeta + + // ClientID is the oauth client ID + ClientID string + // ClientSecret is the oauth client secret + ClientSecret string } -type GitHubOAuthProvider struct { + api.TypeMeta } diff --git a/pkg/cmd/server/api/v1/register.go b/pkg/cmd/server/api/v1/register.go index 4e8a138756ff..cf7ffd1f23e6 100644 --- a/pkg/cmd/server/api/v1/register.go +++ b/pkg/cmd/server/api/v1/register.go @@ -19,10 +19,9 @@ func init() { &DenyAllPasswordIdentityProvider{}, &HTPasswdPasswordIdentityProvider{}, &RequestHeaderIdentityProvider{}, - &OAuthRedirectingIdentityProvider{}, + &GitHubIdentityProvider{}, + &GoogleIdentityProvider{}, &GrantConfig{}, - &GoogleOAuthProvider{}, - &GitHubOAuthProvider{}, ) } @@ -32,10 +31,9 @@ func (*AllowAllPasswordIdentityProvider) IsAnAPIObject() {} func (*DenyAllPasswordIdentityProvider) IsAnAPIObject() {} func (*HTPasswdPasswordIdentityProvider) IsAnAPIObject() {} func (*RequestHeaderIdentityProvider) IsAnAPIObject() {} -func (*OAuthRedirectingIdentityProvider) IsAnAPIObject() {} +func (*GitHubIdentityProvider) IsAnAPIObject() {} +func (*GoogleIdentityProvider) IsAnAPIObject() {} func (*GrantConfig) IsAnAPIObject() {} -func (*GoogleOAuthProvider) IsAnAPIObject() {} -func (*GitHubOAuthProvider) IsAnAPIObject() {} func (*MasterConfig) IsAnAPIObject() {} func (*NodeConfig) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index 2668673aff75..b0ff321c5b08 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -244,19 +244,20 @@ type RequestHeaderIdentityProvider struct { Headers []string `json:"headers"` } -type OAuthRedirectingIdentityProvider struct { +type GitHubIdentityProvider struct { v1beta3.TypeMeta `json:",inline"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` - - Provider runtime.RawExtension `json:"provider"` } -type GoogleOAuthProvider struct { +type GoogleIdentityProvider struct { v1beta3.TypeMeta `json:",inline"` + + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` } -type GitHubOAuthProvider struct { + v1beta3.TypeMeta `json:",inline"` } diff --git a/pkg/cmd/server/api/validation/oauth.go b/pkg/cmd/server/api/validation/oauth.go index ecef17c00bf2..322fb4b1382a 100644 --- a/pkg/cmd/server/api/validation/oauth.go +++ b/pkg/cmd/server/api/validation/oauth.go @@ -96,18 +96,12 @@ func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors case (*api.HTPasswdPasswordIdentityProvider): allErrs = append(allErrs, ValidateFile(provider.File, "provider.file")...) - case (*api.OAuthRedirectingIdentityProvider): - if len(provider.ClientID) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientID")) - } - if len(provider.ClientSecret) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientSecret")) - } - if !api.IsOAuthProviderType(provider.Provider) { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.provider", provider.Provider, fmt.Sprintf("%v is invalid in this context", identityProvider.Provider))) - } - if identityProvider.UseAsChallenger { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("challenge", identityProvider.UseAsChallenger, "oauth providers cannot be used for challenges")) + case (*api.GitHubIdentityProvider): + allErrs = append(allErrs, ValidateOAuthIdentityProvider(provider.ClientID, provider.ClientSecret, identityProvider.UseAsChallenger)...) + + case (*api.GoogleIdentityProvider): + allErrs = append(allErrs, ValidateOAuthIdentityProvider(provider.ClientID, provider.ClientSecret, identityProvider.UseAsChallenger)...) + } } @@ -116,6 +110,22 @@ func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors return allErrs } +func ValidateOAuthIdentityProvider(clientID, clientSecret string, challenge bool) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(clientID) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientID")) + } + if len(clientSecret) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.clientSecret")) + } + if challenge { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("challenge", challenge, "oauth providers cannot be used for challenges")) + } + + return allErrs +} + func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} diff --git a/pkg/cmd/server/origin/auth.go b/pkg/cmd/server/origin/auth.go index 895c60449871..38ffec5e9820 100644 --- a/pkg/cmd/server/origin/auth.go +++ b/pkg/cmd/server/origin/auth.go @@ -305,35 +305,25 @@ func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler hand challengers["login"] = passwordchallenger.NewBasicAuthChallenger("openshift") } - } else { - switch provider := identityProvider.Provider.Object.(type) { - case (*configapi.OAuthRedirectingIdentityProvider): - callbackPath := "" - var oauthProvider external.Provider - switch provider.Provider.Object.(type) { - case (*configapi.GoogleOAuthProvider): - callbackPath = path.Join(OpenShiftOAuthCallbackPrefix, "google") - oauthProvider = google.NewProvider(identityProvider.Name, provider.ClientID, provider.ClientSecret) - case (*configapi.GitHubOAuthProvider): - callbackPath = path.Join(OpenShiftOAuthCallbackPrefix, "github") - oauthProvider = github.NewProvider(identityProvider.Name, provider.ClientID, provider.ClientSecret) - default: - return nil, fmt.Errorf("unexpected oauth provider %#v", provider) - } + } else if configapi.IsOAuthIdentityProvider(identityProvider) { + oauthProvider, err := c.getOAuthProvider(identityProvider) + if err != nil { + return nil, err + } - state := external.DefaultState(getCSRF()) - oauthHandler, err := external.NewExternalOAuthRedirector(oauthProvider, state, c.Options.MasterPublicURL+callbackPath, successHandler, errorHandler, identityMapper) - if err != nil { - return nil, fmt.Errorf("unexpected error: %v", err) - } + state := external.DefaultState(getCSRF()) + callbackPath := path.Join(OpenShiftOAuthCallbackPrefix, identityProvider.Name) + oauthHandler, err := external.NewExternalOAuthRedirector(oauthProvider, state, c.Options.MasterPublicURL+callbackPath, successHandler, errorHandler, identityMapper) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } - mux.Handle(callbackPath, oauthHandler) - if identityProvider.UseAsLogin { - redirectors[identityProvider.Name] = oauthHandler - } - if identityProvider.UseAsChallenger { - return nil, errors.New("oauth identity providers cannot issue challenges") - } + mux.Handle(callbackPath, oauthHandler) + if identityProvider.UseAsLogin { + redirectors[identityProvider.Name] = oauthHandler + } + if identityProvider.UseAsChallenger { + return nil, errors.New("oauth identity providers cannot issue challenges") } } } @@ -342,6 +332,20 @@ func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler hand return authHandler, nil } +func (c *AuthConfig) getOAuthProvider(identityProvider configapi.IdentityProvider) (external.Provider, error) { + switch provider := identityProvider.Provider.Object.(type) { + case (*configapi.GitHubIdentityProvider): + return github.NewProvider(identityProvider.Name, provider.ClientID, provider.ClientSecret), nil + + case (*configapi.GoogleIdentityProvider): + return google.NewProvider(identityProvider.Name, provider.ClientID, provider.ClientSecret) + + default: + return nil, fmt.Errorf("No password auth found that matches %v. The oauth server cannot start!", identityProvider) + } + +} + func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.IdentityProvider) (authenticator.Password, error) { identityMapper := identitymapper.NewAlwaysCreateUserIdentityToUserMapper(c.IdentityRegistry, c.UserRegistry) @@ -393,8 +397,7 @@ func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSu continue } - switch identityProvider.Provider.Object.(type) { - case (*configapi.OAuthRedirectingIdentityProvider): + if configapi.IsOAuthIdentityProvider(identityProvider) { successHandlers = append(successHandlers, external.DefaultState(getCSRF()).(handlers.AuthenticationSuccessHandler)) } From a84072929ef566cccc8f33b38771307e61ed2ac1 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 7 Apr 2015 23:33:51 -0400 Subject: [PATCH 2/2] Add OpenID identity provider --- pkg/auth/oauth/external/github/github.go | 4 + pkg/auth/oauth/external/google/google.go | 112 ++----- pkg/auth/oauth/external/google/google_test.go | 6 +- pkg/auth/oauth/external/handler.go | 8 +- pkg/auth/oauth/external/interfaces.go | 2 + pkg/auth/oauth/external/openid/openid.go | 279 ++++++++++++++++++ pkg/auth/oauth/external/openid/openid_test.go | 23 ++ pkg/cmd/server/api/helpers.go | 5 + pkg/cmd/server/api/register.go | 2 + pkg/cmd/server/api/types.go | 45 +++ pkg/cmd/server/api/v1/register.go | 2 + pkg/cmd/server/api/v1/types.go | 53 +++- pkg/cmd/server/api/validation/oauth.go | 69 ++++- pkg/cmd/server/api/validation/validation.go | 10 +- pkg/cmd/server/origin/auth.go | 35 ++- 15 files changed, 541 insertions(+), 114 deletions(-) create mode 100644 pkg/auth/oauth/external/openid/openid.go create mode 100644 pkg/auth/oauth/external/openid/openid_test.go diff --git a/pkg/auth/oauth/external/github/github.go b/pkg/auth/oauth/external/github/github.go index 817dbfab52fa..0de81e0156d3 100644 --- a/pkg/auth/oauth/external/github/github.go +++ b/pkg/auth/oauth/external/github/github.go @@ -36,6 +36,10 @@ func NewProvider(providerName, clientID, clientSecret string) external.Provider return provider{providerName, clientID, clientSecret} } +func (p provider) GetTransport() (http.RoundTripper, error) { + return nil, nil +} + // NewConfig implements external/interfaces/Provider.NewConfig func (p provider) NewConfig() (*osincli.ClientConfig, error) { config := &osincli.ClientConfig{ diff --git a/pkg/auth/oauth/external/google/google.go b/pkg/auth/oauth/external/google/google.go index 66e5f73d8e63..7fa1a74f6022 100644 --- a/pkg/auth/oauth/external/google/google.go +++ b/pkg/auth/oauth/external/google/google.go @@ -1,113 +1,37 @@ package google import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/RangelReale/osincli" - "github.com/golang/glog" - - authapi "github.com/openshift/origin/pkg/auth/api" "github.com/openshift/origin/pkg/auth/oauth/external" + "github.com/openshift/origin/pkg/auth/oauth/external/openid" ) const ( googleAuthorizeURL = "https://accounts.google.com/o/oauth2/auth" - googleTokenURL = "https://accounts.google.com/o/oauth2/token" - googleOAuthScope = "profile email" + googleTokenURL = "https://www.googleapis.com/oauth2/v3/token" + googleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo" ) +var googleOAuthScopes = []string{"openid", "email", "profile"} + type provider struct { providerName, clientID, clientSecret string } -func NewProvider(providerName, clientID, clientSecret string) external.Provider { - return provider{providerName, clientID, clientSecret} -} - -// NewConfig implements external/interfaces/Provider.NewConfig -func (p provider) NewConfig() (*osincli.ClientConfig, error) { - config := &osincli.ClientConfig{ - ClientId: p.clientID, - ClientSecret: p.clientSecret, - ErrorsInStatusCode: true, - SendClientSecretInParams: true, - AuthorizeUrl: googleAuthorizeURL, - TokenUrl: googleTokenURL, - Scope: googleOAuthScope, - } - return config, nil -} - -// AddCustomParameters implements external/interfaces/Provider.AddCustomParameters -func (p provider) AddCustomParameters(req *osincli.AuthorizeRequest) { - req.CustomParameters["include_granted_scopes"] = "true" - req.CustomParameters["access_type"] = "offline" -} - -// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity -func (p provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { - idToken, ok := data.ResponseData["id_token"].(string) - if !ok { - return nil, false, fmt.Errorf("No id_token returned in %v", data.ResponseData) - } - - userdata, err := decodeJWT(idToken) - if err != nil { - return nil, false, err - } - - subject, _ := userdata["sub"].(string) - if subject == "" { - return nil, false, errors.New("Could not retrieve Google id") - } - - email, _ := userdata["email"].(string) - if email == "" { - return nil, false, errors.New("Could not retrieve Google email") - } - - identity := authapi.NewDefaultUserIdentityInfo(p.providerName, subject) - identity.Extra[authapi.IdentityLoginKey] = email - identity.Extra[authapi.IdentityEmailKey] = email - glog.V(4).Infof("identity=%v", identity) - - return identity, true, nil -} - -// Decode JWT -// http://openid.net/specs/draft-jones-json-web-token-07.html -func decodeJWT(jwt string) (map[string]interface{}, error) { - jwtParts := strings.Split(jwt, ".") - if len(jwtParts) != 3 { - return nil, fmt.Errorf("Invalid JSON Web Token: expected 3 parts, got %d", len(jwtParts)) - } - - encodedPayload := jwtParts[1] - glog.V(4).Infof("got encodedPayload") +func NewProvider(providerName, clientID, clientSecret string) (external.Provider, error) { + config := openid.Config{ + ClientID: clientID, + ClientSecret: clientSecret, - // Re-pad, if needed - if l := len(encodedPayload) % 4; l != 0 { - padding := strings.Repeat("=", 4-l) - encodedPayload += padding - glog.V(4).Infof("added padding: %s\n", padding) - } + AuthorizeURL: googleAuthorizeURL, + TokenURL: googleTokenURL, + UserInfoURL: googleUserInfoURL, - decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload) - if err != nil { - return nil, fmt.Errorf("Error decoding payload: %v\n", err) - } - glog.V(4).Infof("got decodedPayload") + Scopes: googleOAuthScopes, - var data map[string]interface{} - err = json.Unmarshal([]byte(decodedPayload), &data) - if err != nil { - return nil, fmt.Errorf("Error parsing token: %v\n", err) + IDClaims: []string{"sub"}, + PreferredUsernameClaims: []string{"email"}, + EmailClaims: []string{"email"}, + NameClaims: []string{"name", "email"}, } - glog.V(4).Infof("got id_token data") - - return data, nil + return openid.NewProvider(providerName, nil, config) } diff --git a/pkg/auth/oauth/external/google/google_test.go b/pkg/auth/oauth/external/google/google_test.go index 9e2541ebea3f..dee40aa9f85b 100644 --- a/pkg/auth/oauth/external/google/google_test.go +++ b/pkg/auth/oauth/external/google/google_test.go @@ -7,5 +7,9 @@ import ( ) func TestGoogle(t *testing.T) { - _ = external.Provider(NewProvider("google", "clientid", "clientsecret")) + p, err := NewProvider("google", "clientid", "clientsecret") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + _ = external.Provider(p) } diff --git a/pkg/auth/oauth/external/handler.go b/pkg/auth/oauth/external/handler.go index 3a8e72ec766d..3ecb972842b9 100644 --- a/pkg/auth/oauth/external/handler.go +++ b/pkg/auth/oauth/external/handler.go @@ -41,6 +41,12 @@ func NewExternalOAuthRedirector(provider Provider, state State, redirectURL stri return nil, err } + transport, err := provider.GetTransport() + if err != nil { + return nil, err + } + client.Transport = transport + return &Handler{ provider: provider, state: state, @@ -90,7 +96,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { accessReq := h.client.NewAccessRequest(osincli.AUTHORIZATION_CODE, authData) accessData, err := accessReq.GetToken() if err != nil { - glog.V(4).Infof("Error getting access token:", err) + glog.V(4).Infof("Error getting access token: %v", err) h.handleError(err, w, req) return } diff --git a/pkg/auth/oauth/external/interfaces.go b/pkg/auth/oauth/external/interfaces.go index 37c7572d9574..2466119c330b 100644 --- a/pkg/auth/oauth/external/interfaces.go +++ b/pkg/auth/oauth/external/interfaces.go @@ -13,6 +13,8 @@ import ( type Provider interface { // NewConfig returns a client information that allows a standard oauth client to communicate with external oauth NewConfig() (*osincli.ClientConfig, error) + // GetTransport returns the transport to use for server-to-server calls. If nil is returned, http.DefaultTransport is used. + GetTransport() (http.RoundTripper, error) // AddCustomParameters allows an external oauth provider to provide parameters that are extension to the spec. Some providers require this. AddCustomParameters(*osincli.AuthorizeRequest) // GetUserIdentity takes the external oauth token information this and returns the user identity, isAuthenticated, and error diff --git a/pkg/auth/oauth/external/openid/openid.go b/pkg/auth/oauth/external/openid/openid.go new file mode 100644 index 000000000000..3c0a8b62a5be --- /dev/null +++ b/pkg/auth/oauth/external/openid/openid.go @@ -0,0 +1,279 @@ +package openid + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/RangelReale/osincli" + "github.com/golang/glog" + + authapi "github.com/openshift/origin/pkg/auth/api" + "github.com/openshift/origin/pkg/auth/oauth/external" +) + +const ( + // Standard claims (http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) + SubjectClaim = "sub" + PreferredUsernameClaim = "preferred_username" + EmailClaim = "email" + NameClaim = "name" +) + +type Config struct { + ClientID string + ClientSecret string + + Scopes []string + + AuthorizeURL string + TokenURL string + UserInfoURL string + + IDClaims []string + PreferredUsernameClaims []string + EmailClaims []string + NameClaims []string +} + +type provider struct { + providerName string + transport http.RoundTripper + Config +} + +// NewProvider returns an implementation of an OpenID Connect Authorization Code Flow +// See http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth +// ID Token decryption is not supported +// UserInfo decryption is not supported +func NewProvider(providerName string, transport http.RoundTripper, config Config) (external.Provider, error) { + // TODO: Add support for discovery documents + // see http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + // e.g. https://accounts.google.com/.well-known/openid-configuration + + // Validate client id/secret + if len(config.ClientID) == 0 { + return nil, errors.New("ClientID is required") + } + if len(config.ClientSecret) == 0 { + return nil, errors.New("ClientSecret is required") + } + + // Validate url presence + if len(config.AuthorizeURL) == 0 { + return nil, errors.New("Authorize URL is required") + } else if u, err := url.Parse(config.AuthorizeURL); err != nil { + return nil, errors.New("Authorize URL is invalid") + } else if u.Scheme != "https" { + return nil, errors.New("Authorize URL must use https scheme") + } + + if len(config.TokenURL) == 0 { + return nil, errors.New("Token URL is required") + } else if u, err := url.Parse(config.TokenURL); err != nil { + return nil, errors.New("Token URL is invalid") + } else if u.Scheme != "https" { + return nil, errors.New("Token URL must use https scheme") + } + + if len(config.UserInfoURL) > 0 { + if u, err := url.Parse(config.UserInfoURL); err != nil { + return nil, errors.New("UserInfo URL is invalid") + } else if u.Scheme != "https" { + return nil, errors.New("UserInfo URL must use https scheme") + } + } + + if !util.NewStringSet(config.Scopes...).Has("openid") { + return nil, errors.New("Scopes must include openid") + } + + if len(config.IDClaims) == 0 { + return nil, errors.New("IDClaims must specify at least one claim") + } + + return provider{providerName, transport, config}, nil +} + +// NewConfig implements external/interfaces/Provider.NewConfig +func (p provider) NewConfig() (*osincli.ClientConfig, error) { + config := &osincli.ClientConfig{ + ClientId: p.ClientID, + ClientSecret: p.ClientSecret, + ErrorsInStatusCode: true, + SendClientSecretInParams: true, + AuthorizeUrl: p.AuthorizeURL, + TokenUrl: p.TokenURL, + Scope: strings.Join(p.Scopes, " "), + } + return config, nil +} + +func (p provider) GetTransport() (http.RoundTripper, error) { + return p.transport, nil +} + +// AddCustomParameters implements external/interfaces/Provider.AddCustomParameters +func (p provider) AddCustomParameters(req *osincli.AuthorizeRequest) { +} + +// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity +func (p provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { + // Token response MUST include id_token + // http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse + idToken, ok := data.ResponseData["id_token"].(string) + if !ok { + return nil, false, fmt.Errorf("No id_token returned in %v", data.ResponseData) + } + + // id_token MUST be a valid JWT + idTokenClaims, err := decodeJWT(idToken) + if err != nil { + return nil, false, err + } + + // TODO: validate JWT + // http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + + // id_token MUST contain a sub claim as the subject identifier + // http://openid.net/specs/openid-connect-core-1_0.html#IDToken + idTokenSubject, ok := idTokenClaims[SubjectClaim].(string) + if !ok { + return nil, false, fmt.Errorf("id_token did not contain a 'sub' claim: %#v", idTokenClaims) + } + + // Use id_token claims by default + claims := idTokenClaims + + // If we have a userinfo URL, use it to get more detailed claims + if len(p.UserInfoURL) != 0 { + userInfoClaims, err := fetchUserInfo(p.UserInfoURL, data.AccessToken, p.transport) + if err != nil { + return nil, false, err + } + + // The sub (subject) Claim MUST always be returned in the UserInfo Response. + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + userInfoSubject, ok := userInfoClaims[SubjectClaim].(string) + if !ok { + return nil, false, fmt.Errorf("userinfo response did not contain a 'sub' claim: %#v", userInfoClaims) + } + + // The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; + // if they do not match, the UserInfo Response values MUST NOT be used. + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + if userInfoSubject != idTokenSubject { + return nil, false, fmt.Errorf("userinfo 'sub' claim (%s) did not match id_token 'sub' claim (%s)", userInfoSubject, idTokenSubject) + } + + // Merge in userinfo claims in case id_token claims contained some that userinfo did not + for k, v := range userInfoClaims { + claims[k] = v + } + } + + id, _ := getClaimValue(claims, p.IDClaims) + if id == "" { + return nil, false, fmt.Errorf("Could not retrieve id claim for %#v", p.IDClaims) + } + identity := authapi.NewDefaultUserIdentityInfo(p.providerName, id) + + if preferredUsername, _ := getClaimValue(claims, p.PreferredUsernameClaims); len(preferredUsername) != 0 { + identity.Extra[authapi.IdentityLoginKey] = preferredUsername + } + + if email, _ := getClaimValue(claims, p.EmailClaims); len(email) != 0 { + identity.Extra[authapi.IdentityEmailKey] = email + } + + if name, _ := getClaimValue(claims, p.NameClaims); len(name) != 0 { + identity.Extra[authapi.IdentityDisplayNameKey] = name + } + + glog.V(4).Infof("identity=%v", identity) + + return identity, true, nil +} + +func getClaimValue(data map[string]interface{}, claims []string) (string, error) { + for _, claim := range claims { + value, ok := data[claim] + if !ok { + continue + } + stringValue, ok := value.(string) + if !ok { + return "", fmt.Errorf("Claim %s was not a string type", claim) + } + if len(stringValue) > 0 { + return stringValue, nil + } + } + return "", errors.New("No value found") +} + +// fetch and decode JSON from the given UserInfo URL +func fetchUserInfo(url, accessToken string, transport http.RoundTripper) (map[string]interface{}, error) { + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + + client := &http.Client{Transport: transport} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Non-200 response from UserInfo: %d, WWW-Authenticate=%s", resp.StatusCode, resp.Header.Get("WWW-Authenticate")) + } + + // The UserInfo Claims MUST be returned as the members of a JSON object + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + decoded := map[string]interface{}{} + if err := json.Unmarshal(data, &decoded); err != nil { + return nil, err + } + + return decoded, nil +} + +// Decode JWT +// http://openid.net/specs/draft-jones-json-web-token-07.html +func decodeJWT(jwt string) (map[string]interface{}, error) { + jwtParts := strings.Split(jwt, ".") + if len(jwtParts) != 3 { + return nil, fmt.Errorf("Invalid JSON Web Token: expected 3 parts, got %d", len(jwtParts)) + } + + // Re-pad, if needed + encodedPayload := jwtParts[1] + if l := len(encodedPayload) % 4; l != 0 { + encodedPayload += strings.Repeat("=", 4-l) + } + + // Decode base-64 + decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload) + if err != nil { + return nil, fmt.Errorf("Error decoding payload: %v", err) + } + + // Parse JSON + var data map[string]interface{} + err = json.Unmarshal([]byte(decodedPayload), &data) + if err != nil { + return nil, fmt.Errorf("Error parsing token: %v", err) + } + + return data, nil +} diff --git a/pkg/auth/oauth/external/openid/openid_test.go b/pkg/auth/oauth/external/openid/openid_test.go new file mode 100644 index 000000000000..eac7dfd55e2c --- /dev/null +++ b/pkg/auth/oauth/external/openid/openid_test.go @@ -0,0 +1,23 @@ +package openid + +import ( + "testing" + + "github.com/openshift/origin/pkg/auth/oauth/external" +) + +func TestOpenID(t *testing.T) { + p, err := NewProvider("openid", nil, Config{ + ClientID: "foo", + ClientSecret: "secret", + AuthorizeURL: "https://foo", + TokenURL: "https://foo", + Scopes: []string{"openid"}, + IDClaims: []string{"sub"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + _ = external.Provider(p) + +} diff --git a/pkg/cmd/server/api/helpers.go b/pkg/cmd/server/api/helpers.go index e5430f7025f8..362a1d9f4bd9 100644 --- a/pkg/cmd/server/api/helpers.go +++ b/pkg/cmd/server/api/helpers.go @@ -66,6 +66,9 @@ func GetMasterFileReferences(config *MasterConfig) []*string { refs = append(refs, &provider.RemoteConnectionInfo.ClientCert.CertFile) refs = append(refs, &provider.RemoteConnectionInfo.ClientCert.KeyFile) + case (*OpenIDIdentityProvider): + refs = append(refs, &provider.CA) + } } } @@ -265,6 +268,7 @@ func IsIdentityProviderType(provider runtime.EmbeddedObject) bool { (*AllowAllPasswordIdentityProvider), (*DenyAllPasswordIdentityProvider), (*HTPasswdPasswordIdentityProvider), + (*OpenIDIdentityProvider), (*GitHubIdentityProvider), (*GoogleIdentityProvider): @@ -277,6 +281,7 @@ func IsIdentityProviderType(provider runtime.EmbeddedObject) bool { func IsOAuthIdentityProvider(provider IdentityProvider) bool { switch provider.Provider.Object.(type) { case + (*OpenIDIdentityProvider), (*GitHubIdentityProvider), (*GoogleIdentityProvider): diff --git a/pkg/cmd/server/api/register.go b/pkg/cmd/server/api/register.go index f635a77b8e2c..9454beedf8cb 100644 --- a/pkg/cmd/server/api/register.go +++ b/pkg/cmd/server/api/register.go @@ -20,6 +20,7 @@ func init() { &RequestHeaderIdentityProvider{}, &GitHubIdentityProvider{}, &GoogleIdentityProvider{}, + &OpenIDIdentityProvider{}, &GrantConfig{}, ) } @@ -32,6 +33,7 @@ func (*HTPasswdPasswordIdentityProvider) IsAnAPIObject() {} func (*RequestHeaderIdentityProvider) IsAnAPIObject() {} func (*GitHubIdentityProvider) IsAnAPIObject() {} func (*GoogleIdentityProvider) IsAnAPIObject() {} +func (*OpenIDIdentityProvider) IsAnAPIObject() {} func (*GrantConfig) IsAnAPIObject() {} func (*MasterConfig) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/types.go b/pkg/cmd/server/api/types.go index d499d65b4919..0e0d2528ac10 100644 --- a/pkg/cmd/server/api/types.go +++ b/pkg/cmd/server/api/types.go @@ -270,7 +270,52 @@ type GoogleIdentityProvider struct { ClientSecret string } +type OpenIDIdentityProvider struct { api.TypeMeta + + // CA is the optional trusted certificate authority bundle to use when making requests to the server + // If empty, the default system roots are used + CA string + + // ClientID is the oauth client ID + ClientID string + // ClientSecret is the oauth client secret + ClientSecret string + + // ExtraScopes are any scopes to request in addition to the standard "openid" scope. + ExtraScopes []string + + // URLs to use to authenticate + URLs OpenIDURLs + + // Claims mappings + Claims OpenIDClaims +} + +type OpenIDURLs struct { + // Authorize is the oauth authorization URL + Authorize string + // Token is the oauth token granting URL + Token string + // UserInfo is the optional userinfo URL. + // If present, a granted access_token is used to request claims + // If empty, a granted id_token is parsed for claims + UserInfo string +} + +type OpenIDClaims struct { + // ID is the list of claims whose values should be used as the user ID. Required. + // OpenID standard identity claim is "sub" + ID []string + // PreferredUsername is the list of claims whose values should be used as the preferred username. + // If unspecified, the preferred username is determined from the value of the id claim + PreferredUsername []string + // Name is the list of claims whose values should be used as the display name. Optional. + // If unspecified, no display name is set for the identity + Name []string + // Email is the list of claims whose values should be used as the email address. Optional. + // If unspecified, no email is set for the identity + Email []string } type GrantConfig struct { diff --git a/pkg/cmd/server/api/v1/register.go b/pkg/cmd/server/api/v1/register.go index cf7ffd1f23e6..4c69fd122563 100644 --- a/pkg/cmd/server/api/v1/register.go +++ b/pkg/cmd/server/api/v1/register.go @@ -21,6 +21,7 @@ func init() { &RequestHeaderIdentityProvider{}, &GitHubIdentityProvider{}, &GoogleIdentityProvider{}, + &OpenIDIdentityProvider{}, &GrantConfig{}, ) } @@ -33,6 +34,7 @@ func (*HTPasswdPasswordIdentityProvider) IsAnAPIObject() {} func (*RequestHeaderIdentityProvider) IsAnAPIObject() {} func (*GitHubIdentityProvider) IsAnAPIObject() {} func (*GoogleIdentityProvider) IsAnAPIObject() {} +func (*OpenIDIdentityProvider) IsAnAPIObject() {} func (*GrantConfig) IsAnAPIObject() {} func (*MasterConfig) IsAnAPIObject() {} diff --git a/pkg/cmd/server/api/v1/types.go b/pkg/cmd/server/api/v1/types.go index b0ff321c5b08..dd60f23b4f03 100644 --- a/pkg/cmd/server/api/v1/types.go +++ b/pkg/cmd/server/api/v1/types.go @@ -247,18 +247,67 @@ type RequestHeaderIdentityProvider struct { type GitHubIdentityProvider struct { v1beta3.TypeMeta `json:",inline"` - ClientID string `json:"clientID"` + // ClientID is the oauth client ID + ClientID string `json:"clientID"` + // ClientSecret is the oauth client secret ClientSecret string `json:"clientSecret"` } type GoogleIdentityProvider struct { v1beta3.TypeMeta `json:",inline"` - ClientID string `json:"clientID"` + // ClientID is the oauth client ID + ClientID string `json:"clientID"` + // ClientSecret is the oauth client secret ClientSecret string `json:"clientSecret"` } +type OpenIDIdentityProvider struct { v1beta3.TypeMeta `json:",inline"` + + // CA is the optional trusted certificate authority bundle to use when making requests to the server + // If empty, the default system roots are used + CA string `json:"ca"` + + // ClientID is the oauth client ID + ClientID string `json:"clientID"` + // ClientSecret is the oauth client secret + ClientSecret string `json:"clientSecret"` + + // ExtraScopes are any scopes to request in addition to the standard "openid" scope. + ExtraScopes []string `json:"extraScopes"` + + // URLs to use to authenticate + URLs OpenIDURLs `json:"urls"` + + // Claims mappings + Claims OpenIDClaims `json:"claims"` +} + +type OpenIDURLs struct { + // Authorize is the oauth authorization URL + Authorize string `json:"authorize"` + // Token is the oauth token granting URL + Token string `json:"token"` + // UserInfo is the optional userinfo URL. + // If present, a granted access_token is used to request claims + // If empty, a granted id_token is parsed for claims + UserInfo string `json:"userInfo"` +} + +type OpenIDClaims struct { + // ID is the list of claims whose values should be used as the user ID. Required. + // OpenID standard identity claim is "sub" + ID []string `json:"id"` + // PreferredUsername is the list of claims whose values should be used as the preferred username. + // If unspecified, the preferred username is determined from the value of the id claim + PreferredUsername []string `json:"preferredUsername"` + // Name is the list of claims whose values should be used as the display name. Optional. + // If unspecified, no display name is set for the identity + Name []string `json:"name"` + // Email is the list of claims whose values should be used as the email address. Optional. + // If unspecified, no email is set for the identity + Email []string `json:"email"` } type GrantConfig struct { diff --git a/pkg/cmd/server/api/validation/oauth.go b/pkg/cmd/server/api/validation/oauth.go index 322fb4b1382a..07a514f1e536 100644 --- a/pkg/cmd/server/api/validation/oauth.go +++ b/pkg/cmd/server/api/validation/oauth.go @@ -77,18 +77,7 @@ func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors } else { switch provider := identityProvider.Provider.Object.(type) { case (*api.RequestHeaderIdentityProvider): - if len(provider.ClientCA) > 0 { - allErrs = append(allErrs, ValidateFile(provider.ClientCA, "provider.clientCA")...) - } - if len(provider.Headers) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.headers")) - } - if identityProvider.UseAsChallenger { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("challenge", identityProvider.UseAsChallenger, "request header providers cannot be used for challenges")) - } - if identityProvider.UseAsLogin { - allErrs = append(allErrs, fielderrors.NewFieldInvalid("login", identityProvider.UseAsChallenger, "request header providers cannot be used for browser login")) - } + allErrs = append(allErrs, ValidateRequestHeaderIdentityProvider(provider, identityProvider)...) case (*api.BasicAuthPasswordIdentityProvider): allErrs = append(allErrs, ValidateRemoteConnectionInfo(provider.RemoteConnectionInfo).Prefix("provider")...) @@ -102,9 +91,29 @@ func ValidateIdentityProvider(identityProvider api.IdentityProvider) fielderrors case (*api.GoogleIdentityProvider): allErrs = append(allErrs, ValidateOAuthIdentityProvider(provider.ClientID, provider.ClientSecret, identityProvider.UseAsChallenger)...) - } + case (*api.OpenIDIdentityProvider): + allErrs = append(allErrs, ValidateOpenIDIdentityProvider(provider, identityProvider)...) + } + } + return allErrs +} + +func ValidateRequestHeaderIdentityProvider(provider *api.RequestHeaderIdentityProvider, identityProvider api.IdentityProvider) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + if len(provider.ClientCA) > 0 { + allErrs = append(allErrs, ValidateFile(provider.ClientCA, "provider.clientCA")...) + } + if len(provider.Headers) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("provider.headers")) + } + if identityProvider.UseAsChallenger { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("challenge", identityProvider.UseAsChallenger, "request header providers cannot be used for challenges")) + } + if identityProvider.UseAsLogin { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("login", identityProvider.UseAsChallenger, "request header providers cannot be used for browser login")) } return allErrs @@ -126,6 +135,40 @@ func ValidateOAuthIdentityProvider(clientID, clientSecret string, challenge bool return allErrs } +func ValidateOpenIDIdentityProvider(provider *api.OpenIDIdentityProvider, identityProvider api.IdentityProvider) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + + allErrs = append(allErrs, ValidateOAuthIdentityProvider(provider.ClientID, provider.ClientSecret, identityProvider.UseAsChallenger)...) + + // Communication with the Authorization Endpoint MUST utilize TLS + // http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint + _, urlErrs := ValidateSecureURL(provider.URLs.Authorize, "authorize") + allErrs = append(allErrs, urlErrs.Prefix("provider.urls")...) + + // Communication with the Token Endpoint MUST utilize TLS + // http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + _, urlErrs = ValidateSecureURL(provider.URLs.Token, "token") + allErrs = append(allErrs, urlErrs.Prefix("provider.urls")...) + + if len(provider.URLs.UserInfo) != 0 { + // Communication with the UserInfo Endpoint MUST utilize TLS + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfo + _, urlErrs = ValidateSecureURL(provider.URLs.UserInfo, "userInfo") + allErrs = append(allErrs, urlErrs.Prefix("provider.urls")...) + } + + // At least one claim to use as the user id is required + if len(provider.Claims.ID) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("provider.claims.id", "[]", "at least one id claim is required (OpenID standard identity claim is 'sub')")) + } + + if len(provider.CA) != 0 { + allErrs = append(allErrs, ValidateFile(provider.CA, "provider.ca")...) + } + + return allErrs +} + func ValidateGrantConfig(config api.GrantConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} diff --git a/pkg/cmd/server/api/validation/validation.go b/pkg/cmd/server/api/validation/validation.go index 1267d96bb0a9..7e5be30fa913 100644 --- a/pkg/cmd/server/api/validation/validation.go +++ b/pkg/cmd/server/api/validation/validation.go @@ -106,6 +106,14 @@ func ValidateSpecifiedIP(ipString string, field string) fielderrors.ValidationEr return allErrs } +func ValidateSecureURL(urlString string, field string) (*url.URL, fielderrors.ValidationErrorList) { + url, urlErrs := ValidateURL(urlString, field) + if len(urlErrs) == 0 && url.Scheme != "https" { + urlErrs = append(urlErrs, fielderrors.NewFieldInvalid(field, urlString, "must use https scheme")) + } + return url, urlErrs +} + func ValidateURL(urlString string, field string) (*url.URL, fielderrors.ValidationErrorList) { allErrs := fielderrors.ValidationErrorList{} @@ -115,7 +123,7 @@ func ValidateURL(urlString string, field string) (*url.URL, fielderrors.Validati return nil, allErrs } if len(urlObj.Scheme) == 0 { - allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a scheme (e.g. http://)")) + allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a scheme (e.g. https://)")) } if len(urlObj.Host) == 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a host")) diff --git a/pkg/cmd/server/origin/auth.go b/pkg/cmd/server/origin/auth.go index 38ffec5e9820..5de150d4bb6f 100644 --- a/pkg/cmd/server/origin/auth.go +++ b/pkg/cmd/server/origin/auth.go @@ -15,6 +15,7 @@ import ( kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" kuser "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/RangelReale/osin" "github.com/RangelReale/osincli" "github.com/emicklei/go-restful" @@ -34,6 +35,7 @@ import ( "github.com/openshift/origin/pkg/auth/oauth/external" "github.com/openshift/origin/pkg/auth/oauth/external/github" "github.com/openshift/origin/pkg/auth/oauth/external/google" + "github.com/openshift/origin/pkg/auth/oauth/external/openid" "github.com/openshift/origin/pkg/auth/oauth/handlers" "github.com/openshift/origin/pkg/auth/oauth/registry" authnregistry "github.com/openshift/origin/pkg/auth/oauth/registry" @@ -340,8 +342,37 @@ func (c *AuthConfig) getOAuthProvider(identityProvider configapi.IdentityProvide case (*configapi.GoogleIdentityProvider): return google.NewProvider(identityProvider.Name, provider.ClientID, provider.ClientSecret) + case (*configapi.OpenIDIdentityProvider): + transport, err := cmdutil.TransportFor(provider.CA, "", "") + if err != nil { + return nil, err + } + + // OpenID Connect requests MUST contain the openid scope value + // http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + scopes := util.NewStringSet("openid") + scopes.Insert(provider.ExtraScopes...) + + config := openid.Config{ + ClientID: provider.ClientID, + ClientSecret: provider.ClientSecret, + + Scopes: scopes.List(), + + AuthorizeURL: provider.URLs.Authorize, + TokenURL: provider.URLs.Token, + UserInfoURL: provider.URLs.UserInfo, + + IDClaims: provider.Claims.ID, + PreferredUsernameClaims: provider.Claims.PreferredUsername, + EmailClaims: provider.Claims.Email, + NameClaims: provider.Claims.Name, + } + + return openid.NewProvider(identityProvider.Name, transport, config) + default: - return nil, fmt.Errorf("No password auth found that matches %v. The oauth server cannot start!", identityProvider) + return nil, fmt.Errorf("No OAuth provider found that matches %v. The OAuth server cannot start!", identityProvider) } } @@ -379,7 +410,7 @@ func (c *AuthConfig) getPasswordAuthenticator(identityProvider configapi.Identit return basicauthpassword.New(identityProvider.Name, connectionInfo.URL, transport, identityMapper), nil default: - return nil, fmt.Errorf("No password auth found that matches %v. The oauth server cannot start!", identityProvider) + return nil, fmt.Errorf("No password auth found that matches %v. The OAuth server cannot start!", identityProvider) } }