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  })