github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/cf/api/authentication/authentication.go (about)

     1  package authentication
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    13  	"code.cloudfoundry.org/cli/cf/errors"
    14  	. "code.cloudfoundry.org/cli/cf/i18n"
    15  	"code.cloudfoundry.org/cli/cf/net"
    16  )
    17  
    18  //go:generate counterfeiter . TokenRefresher
    19  
    20  type TokenRefresher interface {
    21  	RefreshAuthToken() (updatedToken string, apiErr error)
    22  }
    23  
    24  //go:generate counterfeiter . Repository
    25  
    26  type Repository interface {
    27  	net.RequestDumperInterface
    28  
    29  	RefreshAuthToken() (updatedToken string, apiErr error)
    30  	Authenticate(credentials map[string]string) (apiErr error)
    31  	Authorize(token string) (string, error)
    32  	GetLoginPromptsAndSaveUAAServerURL() (map[string]coreconfig.AuthPrompt, error)
    33  }
    34  
    35  type UAARepository struct {
    36  	config  coreconfig.ReadWriter
    37  	gateway net.Gateway
    38  	dumper  net.RequestDumper
    39  }
    40  
    41  var ErrPreventRedirect = errors.New("prevent-redirect")
    42  
    43  func NewUAARepository(gateway net.Gateway, config coreconfig.ReadWriter, dumper net.RequestDumper) UAARepository {
    44  	return UAARepository{
    45  		config:  config,
    46  		gateway: gateway,
    47  		dumper:  dumper,
    48  	}
    49  }
    50  
    51  func (uaa UAARepository) Authorize(token string) (string, error) {
    52  	httpClient := &http.Client{
    53  		CheckRedirect: func(req *http.Request, _ []*http.Request) error {
    54  			uaa.DumpRequest(req)
    55  			return ErrPreventRedirect
    56  		},
    57  		Timeout: 30 * time.Second,
    58  		Transport: &http.Transport{
    59  			DisableKeepAlives: true,
    60  			TLSClientConfig: &tls.Config{
    61  				InsecureSkipVerify: uaa.config.IsSSLDisabled(),
    62  			},
    63  			Proxy:               http.ProxyFromEnvironment,
    64  			TLSHandshakeTimeout: 10 * time.Second,
    65  		},
    66  	}
    67  
    68  	authorizeURL, err := url.Parse(uaa.config.UaaEndpoint())
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  
    73  	values := url.Values{}
    74  	values.Set("response_type", "code")
    75  	values.Set("grant_type", "authorization_code")
    76  	values.Set("client_id", uaa.config.SSHOAuthClient())
    77  
    78  	authorizeURL.Path = "/oauth/authorize"
    79  	authorizeURL.RawQuery = values.Encode()
    80  
    81  	authorizeReq, err := http.NewRequest("GET", authorizeURL.String(), nil)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	authorizeReq.Header.Add("authorization", token)
    87  
    88  	resp, err := httpClient.Do(authorizeReq)
    89  	if resp != nil {
    90  		uaa.DumpResponse(resp)
    91  	}
    92  	if err == nil {
    93  		return "", errors.New(T("Authorization server did not redirect with one time code"))
    94  	}
    95  
    96  	if netErr, ok := err.(*url.Error); !ok || netErr.Err != ErrPreventRedirect {
    97  		return "", errors.New(T("Error requesting one time code from server: {{.Error}}", map[string]interface{}{"Error": err.Error()}))
    98  	}
    99  
   100  	loc, err := resp.Location()
   101  	if err != nil {
   102  		return "", errors.New(T("Error getting the redirected location: {{.Error}}", map[string]interface{}{"Error": err.Error()}))
   103  	}
   104  
   105  	codes := loc.Query()["code"]
   106  	if len(codes) != 1 {
   107  		return "", errors.New(T("Unable to acquire one time code from authorization response"))
   108  	}
   109  
   110  	return codes[0], nil
   111  }
   112  
   113  func (uaa UAARepository) Authenticate(credentials map[string]string) error {
   114  	data := url.Values{
   115  		"grant_type": {"password"},
   116  		"scope":      {""},
   117  	}
   118  	for key, val := range credentials {
   119  		data[key] = []string{val}
   120  	}
   121  
   122  	err := uaa.getAuthToken(data)
   123  	if err != nil {
   124  		httpError, ok := err.(errors.HTTPError)
   125  		if ok {
   126  			switch {
   127  			case httpError.StatusCode() == http.StatusUnauthorized:
   128  				return errors.New(T("Credentials were rejected, please try again."))
   129  			case httpError.StatusCode() >= http.StatusInternalServerError:
   130  				return errors.New(T("The targeted API endpoint could not be reached."))
   131  			}
   132  		}
   133  
   134  		return err
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func (uaa UAARepository) DumpRequest(req *http.Request) {
   141  	uaa.dumper.DumpRequest(req)
   142  }
   143  
   144  func (uaa UAARepository) DumpResponse(res *http.Response) {
   145  	uaa.dumper.DumpResponse(res)
   146  }
   147  
   148  type LoginResource struct {
   149  	Prompts map[string][]string
   150  	Links   map[string]string
   151  }
   152  
   153  var knownAuthPromptTypes = map[string]coreconfig.AuthPromptType{
   154  	"text":     coreconfig.AuthPromptTypeText,
   155  	"password": coreconfig.AuthPromptTypePassword,
   156  }
   157  
   158  func (r *LoginResource) parsePrompts() (prompts map[string]coreconfig.AuthPrompt) {
   159  	prompts = make(map[string]coreconfig.AuthPrompt)
   160  	for key, val := range r.Prompts {
   161  		prompts[key] = coreconfig.AuthPrompt{
   162  			Type:        knownAuthPromptTypes[val[0]],
   163  			DisplayName: val[1],
   164  		}
   165  	}
   166  	return
   167  }
   168  
   169  func (uaa UAARepository) GetLoginPromptsAndSaveUAAServerURL() (prompts map[string]coreconfig.AuthPrompt, apiErr error) {
   170  	url := fmt.Sprintf("%s/login", uaa.config.AuthenticationEndpoint())
   171  	resource := &LoginResource{}
   172  	apiErr = uaa.gateway.GetResource(url, resource)
   173  
   174  	prompts = resource.parsePrompts()
   175  	if resource.Links["uaa"] == "" {
   176  		uaa.config.SetUaaEndpoint(uaa.config.AuthenticationEndpoint())
   177  	} else {
   178  		uaa.config.SetUaaEndpoint(resource.Links["uaa"])
   179  	}
   180  	return
   181  }
   182  
   183  func (uaa UAARepository) RefreshAuthToken() (string, error) {
   184  	data := url.Values{}
   185  
   186  	// An empty grant_type implies that the authentication grant_type is 'password'
   187  	if uaa.config.UAAGrantType() != "" {
   188  		data.Add("client_id", uaa.config.UAAOAuthClient())
   189  		data.Add("client_secret", uaa.config.UAAOAuthClientSecret())
   190  		data.Add("grant_type", "client_credentials")
   191  	} else {
   192  		data.Add("grant_type", "refresh_token")
   193  		data.Add("refresh_token", uaa.config.RefreshToken())
   194  		data.Add("scope", "")
   195  	}
   196  
   197  	apiErr := uaa.getAuthToken(data)
   198  	updatedToken := uaa.config.AccessToken()
   199  
   200  	return updatedToken, apiErr
   201  }
   202  
   203  func (uaa UAARepository) getAuthToken(data url.Values) error {
   204  	var accessToken string
   205  
   206  	type uaaErrorResponse struct {
   207  		Code        string `json:"error"`
   208  		Description string `json:"error_description"`
   209  	}
   210  
   211  	type AuthenticationResponse struct {
   212  		AccessToken  string           `json:"access_token"`
   213  		TokenType    string           `json:"token_type"`
   214  		RefreshToken string           `json:"refresh_token"`
   215  		Error        uaaErrorResponse `json:"error"`
   216  	}
   217  
   218  	path := fmt.Sprintf("%s/oauth/token", uaa.config.AuthenticationEndpoint())
   219  
   220  	if uaa.config.UAAGrantType() != "client_credentials" {
   221  		accessToken = "Basic " + base64.StdEncoding.EncodeToString([]byte(uaa.config.UAAOAuthClient()+":"+uaa.config.UAAOAuthClientSecret()))
   222  	}
   223  
   224  	request, err := uaa.gateway.NewRequest("POST", path, accessToken, strings.NewReader(data.Encode()))
   225  
   226  	if err != nil {
   227  		return fmt.Errorf("%s: %s", T("Failed to start oauth request"), err.Error())
   228  	}
   229  	request.HTTPReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   230  
   231  	response := new(AuthenticationResponse)
   232  	_, err = uaa.gateway.PerformRequestForJSONResponse(request, &response)
   233  
   234  	switch err.(type) {
   235  	case nil:
   236  	case errors.HTTPError:
   237  		return err
   238  	case *errors.InvalidTokenError:
   239  		return errors.New(T("Authentication has expired.  Please log back in to re-authenticate.\n\nTIP: Use `cf login -a <endpoint> -u <user> -o <org> -s <space>` to log back in and re-authenticate."))
   240  	default:
   241  		return fmt.Errorf("%s: %s", T("auth request failed"), err.Error())
   242  	}
   243  
   244  	// TODO: get the actual status code
   245  	if response.Error.Code != "" {
   246  		return errors.NewHTTPError(0, response.Error.Code, response.Error.Description)
   247  	}
   248  
   249  	uaa.config.SetAccessToken(fmt.Sprintf("%s %s", response.TokenType, response.AccessToken))
   250  	uaa.config.SetRefreshToken(response.RefreshToken)
   251  
   252  	return nil
   253  }