github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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 "refresh_token": {uaa.config.RefreshToken()}, 186 "grant_type": {"refresh_token"}, 187 "scope": {""}, 188 } 189 190 apiErr := uaa.getAuthToken(data) 191 updatedToken := uaa.config.AccessToken() 192 193 return updatedToken, apiErr 194 } 195 196 func (uaa UAARepository) getAuthToken(data url.Values) error { 197 type uaaErrorResponse struct { 198 Code string `json:"error"` 199 Description string `json:"error_description"` 200 } 201 202 type AuthenticationResponse struct { 203 AccessToken string `json:"access_token"` 204 TokenType string `json:"token_type"` 205 RefreshToken string `json:"refresh_token"` 206 Error uaaErrorResponse `json:"error"` 207 } 208 209 path := fmt.Sprintf("%s/oauth/token", uaa.config.AuthenticationEndpoint()) 210 accessToken := "Basic " + base64.StdEncoding.EncodeToString([]byte(uaa.config.UAAOAuthClient()+":"+uaa.config.UAAOAuthClientSecret())) 211 request, err := uaa.gateway.NewRequest("POST", path, accessToken, strings.NewReader(data.Encode())) 212 if err != nil { 213 return fmt.Errorf("%s: %s", T("Failed to start oauth request"), err.Error()) 214 } 215 request.HTTPReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 216 217 response := new(AuthenticationResponse) 218 _, err = uaa.gateway.PerformRequestForJSONResponse(request, &response) 219 220 switch err.(type) { 221 case nil: 222 case errors.HTTPError: 223 return err 224 case *errors.InvalidTokenError: 225 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.")) 226 default: 227 return fmt.Errorf("%s: %s", T("auth request failed"), err.Error()) 228 } 229 230 // TODO: get the actual status code 231 if response.Error.Code != "" { 232 return errors.NewHTTPError(0, response.Error.Code, response.Error.Description) 233 } 234 235 uaa.config.SetAccessToken(fmt.Sprintf("%s %s", response.TokenType, response.AccessToken)) 236 uaa.config.SetRefreshToken(response.RefreshToken) 237 238 return nil 239 }