Skip to content

Commit

Permalink
Show tokenURL if only IDP is bootstrap IDP and user has not provided …
Browse files Browse the repository at this point in the history
…"kubeadm" as user
  • Loading branch information
sallyom committed Sep 14, 2020
1 parent a4a0511 commit 0ea5a8b
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 7 deletions.
51 changes: 46 additions & 5 deletions pkg/helpers/tokencmd/basicauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@ import (
"github.com/openshift/oc/pkg/helpers/term"
)

const (
// bootstrapRealm is the realm with basic auth challenges specific to the kubeadmin bootstrap user
bootstrapRealm = "openshift:kubeadmin"

// bootstrapUser is the username for basic auth bootstrap user
bootstrapUser = "kubeadmin"
)

type bootstrapPromptError struct{}

func (e *bootstrapPromptError) Error() string {
return "did not receive username 'kubeadmin' - not showing a prompt"
}

func BootstrapPromptError() error {
return &bootstrapPromptError{}
}

func IsBootstrapPromptErr(err error) bool {
if _, ok := err.(*bootstrapPromptError); ok {
return true
}
return false
}

type BasicChallengeHandler struct {
// Host is the server being authenticated to. Used only for displaying messages when prompting for username/password
Host string
Expand Down Expand Up @@ -54,13 +79,17 @@ func (c *BasicChallengeHandler) HandleChallenge(requestURL string, headers http.
missingUsername := len(username) == 0
missingPassword := len(password) == 0

_, realm := basicRealm(headers)
if missingUsername && realm == bootstrapRealm {
return nil, false, BootstrapPromptError()
}
if (missingUsername || missingPassword) && c.Reader != nil {
w := c.Writer
if w == nil {
w = os.Stdout
}

if _, realm := basicRealm(headers); len(realm) > 0 {
if len(realm) > 0 {
fmt.Fprintf(w, "Authentication required for %s (%s)\n", c.Host, realm)
} else {
fmt.Fprintf(w, "Authentication required for %s\n", c.Host)
Expand All @@ -73,6 +102,7 @@ func (c *BasicChallengeHandler) HandleChallenge(requestURL string, headers http.
if missingPassword {
password = term.PromptForPasswordString(c.Reader, w, "Password: ")
}

// remember so we don't re-prompt
c.prompted = true
}
Expand All @@ -85,6 +115,9 @@ func (c *BasicChallengeHandler) HandleChallenge(requestURL string, headers http.
}
responseHeaders := http.Header{}
responseHeaders.Set("Authorization", getBasicHeader(username, password))
if realm == bootstrapRealm && username != bootstrapUser {
return nil, false, BootstrapPromptError()
}
// remember so we don't re-handle non-interactively
c.handled = true
return responseHeaders, true, nil
Expand Down Expand Up @@ -113,18 +146,26 @@ var basicRegexes = []*regexp.Regexp{
}

func basicRealm(headers http.Header) (bool, string) {
matchedBootstrap := false
for _, challengeHeader := range headers[http.CanonicalHeaderKey("WWW-Authenticate")] {
for _, r := range basicRegexes {
if matches := r.FindStringSubmatch(challengeHeader); matches != nil {
if len(matches) > 1 {
// We got a realm as well
if len(matches) == 0 {
// No realm, but still basic
return true, ""
}
if !strings.Contains(challengeHeader, bootstrapRealm) {
// We got non-bootstrap realm
return true, matches[1]
}
// No realm, but still basic
return true, ""
matchedBootstrap = true
}
}
}
// We got bootstrap realm
if matchedBootstrap {
return true, bootstrapRealm
}
return false, ""
}
func getBasicHeader(username, password string) string {
Expand Down
17 changes: 15 additions & 2 deletions pkg/helpers/tokencmd/request_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func NewRequestTokenOptions(clientCfg *restclient.Config, reader io.Reader, defa
}

handlers = append(handlers, &BasicChallengeHandler{Host: clientCfg.Host, Reader: reader, Username: defaultUsername, Password: defaultPassword})

var handler ChallengeHandler
if len(handlers) == 1 {
handler = handlers[0]
Expand Down Expand Up @@ -225,13 +224,27 @@ func (o *RequestTokenOptions) RequestToken() (string, error) {
defer resp.Body.Close()

if resp.StatusCode == http.StatusUnauthorized {
if resp.Header.Get("WWW-Authenticate") != "" {
if len(resp.Header.Get("WWW-Authenticate")) > 0 {
if !o.Handler.CanHandle(resp.Header) {
return "", apierrs.NewUnauthorized("unhandled challenge")
}
// Handle the challenge
newRequestHeaders, shouldRetry, err := o.Handler.HandleChallenge(requestURL, resp.Header)
if err != nil {
if IsBootstrapPromptErr(err) {
tokenPromptErr := apierrs.NewUnauthorized("")
klog.V(4).Infof("%v", err)
msg := fmt.Sprintf("You must obtain an API token by visting %s/request\n", o.OsinConfig.TokenUrl)
details, err := ioutil.ReadAll(resp.Body)
if err == nil && len(details) == 0 {
tokenPromptErr.ErrStatus.Details = &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{Message: msg},
},
}
}
return "", tokenPromptErr
}
return "", err
}
if !shouldRetry {
Expand Down

0 comments on commit 0ea5a8b

Please sign in to comment.