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