package origin import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "net/http" "net/url" "path" "strings" "code.google.com/p/go-uuid/uuid" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" kuser "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/RangelReale/osin" "github.com/RangelReale/osincli" "github.com/emicklei/go-restful" "github.com/golang/glog" "github.com/openshift/origin/pkg/auth/authenticator" "github.com/openshift/origin/pkg/auth/authenticator/challenger/passwordchallenger" "github.com/openshift/origin/pkg/auth/authenticator/password/allowanypassword" "github.com/openshift/origin/pkg/auth/authenticator/password/basicauthpassword" "github.com/openshift/origin/pkg/auth/authenticator/password/denypassword" "github.com/openshift/origin/pkg/auth/authenticator/password/htpasswd" "github.com/openshift/origin/pkg/auth/authenticator/request/basicauthrequest" "github.com/openshift/origin/pkg/auth/authenticator/request/bearertoken" "github.com/openshift/origin/pkg/auth/authenticator/request/headerrequest" "github.com/openshift/origin/pkg/auth/authenticator/request/unionrequest" "github.com/openshift/origin/pkg/auth/authenticator/request/x509request" "github.com/openshift/origin/pkg/auth/authenticator/token/filetoken" "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/handlers" "github.com/openshift/origin/pkg/auth/oauth/registry" authnregistry "github.com/openshift/origin/pkg/auth/oauth/registry" "github.com/openshift/origin/pkg/auth/server/csrf" "github.com/openshift/origin/pkg/auth/server/grant" "github.com/openshift/origin/pkg/auth/server/login" "github.com/openshift/origin/pkg/auth/server/session" "github.com/openshift/origin/pkg/auth/server/tokenrequest" "github.com/openshift/origin/pkg/auth/userregistry/identitymapper" cmdutil "github.com/openshift/origin/pkg/cmd/util" oauthapi "github.com/openshift/origin/pkg/oauth/api" clientregistry "github.com/openshift/origin/pkg/oauth/registry/client" oauthclient "github.com/openshift/origin/pkg/oauth/registry/client" "github.com/openshift/origin/pkg/oauth/registry/clientauthorization" oauthetcd "github.com/openshift/origin/pkg/oauth/registry/etcd" "github.com/openshift/origin/pkg/oauth/server/osinserver" "github.com/openshift/origin/pkg/oauth/server/osinserver/registrystorage" "github.com/openshift/origin/pkg/user" useretcd "github.com/openshift/origin/pkg/user/registry/etcd" ) const ( OpenShiftOAuthAPIPrefix = "/oauth" OpenShiftLoginPrefix = "/login" OpenShiftApprovePrefix = "/oauth/approve" OpenShiftOAuthCallbackPrefix = "/oauth2callback" OpenShiftWebConsoleClientID = "openshift-web-console" ) var ( OSWebConsoleClientBase = oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{ Name: OpenShiftWebConsoleClientID, }, Secret: uuid.NewUUID().String(), // random secret so no one knows what it is ahead of time. } // OSBrowserClientBase is used as a skeleton for building a Client. We can't set the allowed redirecturis because we don't yet know the host:port of the auth server OSBrowserClientBase = oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{ Name: "openshift-browser-client", }, Secret: uuid.NewUUID().String(), // random secret so no one knows what it is ahead of time. This still allows us to loop back for /requestToken } OSCliClientBase = oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{ Name: "openshift-challenging-client", }, Secret: uuid.NewUUID().String(), // random secret so no one knows what it is ahead of time. This still allows us to loop back for /requestToken RespondWithChallenges: true, } ) type AuthRequestHandlerType string const ( // AuthRequestHandlerBearer validates a passed "Authorization: Bearer" token, using the specified TokenStore AuthRequestHandlerBearer AuthRequestHandlerType = "bearer" // AuthRequestHandlerRequestHeader treats any request with a value in one of the RequestHeaders headers as authenticated AuthRequestHandlerRequestHeader AuthRequestHandlerType = "requestheader" // AuthRequestHandlerBasicAuth validates a passed "Authorization: Basic" header using the specified PasswordAuth AuthRequestHandlerBasicAuth AuthRequestHandlerType = "basicauth" // AuthRequestHandlerSession authenticates requests containing user information in the request session AuthRequestHandlerSession AuthRequestHandlerType = "session" ) type AuthHandlerType string const ( // AuthHandlerLogin redirects unauthenticated requests to a login page, or sends a www-authenticate challenge. Logins are validated using the specified PasswordAuth AuthHandlerLogin AuthHandlerType = "login" // AuthHandlerGithub redirects unauthenticated requests to GitHub to request an OAuth token. AuthHandlerGithub AuthHandlerType = "github" // AuthHandlerGoogle redirects unauthenticated requests to Google to request an OAuth token. AuthHandlerGoogle AuthHandlerType = "google" // AuthHandlerDeny treats unauthenticated requests as failures AuthHandlerDeny AuthHandlerType = "deny" ) type GrantHandlerType string const ( // GrantHandlerAuto auto-approves client authorization grant requests GrantHandlerAuto GrantHandlerType = "auto" // GrantHandlerPrompt prompts the user to approve new client authorization grant requests GrantHandlerPrompt GrantHandlerType = "prompt" // GrantHandlerDeny auto-denies client authorization grant requests GrantHandlerDeny GrantHandlerType = "deny" ) type PasswordAuthType string const ( // PasswordAuthAnyPassword treats any non-empty username and password combination as a successful authentication PasswordAuthAnyPassword PasswordAuthType = "anypassword" // PasswordAuthBasicAuthURL validates password credentials by making a request to a remote url using basic auth. See basicauthpassword.Authenticator PasswordAuthBasicAuthURL PasswordAuthType = "basicauthurl" // PasswordAuthHTPasswd validates usernames and passwords against an htpasswd file PasswordAuthHTPasswd PasswordAuthType = "htpasswd" // PasswordAuthDeny treats any username and password combination as an unsuccessful authentication PasswordAuthDeny PasswordAuthType = "deny" ) type TokenStoreType string const ( // Validate bearer tokens by looking in the OAuth access token registry TokenStoreOAuth TokenStoreType = "oauth" // Validate bearer tokens by looking in a CSV file located at the specified TokenFilePath TokenStoreFile TokenStoreType = "file" ) func ParseAuthRequestHandlerTypes(types string) []AuthRequestHandlerType { handlerTypes := []AuthRequestHandlerType{} for _, handlerType := range strings.Split(types, ",") { trimmedType := AuthRequestHandlerType(strings.TrimSpace(handlerType)) switch trimmedType { case AuthRequestHandlerBearer, AuthRequestHandlerRequestHeader, AuthRequestHandlerBasicAuth, AuthRequestHandlerSession: handlerTypes = append(handlerTypes, trimmedType) default: glog.Fatalf("Unrecognized request handler type: %s", trimmedType) } } return handlerTypes } // InstallSupport registers endpoints for an OAuth2 server into the provided mux, // then returns an array of strings indicating what endpoints were started // (these are format strings that will expect to be sent a single string value). func (c *AuthConfig) InstallAPI(container *restful.Container) []string { // TODO: register into container mux := container.ServeMux oauthEtcd := oauthetcd.New(c.EtcdHelper) authRequestHandler, authHandler, authFinalizer, err := c.getAuthorizeAuthenticationHandlers(mux) if err != nil { glog.Fatal(err) } storage := registrystorage.New(oauthEtcd, oauthEtcd, oauthEtcd, registry.NewUserConversion()) config := osinserver.NewDefaultServerConfig() if c.AuthorizeTokenMaxAgeSeconds > 0 { config.AuthorizationExpiration = c.AuthorizeTokenMaxAgeSeconds } if c.AccessTokenMaxAgeSeconds > 0 { config.AccessExpiration = c.AccessTokenMaxAgeSeconds } grantChecker := registry.NewClientAuthorizationGrantChecker(oauthEtcd) grantHandler := c.getGrantHandler(mux, authRequestHandler, oauthEtcd, oauthEtcd) server := osinserver.New( config, storage, osinserver.AuthorizeHandlers{ handlers.NewAuthorizeAuthenticator( authRequestHandler, authHandler, handlers.EmptyError{}, ), handlers.NewGrantCheck( grantChecker, grantHandler, handlers.EmptyError{}, ), authFinalizer, }, osinserver.AccessHandlers{ handlers.NewDenyAccessAuthenticator(), }, osinserver.NewDefaultErrorHandler(), ) server.Install(mux, OpenShiftOAuthAPIPrefix) CreateOrUpdateDefaultOAuthClients(c.MasterPublicAddr, c.AssetPublicAddresses, oauthEtcd) osOAuthClientConfig := c.NewOpenShiftOAuthClientConfig(&OSBrowserClientBase) osOAuthClientConfig.RedirectUrl = c.MasterPublicAddr + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint) osOAuthClient, _ := osincli.NewClient(osOAuthClientConfig) if c.MasterRoots != nil { // Copy the default transport var transport http.Transport = *http.DefaultTransport.(*http.Transport) // Set TLS CA roots transport.TLSClientConfig = &tls.Config{RootCAs: c.MasterRoots} osOAuthClient.Transport = &transport } tokenRequestEndpoints := tokenrequest.NewEndpoints(osOAuthClient) tokenRequestEndpoints.Install(mux, OpenShiftOAuthAPIPrefix) // glog.Infof("oauth server configured as: %#v", server) // glog.Infof("auth handler: %#v", authHandler) // glog.Infof("auth request handler: %#v", authRequestHandler) // glog.Infof("grant checker: %#v", grantChecker) // glog.Infof("grant handler: %#v", grantHandler) return []string{ fmt.Sprintf("Started OAuth2 API at %%s%s", OpenShiftOAuthAPIPrefix), fmt.Sprintf("Started login server at %%s%s", OpenShiftLoginPrefix), } } // NewOpenShiftOAuthClientConfig provides config for OpenShift OAuth client func (c *AuthConfig) NewOpenShiftOAuthClientConfig(client *oauthapi.OAuthClient) *osincli.ClientConfig { config := &osincli.ClientConfig{ ClientId: client.Name, ClientSecret: client.Secret, ErrorsInStatusCode: true, SendClientSecretInParams: true, AuthorizeUrl: OpenShiftOAuthAuthorizeURL(c.MasterPublicAddr), TokenUrl: OpenShiftOAuthTokenURL(c.MasterPublicAddr), Scope: "", } return config } func OpenShiftOAuthAuthorizeURL(masterAddr string) string { return masterAddr + path.Join(OpenShiftOAuthAPIPrefix, osinserver.AuthorizePath) } func OpenShiftOAuthTokenURL(masterAddr string) string { return masterAddr + path.Join(OpenShiftOAuthAPIPrefix, osinserver.TokenPath) } func CreateOrUpdateDefaultOAuthClients(masterPublicAddr string, assetPublicAddresses []string, clientRegistry oauthclient.Registry) { clientsToEnsure := []*oauthapi.OAuthClient{ { ObjectMeta: kapi.ObjectMeta{ Name: OSWebConsoleClientBase.Name, }, Secret: OSWebConsoleClientBase.Secret, RespondWithChallenges: OSWebConsoleClientBase.RespondWithChallenges, RedirectURIs: assetPublicAddresses, }, { ObjectMeta: kapi.ObjectMeta{ Name: OSBrowserClientBase.Name, }, Secret: OSBrowserClientBase.Secret, RespondWithChallenges: OSBrowserClientBase.RespondWithChallenges, RedirectURIs: []string{masterPublicAddr + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint)}, }, { ObjectMeta: kapi.ObjectMeta{ Name: OSCliClientBase.Name, }, Secret: OSCliClientBase.Secret, RespondWithChallenges: OSCliClientBase.RespondWithChallenges, RedirectURIs: []string{masterPublicAddr + path.Join(OpenShiftOAuthAPIPrefix, tokenrequest.DisplayTokenEndpoint)}, }, } for _, currClient := range clientsToEnsure { if existing, err := clientRegistry.GetClient(currClient.Name); err == nil || strings.Contains(err.Error(), " not found") { if existing != nil { clientRegistry.DeleteClient(currClient.Name) } if err = clientRegistry.CreateClient(currClient); err != nil { glog.Errorf("Error creating client: %v due to %v\n", currClient, err) } } else { glog.Errorf("Error getting client: %v due to %v\n", currClient, err) } } } // getCSRF returns the object responsible for generating and checking CSRF tokens func getCSRF() csrf.CSRF { return csrf.NewCookieCSRF("csrf", "/", "", false, false) } func (c *AuthConfig) getSessionAuth() *session.Authenticator { if c.sessionAuth == nil { sessionStore := session.NewStore(int(c.SessionMaxAgeSeconds), c.SessionSecrets...) c.sessionAuth = session.NewAuthenticator(sessionStore, c.SessionName) } return c.sessionAuth } func (c *AuthConfig) getAuthorizeAuthenticationHandlers(mux cmdutil.Mux) (authenticator.Request, handlers.AuthenticationHandler, osinserver.AuthorizeHandler, error) { authRequestHandler, err := c.getAuthenticationRequestHandler() if err != nil { return nil, nil, nil, err } authHandler, err := c.getAuthenticationHandler(mux, handlers.EmptyError{}) if err != nil { return nil, nil, nil, err } authFinalizer, err := c.getAuthenticationFinalizer() if err != nil { return nil, nil, nil, err } return authRequestHandler, authHandler, authFinalizer, nil } // getGrantHandler returns the object that handles approving or rejecting grant requests func (c *AuthConfig) getGrantHandler(mux cmdutil.Mux, auth authenticator.Request, clientregistry clientregistry.Registry, authregistry clientauthorization.Registry) handlers.GrantHandler { var grantHandler handlers.GrantHandler grantHandlerType := c.GrantHandler switch grantHandlerType { case GrantHandlerDeny: grantHandler = handlers.NewEmptyGrant() case GrantHandlerAuto: grantHandler = handlers.NewAutoGrant() case GrantHandlerPrompt: grantServer := grant.NewGrant(getCSRF(), auth, grant.DefaultFormRenderer, clientregistry, authregistry) grantServer.Install(mux, OpenShiftApprovePrefix) grantHandler = handlers.NewRedirectGrant(OpenShiftApprovePrefix) default: glog.Fatalf("No grant handler found that matches %v. The oauth server cannot start!", grantHandlerType) } return grantHandler } // getAuthenticationFinalizer returns an authentication finalizer which is called just prior to writing a response to an authorization request func (c *AuthConfig) getAuthenticationFinalizer() (osinserver.AuthorizeHandler, error) { for _, requestHandler := range c.AuthRequestHandlers { switch requestHandler { case AuthRequestHandlerSession: // The session needs to know the authorize flow is done so it can invalidate the session return osinserver.AuthorizeHandlerFunc(func(ar *osin.AuthorizeRequest, w http.ResponseWriter) (bool, error) { _ = c.getSessionAuth().InvalidateAuthentication(w, ar.HttpRequest) return false, nil }), nil } } // Otherwise return a no-op finalizer return osinserver.AuthorizeHandlerFunc(func(ar *osin.AuthorizeRequest, w http.ResponseWriter) (bool, error) { return false, nil }), nil } func (c *AuthConfig) getAuthenticationHandler(mux cmdutil.Mux, errorHandler handlers.AuthenticationErrorHandler) (handlers.AuthenticationHandler, error) { successHandler := c.getAuthenticationSuccessHandler() // TODO presumably we'll want either a list of what we've got or a way to describe a registry of these // hard-coded strings as a stand-in until it gets sorted out var authHandler handlers.AuthenticationHandler authHandlerType := c.AuthHandler switch authHandlerType { case AuthHandlerGithub, AuthHandlerGoogle: callbackPath := path.Join(OpenShiftOAuthCallbackPrefix, string(authHandlerType)) userRegistry := useretcd.New(c.EtcdHelper, user.NewDefaultUserInitStrategy()) identityMapper := identitymapper.NewAlwaysCreateUserIdentityToUserMapper(string(authHandlerType) /*for now*/, userRegistry) var oauthProvider external.Provider if authHandlerType == AuthHandlerGoogle { oauthProvider = google.NewProvider(c.GoogleClientID, c.GoogleClientSecret) } else if authHandlerType == AuthHandlerGithub { oauthProvider = github.NewProvider(c.GithubClientID, c.GithubClientSecret) } state := external.DefaultState() oauthHandler, err := external.NewExternalOAuthRedirector(oauthProvider, state, c.MasterPublicAddr+callbackPath, successHandler, errorHandler, identityMapper) if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } mux.Handle(callbackPath, oauthHandler) authHandler = handlers.NewUnionAuthenticationHandler(nil, map[string]handlers.AuthenticationRedirector{string(authHandlerType): oauthHandler}, errorHandler) case AuthHandlerLogin: passwordAuth, err := c.getPasswordAuthenticator() if err != nil { return nil, err } authHandler = handlers.NewUnionAuthenticationHandler( map[string]handlers.AuthenticationChallenger{"login": passwordchallenger.NewBasicAuthChallenger("openshift")}, map[string]handlers.AuthenticationRedirector{"login": &redirector{RedirectURL: OpenShiftLoginPrefix, ThenParam: "then"}}, errorHandler, ) login := login.NewLogin(getCSRF(), &callbackPasswordAuthenticator{passwordAuth, successHandler}, login.DefaultLoginFormRenderer) login.Install(mux, OpenShiftLoginPrefix) case AuthHandlerDeny: authHandler = handlers.EmptyAuth{} default: return nil, fmt.Errorf("No AuthenticationHandler found that matches %v. The oauth server cannot start!", authHandlerType) } return authHandler, nil } func (c *AuthConfig) getPasswordAuthenticator() (authenticator.Password, error) { // TODO presumably we'll want either a list of what we've got or a way to describe a registry of these // hard-coded strings as a stand-in until it gets sorted out passwordAuthType := c.PasswordAuth userRegistry := useretcd.New(c.EtcdHelper, user.NewDefaultUserInitStrategy()) identityMapper := identitymapper.NewAlwaysCreateUserIdentityToUserMapper(string(passwordAuthType) /*for now*/, userRegistry) var passwordAuth authenticator.Password switch passwordAuthType { case PasswordAuthBasicAuthURL: basicAuthURL := c.BasicAuthURL if len(basicAuthURL) == 0 { return nil, fmt.Errorf("BasicAuthURL is required to support basic password auth") } passwordAuth = basicauthpassword.New(basicAuthURL, identityMapper) case PasswordAuthAnyPassword: // Accepts any username and password passwordAuth = allowanypassword.New(identityMapper) case PasswordAuthDeny: // Deny any username and password passwordAuth = denypassword.New() case PasswordAuthHTPasswd: htpasswdFile := c.HTPasswdFile if len(htpasswdFile) == 0 { return nil, fmt.Errorf("HTPasswdFile is required to support htpasswd auth") } if htpasswordAuth, err := htpasswd.New(htpasswdFile, identityMapper); err != nil { return nil, fmt.Errorf("Error loading htpasswd file %s: %v", htpasswdFile, err) } else { passwordAuth = htpasswordAuth } default: return nil, fmt.Errorf("No password auth found that matches %v. The oauth server cannot start!", passwordAuthType) } return passwordAuth, nil } func (c *AuthConfig) getAuthenticationSuccessHandler() handlers.AuthenticationSuccessHandler { successHandlers := handlers.AuthenticationSuccessHandlers{} for _, requestHandler := range c.AuthRequestHandlers { switch requestHandler { case AuthRequestHandlerSession: // The session needs to know so it can write auth info into the session successHandlers = append(successHandlers, c.getSessionAuth()) } } switch c.AuthHandler { case AuthHandlerGithub, AuthHandlerGoogle: successHandlers = append(successHandlers, external.DefaultState().(handlers.AuthenticationSuccessHandler)) case AuthHandlerLogin: successHandlers = append(successHandlers, redirectSuccessHandler{}) } return successHandlers } func (c *AuthConfig) getAuthenticationRequestHandlerFromType(authRequestHandlerType AuthRequestHandlerType) (authenticator.Request, error) { // TODO presumably we'll want either a list of what we've got or a way to describe a registry of these // hard-coded strings as a stand-in until it gets sorted out var authRequestHandler authenticator.Request switch authRequestHandlerType { case AuthRequestHandlerBearer: switch c.TokenStore { case TokenStoreOAuth: tokenAuthenticator, err := GetEtcdTokenAuthenticator(c.EtcdHelper) if err != nil { return nil, fmt.Errorf("Error creating TokenAuthenticator: %v. The oauth server cannot start!", err) } authRequestHandler = bearertoken.New(tokenAuthenticator, true) case TokenStoreFile: tokenAuthenticator, err := GetCSVTokenAuthenticator(c.TokenFilePath) if err != nil { return nil, fmt.Errorf("Error creating TokenAuthenticator: %v. The oauth server cannot start!", err) } authRequestHandler = bearertoken.New(tokenAuthenticator, true) default: return nil, fmt.Errorf("Unknown TokenStore %s. Must be oauth or file. The oauth server cannot start!", c.TokenStore) } case AuthRequestHandlerRequestHeader: userRegistry := useretcd.New(c.EtcdHelper, user.NewDefaultUserInitStrategy()) identityMapper := identitymapper.NewAlwaysCreateUserIdentityToUserMapper(string(authRequestHandlerType) /*for now*/, userRegistry) authRequestConfig := &headerrequest.Config{ UserNameHeaders: c.RequestHeaders, } authRequestHandler = headerrequest.NewAuthenticator(authRequestConfig, identityMapper) // Wrap with an x509 verifier if len(c.RequestHeaderCAFile) > 0 { caData, err := ioutil.ReadFile(c.RequestHeaderCAFile) if err != nil { return nil, fmt.Errorf("Error reading %s: %v", c.RequestHeaderCAFile, err) } opts := x509request.DefaultVerifyOptions() opts.Roots = x509.NewCertPool() if ok := opts.Roots.AppendCertsFromPEM(caData); !ok { return nil, fmt.Errorf("Error loading certs from %s: %v", c.RequestHeaderCAFile, err) } authRequestHandler = x509request.NewVerifier(opts, authRequestHandler) } case AuthRequestHandlerBasicAuth: passwordAuthenticator, err := c.getPasswordAuthenticator() if err != nil { return nil, err } authRequestHandler = basicauthrequest.NewBasicAuthAuthentication(passwordAuthenticator, true) case AuthRequestHandlerSession: authRequestHandler = c.getSessionAuth() default: return nil, fmt.Errorf("No AuthenticationRequestHandler found that matches %v. The oauth server cannot start!", authRequestHandlerType) } return authRequestHandler, nil } func (c *AuthConfig) getAuthenticationRequestHandler() (authenticator.Request, error) { // TODO presumably we'll want either a list of what we've got or a way to describe a registry of these // hard-coded strings as a stand-in until it gets sorted out var authRequestHandlers []authenticator.Request for _, requestHandler := range c.AuthRequestHandlers { authRequestHandler, err := c.getAuthenticationRequestHandlerFromType(requestHandler) if err != nil { return nil, err } authRequestHandlers = append(authRequestHandlers, authRequestHandler) } authRequestHandler := unionrequest.NewUnionAuthentication(authRequestHandlers...) return authRequestHandler, nil } func GetEtcdTokenAuthenticator(etcdHelper tools.EtcdHelper) (authenticator.Token, error) { oauthRegistry := oauthetcd.New(etcdHelper) return authnregistry.NewTokenAuthenticator(oauthRegistry), nil } func GetCSVTokenAuthenticator(path string) (authenticator.Token, error) { return filetoken.NewTokenAuthenticator(path) } // Captures the original request url as a "then" param in a redirect to a login flow type redirector struct { RedirectURL string ThenParam string } // AuthenticationRedirectNeeded redirects HTTP request to authorization URL func (auth *redirector) AuthenticationRedirect(w http.ResponseWriter, req *http.Request) error { redirectURL, err := url.Parse(auth.RedirectURL) if err != nil { return err } if len(auth.ThenParam) != 0 { redirectURL.RawQuery = url.Values{ auth.ThenParam: {req.URL.String()}, }.Encode() } http.Redirect(w, req, redirectURL.String(), http.StatusFound) return nil } // // Combines password auth, successful login callback, and "then" param redirection // type callbackPasswordAuthenticator struct { authenticator.Password handlers.AuthenticationSuccessHandler } // Redirects to the then param on successful authentication type redirectSuccessHandler struct{} // AuthenticationSuccess informs client when authentication was successful func (redirectSuccessHandler) AuthenticationSucceeded(user kuser.Info, then string, w http.ResponseWriter, req *http.Request) (bool, error) { if len(then) == 0 { return false, fmt.Errorf("Auth succeeded, but no redirect existed - user=%#v", user) } http.Redirect(w, req, then, http.StatusFound) return true, nil } // authenticationHandlerFilter creates a filter object that will enforce authentication directly func authenticationHandlerFilter(handler http.Handler, authenticator authenticator.Request, contextMapper kapi.RequestContextMapper) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { user, ok, err := authenticator.AuthenticateRequest(req) if err != nil || !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } glog.V(6).Infof("user %v -> %v", user, req.URL) ctx, ok := contextMapper.Get(req) if !ok { http.Error(w, "Unable to find request context", http.StatusInternalServerError) return } if err := contextMapper.Update(req, kapi.WithUser(ctx, user)); err != nil { glog.V(4).Infof("Error setting authenticated context: %v", err) http.Error(w, "Unable to set authenticated request context", http.StatusInternalServerError) return } handler.ServeHTTP(w, req) }) }