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