package tokencmd import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "github.com/RangelReale/osincli" "github.com/golang/glog" kclient "k8s.io/kubernetes/pkg/client" "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/oauth/server/osinserver" ) // 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 *kclient.Config, reader io.Reader, defaultUsername string, defaultPassword string) (string, error) { tokenGetter := &tokenGetterInfo{} osClient, err := client.New(clientCfg) 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(clientCfg) if err != nil { return "", err } httpClient := &http.Client{ Transport: clientTransport, CheckRedirect: tokenGetter.checkRedirect, } osClient.Client = &challengingClient{httpClient, reader, defaultUsername, defaultPassword} result := osClient.Get().AbsPath("/oauth", osinserver.AuthorizePath). Param("response_type", "token"). Param("client_id", "openshift-challenging-client"). Do() if err := result.Error(); err != nil && !isRedirectError(err) { return "", err } if len(tokenGetter.accessToken) == 0 { r, _ := result.Raw() if description, ok := rawOAuthJSONErrorDescription(r); ok { return "", fmt.Errorf("cannot retrieve a token: %s", description) } glog.V(4).Infof("A request token could not be created, server returned: %s", string(r)) return "", fmt.Errorf("the server did not return a token (possible server error)") } return tokenGetter.accessToken, nil } func rawOAuthJSONErrorDescription(data []byte) (string, bool) { output := osincli.ResponseData{} decoder := json.NewDecoder(bytes.NewBuffer(data)) if err := decoder.Decode(&output); err != nil { return "", false } if _, ok := output["error"]; !ok { return "", false } desc, ok := output["error_description"] if !ok { return "", false } s, ok := desc.(string) if !ok || len(s) == 0 { return "", false } return s, true } const accessTokenKey = "access_token" var errRedirectComplete = errors.New("found access token") type tokenGetterInfo struct { accessToken string } // 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 { fragment := req.URL.Fragment if values, err := url.ParseQuery(fragment); err == nil { if v, ok := values[accessTokenKey]; ok { if len(v) > 0 { tokenGetter.accessToken = v[0] } return errRedirectComplete } } if len(via) >= 10 { return errors.New("stopped after 10 redirects") } return nil } func isRedirectError(err error) bool { if err == errRedirectComplete { return true } switch t := err.(type) { case *url.Error: return t.Err == errRedirectComplete } return false }