package handlers import ( "fmt" "net/http" "regexp" "strings" kerrors "k8s.io/kubernetes/pkg/util/errors" authapi "github.com/openshift/origin/pkg/auth/api" oauthapi "github.com/openshift/origin/pkg/oauth/api" ) // unionAuthenticationHandler is an oauth.AuthenticationHandler that muxes multiple challenge handlers and redirect handlers type unionAuthenticationHandler struct { challengers map[string]AuthenticationChallenger redirectors map[string]AuthenticationRedirector errorHandler AuthenticationErrorHandler } // NewUnionAuthenticationHandler returns an oauth.AuthenticationHandler that muxes multiple challenge handlers and redirect handlers func NewUnionAuthenticationHandler(passedChallengers map[string]AuthenticationChallenger, passedRedirectors map[string]AuthenticationRedirector, errorHandler AuthenticationErrorHandler) AuthenticationHandler { challengers := passedChallengers if challengers == nil { challengers = make(map[string]AuthenticationChallenger, 1) } redirectors := passedRedirectors if redirectors == nil { redirectors = make(map[string]AuthenticationRedirector, 1) } return &unionAuthenticationHandler{challengers, redirectors, errorHandler} } const ( // WarningHeaderMiscCode is the code for "Miscellaneous warning", which may be displayed to human users WarningHeaderMiscCode = "199" // WarningHeaderOpenShiftSource is the name of the agent adding the warning header WarningHeaderOpenShiftSource = "Origin" warningHeaderCodeIndex = 1 warningHeaderAgentIndex = 2 warningHeaderTextIndex = 3 warningHeaderDateIndex = 4 ) var ( // http://tools.ietf.org/html/rfc2616#section-14.46 warningRegex = regexp.MustCompile(strings.Join([]string{ // Beginning of the string `^`, // Exactly 3 digits (captured in group 1) `([0-9]{3})`, // A single space ` `, // 1+ non-space characters (captured in group 2) `([^ ]+)`, // A single space ` `, // quoted-string (value inside quotes is captured in group 3) `"((?:[^"\\]|\\.)*)"`, // Optionally followed by quoted HTTP-Date `(?: "([^"]+)")?`, // End of the string `$`, }, "")) ) // AuthenticationNeeded looks at the oauth Client to determine whether it wants try to authenticate with challenges or using a redirect path // If the client wants a challenge path, it muxes together all the different challenges from the challenge handlers // If (the client wants a redirect path) and ((there is one redirect handler) or (a redirect handler was requested via the "useRedirectHandler" parameter), // then the redirect handler is called. Otherwise, you get an error (currently) or a redirect to a page letting you choose how you'd like to authenticate. // It returns whether the response was written and/or an error func (authHandler *unionAuthenticationHandler) AuthenticationNeeded(apiClient authapi.Client, w http.ResponseWriter, req *http.Request) (bool, error) { client, ok := apiClient.GetUserData().(*oauthapi.OAuthClient) if !ok { return false, fmt.Errorf("apiClient data was not an oauthapi.OAuthClient") } if client.RespondWithChallenges { errors := []error{} headers := http.Header(make(map[string][]string)) for _, challengingHandler := range authHandler.challengers { currHeaders, err := challengingHandler.AuthenticationChallenge(req) if err != nil { errors = append(errors, err) continue } // merge header values mergeHeaders(headers, currHeaders) } if len(headers) > 0 { mergeHeaders(w.Header(), headers) w.WriteHeader(http.StatusUnauthorized) // Print Misc Warning headers (code 199) to the body if warnings, hasWarnings := w.Header()[http.CanonicalHeaderKey("Warning")]; hasWarnings { for _, warning := range warnings { warningParts := warningRegex.FindStringSubmatch(warning) if len(warningParts) != 0 && warningParts[warningHeaderCodeIndex] == WarningHeaderMiscCode { fmt.Fprintln(w, warningParts[warningHeaderTextIndex]) } } } return true, nil } return false, kerrors.NewAggregate(errors) } redirectHandlerName := req.URL.Query().Get("useRedirectHandler") if len(redirectHandlerName) > 0 { redirectHandler := authHandler.redirectors[redirectHandlerName] if redirectHandler == nil { return false, fmt.Errorf("Unable to locate redirect handler: %v", redirectHandlerName) } err := redirectHandler.AuthenticationRedirect(w, req) if err != nil { return authHandler.errorHandler.AuthenticationError(err, w, req) } return true, nil } if (len(authHandler.redirectors)) == 1 { // there has to be a better way for _, redirectHandler := range authHandler.redirectors { err := redirectHandler.AuthenticationRedirect(w, req) if err != nil { return authHandler.errorHandler.AuthenticationError(err, w, req) } return true, nil } } else if len(authHandler.redirectors) > 1 { // TODO this clearly doesn't work right. There should probably be a redirect to an interstitial page. // however, this is just as good as we have now. return false, fmt.Errorf("Too many potential redirect handlers: %v", authHandler.redirectors) } return false, nil } func mergeHeaders(dest http.Header, toAdd http.Header) { for key, values := range toAdd { for _, value := range values { dest.Add(key, value) } } }