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