github.com/chenbh/concourse/v6@v6.4.2/fly/integration/login_test.go (about) 1 package integration_test 2 3 import ( 4 "bufio" 5 "bytes" 6 "crypto/tls" 7 "encoding/base64" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "os/exec" 13 "regexp" 14 15 . "github.com/onsi/ginkgo" 16 . "github.com/onsi/gomega" 17 "github.com/onsi/gomega/gbytes" 18 "github.com/onsi/gomega/gexec" 19 "github.com/onsi/gomega/ghttp" 20 21 "github.com/chenbh/concourse/v6/atc" 22 "github.com/chenbh/concourse/v6/fly/version" 23 ) 24 25 var _ = Describe("login Command", func() { 26 var ( 27 loginATCServer *ghttp.Server 28 ) 29 30 Describe("login with no target name", func() { 31 var ( 32 flyCmd *exec.Cmd 33 ) 34 35 BeforeEach(func() { 36 loginATCServer = ghttp.NewServer() 37 loginATCServer.AppendHandlers( 38 infoHandler(), 39 ) 40 flyCmd = exec.Command(flyPath, "login", "-c", loginATCServer.URL()) 41 }) 42 43 AfterEach(func() { 44 loginATCServer.Close() 45 }) 46 47 It("instructs the user to specify --target", func() { 48 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 49 Expect(err).NotTo(HaveOccurred()) 50 51 <-sess.Exited 52 Expect(sess.ExitCode()).To(Equal(1)) 53 54 Expect(sess.Err).To(gbytes.Say(`name for the target must be specified \(--target/-t\)`)) 55 }) 56 }) 57 58 Context("with no team name", func() { 59 BeforeEach(func() { 60 loginATCServer = ghttp.NewServer() 61 }) 62 63 AfterEach(func() { 64 loginATCServer.Close() 65 }) 66 67 It("falls back to atc.DefaultTeamName team", func() { 68 loginATCServer.AppendHandlers( 69 infoHandler(), 70 tokenHandler(), 71 userInfoHandler(), 72 ) 73 74 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-u", "user", "-p", "pass") 75 76 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 77 Expect(err).NotTo(HaveOccurred()) 78 79 Eventually(sess).Should(gbytes.Say("logging in to team 'main'")) 80 81 <-sess.Exited 82 Expect(sess.ExitCode()).To(Equal(0)) 83 }) 84 85 Context("when already logged in as different team", func() { 86 BeforeEach(func() { 87 loginATCServer.AppendHandlers( 88 infoHandler(), 89 tokenHandler(), 90 userInfoHandler(), 91 ) 92 93 setupFlyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "-u", "user", "-p", "pass") 94 err := setupFlyCmd.Run() 95 Expect(err).NotTo(HaveOccurred()) 96 }) 97 98 It("uses the saved team name", func() { 99 loginATCServer.AppendHandlers( 100 infoHandler(), 101 tokenHandler(), 102 userInfoHandler(), 103 ) 104 105 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-u", "user", "-p", "pass") 106 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 107 Expect(err).NotTo(HaveOccurred()) 108 Eventually(sess).Should(gbytes.Say("logging in to team 'some-team'")) 109 110 <-sess.Exited 111 Expect(sess.ExitCode()).To(Equal(0)) 112 }) 113 }) 114 }) 115 116 Context("with no specified flag but extra arguments ", func() { 117 118 BeforeEach(func() { 119 loginATCServer = ghttp.NewServer() 120 }) 121 122 AfterEach(func() { 123 loginATCServer.Close() 124 }) 125 126 It("return error indicating login failed with unknown arguments", func() { 127 128 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "unknown-argument", "blah") 129 130 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 131 Expect(err).NotTo(HaveOccurred()) 132 133 <-sess.Exited 134 Expect(sess.ExitCode()).NotTo(Equal(0)) 135 Expect(sess.Err).To(gbytes.Say(`unexpected argument \[unknown-argument, blah\]`)) 136 }) 137 }) 138 139 Context("with a team name", func() { 140 141 BeforeEach(func() { 142 loginATCServer = ghttp.NewServer() 143 }) 144 145 AfterEach(func() { 146 loginATCServer.Close() 147 }) 148 149 It("uses specified team", func() { 150 loginATCServer.AppendHandlers( 151 infoHandler(), 152 tokenHandler(), 153 userInfoHandler(), 154 ) 155 156 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "-u", "user", "-p", "pass") 157 158 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 159 Expect(err).NotTo(HaveOccurred()) 160 161 Eventually(sess).Should(gbytes.Say("logging in to team 'some-team'")) 162 163 <-sess.Exited 164 Expect(sess.ExitCode()).To(Equal(0)) 165 }) 166 167 Context("when tracing is not enabled", func() { 168 It("does not print out API calls", func() { 169 loginATCServer.AppendHandlers( 170 infoHandler(), 171 tokenHandler(), 172 userInfoHandler(), 173 ) 174 175 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "-u", "user", "-p", "pass") 176 177 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 178 Expect(err).NotTo(HaveOccurred()) 179 180 Consistently(sess.Err).ShouldNot(gbytes.Say("HTTP/1.1 200 OK")) 181 Consistently(sess.Out).ShouldNot(gbytes.Say("HTTP/1.1 200 OK")) 182 183 <-sess.Exited 184 Expect(sess.ExitCode()).To(Equal(0)) 185 }) 186 }) 187 188 Context("when tracing is enabled", func() { 189 It("prints out API calls", func() { 190 loginATCServer.AppendHandlers( 191 infoHandler(), 192 tokenHandler(), 193 userInfoHandler(), 194 ) 195 196 flyCmd := exec.Command(flyPath, "--verbose", "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "-u", "user", "-p", "pass") 197 198 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 199 Expect(err).NotTo(HaveOccurred()) 200 201 Eventually(sess.Err).Should(gbytes.Say("HTTP/1.1 200 OK")) 202 203 <-sess.Exited 204 Expect(sess.ExitCode()).To(Equal(0)) 205 }) 206 }) 207 208 Context("when already logged in as different team", func() { 209 BeforeEach(func() { 210 loginATCServer.AppendHandlers( 211 infoHandler(), 212 tokenHandler(), 213 userInfoHandler(), 214 ) 215 216 setupFlyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "-u", "user", "-p", "pass") 217 err := setupFlyCmd.Run() 218 Expect(err).NotTo(HaveOccurred()) 219 }) 220 221 It("passes provided team name", func() { 222 loginATCServer.AppendHandlers( 223 infoHandler(), 224 tokenHandler(), 225 userInfoHandler(), 226 ) 227 228 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-n", "some-other-team", "-u", "user", "-p", "pass") 229 230 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 231 Expect(err).NotTo(HaveOccurred()) 232 233 <-sess.Exited 234 Expect(sess.ExitCode()).To(Equal(0)) 235 }) 236 }) 237 }) 238 239 Describe("with ca cert", func() { 240 BeforeEach(func() { 241 loginATCServer = ghttp.NewUnstartedServer() 242 cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) 243 Expect(err).NotTo(HaveOccurred()) 244 245 loginATCServer.HTTPTestServer.TLS = &tls.Config{ 246 Certificates: []tls.Certificate{cert}, 247 } 248 loginATCServer.HTTPTestServer.StartTLS() 249 }) 250 251 AfterEach(func() { 252 loginATCServer.Close() 253 }) 254 255 Context("when already logged in with ca cert", func() { 256 var caCertFilePath string 257 258 BeforeEach(func() { 259 loginATCServer.AppendHandlers( 260 infoHandler(), 261 tokenHandler(), 262 userInfoHandler(), 263 ) 264 265 caCertFile, err := ioutil.TempFile("", "fly-login-test") 266 Expect(err).NotTo(HaveOccurred()) 267 caCertFilePath = caCertFile.Name() 268 269 err = ioutil.WriteFile(caCertFilePath, []byte(serverCert), os.ModePerm) 270 Expect(err).NotTo(HaveOccurred()) 271 272 setupFlyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "some-team", "--ca-cert", caCertFilePath, "-u", "user", "-p", "pass") 273 274 sess, err := gexec.Start(setupFlyCmd, GinkgoWriter, GinkgoWriter) 275 Expect(err).NotTo(HaveOccurred()) 276 <-sess.Exited 277 Expect(sess.ExitCode()).To(Equal(0)) 278 }) 279 280 AfterEach(func() { 281 os.RemoveAll(caCertFilePath) 282 }) 283 284 Context("when ca cert is not provided", func() { 285 It("is using saved ca cert", func() { 286 loginATCServer.AppendHandlers( 287 infoHandler(), 288 tokenHandler(), 289 userInfoHandler(), 290 ) 291 292 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-n", "some-team", "-u", "user", "-p", "pass") 293 294 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 295 Expect(err).NotTo(HaveOccurred()) 296 297 <-sess.Exited 298 Expect(sess.ExitCode()).To(Equal(0)) 299 }) 300 }) 301 }) 302 }) 303 304 Describe("login", func() { 305 var ( 306 flyCmd *exec.Cmd 307 ) 308 309 BeforeEach(func() { 310 loginATCServer = ghttp.NewServer() 311 }) 312 313 AfterEach(func() { 314 loginATCServer.Close() 315 }) 316 317 Context("with authorization_code grant", func() { 318 BeforeEach(func() { 319 loginATCServer.AppendHandlers( 320 infoHandler(), 321 userInfoHandler(), 322 ) 323 }) 324 325 It("allows providing the token via stdin", func() { 326 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL()) 327 328 stdin, err := flyCmd.StdinPipe() 329 Expect(err).NotTo(HaveOccurred()) 330 331 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 332 Expect(err).NotTo(HaveOccurred()) 333 334 Eventually(sess.Out).Should(gbytes.Say("navigate to the following URL in your browser:")) 335 Eventually(sess.Out).Should(gbytes.Say("http://127.0.0.1:(\\d+)/login\\?fly_port=(\\d+)")) 336 Eventually(sess.Out).Should(gbytes.Say("or enter token manually")) 337 338 _, err = fmt.Fprintf(stdin, "Bearer some-token\n") 339 Expect(err).NotTo(HaveOccurred()) 340 341 err = stdin.Close() 342 Expect(err).NotTo(HaveOccurred()) 343 344 <-sess.Exited 345 Expect(sess.ExitCode()).To(Equal(0)) 346 }) 347 348 Context("when the token from stdin is malformed", func() { 349 It("logs an error and accepts further input", func() { 350 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL()) 351 352 stdin, err := flyCmd.StdinPipe() 353 Expect(err).NotTo(HaveOccurred()) 354 355 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 356 Expect(err).NotTo(HaveOccurred()) 357 358 Eventually(sess.Out).Should(gbytes.Say("or enter token manually")) 359 360 _, err = fmt.Fprintf(stdin, "not a token\n") 361 Expect(err).NotTo(HaveOccurred()) 362 363 Eventually(sess.Out).Should(gbytes.Say("token must be of the format 'TYPE VALUE', e.g. 'Bearer ...'")) 364 365 _, err = fmt.Fprintf(stdin, "Bearer ok-this-time-its-the-real-deal\n") 366 Expect(err).NotTo(HaveOccurred()) 367 368 err = stdin.Close() 369 Expect(err).NotTo(HaveOccurred()) 370 371 <-sess.Exited 372 Expect(sess.ExitCode()).To(Equal(0)) 373 }) 374 }) 375 376 Context("when the token from stdin is terminated with an EOF", func() { 377 It("accepts the input", func() { 378 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL()) 379 380 stdin, err := flyCmd.StdinPipe() 381 Expect(err).NotTo(HaveOccurred()) 382 383 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 384 Expect(err).NotTo(HaveOccurred()) 385 386 Eventually(sess.Out).Should(gbytes.Say("or enter token manually")) 387 388 _, err = fmt.Fprintf(stdin, "bearer no-new-line-here") 389 Expect(err).NotTo(HaveOccurred()) 390 391 err = stdin.Close() 392 Expect(err).NotTo(HaveOccurred()) 393 394 <-sess.Exited 395 Expect(sess.ExitCode()).To(Equal(0)) 396 }) 397 398 It("ignores empty input", func() { 399 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL()) 400 401 stdin, err := flyCmd.StdinPipe() 402 Expect(err).NotTo(HaveOccurred()) 403 404 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 405 Expect(err).NotTo(HaveOccurred()) 406 407 err = stdin.Close() 408 Expect(err).NotTo(HaveOccurred()) 409 410 Consistently(sess.Out).ShouldNot(gbytes.Say("error")) 411 412 sess.Kill() 413 }) 414 }) 415 416 Context("token callback listener", func() { 417 var resp *http.Response 418 var req *http.Request 419 var sess *gexec.Session 420 421 BeforeEach(func() { 422 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL()) 423 _, err := flyCmd.StdinPipe() 424 Expect(err).NotTo(HaveOccurred()) 425 sess, err = gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 426 Expect(err).NotTo(HaveOccurred()) 427 Eventually(sess.Out).Should(gbytes.Say("or enter token manually")) 428 scanner := bufio.NewScanner(bytes.NewBuffer(sess.Out.Contents())) 429 var match []string 430 for scanner.Scan() { 431 re := regexp.MustCompile("fly_port=(\\d+)") 432 match = re.FindStringSubmatch(scanner.Text()) 433 if len(match) > 0 { 434 break 435 } 436 } 437 flyPort := match[1] 438 listenerURL := fmt.Sprintf("http://127.0.0.1:%s?token=Bearer%%20some-token", flyPort) 439 req, err = http.NewRequest("GET", listenerURL, nil) 440 Expect(err).NotTo(HaveOccurred()) 441 }) 442 443 JustBeforeEach(func() { 444 loginATCServer.AppendHandlers(ghttp.CombineHandlers( 445 ghttp.VerifyRequest("GET", "/fly_success"), 446 ghttp.RespondWith(200, ""), 447 )) 448 client := &http.Client{ 449 CheckRedirect: func(req *http.Request, via []*http.Request) error { 450 return http.ErrUseLastResponse 451 }, 452 } 453 var err error 454 resp, err = client.Do(req) 455 Expect(err).NotTo(HaveOccurred()) 456 <-sess.Exited 457 Expect(sess.ExitCode()).To(Equal(0)) 458 }) 459 460 It("sets a CORS header for the ATC being logged in to", func() { 461 corsHeader := resp.Header.Get("Access-Control-Allow-Origin") 462 Expect(corsHeader).To(Equal(loginATCServer.URL())) 463 }) 464 465 It("responds successfully", func() { 466 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 467 }) 468 469 Context("when the request comes from a human operating a browser", func() { 470 BeforeEach(func() { 471 req.Header.Add("Upgrade-Insecure-Requests", "1") 472 }) 473 474 It("redirects back to noop fly success page", func() { 475 Expect(resp.StatusCode).To(Equal(http.StatusFound)) 476 locationHeader := resp.Header.Get("Location") 477 Expect(locationHeader).To(Equal(fmt.Sprintf("%s/fly_success?noop=true", loginATCServer.URL()))) 478 }) 479 }) 480 }) 481 }) 482 483 Context("with password grant", func() { 484 BeforeEach(func() { 485 credentials := base64.StdEncoding.EncodeToString([]byte("fly:Zmx5")) 486 loginATCServer.AppendHandlers( 487 infoHandler(), 488 ghttp.CombineHandlers( 489 ghttp.VerifyRequest("POST", "/sky/issuer/token"), 490 ghttp.VerifyHeaderKV("Content-Type", "application/x-www-form-urlencoded"), 491 ghttp.VerifyHeaderKV("Authorization", fmt.Sprintf("Basic %s", credentials)), 492 ghttp.VerifyFormKV("grant_type", "password"), 493 ghttp.VerifyFormKV("username", "some_username"), 494 ghttp.VerifyFormKV("password", "some_password"), 495 ghttp.VerifyFormKV("scope", "openid profile email federated:id groups"), 496 ghttp.RespondWithJSONEncoded(200, map[string]string{ 497 "token_type": "Bearer", 498 "access_token": "access-token", 499 }), 500 ), 501 userInfoHandler(), 502 ) 503 }) 504 505 It("takes username and password as cli arguments", func() { 506 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-u", "some_username", "-p", "some_password") 507 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 508 Expect(err).NotTo(HaveOccurred()) 509 510 Consistently(sess.Out.Contents).ShouldNot(ContainSubstring("some_password")) 511 512 Eventually(sess.Out).Should(gbytes.Say("target saved")) 513 514 <-sess.Exited 515 Expect(sess.ExitCode()).To(Equal(0)) 516 }) 517 518 Context("after logging in succeeds", func() { 519 BeforeEach(func() { 520 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-u", "some_username", "-p", "some_password") 521 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 522 Expect(err).NotTo(HaveOccurred()) 523 524 Consistently(sess.Out.Contents).ShouldNot(ContainSubstring("some_password")) 525 526 Eventually(sess.Out).Should(gbytes.Say("target saved")) 527 528 <-sess.Exited 529 Expect(sess.ExitCode()).To(Equal(0)) 530 }) 531 532 It("flyrc is backwards-compatible with pre-v5.4.0", func() { 533 flyRcContents, err := ioutil.ReadFile(homeDir + "/.flyrc") 534 Expect(err).NotTo(HaveOccurred()) 535 Expect(string(flyRcContents)).To(HavePrefix("targets:")) 536 }) 537 538 Describe("running other commands", func() { 539 BeforeEach(func() { 540 loginATCServer.AppendHandlers( 541 infoHandler(), 542 ghttp.CombineHandlers( 543 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines"), 544 ghttp.VerifyHeaderKV("Authorization", "Bearer access-token"), 545 ghttp.RespondWithJSONEncoded(200, []atc.Pipeline{ 546 {Name: "pipeline-1"}, 547 }), 548 ), 549 ) 550 }) 551 552 It("uses the saved token", func() { 553 otherCmd := exec.Command(flyPath, "-t", "some-target", "pipelines") 554 555 sess, err := gexec.Start(otherCmd, GinkgoWriter, GinkgoWriter) 556 Expect(err).NotTo(HaveOccurred()) 557 558 <-sess.Exited 559 560 Expect(sess).To(gbytes.Say("pipeline-1")) 561 562 Expect(sess.ExitCode()).To(Equal(0)) 563 }) 564 }) 565 566 Describe("logging in again with the same target", func() { 567 BeforeEach(func() { 568 credentials := base64.StdEncoding.EncodeToString([]byte("fly:Zmx5")) 569 570 loginATCServer.AppendHandlers( 571 infoHandler(), 572 ghttp.CombineHandlers( 573 ghttp.VerifyRequest("POST", "/sky/issuer/token"), 574 ghttp.VerifyHeaderKV("Content-Type", "application/x-www-form-urlencoded"), 575 ghttp.VerifyHeaderKV("Authorization", fmt.Sprintf("Basic %s", credentials)), 576 ghttp.VerifyFormKV("grant_type", "password"), 577 ghttp.VerifyFormKV("username", "some_other_user"), 578 ghttp.VerifyFormKV("password", "some_other_pass"), 579 ghttp.VerifyFormKV("scope", "openid profile email federated:id groups"), 580 ghttp.RespondWithJSONEncoded(200, map[string]string{ 581 "token_type": "Bearer", 582 "access_token": "some-new-token", 583 }), 584 ), 585 userInfoHandler(), 586 infoHandler(), 587 ghttp.CombineHandlers( 588 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines"), 589 ghttp.VerifyHeaderKV("Authorization", "Bearer some-new-token"), 590 ghttp.RespondWithJSONEncoded(200, []atc.Pipeline{ 591 {Name: "pipeline-2"}, 592 }), 593 ), 594 ) 595 }) 596 597 It("updates the token", func() { 598 loginAgainCmd := exec.Command(flyPath, "-t", "some-target", "login", "-u", "some_other_user", "-p", "some_other_pass") 599 600 sess, err := gexec.Start(loginAgainCmd, GinkgoWriter, GinkgoWriter) 601 Expect(err).NotTo(HaveOccurred()) 602 603 Consistently(sess.Out.Contents).ShouldNot(ContainSubstring("some_other_pass")) 604 605 Eventually(sess.Out).Should(gbytes.Say("target saved")) 606 607 <-sess.Exited 608 Expect(sess.ExitCode()).To(Equal(0)) 609 610 otherCmd := exec.Command(flyPath, "-t", "some-target", "pipelines") 611 612 sess, err = gexec.Start(otherCmd, GinkgoWriter, GinkgoWriter) 613 Expect(err).NotTo(HaveOccurred()) 614 615 <-sess.Exited 616 617 Expect(sess).To(gbytes.Say("pipeline-2")) 618 619 Expect(sess.ExitCode()).To(Equal(0)) 620 }) 621 }) 622 }) 623 }) 624 625 Context("when fly and atc differ in major versions", func() { 626 var flyVersion string 627 628 BeforeEach(func() { 629 major, minor, patch, err := version.GetSemver(atcVersion) 630 Expect(err).NotTo(HaveOccurred()) 631 632 flyVersion = fmt.Sprintf("%d.%d.%d", major+1, minor, patch) 633 flyPath, err := gexec.Build( 634 "github.com/chenbh/concourse/v6/fly", 635 "-ldflags", fmt.Sprintf("-X github.com/chenbh/concourse/v6.Version=%s", flyVersion), 636 ) 637 Expect(err).NotTo(HaveOccurred()) 638 flyCmd = exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-u", "user", "-p", "pass") 639 640 loginATCServer.AppendHandlers( 641 infoHandler(), 642 tokenHandler(), 643 userInfoHandler(), 644 ) 645 }) 646 647 It("warns user and does not fail", func() { 648 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 649 Expect(err).NotTo(HaveOccurred()) 650 651 Eventually(sess).Should(gexec.Exit(0)) 652 Expect(sess.Err).To(gbytes.Say(`fly version \(%s\) is out of sync with the target \(%s\). to sync up, run the following:\n\n `, flyVersion, atcVersion)) 653 Expect(sess.Err).To(gbytes.Say(`fly.* -t some-target sync\n`)) 654 }) 655 }) 656 657 Context("cannot successfully login", func() { 658 Context("team does not exist", func() { 659 It("returns a warning", func() { 660 loginATCServer.AppendHandlers( 661 infoHandler(), 662 tokenHandler(), 663 ghttp.CombineHandlers( 664 ghttp.VerifyRequest("GET", "/api/v1/user"), 665 ghttp.RespondWithJSONEncoded(200, map[string]interface{}{ 666 "user_name": "user", 667 "teams": map[string][]string{ 668 "other_team": {"owner"}, 669 }, 670 }), 671 ), 672 ) 673 674 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "any-team", "-u", "user", "-p", "pass") 675 676 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 677 Expect(err).NotTo(HaveOccurred()) 678 679 Eventually(sess.Err).Should(gbytes.Say("you are not a member of 'any-team' or the team does not exist")) 680 681 <-sess.Exited 682 Expect(sess.ExitCode()).To(Equal(1)) 683 }) 684 }) 685 Context("/api/v1/user returns garbage", func() { 686 It("returns a warning", func() { 687 loginATCServer.AppendHandlers( 688 infoHandler(), 689 tokenHandler(), 690 ghttp.CombineHandlers( 691 ghttp.VerifyRequest("GET", "/api/v1/user"), 692 ghttp.RespondWithJSONEncoded(200, map[string]interface{}{ 693 "a-key": "a-value", 694 }), 695 ), 696 ) 697 698 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "any-team", "-u", "user", "-p", "pass") 699 700 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 701 Expect(err).NotTo(HaveOccurred()) 702 703 Eventually(sess.Err).Should(gbytes.Say("unable to verify role on team")) 704 705 <-sess.Exited 706 Expect(sess.ExitCode()).To(Equal(1)) 707 }) 708 }) 709 }) 710 711 Context("when logging in as an admin user", func() { 712 It("can login to any team", func() { 713 loginATCServer.AppendHandlers( 714 infoHandler(), 715 tokenHandler(), 716 ghttp.CombineHandlers( 717 ghttp.VerifyRequest("GET", "/api/v1/user"), 718 ghttp.RespondWithJSONEncoded(200, map[string]interface{}{ 719 "user_name": "admin_user", 720 "is_admin": true, 721 }), 722 ), 723 ) 724 725 flyCmd := exec.Command(flyPath, "-t", "some-target", "login", "-c", loginATCServer.URL(), "-n", "any-team", "-u", "admin_user", "-p", "pass") 726 727 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 728 Expect(err).NotTo(HaveOccurred()) 729 730 Eventually(sess.Out).Should(gbytes.Say("target saved")) 731 732 <-sess.Exited 733 Expect(sess.ExitCode()).To(Equal(0)) 734 }) 735 }) 736 }) 737 })