github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/cf/api/authentication/authentication_test.go (about) 1 package authentication_test 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 9 "github.com/cloudfoundry/cli/cf/configuration/coreconfig" 10 "github.com/cloudfoundry/cli/cf/net" 11 "github.com/cloudfoundry/cli/cf/terminal/terminalfakes" 12 testconfig "github.com/cloudfoundry/cli/testhelpers/configuration" 13 testnet "github.com/cloudfoundry/cli/testhelpers/net" 14 15 . "github.com/cloudfoundry/cli/cf/api/authentication" 16 "github.com/cloudfoundry/cli/cf/trace/tracefakes" 17 . "github.com/cloudfoundry/cli/testhelpers/matchers" 18 . "github.com/onsi/ginkgo" 19 . "github.com/onsi/gomega" 20 "github.com/onsi/gomega/ghttp" 21 ) 22 23 var _ = Describe("AuthenticationRepository", func() { 24 Describe("legacy tests", func() { 25 var ( 26 gateway net.Gateway 27 testServer *httptest.Server 28 handler *testnet.TestHandler 29 config coreconfig.ReadWriter 30 auth Repository 31 dumper net.RequestDumper 32 fakePrinter *tracefakes.FakePrinter 33 ) 34 35 BeforeEach(func() { 36 config = testconfig.NewRepository() 37 fakePrinter = new(tracefakes.FakePrinter) 38 gateway = net.NewUAAGateway(config, new(terminalfakes.FakeUI), fakePrinter, "") 39 dumper = net.NewRequestDumper(fakePrinter) 40 auth = NewUAARepository(gateway, config, dumper) 41 }) 42 43 AfterEach(func() { 44 testServer.Close() 45 }) 46 47 var setupTestServer = func(request testnet.TestRequest) { 48 testServer, handler = testnet.NewServer([]testnet.TestRequest{request}) 49 config.SetAuthenticationEndpoint(testServer.URL) 50 } 51 52 Describe("authenticating", func() { 53 var err error 54 55 JustBeforeEach(func() { 56 err = auth.Authenticate(map[string]string{ 57 "username": "foo@example.com", 58 "password": "bar", 59 }) 60 }) 61 62 Describe("when login succeeds", func() { 63 BeforeEach(func() { 64 setupTestServer(successfulLoginRequest) 65 }) 66 67 It("stores the access and refresh tokens in the config", func() { 68 Expect(handler).To(HaveAllRequestsCalled()) 69 Expect(err).NotTo(HaveOccurred()) 70 Expect(config.AuthenticationEndpoint()).To(Equal(testServer.URL)) 71 Expect(config.AccessToken()).To(Equal("BEARER my_access_token")) 72 Expect(config.RefreshToken()).To(Equal("my_refresh_token")) 73 }) 74 }) 75 76 Describe("when login fails", func() { 77 BeforeEach(func() { 78 setupTestServer(unsuccessfulLoginRequest) 79 }) 80 81 It("returns an error", func() { 82 Expect(handler).To(HaveAllRequestsCalled()) 83 Expect(err).NotTo(BeNil()) 84 Expect(err.Error()).To(Equal("Credentials were rejected, please try again.")) 85 Expect(config.AccessToken()).To(BeEmpty()) 86 Expect(config.RefreshToken()).To(BeEmpty()) 87 }) 88 }) 89 90 Context("when the authentication server returns status code 500", func() { 91 BeforeEach(func() { 92 setupTestServer(errorLoginRequest) 93 }) 94 95 It("returns a failure response", func() { 96 Expect(handler).To(HaveAllRequestsCalled()) 97 Expect(err).To(HaveOccurred()) 98 Expect(err.Error()).To(Equal("The targeted API endpoint could not be reached.")) 99 Expect(config.AccessToken()).To(BeEmpty()) 100 }) 101 }) 102 103 Context("when the authentication server returns status code 502", func() { 104 var request testnet.TestRequest 105 106 BeforeEach(func() { 107 request = testnet.TestRequest{ 108 Method: "POST", 109 Path: "/oauth/token", 110 Response: testnet.TestResponse{ 111 Status: http.StatusBadGateway, 112 }, 113 } 114 setupTestServer(request) 115 }) 116 117 It("returns a failure response", func() { 118 Expect(handler).To(HaveAllRequestsCalled()) 119 Expect(err).To(HaveOccurred()) 120 Expect(err.Error()).To(Equal("The targeted API endpoint could not be reached.")) 121 Expect(config.AccessToken()).To(BeEmpty()) 122 }) 123 }) 124 125 Describe("when the UAA server has an error but still returns a 200", func() { 126 BeforeEach(func() { 127 setupTestServer(errorMaskedAsSuccessLoginRequest) 128 }) 129 130 It("returns an error", func() { 131 Expect(handler).To(HaveAllRequestsCalled()) 132 Expect(err).To(HaveOccurred()) 133 Expect(err.Error()).To(ContainSubstring("I/O error: uaa.10.244.0.22.xip.io; nested exception is java.net.UnknownHostException: uaa.10.244.0.22.xip.io")) 134 Expect(config.AccessToken()).To(BeEmpty()) 135 }) 136 }) 137 }) 138 139 Describe("getting login info", func() { 140 var ( 141 apiErr error 142 prompts map[string]coreconfig.AuthPrompt 143 ) 144 145 JustBeforeEach(func() { 146 prompts, apiErr = auth.GetLoginPromptsAndSaveUAAServerURL() 147 }) 148 149 Describe("when the login info API succeeds", func() { 150 BeforeEach(func() { 151 setupTestServer(loginServerLoginRequest) 152 }) 153 154 It("does not return an error", func() { 155 Expect(apiErr).NotTo(HaveOccurred()) 156 }) 157 158 It("gets the login prompts", func() { 159 Expect(prompts).To(Equal(map[string]coreconfig.AuthPrompt{ 160 "username": { 161 DisplayName: "Email", 162 Type: coreconfig.AuthPromptTypeText, 163 }, 164 "pin": { 165 DisplayName: "PIN Number", 166 Type: coreconfig.AuthPromptTypePassword, 167 }, 168 })) 169 }) 170 171 It("saves the UAA server to the config", func() { 172 Expect(config.UaaEndpoint()).To(Equal("https://uaa.run.pivotal.io")) 173 }) 174 }) 175 176 Describe("when the login info API fails", func() { 177 BeforeEach(func() { 178 setupTestServer(loginServerLoginFailureRequest) 179 }) 180 181 It("returns a failure response when the login info API fails", func() { 182 Expect(handler).To(HaveAllRequestsCalled()) 183 Expect(apiErr).To(HaveOccurred()) 184 Expect(prompts).To(BeEmpty()) 185 }) 186 }) 187 188 Context("when the response does not contain links", func() { 189 BeforeEach(func() { 190 setupTestServer(uaaServerLoginRequest) 191 }) 192 193 It("presumes that the authorization server is the UAA", func() { 194 Expect(config.UaaEndpoint()).To(Equal(config.AuthenticationEndpoint())) 195 }) 196 }) 197 }) 198 199 Describe("refreshing the auth token", func() { 200 var apiErr error 201 202 JustBeforeEach(func() { 203 _, apiErr = auth.RefreshAuthToken() 204 }) 205 206 Context("when the refresh token has expired", func() { 207 BeforeEach(func() { 208 setupTestServer(refreshTokenExpiredRequestError) 209 }) 210 It("the returns the reauthentication error message", func() { 211 Expect(apiErr.Error()).To(Equal("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.")) 212 }) 213 }) 214 Context("when there is a UAA error", func() { 215 BeforeEach(func() { 216 setupTestServer(errorLoginRequest) 217 }) 218 219 It("returns the API error", func() { 220 Expect(apiErr).NotTo(BeNil()) 221 }) 222 }) 223 }) 224 }) 225 226 Describe("Authorize", func() { 227 var ( 228 uaaServer *ghttp.Server 229 gateway net.Gateway 230 config coreconfig.ReadWriter 231 authRepo Repository 232 dumper net.RequestDumper 233 fakePrinter *tracefakes.FakePrinter 234 ) 235 236 BeforeEach(func() { 237 uaaServer = ghttp.NewServer() 238 config = testconfig.NewRepository() 239 config.SetUaaEndpoint(uaaServer.URL()) 240 config.SetSSHOAuthClient("ssh-oauth-client") 241 242 fakePrinter = new(tracefakes.FakePrinter) 243 gateway = net.NewUAAGateway(config, new(terminalfakes.FakeUI), fakePrinter, "") 244 dumper = net.NewRequestDumper(fakePrinter) 245 authRepo = NewUAARepository(gateway, config, dumper) 246 247 uaaServer.AppendHandlers( 248 ghttp.CombineHandlers( 249 ghttp.VerifyHeader(http.Header{"authorization": []string{"auth-token"}}), 250 ghttp.VerifyRequest("GET", "/oauth/authorize", 251 "response_type=code&grant_type=authorization_code&client_id=ssh-oauth-client", 252 ), 253 ghttp.RespondWith(http.StatusFound, ``, http.Header{ 254 "Location": []string{"https://www.cloudfoundry.example.com?code=F45jH"}, 255 }), 256 ), 257 ) 258 }) 259 260 AfterEach(func() { 261 uaaServer.Close() 262 }) 263 264 It("requests the one time code", func() { 265 _, err := authRepo.Authorize("auth-token") 266 Expect(err).NotTo(HaveOccurred()) 267 Expect(uaaServer.ReceivedRequests()).To(HaveLen(1)) 268 }) 269 270 It("returns the one time code", func() { 271 code, err := authRepo.Authorize("auth-token") 272 Expect(err).NotTo(HaveOccurred()) 273 Expect(code).To(Equal("F45jH")) 274 }) 275 276 Context("when the authentication endpoint is malformed", func() { 277 BeforeEach(func() { 278 config.SetUaaEndpoint(":not-well-formed") 279 }) 280 281 It("returns an error", func() { 282 _, err := authRepo.Authorize("auth-token") 283 Expect(err).To(HaveOccurred()) 284 }) 285 }) 286 287 Context("when the authorization server does not return a redirect", func() { 288 BeforeEach(func() { 289 uaaServer.SetHandler(0, ghttp.RespondWith(http.StatusOK, ``)) 290 }) 291 292 It("returns an error", func() { 293 _, err := authRepo.Authorize("auth-token") 294 Expect(err).To(HaveOccurred()) 295 Expect(err.Error()).To(Equal("Authorization server did not redirect with one time code")) 296 }) 297 }) 298 299 Context("when the authorization server does not return a redirect", func() { 300 BeforeEach(func() { 301 config.SetUaaEndpoint("https://127.0.0.1:1") 302 }) 303 304 It("returns an error", func() { 305 _, err := authRepo.Authorize("auth-token") 306 Expect(err).To(HaveOccurred()) 307 Expect(err.Error()).To(ContainSubstring("Error requesting one time code from server")) 308 }) 309 }) 310 311 Context("when the authorization server returns multiple codes", func() { 312 BeforeEach(func() { 313 uaaServer.SetHandler(0, ghttp.RespondWith(http.StatusFound, ``, http.Header{ 314 "Location": []string{"https://www.cloudfoundry.example.com?code=F45jH&code=LLLLL"}, 315 })) 316 }) 317 318 It("returns an error", func() { 319 _, err := authRepo.Authorize("auth-token") 320 Expect(err).To(HaveOccurred()) 321 Expect(err.Error()).To(Equal("Unable to acquire one time code from authorization response")) 322 }) 323 }) 324 }) 325 }) 326 327 var authHeaders = http.Header{ 328 "accept": {"application/json"}, 329 "content-type": {"application/x-www-form-urlencoded"}, 330 "authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("cf:"))}, 331 } 332 333 var successfulLoginRequest = testnet.TestRequest{ 334 Method: "POST", 335 Path: "/oauth/token", 336 Header: authHeaders, 337 Matcher: successfulLoginMatcher, 338 Response: testnet.TestResponse{ 339 Status: http.StatusOK, 340 Body: ` 341 { 342 "access_token": "my_access_token", 343 "token_type": "BEARER", 344 "refresh_token": "my_refresh_token", 345 "scope": "openid", 346 "expires_in": 98765 347 } `}, 348 } 349 350 var successfulLoginMatcher = func(request *http.Request) { 351 err := request.ParseForm() 352 if err != nil { 353 Fail(fmt.Sprintf("Failed to parse form: %s", err)) 354 return 355 } 356 357 Expect(request.Form.Get("username")).To(Equal("foo@example.com")) 358 Expect(request.Form.Get("password")).To(Equal("bar")) 359 Expect(request.Form.Get("grant_type")).To(Equal("password")) 360 Expect(request.Form.Get("scope")).To(Equal("")) 361 } 362 363 var unsuccessfulLoginRequest = testnet.TestRequest{ 364 Method: "POST", 365 Path: "/oauth/token", 366 Response: testnet.TestResponse{ 367 Status: http.StatusUnauthorized, 368 }, 369 } 370 var refreshTokenExpiredRequestError = testnet.TestRequest{ 371 Method: "POST", 372 Path: "/oauth/token", 373 Response: testnet.TestResponse{ 374 Status: http.StatusUnauthorized, 375 Body: ` 376 { 377 "error": "invalid_token", 378 "error_description": "Invalid auth token: Invalid refresh token (expired): eyJhbGckjsdfdf" 379 } 380 `}, 381 } 382 383 var errorLoginRequest = testnet.TestRequest{ 384 Method: "POST", 385 Path: "/oauth/token", 386 Response: testnet.TestResponse{ 387 Status: http.StatusInternalServerError, 388 }, 389 } 390 391 var errorMaskedAsSuccessLoginRequest = testnet.TestRequest{ 392 Method: "POST", 393 Path: "/oauth/token", 394 Response: testnet.TestResponse{ 395 Status: http.StatusOK, 396 Body: ` 397 { 398 "error": { 399 "error": "rest_client_error", 400 "error_description": "I/O error: uaa.10.244.0.22.xip.io; nested exception is java.net.UnknownHostException: uaa.10.244.0.22.xip.io" 401 } 402 } 403 `}, 404 } 405 406 var loginServerLoginRequest = testnet.TestRequest{ 407 Method: "GET", 408 Path: "/login", 409 Response: testnet.TestResponse{ 410 Status: http.StatusOK, 411 Body: ` 412 { 413 "timestamp":"2013-12-18T11:26:53-0700", 414 "app":{ 415 "artifact":"cloudfoundry-identity-uaa", 416 "description":"User Account and Authentication Service", 417 "name":"UAA", 418 "version":"1.4.7" 419 }, 420 "commit_id":"2701cc8", 421 "links":{ 422 "register":"https://console.run.pivotal.io/register", 423 "passwd":"https://console.run.pivotal.io/password_resets/new", 424 "home":"https://console.run.pivotal.io", 425 "support":"https://support.cloudfoundry.com/home", 426 "login":"https://login.run.pivotal.io", 427 "uaa":"https://uaa.run.pivotal.io" 428 }, 429 "prompts":{ 430 "username": ["text","Email"], 431 "pin": ["password", "PIN Number"] 432 } 433 }`, 434 }, 435 } 436 437 var loginServerLoginFailureRequest = testnet.TestRequest{ 438 Method: "GET", 439 Path: "/login", 440 Response: testnet.TestResponse{ 441 Status: http.StatusInternalServerError, 442 }, 443 } 444 445 var uaaServerLoginRequest = testnet.TestRequest{ 446 Method: "GET", 447 Path: "/login", 448 Response: testnet.TestResponse{ 449 Status: http.StatusOK, 450 Body: ` 451 { 452 "timestamp":"2013-12-18T11:26:53-0700", 453 "app":{ 454 "artifact":"cloudfoundry-identity-uaa", 455 "description":"User Account and Authentication Service", 456 "name":"UAA", 457 "version":"1.4.7" 458 }, 459 "commit_id":"2701cc8", 460 "prompts":{ 461 "username": ["text","Email"], 462 "pin": ["password", "PIN Number"] 463 } 464 }`, 465 }, 466 }