forked from openshift/origin
/
request_token.go
130 lines (107 loc) · 3.93 KB
/
request_token.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package tokencmd
import (
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strings"
kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/openshift/origin/pkg/auth/server/tokenrequest"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)
const (
accessTokenRedirectPattern = `#access_token=([\w]+)&`
)
var (
accessTokenRedirectRegex = regexp.MustCompile(accessTokenRedirectPattern)
)
type tokenGetterInfo struct {
accessToken string
}
// RequestToken uses the cmd arguments to locate an openshift oauth server and attempts to authenticate
// it returns the access token if it gets one. An error if it does not
func RequestToken(clientCfg *clientcmd.Config, reader io.Reader) (string, error) {
tokenGetter := &tokenGetterInfo{}
osCfg := clientCfg.OpenShiftConfig()
osClient, err := client.New(osCfg)
if err != nil {
return "", err
}
// get the transport, so that we can use it to build our own client that wraps it
// our client understands certain challenges and can respond to them
clientTransport, err := kclient.TransportFor(osCfg)
if err != nil {
return "", err
}
httpClient := &http.Client{
Transport: clientTransport,
CheckRedirect: tokenGetter.checkRedirect,
}
osClient.Client = &challengingClient{httpClient, reader}
_ = osClient.Get().AbsPath("oauth", "authorize").Param("response_type", "token").Param("client_id", "openshift-challenging-client").Do()
if len(tokenGetter.accessToken) == 0 {
requestTokenURL := osCfg.Host + "/oauth" /* clean up after auth.go dies */ + tokenrequest.RequestTokenEndpoint
return "", errors.New("Unable to get token. Try visiting " + requestTokenURL + " for a new token.")
}
return tokenGetter.accessToken, nil
}
// checkRedirect watches the redirects to see if any contain the access_token anchor. It then stores the value of the access token for later retrieval
func (tokenGetter *tokenGetterInfo) checkRedirect(req *http.Request, via []*http.Request) error {
// if we're redirected with an access token in the anchor, use it to set our transport to a proper bearer auth
if matches := accessTokenRedirectRegex.FindAllStringSubmatch(req.URL.String(), 1); matches != nil {
tokenGetter.accessToken = matches[0][1]
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
// challengingClient conforms the kclient.HTTPClient interface. It introspects responses for auth challenges and
// tries to response to those challenges in order to get a token back.
type challengingClient struct {
delegate *http.Client
reader io.Reader
}
const (
basicAuthPattern = `[\s]*Basic[\s]*realm="([\w]+)"`
)
var (
basicAuthRegex = regexp.MustCompile(basicAuthPattern)
)
// Do watches for unauthorized challenges. If we know to respond, we respond to the challenge
func (client *challengingClient) Do(req *http.Request) (*http.Response, error) {
resp, err := client.delegate.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusUnauthorized {
if wantsBasicAuth, realm := isBasicAuthChallenge(resp); wantsBasicAuth {
fmt.Printf("Authenticate for \"%v\"\n", realm)
username := promptForString("username", client.reader)
password := promptForString("password", client.reader)
client.delegate.Transport = kclient.NewBasicAuthRoundTripper(username, password, client.delegate.Transport)
return client.Do(resp.Request)
}
}
return resp, err
}
func isBasicAuthChallenge(resp *http.Response) (bool, string) {
for currHeader, headerValue := range resp.Header {
if strings.EqualFold(currHeader, "WWW-Authenticate") {
for _, currAuthorizeHeader := range headerValue {
if matches := basicAuthRegex.FindAllStringSubmatch(currAuthorizeHeader, 1); matches != nil {
return true, matches[0][1]
}
}
}
}
return false, ""
}
func promptForString(field string, r io.Reader) string {
fmt.Printf("Please enter %s: ", field)
var result string
fmt.Fscan(r, &result)
return result
}