github.com/wanddynosios/cli@v7.1.0+incompatible/integration/v6/isolated/login_command_test.go (about)

     1  package isolated
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"time"
    13  
    14  	"code.cloudfoundry.org/cli/integration/helpers"
    15  	"code.cloudfoundry.org/cli/util/configv3"
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  	. "github.com/onsi/gomega/gbytes"
    19  	. "github.com/onsi/gomega/gexec"
    20  	"github.com/onsi/gomega/ghttp"
    21  )
    22  
    23  var _ = Describe("login command", func() {
    24  	BeforeEach(func() {
    25  		helpers.SkipIfClientCredentialsTestMode()
    26  	})
    27  
    28  	Describe("Help Text", func() {
    29  		When("--help flag is set", func() {
    30  			It("displays the command usage", func() {
    31  				session := helpers.CF("login", "--help")
    32  				Eventually(session).Should(Exit(0))
    33  
    34  				Expect(session).Should(Say("NAME:\n"))
    35  				Expect(session).Should(Say("login - Log user in"))
    36  
    37  				Expect(session).Should(Say("USAGE:\n"))
    38  				Expect(session).Should(Say(`cf login \[-a API_URL\] \[-u USERNAME\] \[-p PASSWORD\] \[-o ORG\] \[-s SPACE\] \[--sso | --sso-passcode PASSCODE\] \[--origin ORIGIN\]`))
    39  
    40  				Expect(session).Should(Say("WARNING:\n"))
    41  				Expect(session).Should(Say("Providing your password as a command line option is highly discouraged\n"))
    42  				Expect(session).Should(Say("Your password may be visible to others and may be recorded in your shell history\n"))
    43  
    44  				Expect(session).Should(Say("EXAMPLES:\n"))
    45  				Expect(session).Should(Say(regexp.QuoteMeta("cf login (omit username and password to login interactively -- cf will prompt for both)")))
    46  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p pa55woRD (specify username and password as arguments)")))
    47  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p \"my password\" (use quotes for passwords with a space)")))
    48  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p \"\\\"password\\\"\" (escape quotes if used in password)")))
    49  				Expect(session).Should(Say(regexp.QuoteMeta("cf login --sso (cf will provide a url to obtain a one-time passcode to login)")))
    50  				Expect(session).Should(Say(regexp.QuoteMeta("cf login --origin ldap")))
    51  
    52  				Expect(session).Should(Say("ALIAS:\n"))
    53  				Expect(session).Should(Say("l"))
    54  
    55  				Expect(session).Should(Say("OPTIONS:\n"))
    56  				Expect(session).Should(Say(`-a\s+API endpoint \(e.g. https://api\.example\.com\)`))
    57  				Expect(session).Should(Say(`-o\s+Org`))
    58  				Expect(session).Should(Say(`-p\s+Password`))
    59  				Expect(session).Should(Say(`-s\s+Space`))
    60  				Expect(session).Should(Say(`--skip-ssl-validation\s+Skip verification of the API endpoint\. Not recommended\!`))
    61  				Expect(session).Should(Say(`--sso\s+Prompt for a one-time passcode to login`))
    62  				Expect(session).Should(Say(`--sso-passcode\s+One-time passcode`))
    63  				Expect(session).Should(Say(`-u\s+Username`))
    64  
    65  				Expect(session).Should(Say("SEE ALSO:\n"))
    66  				Expect(session).Should(Say("api, auth, target"))
    67  			})
    68  		})
    69  	})
    70  
    71  	Describe("Invalid Command Usage", func() {
    72  		When("a random flag is passed in", func() {
    73  			It("exits 1 and displays an unknown flag error message", func() {
    74  				session := helpers.CF("login", "--test")
    75  				Eventually(session).Should(Exit(1))
    76  
    77  				Expect(session.Err).Should(Say("Incorrect Usage: unknown flag `test'"))
    78  			})
    79  		})
    80  	})
    81  
    82  	Describe("authorization endpoint", func() {
    83  		When("an authorization endpoint is advertised by the server", func() {
    84  			var server *ghttp.Server
    85  
    86  			BeforeEach(func() {
    87  				server = helpers.StartMockServerWithCustomAuthorizationEndpoint("/custom/authorization/endpoint")
    88  
    89  				fakeTokenResponse := map[string]string{
    90  					"access_token":  helpers.BuildTokenString(time.Now().Add(time.Hour)),
    91  					"token_type":    "bearer",
    92  					"refresh_token": "refresh-token",
    93  				}
    94  				server.RouteToHandler(http.MethodGet, "/login",
    95  					func(http.ResponseWriter, *http.Request) {
    96  						Fail("The advertized authorization_endpoint should be used in preference to the UAA endpoint")
    97  					},
    98  				)
    99  				server.RouteToHandler(http.MethodGet, "/v3/organizations",
   100  					ghttp.RespondWith(http.StatusOK, `{"total_results": 0, "total_pages": 1, "resources": []}`))
   101  
   102  				server.AppendHandlers(
   103  					ghttp.CombineHandlers(
   104  						ghttp.VerifyRequest("POST", "/custom/authorization/endpoint/oauth/token"),
   105  						ghttp.RespondWithJSONEncoded(http.StatusOK, fakeTokenResponse),
   106  					),
   107  				)
   108  			})
   109  
   110  			AfterEach(func() {
   111  				server.Close()
   112  			})
   113  
   114  			It("requests a token from the authorization endpoint and exits successfully", func() {
   115  				username, password := helpers.GetCredentials()
   116  				session := helpers.CF("-v", "login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   117  				Eventually(session).Should(Exit(0))
   118  			})
   119  		})
   120  	})
   121  
   122  	Describe("Minimum Version Check", func() {
   123  		When("the v2 API version is less than the minimum supported version", func() {
   124  			var server *ghttp.Server
   125  
   126  			BeforeEach(func() {
   127  				server = helpers.StartMockServerWithAPIVersions("2.99.9", "3.36.0")
   128  
   129  				fakeTokenResponse := map[string]string{
   130  					"access_token":  helpers.BuildTokenString(time.Now()),
   131  					"token_type":    "bearer",
   132  					"refresh_token": "refresh-token",
   133  				}
   134  				server.RouteToHandler(http.MethodPost, "/oauth/token",
   135  					ghttp.RespondWithJSONEncoded(http.StatusOK, fakeTokenResponse))
   136  				server.RouteToHandler(http.MethodGet, "/v3/organizations",
   137  					ghttp.RespondWith(http.StatusOK, `{
   138  					 "total_results": 0,
   139  					 "total_pages": 1,
   140  					 "resources": []}`))
   141  			})
   142  
   143  			AfterEach(func() {
   144  				server.Close()
   145  			})
   146  
   147  			It("displays the warning to stderr and exits successfully", func() {
   148  				username, password := helpers.GetCredentials()
   149  				session := helpers.CF("login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   150  
   151  				Eventually(session.Err).Should(Say("Your CF API version .+ is no longer supported. Upgrade to a newer version of the API .+"))
   152  				Eventually(session).Should(Exit(0))
   153  			})
   154  
   155  			It("displays the warning before authenticating", func() {
   156  				username, password := helpers.GetCredentials()
   157  				cmd := exec.Command("cf", "login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   158  				stdErrAndStdOut, err := cmd.CombinedOutput()
   159  				Expect(err).ToNot(HaveOccurred())
   160  
   161  				output := BufferWithBytes(stdErrAndStdOut)
   162  				Expect(output).To(Say("Your CF API version .+ is no longer supported. Upgrade to a newer version of the API .+"))
   163  				Expect(output).To(Say("Authenticating..."), "Expected Min API version warning before 'Authenticating...' message.")
   164  			})
   165  		})
   166  
   167  		When("the CLI version is lower than the minimum supported version by the CC", func() {
   168  			var server *ghttp.Server
   169  
   170  			BeforeEach(func() {
   171  				server = helpers.StartMockServerWithMinimumCLIVersion("9000.0.0")
   172  
   173  				fakeTokenResponse := map[string]string{
   174  					"access_token": "",
   175  					"token_type":   "bearer",
   176  				}
   177  				server.RouteToHandler(http.MethodPost, "/oauth/token",
   178  					ghttp.RespondWithJSONEncoded(http.StatusOK, fakeTokenResponse))
   179  				server.RouteToHandler(http.MethodGet, "/v3/organizations",
   180  					ghttp.RespondWith(http.StatusOK, `{
   181  					 "total_results": 0,
   182  					 "total_pages": 1,
   183  					 "resources": []}`))
   184  			})
   185  
   186  			AfterEach(func() {
   187  				server.Close()
   188  			})
   189  
   190  			It("displays the warning to stderr and exits successfully", func() {
   191  				username, password := helpers.GetCredentials()
   192  				session := helpers.CF("login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   193  
   194  				Eventually(session.Err).Should(Say(`Cloud Foundry API version .+ requires CLI version .+\. You are currently on version .+\. To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads`))
   195  				Eventually(session).Should(Exit(0))
   196  			})
   197  
   198  			It("displays the warning before authenticating", func() {
   199  				username, password := helpers.GetCredentials()
   200  				cmd := exec.Command("cf", "login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   201  				stdErrAndStdOut, err := cmd.CombinedOutput()
   202  				Expect(err).ToNot(HaveOccurred())
   203  
   204  				output := BufferWithBytes(stdErrAndStdOut)
   205  				Expect(output).To(Say(`Cloud Foundry API version .+ requires CLI version .+\. You are currently on version .+\. To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads`))
   206  				Expect(output).To(Say("Authenticating..."), "Expected Min CLI version warning before 'Authenticating...' message.")
   207  			})
   208  		})
   209  	})
   210  
   211  	Describe("API Endpoint", func() {
   212  		When("the API endpoint is not set", func() {
   213  			BeforeEach(func() {
   214  				helpers.UnsetAPI()
   215  			})
   216  
   217  			When("the user does not provide the -a flag", func() {
   218  				It("prompts the user for an endpoint", func() {
   219  					input := NewBuffer()
   220  					_, err := input.Write([]byte("\n"))
   221  					Expect(err).ToNot(HaveOccurred())
   222  					session := helpers.CFWithStdin(input, "login")
   223  					Eventually(session).Should(Say("API endpoint:"))
   224  					session.Interrupt()
   225  					Eventually(session).Should(Exit())
   226  				})
   227  
   228  				When("the API endpoint provided at the prompt is unreachable", func() {
   229  					It("returns an error", func() {
   230  						input := NewBuffer()
   231  						_, err := input.Write([]byte("does.not.exist\n"))
   232  						Expect(err).ToNot(HaveOccurred())
   233  						session := helpers.CFWithStdin(input, "login")
   234  						Eventually(session).Should(Say("API endpoint:"))
   235  						Eventually(session).Should(Say("FAILED"))
   236  						Eventually(session.Err).Should(Say("Request error: "))
   237  						Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
   238  						Eventually(session).Should(Exit(1))
   239  					})
   240  				})
   241  			})
   242  
   243  			When("the user provides the -a flag", func() {
   244  
   245  				var session *Session
   246  				BeforeEach(func() {
   247  					if skipSSLValidation {
   248  						session = helpers.CF("login", "-a", apiURL, "--skip-ssl-validation")
   249  					} else {
   250  						session = helpers.CF("login", "-a", apiURL)
   251  					}
   252  					Eventually(session).Should(Say("API endpoint: %s", apiURL))
   253  					// TODO https://www.pivotaltracker.com/story/show/166938709/comments/204492216
   254  					//Consistently(session).ShouldNot(Say("API endpoint:"))
   255  					//session.Interrupt()
   256  					Eventually(session).Should(Exit())
   257  
   258  				})
   259  				It("sets the API endpoint and does not prompt the user for the API endpoint", func() {
   260  					session = helpers.CF("api")
   261  					Eventually(session).Should(Exit(0))
   262  					Expect(session).Should(Say("api endpoint:   %s", apiURL))
   263  
   264  				})
   265  				It("writes fields to the config file when targeting an API", func() {
   266  					rawConfig, err := ioutil.ReadFile(filepath.Join(homeDir, ".cf", "config.json"))
   267  					Expect(err).NotTo(HaveOccurred())
   268  
   269  					var configFile configv3.JSONConfig
   270  					err = json.Unmarshal(rawConfig, &configFile)
   271  					Expect(err).NotTo(HaveOccurred())
   272  
   273  					Expect(configFile.ConfigVersion).To(Equal(configv3.CurrentConfigVersion))
   274  					Expect(configFile.Target).To(Equal(apiURL))
   275  					Expect(configFile.APIVersion).To(MatchRegexp(`\d+\.\d+\.\d+`))
   276  					Expect(configFile.AuthorizationEndpoint).ToNot(BeEmpty())
   277  					Expect(configFile.DopplerEndpoint).To(MatchRegexp("^wss://"))
   278  					Expect(configFile.LogCacheEndpoint).To(MatchRegexp(".*log-cache.*"))
   279  
   280  				})
   281  
   282  				When("the provided API endpoint is unreachable", func() {
   283  					It("displays an error and fails", func() {
   284  						var session *Session
   285  						if skipSSLValidation {
   286  							session = helpers.CF("login", "-a", "does.not.exist", "--skip-ssl-validation")
   287  						} else {
   288  							session = helpers.CF("login", "-a", "does.not.exist")
   289  						}
   290  
   291  						Eventually(session).Should(Say("API endpoint: does.not.exist"))
   292  						Eventually(session).Should(Say("FAILED"))
   293  						Eventually(session.Err).Should(Say("Request error: "))
   294  						Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
   295  						Eventually(session).Should(Exit(1))
   296  					})
   297  				})
   298  
   299  				When("the provided API endpoint has trailing slashes", func() {
   300  					It("removes the extra slashes", func() {
   301  						username, password := helpers.GetCredentials()
   302  						apiURLWithSlash := apiURL + "////"
   303  						session := helpers.CF("login", "-a", apiURLWithSlash, "-u", username, "-p", password, "--skip-ssl-validation")
   304  						Eventually(session).Should(Exit(0))
   305  
   306  						session = helpers.CF("api")
   307  						Eventually(session).Should(Say("api endpoint:\\s+%s\n", apiURL))
   308  						Eventually(session).Should(Exit(0))
   309  					})
   310  				})
   311  			})
   312  		})
   313  
   314  		When("the API endpoint is already set", func() {
   315  			It("does not prompt the user for API endpoint", func() {
   316  				session := helpers.CF("login")
   317  				Consistently(session).ShouldNot(Say("API endpoint>"))
   318  				session.Interrupt()
   319  				Eventually(session).Should(Exit())
   320  			})
   321  
   322  			When("the user provides a new API endpoint with the -a flag", func() {
   323  				When("the provided API endpoint is unreachable", func() {
   324  					It("displays an error and does not change the API endpoint", func() {
   325  						var session *Session
   326  						if skipSSLValidation {
   327  							session = helpers.CF("login", "-a", "does.not.exist", "--skip-ssl-validation")
   328  						} else {
   329  							session = helpers.CF("login", "-a", "does.not.exist")
   330  						}
   331  						Eventually(session).Should(Say("API endpoint: does.not.exist"))
   332  						Eventually(session).Should(Say("FAILED"))
   333  						Eventually(session.Err).Should(Say("Request error: "))
   334  						Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
   335  						Eventually(session).Should(Exit(1))
   336  
   337  						apiSession := helpers.CF("api")
   338  						Eventually(apiSession).Should(Exit(0))
   339  						Eventually(apiSession).Should(Say("api endpoint:   %s", apiURL))
   340  					})
   341  				})
   342  			})
   343  		})
   344  	})
   345  
   346  	Describe("https", func() {
   347  
   348  		When("no scheme is included in the API endpoint", func() {
   349  			var (
   350  				hostname  string
   351  				serverURL *url.URL
   352  				err       error
   353  				session   *Session
   354  			)
   355  
   356  			BeforeEach(func() {
   357  				serverURL, err = url.Parse(helpers.GetAPI())
   358  				Expect(err).NotTo(HaveOccurred())
   359  
   360  				hostname = serverURL.Hostname()
   361  
   362  				username, password := helpers.GetCredentials()
   363  				if skipSSLValidation {
   364  					session = helpers.CF("login", "-u", username, "-p", password, "-a", hostname, "--skip-ssl-validation")
   365  				} else {
   366  					session = helpers.CF("login", "-u", username, "-p", password, "-a", hostname)
   367  				}
   368  			})
   369  
   370  			It("displays the API endpoint you are about to target as https", func() {
   371  				Eventually(session).Should(Say("API endpoint: %s", hostname))
   372  				Eventually(session).Should(Exit(0))
   373  			})
   374  
   375  			It("displays the API endpoint you have targeted as https", func() {
   376  				Eventually(session).Should(Say(`API endpoint:\s+https://%s\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`, hostname))
   377  				Eventually(session).Should(Exit(0))
   378  			})
   379  
   380  		})
   381  
   382  		When("the API endpoint's scheme is http", func() {
   383  			var httpURL string
   384  
   385  			BeforeEach(func() {
   386  				apiURL, err := url.Parse(helpers.GetAPI())
   387  				Expect(err).NotTo(HaveOccurred())
   388  				apiURL.Scheme = "http"
   389  
   390  				httpURL = apiURL.String()
   391  			})
   392  
   393  			It("shows a warning to the user", func() {
   394  				username, password := helpers.GetCredentials()
   395  				session := helpers.CF("login", "-u", username, "-p", password, "-a", httpURL, "--skip-ssl-validation")
   396  
   397  				Eventually(session).Should(Say("API endpoint: %s", httpURL))
   398  				Eventually(session.Err).Should(Say("Warning: Insecure http API endpoint detected: secure https API endpoints are recommended"))
   399  				Eventually(session).Should(Exit(0))
   400  			})
   401  		})
   402  
   403  		// This test is redundant:
   404  		// If SKIP_SSL_VALIDATION is disabled
   405  		// then the test suite would have already logged in
   406  		// with a valid cert by the time this test gets run
   407  		When("the OS provides a valid SSL Certificate (Unix: SSL_CERT_FILE or SSL_CERT_DIR Environment variables) (Windows: Import-Certificate call)", func() {
   408  			BeforeEach(func() {
   409  				if skipSSLValidation {
   410  					Skip("SKIP_SSL_VALIDATION is enabled")
   411  				}
   412  			})
   413  
   414  			It("trusts the cert and allows the users to log in", func() {
   415  				username, password := helpers.GetCredentials()
   416  				session := helpers.CF("login", "-u", username, "-p", password, "-a", helpers.GetAPI())
   417  				Eventually(session).Should(Say("API endpoint: %s", apiURL))
   418  				Eventually(session).Should(Exit())
   419  
   420  				session = helpers.CF("api")
   421  				Eventually(session).Should(Exit(0))
   422  				Expect(session).Should(Say("api endpoint:   %s", apiURL))
   423  			})
   424  		})
   425  
   426  		When("the SSL Certificate is invalid", func() {
   427  			var server *ghttp.Server
   428  
   429  			BeforeEach(func() {
   430  				cliVersion := "1.0.0"
   431  				server = helpers.StartMockServerWithMinimumCLIVersion(cliVersion)
   432  			})
   433  
   434  			AfterEach(func() {
   435  				server.Close()
   436  			})
   437  
   438  			It("displays a helpful error message and exits 1", func() {
   439  				session := helpers.CF("login", "-a", server.URL())
   440  				Eventually(session).Should(Say("API endpoint: %s", server.URL()))
   441  				Eventually(session).Should(Say("FAILED"))
   442  				Eventually(session.Err).Should(Say("Invalid SSL Cert for %s", server.URL()))
   443  				Eventually(session.Err).Should(Say("TIP: Use 'cf login --skip-ssl-validation' to continue with an insecure API endpoint"))
   444  				Eventually(session).Should(Exit(1))
   445  			})
   446  
   447  			When("targeted with --skip-ssl-validation", func() {
   448  				BeforeEach(func() {
   449  					Eventually(helpers.CF("api", server.URL(), "--skip-ssl-validation")).Should(Exit(0))
   450  				})
   451  
   452  				When("logging in without --skip-ssl-validation", func() {
   453  					It("displays a helpful error message and exits 1", func() {
   454  						session := helpers.CF("login", "-a", server.URL())
   455  						Eventually(session).Should(Say("API endpoint: %s", server.URL()))
   456  						Eventually(session).Should(Say("FAILED"))
   457  						Eventually(session.Err).Should(Say("Invalid SSL Cert for %s", server.URL()))
   458  						Eventually(session.Err).Should(Say("TIP: Use 'cf login --skip-ssl-validation' to continue with an insecure API endpoint"))
   459  						Eventually(session).Should(Exit(1))
   460  					})
   461  				})
   462  			})
   463  
   464  			When("the server accepts logins", func() {
   465  				BeforeEach(func() {
   466  					fakeTokenResponse := map[string]string{
   467  						"access_token": "",
   468  						"token_type":   "bearer",
   469  					}
   470  					server.RouteToHandler(http.MethodPost, "/oauth/token",
   471  						ghttp.RespondWithJSONEncoded(http.StatusOK, fakeTokenResponse))
   472  					server.RouteToHandler(http.MethodGet, "/v3/organizations",
   473  						ghttp.RespondWith(http.StatusOK, `{
   474                                                          "total_results": 0,
   475                                                          "total_pages": 1,
   476                                                          "resources": []}`))
   477  				})
   478  
   479  				It("doesn't complain about an invalid cert when we specify --skip-ssl-validation", func() {
   480  					session := helpers.CF("login", "-a", server.URL(), "--skip-ssl-validation")
   481  					//session := helpers.CF("api", server.URL(), "--skip-ssl-validation")
   482  					Eventually(session).Should(Exit(0))
   483  					Expect(session).Should(Say("API endpoint:\\s+" + server.URL()))
   484  					Expect(session).Should(Say(`Authenticating\.\.\.`))
   485  					Expect(session).Should(Say(`OK`))
   486  					Expect(session).Should(Say(`API endpoint:\s+` + server.URL() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   487  
   488  					Expect(string(session.Err.Contents())).Should(Not(ContainSubstring("Invalid SSL Cert for %s", server.URL())))
   489  				})
   490  
   491  			})
   492  		})
   493  	})
   494  
   495  	Describe("SSO", func() {
   496  		When("--sso-passcode is provided", func() {
   497  			Context("and --sso is also passed", func() {
   498  				It("fails with a useful error message", func() {
   499  					session := helpers.CF("login", "--sso-passcode", "some-passcode", "--sso")
   500  					Eventually(session.Err).Should(Say("Incorrect Usage: The following arguments cannot be used together: --sso-passcode, --sso"))
   501  					Eventually(session).Should(Exit(1))
   502  				})
   503  			})
   504  
   505  			Context("and the provided passcode is incorrect", func() {
   506  				It("prompts twice, displays an error and fails", func() {
   507  					input := NewBuffer()
   508  					_, err := input.Write([]byte("bad-passcode-again\nbad-passcode-strikes-back\n"))
   509  					Expect(err).ToNot(HaveOccurred())
   510  					session := helpers.CFWithStdin(input, "login", "--sso-passcode", "some-passcode")
   511  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   512  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   513  					Eventually(session.Err).Should(Say(`Invalid passcode`))
   514  
   515  					// Leaving out expectation of prompt text, since it comes from UAA (and doesn't show up on Windows)
   516  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   517  					Eventually(session.Err).Should(Say(`Invalid passcode`))
   518  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   519  					Eventually(session.Err).Should(Say(`Invalid passcode`))
   520  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   521  					Eventually(session).Should(Say(`Not logged in. Use 'cf login' or 'cf login --sso' to log in.`))
   522  					Eventually(session.Err).Should(Say(`Unable to authenticate`))
   523  					Eventually(session).Should(Say(`FAILED`))
   524  
   525  					Eventually(session).Should(Exit(1))
   526  				})
   527  			})
   528  
   529  			When("a passcode isn't provided", func() {
   530  				It("prompts the user to try again", func() {
   531  					session := helpers.CF("login", "--sso-passcode")
   532  					Eventually(session.Err).Should(Say("Incorrect Usage: expected argument for flag `--sso-passcode'"))
   533  					Eventually(session).Should(Exit(1))
   534  				})
   535  			})
   536  		})
   537  
   538  		When("a user authenticates with valid client credentials", func() {
   539  			BeforeEach(func() {
   540  				clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet()
   541  				session := helpers.CF("auth", clientID, clientSecret, "--client-credentials")
   542  				Eventually(session).Should(Exit(0))
   543  			})
   544  
   545  			When("a different user logs in with valid sso passcode", func() {
   546  				It("should fail log in and display an error informing the user they need to log out", func() {
   547  					session := helpers.CF("login", "--sso-passcode", "my-fancy-sso")
   548  
   549  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   550  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   551  					Eventually(session.Err).Should(Say(`Service account currently logged in\. Use 'cf logout' to log out service account and try again\.`))
   552  					Eventually(session).Should(Say("FAILED"))
   553  					Eventually(session).Should(Exit(1))
   554  
   555  					//And I am still logged in
   556  					targetSession := helpers.CF("target")
   557  					Eventually(targetSession).Should(Exit(0))
   558  				})
   559  			})
   560  		})
   561  	})
   562  
   563  	Describe("Target Organization", func() {
   564  		var (
   565  			orgName  string
   566  			username string
   567  			password string
   568  		)
   569  
   570  		BeforeEach(func() {
   571  			helpers.LoginCF()
   572  			orgName = helpers.NewOrgName()
   573  			session := helpers.CF("create-org", orgName)
   574  			Eventually(session).Should(Exit(0))
   575  			username, password = helpers.CreateUserInOrgRole(orgName, "OrgManager")
   576  		})
   577  
   578  		When("there is only one org available to the user", func() {
   579  			It("logs the user in and targets the organization automatically", func() {
   580  				session := helpers.CF("login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   581  				Eventually(session).Should(Exit(0))
   582  
   583  				targetSession := helpers.CF("target")
   584  				Eventually(targetSession).Should(Exit(0))
   585  				Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   586  			})
   587  
   588  			It("uses v3 endpoints", func() {
   589  				session := helpers.CF("login", "-v", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   590  				Eventually(session).Should(Exit(0))
   591  
   592  				Eventually(session).Should(Say(`GET /v3/organizations.*HTTP/`))
   593  				Eventually(session).Should(Say(`GET /v3/spaces.*HTTP/`))
   594  			})
   595  		})
   596  
   597  		When("the -o flag is not passed", func() {
   598  			When("there are multiple orgs available to the user", func() {
   599  				BeforeEach(func() {
   600  					orgName = helpers.NewOrgName()
   601  					createOrgSession := helpers.CF("create-org", orgName)
   602  					Eventually(createOrgSession).Should(Exit(0))
   603  					setOrgRoleSession := helpers.CF("set-org-role", username, orgName, "OrgManager")
   604  					Eventually(setOrgRoleSession).Should(Exit(0))
   605  				})
   606  
   607  				When("there are more than 50 orgs", func() {
   608  					var server *ghttp.Server
   609  
   610  					BeforeEach(func() {
   611  						server = helpers.StartAndTargetMockServerWithAPIVersions(helpers.DefaultV2Version, helpers.DefaultV3Version)
   612  						helpers.AddLoginRoutes(server)
   613  						helpers.AddFiftyOneOrgs(server)
   614  						// handle request for spaces under "org20"
   615  						helpers.AddEmptyPaginatedResponse(server, "/v3/spaces?organization_guids=f6653aac-938e-4469-9a66-56a02796412b")
   616  					})
   617  
   618  					AfterEach(func() {
   619  						server.Close()
   620  					})
   621  
   622  					It("displays a message and prompts the user for the org name", func() {
   623  						input := NewBuffer()
   624  						_, wErr := input.Write([]byte(fmt.Sprintf("%s\n", "org20"))) // "org20" is one of the orgs in the test fixture
   625  						Expect(wErr).ToNot(HaveOccurred())
   626  
   627  						session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "--skip-ssl-validation")
   628  
   629  						Eventually(session).Should(Say("Select an org:"))
   630  						Eventually(session).Should(Say("There are too many options to display; please type in the name."))
   631  						Eventually(session).Should(Say("\n\n"))
   632  						Eventually(session).Should(Say(regexp.QuoteMeta(`Org (enter to skip):`)))
   633  						Eventually(session).Should(Say("Targeted org org20"))
   634  
   635  						Eventually(session).Should(Exit(0))
   636  					})
   637  				})
   638  
   639  				When("user selects an organization by using numbered list", func() {
   640  					// required
   641  					It("prompts the user for org and targets the selected org", func() {
   642  						input := NewBuffer()
   643  						_, err := input.Write([]byte("1\n"))
   644  						Expect(err).ToNot(HaveOccurred())
   645  						var session *Session
   646  						// TODO: do we still need this?
   647  						if skipSSLValidation {
   648  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   649  						} else {
   650  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL)
   651  						}
   652  
   653  						Eventually(session).Should(Exit(0))
   654  
   655  						re := regexp.MustCompile("1\\. (?P<OrgName>.*)\n")
   656  						matches := re.FindStringSubmatch(string(session.Out.Contents()))
   657  						Expect(matches).To(HaveLen((2)))
   658  						expectedOrgName := matches[1]
   659  
   660  						targetSession := helpers.CF("target")
   661  						Eventually(targetSession).Should(Exit(0))
   662  						Eventually(targetSession).Should(Say(`org:\s+%s`, expectedOrgName))
   663  					})
   664  
   665  					When("the user selects a number greater than the number of orgs", func() {
   666  						// allowed to change
   667  						It("prompts the user until a valid number is entered", func() {
   668  							input := NewBuffer()
   669  							_, err := input.Write([]byte("3\n"))
   670  							Expect(err).ToNot(HaveOccurred())
   671  
   672  							session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password)
   673  
   674  							Eventually(session).Should(Say(regexp.QuoteMeta("Select an org:")))
   675  							Eventually(session).Should(Say(regexp.QuoteMeta(`Org (enter to skip):`)))
   676  							Eventually(session).Should(Say(regexp.QuoteMeta(`Org (enter to skip):`)))
   677  
   678  							session.Interrupt()
   679  							Eventually(session).Should(Exit())
   680  						})
   681  					})
   682  				})
   683  
   684  				When("user selects an organization by org name", func() {
   685  					// required
   686  					It("prompts the user for an org and then targets the selected org", func() {
   687  						input := NewBuffer()
   688  						_, err := input.Write([]byte(fmt.Sprintf("%s\n", orgName)))
   689  						Expect(err).ToNot(HaveOccurred())
   690  
   691  						var session *Session
   692  						if skipSSLValidation {
   693  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   694  						} else {
   695  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL)
   696  						}
   697  						Eventually(session).Should(Say(`\d\. %s`, orgName))
   698  						Eventually(session).Should(Exit(0))
   699  
   700  						targetSession := helpers.CF("target")
   701  						Eventually(targetSession).Should(Exit(0))
   702  						Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   703  					})
   704  				})
   705  
   706  				When("user does not select an organization", func() {
   707  					// allowed to change
   708  					It("succesfully logs in but does not target any org", func() {
   709  						input := NewBuffer()
   710  						_, err := input.Write([]byte("\n"))
   711  						Expect(err).ToNot(HaveOccurred())
   712  
   713  						var session *Session
   714  						if skipSSLValidation {
   715  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   716  						} else {
   717  							session = helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL)
   718  						}
   719  						Eventually(session).Should(Say(`Org \(enter to skip\):`))
   720  						Consistently(session).ShouldNot(Say(`Org \(enter to skip\):`))
   721  						Eventually(session).Should(Exit(0))
   722  
   723  						targetSession := helpers.CF("target")
   724  						Eventually(targetSession).Should(Exit(0))
   725  						Eventually(targetSession).Should(Say("No org or space targeted, use 'cf target -o ORG -s SPACE'"))
   726  					})
   727  				})
   728  
   729  				When("the user enters an invalid organization at the prompt", func() {
   730  					It("displays an error message and does not target the org", func() {
   731  						orgName = "invalid-org"
   732  						input := NewBuffer()
   733  						_, err := input.Write([]byte(fmt.Sprintf("%s\n", orgName)))
   734  						Expect(err).ToNot(HaveOccurred())
   735  
   736  						session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "--skip-ssl-validation")
   737  						Eventually(session).Should(Exit(1))
   738  						Eventually(session).Should(Say("FAILED"))
   739  						Eventually(session.Err).Should(Say("Organization '%s' not found", orgName))
   740  
   741  						targetSession := helpers.CF("target")
   742  						Eventually(targetSession).Should(Exit(0))
   743  						Eventually(targetSession).Should(Say(`user:\s+%s`, username))
   744  						Eventually(targetSession).ShouldNot(Say(`org:\s+%s`, orgName))
   745  						Eventually(targetSession).Should(Say("No org or space targeted, use 'cf target -o ORG -s SPACE'"))
   746  					})
   747  				})
   748  			})
   749  		})
   750  
   751  		When("the -o flag is passed", func() {
   752  			BeforeEach(func() {
   753  				helpers.LogoutCF()
   754  			})
   755  
   756  			When("the organization is valid", func() {
   757  				It("targets the organization that was passed as an argument", func() {
   758  					session := helpers.CF("login", "-u", username, "-p", password, "-o", orgName)
   759  
   760  					Eventually(session).Should(Exit(0))
   761  					Eventually(session).Should(Say(`Org:\s+%s`, orgName))
   762  
   763  					targetSession := helpers.CF("target")
   764  					Eventually(targetSession).Should(Exit(0))
   765  					Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   766  				})
   767  			})
   768  
   769  			When("the organization is invalid", func() {
   770  				It("logs in the user, displays an error message, and does not target any organization", func() {
   771  					orgName = "invalid-org"
   772  					session := helpers.CF("login", "-u", username, "-p", password, "-o", orgName)
   773  
   774  					Eventually(session).Should(Exit(1))
   775  					Eventually(session).Should(Say("FAILED"))
   776  					Eventually(session.Err).Should(Say("Organization '%s' not found", orgName))
   777  
   778  					targetSession := helpers.CF("target")
   779  					Eventually(targetSession).Should(Exit(0))
   780  					Eventually(targetSession).Should(Say(`user:\s+%s`, username))
   781  					Eventually(targetSession).ShouldNot(Say(`org:\s+%s`, orgName))
   782  					Eventually(targetSession).Should(Say("No org or space targeted, use 'cf target -o ORG -s SPACE'"))
   783  				})
   784  			})
   785  		})
   786  	})
   787  
   788  	Describe("Target Space", func() {
   789  		var (
   790  			orgName  string
   791  			username string
   792  			password string
   793  		)
   794  
   795  		BeforeEach(func() {
   796  			helpers.LoginCF()
   797  			orgName = helpers.NewOrgName()
   798  			session := helpers.CF("create-org", orgName)
   799  			Eventually(session).Should(Exit(0))
   800  			username, password = helpers.CreateUser()
   801  		})
   802  
   803  		When("only one space is available to the user", func() {
   804  			var spaceName string
   805  
   806  			BeforeEach(func() {
   807  				spaceName = helpers.NewSpaceName()
   808  				session := helpers.CF("create-space", "-o", orgName, spaceName)
   809  				Eventually(session).Should(Exit(0))
   810  				roleSession := helpers.CF("set-space-role", username, orgName, spaceName, "SpaceManager")
   811  				Eventually(roleSession).Should(Exit(0))
   812  			})
   813  
   814  			It("logs the user in and targets the org and the space", func() {
   815  				session := helpers.CF("login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   816  				Eventually(session).Should(Exit(0))
   817  
   818  				targetSession := helpers.CF("target")
   819  				Eventually(targetSession).Should(Exit(0))
   820  				Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   821  				Eventually(targetSession).Should(Say(`space:\s+%s`, spaceName))
   822  			})
   823  
   824  			When("the -s flag is passed", func() {
   825  				It("targets the org and the space", func() {
   826  					session := helpers.CF("login", "-u", username, "-p", password, "-a", apiURL, "-s", spaceName, "--skip-ssl-validation")
   827  
   828  					Eventually(session).Should(Say(`Targeted org\s+%s`, orgName))
   829  					Eventually(session).Should(Say(`\n\nTargeted space\s+%s`, spaceName))
   830  
   831  					Eventually(session).Should(Say(`Org:\s+%s`, orgName))
   832  					Eventually(session).Should(Say(`Space:\s+%s`, spaceName))
   833  					Eventually(session).Should(Exit(0))
   834  
   835  					sessionOutput := string(session.Out.Contents())
   836  					Expect(sessionOutput).To(MatchRegexp(`\S\n\n\n\nAPI`))
   837  
   838  					targetSession := helpers.CF("target")
   839  					Eventually(targetSession).Should(Exit(0))
   840  					Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   841  					Eventually(targetSession).Should(Say(`space:\s+%s`, spaceName))
   842  				})
   843  			})
   844  		})
   845  
   846  		When("multiple spaces are available to the user", func() {
   847  			var (
   848  				spaceName  string
   849  				spaceName2 string
   850  			)
   851  
   852  			BeforeEach(func() {
   853  				spaceName = helpers.NewSpaceName()
   854  				session := helpers.CF("create-space", "-o", orgName, spaceName)
   855  				Eventually(session).Should(Exit(0))
   856  				roleSession := helpers.CF("set-space-role", username, orgName, spaceName, "SpaceManager")
   857  				Eventually(roleSession).Should(Exit(0))
   858  
   859  				spaceName2 = helpers.NewSpaceName()
   860  				session2 := helpers.CF("create-space", "-o", orgName, spaceName2)
   861  				Eventually(session2).Should(Exit(0))
   862  				roleSession2 := helpers.CF("set-space-role", username, orgName, spaceName2, "SpaceManager")
   863  				Eventually(roleSession2).Should(Exit(0))
   864  			})
   865  
   866  			When("the -s flag is passed", func() {
   867  				BeforeEach(func() {
   868  					orgName2 := helpers.NewOrgName()
   869  					session := helpers.CF("create-org", orgName2)
   870  					Eventually(session).Should(Exit(0))
   871  					session = helpers.CF("set-org-role", username, orgName2, "OrgManager")
   872  					Eventually(session).Should(Exit(0))
   873  				})
   874  
   875  				It("targets the org and the space", func() {
   876  					stdin := NewBuffer()
   877  					_, writeErr := stdin.Write([]byte(orgName + "\n"))
   878  					Expect(writeErr).ToNot(HaveOccurred())
   879  					session := helpers.CFWithStdin(stdin, "login", "-u", username, "-p", password, "-a", apiURL, "-s", spaceName, "--skip-ssl-validation")
   880  
   881  					Eventually(session).Should(Say(`Targeted org\s+%s`, orgName))
   882  					Eventually(session).Should(Say(`\n\nTargeted space\s+%s`, spaceName))
   883  
   884  					Eventually(session).Should(Say(`Org:\s+%s`, orgName))
   885  					Eventually(session).Should(Say(`Space:\s+%s`, spaceName))
   886  					Eventually(session).Should(Exit(0))
   887  
   888  					sessionOutput := string(session.Out.Contents())
   889  					Expect(sessionOutput).To(MatchRegexp(`\S\n\n\n\nAPI`))
   890  
   891  					targetSession := helpers.CF("target")
   892  					Eventually(targetSession).Should(Exit(0))
   893  					Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   894  					Eventually(targetSession).Should(Say(`space:\s+%s`, spaceName))
   895  				})
   896  
   897  				When("the space name is invalid", func() {
   898  					BeforeEach(func() {
   899  						spaceName = "invalid-space-name"
   900  					})
   901  
   902  					It("the command fails and displays an error message. It targets the org but not the space.", func() {
   903  						stdin := NewBuffer()
   904  						_, writeErr := stdin.Write([]byte(orgName + "\n"))
   905  						Expect(writeErr).ToNot(HaveOccurred())
   906  						session := helpers.CFWithStdin(stdin, "login", "-u", username, "-p", password, "-a", apiURL, "-s", spaceName, "--skip-ssl-validation")
   907  						Eventually(session).Should(Exit(1))
   908  						Eventually(session).Should(Say("FAILED"))
   909  						Eventually(session.Err).Should(Say("Space '%s' not found", spaceName))
   910  
   911  						targetSession := helpers.CF("target")
   912  						Eventually(targetSession).Should(Exit(0))
   913  						Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   914  						Eventually(targetSession).ShouldNot(Say(`space:\s+%s`, spaceName))
   915  						Eventually(targetSession).Should(Say("No space targeted, use 'cf target -s SPACE'"))
   916  					})
   917  				})
   918  			})
   919  
   920  			When("the -s flag is not passed", func() {
   921  				It("prompts the user to pick their space by list position", func() {
   922  					input := NewBuffer()
   923  					_, err := input.Write([]byte("1\n"))
   924  					Expect(err).ToNot(HaveOccurred())
   925  
   926  					session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   927  					Eventually(session).Should(Exit(0))
   928  
   929  					re := regexp.MustCompile("1\\. (?P<SpaceName>.*)\n")
   930  					submatches := re.FindStringSubmatch(string(session.Out.Contents()))
   931  					Expect(submatches).ToNot(BeEmpty(), "missing numbered space list")
   932  					expectedSpaceName := submatches[1]
   933  
   934  					targetSession := helpers.CF("target")
   935  					Eventually(targetSession).Should(Exit(0))
   936  					Eventually(targetSession).Should(Say(`space:\s+%s`, expectedSpaceName))
   937  				})
   938  
   939  				It("reprompts the user if an invalid number is entered", func() {
   940  					input := NewBuffer()
   941  					_, err := input.Write([]byte("4\n"))
   942  					Expect(err).ToNot(HaveOccurred())
   943  
   944  					session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   945  					Eventually(session).Should(Say(regexp.QuoteMeta("Space (enter to skip):")))
   946  					Eventually(session).Should(Say(regexp.QuoteMeta("Space (enter to skip):")))
   947  					session.Interrupt()
   948  					Eventually(session).Should(Exit())
   949  				})
   950  
   951  				It("prompts the user to pick their space by name", func() {
   952  					input := NewBuffer()
   953  					_, err := input.Write([]byte(spaceName + "\n"))
   954  					Expect(err).ToNot(HaveOccurred())
   955  
   956  					session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   957  					Eventually(session).Should(Exit(0))
   958  
   959  					targetSession := helpers.CF("target")
   960  					Eventually(targetSession).Should(Exit(0))
   961  					Eventually(targetSession).Should(Say(`space:\s+%s`, spaceName))
   962  				})
   963  
   964  				It("allows the user to skip picking a space", func() {
   965  					input := NewBuffer()
   966  					_, err := input.Write([]byte("\n"))
   967  					Expect(err).ToNot(HaveOccurred())
   968  
   969  					session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   970  					Eventually(session).Should(Exit(0))
   971  
   972  					targetSession := helpers.CF("target")
   973  					Eventually(targetSession).Should(Exit(0))
   974  					Eventually(targetSession).Should(Say(`No space targeted, use 'cf target -s SPACE'`))
   975  				})
   976  
   977  				When("the input space name is invalid", func() {
   978  					BeforeEach(func() {
   979  						spaceName = "invalid-space-name"
   980  					})
   981  
   982  					It("the command fails and displays an error message. It does not target the space.", func() {
   983  						input := NewBuffer()
   984  						_, err := input.Write([]byte(spaceName + "\n"))
   985  						Expect(err).ToNot(HaveOccurred())
   986  
   987  						session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "-a", apiURL, "--skip-ssl-validation")
   988  						Eventually(session).Should(Exit(1))
   989  						Eventually(session).Should(Say("FAILED"))
   990  						Eventually(session.Err).Should(Say("Space '%s' not found", spaceName))
   991  
   992  						targetSession := helpers.CF("target")
   993  						Eventually(targetSession).Should(Exit(0))
   994  						Eventually(targetSession).Should(Say(`org:\s+%s`, orgName))
   995  						Eventually(targetSession).ShouldNot(Say(`space:\s+%s`, spaceName))
   996  						Eventually(targetSession).Should(Say("No space targeted, use 'cf target -s SPACE'"))
   997  					})
   998  				})
   999  
  1000  				When("there are more than 50 spaces", func() {
  1001  					var server *ghttp.Server
  1002  					BeforeEach(func() {
  1003  						server = helpers.StartAndTargetMockServerWithAPIVersions(helpers.DefaultV2Version, helpers.DefaultV3Version)
  1004  						helpers.AddLoginRoutes(server)
  1005  						helpers.AddFiftyOneSpaces(server)
  1006  					})
  1007  
  1008  					AfterEach(func() {
  1009  						server.Close()
  1010  					})
  1011  
  1012  					It("displays a message and prompts the user for the space name", func() {
  1013  						input := NewBuffer()
  1014  						_, wErr := input.Write([]byte(fmt.Sprintf("%s\n", "test-space-1")))
  1015  						Expect(wErr).ToNot(HaveOccurred())
  1016  
  1017  						session := helpers.CFWithStdin(input, "login", "-u", username, "-p", password, "--skip-ssl-validation")
  1018  
  1019  						Eventually(session).Should(Say("Select a space:"))
  1020  						Eventually(session).Should(Say("There are too many options to display; please type in the name."))
  1021  						Eventually(session).Should(Say("\n\n"))
  1022  						Eventually(session).Should(Say(regexp.QuoteMeta(`Space (enter to skip):`)))
  1023  						Eventually(session).Should(Say("Targeted space test-space-1"))
  1024  
  1025  						Eventually(session).Should(Exit(0))
  1026  					})
  1027  				})
  1028  			})
  1029  		})
  1030  
  1031  	})
  1032  
  1033  	Describe("Full interactive happy path", func() {
  1034  		var (
  1035  			orgName1   string
  1036  			orgName2   string
  1037  			spaceName1 string
  1038  			spaceName2 string
  1039  			username   string
  1040  			password   string
  1041  		)
  1042  
  1043  		BeforeEach(func() {
  1044  			helpers.LoginCF()
  1045  			orgName1 = helpers.NewOrgName()
  1046  			orgName2 = helpers.NewOrgName()
  1047  			spaceName1 = helpers.NewSpaceName()
  1048  			spaceName2 = helpers.NewSpaceName()
  1049  
  1050  			Eventually(helpers.CF("create-org", orgName1)).Should(Exit(0))
  1051  			Eventually(helpers.CF("create-org", orgName2)).Should(Exit(0))
  1052  			Eventually(helpers.CF("create-space", "-o", orgName1, spaceName1)).Should(Exit(0))
  1053  			Eventually(helpers.CF("create-space", "-o", orgName1, spaceName2)).Should(Exit(0))
  1054  
  1055  			username, password = helpers.CreateUser()
  1056  			Eventually(helpers.CF("set-org-role", username, orgName1, "OrgManager")).Should(Exit(0))
  1057  			Eventually(helpers.CF("set-org-role", username, orgName2, "OrgManager")).Should(Exit(0))
  1058  			Eventually(helpers.CF("set-space-role", username, orgName1, spaceName1, "SpaceManager")).Should(Exit(0))
  1059  			Eventually(helpers.CF("set-space-role", username, orgName1, spaceName2, "SpaceManager")).Should(Exit(0))
  1060  		})
  1061  
  1062  		When("there are multiple orgs and spaces available to a user", func() {
  1063  			It("prompts for username, password, org, and space. Then logs in and targets correctly", func() {
  1064  				buffer := NewBuffer()
  1065  				_, err := buffer.Write([]byte(fmt.Sprintf("%s\n%s\n%s\n%s\n", username, password, orgName1, spaceName2)))
  1066  				Expect(err).ToNot(HaveOccurred())
  1067  
  1068  				session := helpers.CFWithStdin(buffer, "login")
  1069  				Eventually(session).Should(Say("Email:"))
  1070  				Eventually(session).Should(Say("\n\n"))
  1071  				Eventually(session).Should(Say("Password:"))
  1072  				Eventually(session).Should(Say("OK"))
  1073  				Eventually(session).Should(Say("\n\n"))
  1074  				Eventually(session).Should(Say("Select an org:"))
  1075  				Eventually(session).Should(Say("\n\n"))
  1076  				Eventually(session).Should(Say(regexp.QuoteMeta(`Org (enter to skip):`)))
  1077  				Eventually(session).Should(Say(fmt.Sprintf("Targeted org %s", orgName1)))
  1078  				Eventually(session).Should(Say("\n\n"))
  1079  				Eventually(session).Should(Say("Select a space:"))
  1080  				Eventually(session).Should(Say("\n\n"))
  1081  				Eventually(session).Should(Say(regexp.QuoteMeta(`Space (enter to skip):`)))
  1082  				Eventually(session).Should(Say(fmt.Sprintf("Targeted space %s", spaceName2)))
  1083  				Eventually(session).Should(Say("\n\n"))
  1084  				Eventually(session).Should(Say(`Org:\s+%s`, orgName1))
  1085  				Eventually(session).Should(Say(`Space:\s+%s`, spaceName2))
  1086  				Eventually(session).Should(Exit(0))
  1087  
  1088  				targetSession := helpers.CF("target")
  1089  				Eventually(targetSession).Should(Exit(0))
  1090  				Eventually(targetSession).Should(Say(`org:\s+%s`, orgName1))
  1091  				Eventually(targetSession).Should(Say(`space:\s+%s`, spaceName2))
  1092  			})
  1093  		})
  1094  	})
  1095  
  1096  	Describe("User Credentials", func() {
  1097  		It("prompts the user for email and password", func() {
  1098  			username, password := helpers.GetCredentials()
  1099  			buffer := NewBuffer()
  1100  			_, err := buffer.Write([]byte(fmt.Sprintf("%s\n%s\n", username, password)))
  1101  			Expect(err).ToNot(HaveOccurred())
  1102  			session := helpers.CFWithStdin(buffer, "login")
  1103  			Eventually(session).Should(Say("Email:"))
  1104  			Eventually(session).Should(Say("\n\n"))
  1105  			Eventually(session).Should(Say("Password:"))
  1106  			Eventually(session).Should(Say("\n\n"))
  1107  			Eventually(session).Should(Exit(0))
  1108  		})
  1109  
  1110  		When("the user's account has been locked due to too many failed attempts", func() {
  1111  			var username string
  1112  
  1113  			BeforeEach(func() {
  1114  				helpers.LoginCF()
  1115  				username, _ = helpers.CreateUser()
  1116  				helpers.LogoutCF()
  1117  			})
  1118  
  1119  			It("displays a helpful error and does not reprompt", func() {
  1120  				input := NewBuffer()
  1121  				_, err := input.Write([]byte("garbage\ngarbage\ngarbage\n"))
  1122  				Expect(err).ToNot(HaveOccurred())
  1123  				session := helpers.CFWithStdin(input, "login", "-u", username)
  1124  				Eventually(session).Should(Exit(1))
  1125  
  1126  				input = NewBuffer()
  1127  				_, err = input.Write([]byte("garbage\ngarbage\ngarbage\n"))
  1128  				Expect(err).ToNot(HaveOccurred())
  1129  				session = helpers.CFWithStdin(input, "login", "-u", username)
  1130  				Eventually(session).Should(Exit(1))
  1131  
  1132  				input = NewBuffer()
  1133  				_, err = input.Write([]byte("garbage\ngarbage\ngarbage\n"))
  1134  				Expect(err).NotTo(HaveOccurred())
  1135  				session = helpers.CFWithStdin(input, "login", "-u", username)
  1136  				Eventually(session).Should(Say(`Password`))
  1137  				Eventually(session.Err).Should(Say(`Your account has been locked because of too many failed attempts to login\.`))
  1138  				Consistently(session).ShouldNot(Say(`Password`))
  1139  				Eventually(session.Err).Should(Say(`Unable to authenticate.`))
  1140  				Eventually(session).Should(Say("FAILED"))
  1141  				Eventually(session).Should(Exit(1))
  1142  			})
  1143  		})
  1144  
  1145  		When("the -u flag is provided", func() {
  1146  			It("prompts the user for their password", func() {
  1147  				username, password := helpers.GetCredentials()
  1148  				buffer := NewBuffer()
  1149  				_, err := buffer.Write([]byte(fmt.Sprintf("%s\n", password)))
  1150  				Expect(err).ToNot(HaveOccurred())
  1151  				session := helpers.CFWithStdin(buffer, "login", "-u", username)
  1152  				Eventually(session).Should(Say("Password: "))
  1153  				Eventually(session).Should(Exit(0))
  1154  			})
  1155  		})
  1156  
  1157  		When("the user provides the -p flag", func() {
  1158  			It("prompts the user for their email and logs in successfully", func() {
  1159  				username, password := helpers.GetCredentials()
  1160  				input := NewBuffer()
  1161  				_, err := input.Write([]byte(username + "\n"))
  1162  				Expect(err).ToNot(HaveOccurred())
  1163  				session := helpers.CFWithStdin(input, "login", "-p", password)
  1164  				Eventually(session).Should(Say("Email: "))
  1165  				Eventually(session).Should(Exit(0))
  1166  			})
  1167  
  1168  			When("the password flag is given incorrectly", func() {
  1169  				It("Prompts the user two more times before exiting with an error", func() {
  1170  					username, _ := helpers.GetCredentials()
  1171  					input := NewBuffer()
  1172  					_, err := input.Write([]byte(username + "\n" + "bad-password\n" + "bad-password\n"))
  1173  					Expect(err).ToNot(HaveOccurred())
  1174  					session := helpers.CFWithStdin(input, "login", "-p", "bad-password")
  1175  					Eventually(session).Should(Say("Email: "))
  1176  					Eventually(session.Err).Should(Say("Credentials were rejected, please try again."))
  1177  					Eventually(session).Should(Say("Password: "))
  1178  					Eventually(session.Err).Should(Say("Credentials were rejected, please try again."))
  1179  					Eventually(session).Should(Say("Not logged in. Use 'cf login' or 'cf login --sso' to log in."))
  1180  					Eventually(session).Should(Say("FAILED"))
  1181  					Eventually(session.Err).Should(Say("Unable to authenticate."))
  1182  					Eventually(session).Should(Exit(1))
  1183  				})
  1184  			})
  1185  		})
  1186  
  1187  		When("multiple interactive prompts are used", func() {
  1188  			var (
  1189  				orgName  string
  1190  				orgName2 string
  1191  				username string
  1192  				password string
  1193  			)
  1194  
  1195  			BeforeEach(func() {
  1196  				helpers.LoginCF()
  1197  				orgName = helpers.NewOrgName()
  1198  				session := helpers.CF("create-org", orgName)
  1199  				Eventually(session).Should(Exit(0))
  1200  				username, password = helpers.CreateUserInOrgRole(orgName, "OrgManager")
  1201  
  1202  				orgName2 = helpers.NewOrgName()
  1203  				Eventually(helpers.CF("create-org", orgName2)).Should(Exit(0))
  1204  				setOrgRoleSession := helpers.CF("set-org-role", username, orgName2, "OrgManager")
  1205  				Eventually(setOrgRoleSession).Should(Exit(0))
  1206  			})
  1207  
  1208  			It("should accept each value", func() {
  1209  				input := NewBuffer()
  1210  				_, err := input.Write([]byte(username + "\n" + password + "\n" + orgName + "\n"))
  1211  				Expect(err).ToNot(HaveOccurred())
  1212  				session := helpers.CFWithStdin(input, "login")
  1213  				Eventually(session).Should(Exit(0))
  1214  			})
  1215  
  1216  			When("MFA is enabled", func() {
  1217  				var (
  1218  					password string
  1219  					mfaCode  string
  1220  					server   *ghttp.Server
  1221  				)
  1222  
  1223  				BeforeEach(func() {
  1224  					password = "some-password"
  1225  					mfaCode = "123456"
  1226  					server = helpers.StartAndTargetMockServerWithAPIVersions(helpers.DefaultV2Version, helpers.DefaultV3Version)
  1227  					helpers.AddMfa(server, password, mfaCode)
  1228  				})
  1229  
  1230  				AfterEach(func() {
  1231  					server.Close()
  1232  				})
  1233  
  1234  				When("correct MFA code and credentials are provided", func() {
  1235  					BeforeEach(func() {
  1236  						fakeTokenResponse := map[string]string{
  1237  							"access_token": "",
  1238  							"token_type":   "bearer",
  1239  						}
  1240  						server.RouteToHandler(http.MethodPost, "/oauth/token",
  1241  							ghttp.RespondWithJSONEncoded(http.StatusOK, fakeTokenResponse))
  1242  						server.RouteToHandler(http.MethodGet, "/v3/organizations",
  1243  							ghttp.RespondWith(http.StatusOK, `{
  1244  							 "total_results": 0,
  1245  							 "total_pages": 1,
  1246  							 "resources": []}`))
  1247  					})
  1248  
  1249  					It("logs in the user", func() {
  1250  						input := NewBuffer()
  1251  						_, err := input.Write([]byte(username + "\n" + password + "\n" + mfaCode + "\n"))
  1252  						Expect(err).ToNot(HaveOccurred())
  1253  						session := helpers.CFWithStdin(input, "login")
  1254  						Eventually(session).Should(Say("Email: "))
  1255  						Eventually(session).Should(Say("\n\n"))
  1256  						Eventually(session).Should(Say("Password:"))
  1257  						Eventually(session).Should(Say("\n\n"))
  1258  						Eventually(session).Should(Say("MFA Code \\( Register at %[1]s \\)", server.URL()))
  1259  						Eventually(session).Should(Exit(0))
  1260  					})
  1261  				})
  1262  
  1263  				When("incorrect MFA code and credentials are provided", func() {
  1264  					It("fails", func() {
  1265  						input := NewBuffer()
  1266  						wrongMfaCode := mfaCode + "foo"
  1267  						_, err := input.Write([]byte(username + "\n" + password + "\n" + wrongMfaCode + "\n" + password + "\n" + wrongMfaCode + "\n"))
  1268  						Expect(err).ToNot(HaveOccurred())
  1269  						session := helpers.CFWithStdin(input, "login")
  1270  						Eventually(session).Should(Say("Password: "))
  1271  						Eventually(session).Should(Say("MFA Code \\( Register at %[1]s \\)", server.URL()))
  1272  						Eventually(session).Should(Say("Password: "))
  1273  						Eventually(session).Should(Say("MFA Code \\( Register at %[1]s \\)", server.URL()))
  1274  						Eventually(session).Should(Say("Not logged in. Use 'cf login' or 'cf login --sso' to log in."))
  1275  						Eventually(session).Should(Say("FAILED"))
  1276  						Eventually(session.Err).Should(Say("Unable to authenticate."))
  1277  
  1278  						Eventually(session).Should(Exit(1))
  1279  					})
  1280  				})
  1281  			})
  1282  		})
  1283  
  1284  		When("the user provides the -p and -u flags", func() {
  1285  			Context("and the credentials are correct", func() {
  1286  				It("logs in successfully", func() {
  1287  					username, password := helpers.GetCredentials()
  1288  					session := helpers.CF("login", "-p", password, "-u", username)
  1289  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
  1290  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
  1291  					Eventually(session).Should(Say(`OK`))
  1292  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
  1293  					Eventually(session).Should(Say("User:\\s+" + username))
  1294  					Eventually(session).Should(Exit(0))
  1295  				})
  1296  			})
  1297  
  1298  			Context("and the credentials are incorrect", func() {
  1299  				It("prompts twice, displays an error and fails", func() {
  1300  					input := NewBuffer()
  1301  					_, err := input.Write([]byte("garbage\ngarbage\n"))
  1302  					Expect(err).ToNot(HaveOccurred())
  1303  					session := helpers.CFWithStdin(input, "login", "-p", "nope", "-u", "faker")
  1304  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
  1305  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
  1306  					Eventually(session.Err).Should(Say(`Credentials were rejected, please try again.`))
  1307  					Eventually(session).Should(Say(`Password:`))
  1308  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
  1309  					Eventually(session.Err).Should(Say(`Credentials were rejected, please try again.`))
  1310  					Eventually(session).Should(Say(`Password:`))
  1311  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
  1312  					Eventually(session.Err).Should(Say(`Credentials were rejected, please try again.`))
  1313  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
  1314  					Eventually(session).Should(Say(`Not logged in. Use 'cf login' or 'cf login --sso' to log in.`))
  1315  					Eventually(session.Err).Should(Say(`Unable to authenticate.`))
  1316  					Eventually(session).Should(Say(`FAILED`))
  1317  
  1318  					Eventually(session).Should(Exit(1))
  1319  				})
  1320  
  1321  				Context("and the user was previously logged in", func() {
  1322  					BeforeEach(func() {
  1323  						helpers.LoginCF()
  1324  					})
  1325  
  1326  					It("logs them out", func() {
  1327  						session := helpers.CF("login", "-p", "nope", "-u", "faker")
  1328  						Eventually(session).Should(Say(`Not logged in. Use 'cf login' or 'cf login --sso' to log in.`))
  1329  						Eventually(session).Should(Exit())
  1330  
  1331  						orgsSession := helpers.CF("orgs")
  1332  						Eventually(orgsSession.Err).Should(Say(`Not logged in. Use 'cf login' or 'cf login --sso' to log in.`))
  1333  						Eventually(orgsSession).Should(Exit())
  1334  					})
  1335  				})
  1336  			})
  1337  
  1338  			When("already logged in with client credentials", func() {
  1339  				BeforeEach(func() {
  1340  					clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet()
  1341  					session := helpers.CF("auth", clientID, clientSecret, "--client-credentials")
  1342  					Eventually(session).Should(Exit(0))
  1343  				})
  1344  
  1345  				It("should fail log in and display an error informing the user they need to log out", func() {
  1346  					username, password := helpers.GetCredentials()
  1347  					session := helpers.CF("login", "-p", password, "-u", username)
  1348  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
  1349  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
  1350  					Eventually(session).Should(Say("FAILED"))
  1351  					Eventually(session.Err).Should(Say("Service account currently logged in. Use 'cf logout' to log out service account and try again."))
  1352  					Eventually(session).Should(Exit(1))
  1353  				})
  1354  			})
  1355  		})
  1356  	})
  1357  
  1358  	Describe("Authenticating as a user, through a custom client", func() {
  1359  		var (
  1360  			accessTokenExpiration time.Duration
  1361  			username              string
  1362  			password              string
  1363  			customClientID        string
  1364  			customClientSecret    string
  1365  		)
  1366  
  1367  		BeforeEach(func() {
  1368  			customClientID, customClientSecret = helpers.SkipIfCustomClientCredentialsNotSet()
  1369  
  1370  			helpers.LoginCF()
  1371  			username, password = helpers.CreateUser()
  1372  
  1373  			helpers.SetConfig(func(config *configv3.Config) {
  1374  				config.ConfigFile.UAAOAuthClient = customClientID
  1375  				config.ConfigFile.UAAOAuthClientSecret = customClientSecret
  1376  				config.ConfigFile.UAAGrantType = ""
  1377  			})
  1378  
  1379  			session := helpers.CF("login", "-u", username, "-p", password)
  1380  			Eventually(session).Should(Exit(0))
  1381  		})
  1382  
  1383  		It("gets a token whose settings match those of the custom client", func() {
  1384  			accessTokenExpiration = 120 // this was configured in the pipeline
  1385  
  1386  			config := helpers.GetConfig()
  1387  
  1388  			jwt := helpers.ParseTokenString(config.ConfigFile.AccessToken)
  1389  			expires, expIsSet := jwt.Claims().Expiration()
  1390  			Expect(expIsSet).To(BeTrue())
  1391  
  1392  			iat, iatIsSet := jwt.Claims().IssuedAt()
  1393  
  1394  			Expect(iatIsSet).To(BeTrue())
  1395  			Expect(expires.Sub(iat)).To(Equal(accessTokenExpiration * time.Second))
  1396  		})
  1397  
  1398  		It("warns the user that this configuration is deprecated", func() {
  1399  			deprecationMessage := "Deprecation warning: Manually writing your client credentials to the config.json is deprecated and will be removed in the future. For similar functionality, please use the `cf auth --client-credentials` command instead."
  1400  
  1401  			session := helpers.CF("login", "-u", username, "-p", password)
  1402  			Eventually(session.Err).Should(Say(deprecationMessage))
  1403  			Eventually(session).Should(Exit(0))
  1404  		})
  1405  
  1406  		When("the token has expired", func() {
  1407  			BeforeEach(func() {
  1408  				helpers.SetConfig(func(config *configv3.Config) {
  1409  					config.ConfigFile.AccessToken = helpers.ExpiredAccessToken()
  1410  				})
  1411  			})
  1412  
  1413  			It("re-authenticates using the custom client", func() {
  1414  				session := helpers.CF("orgs")
  1415  				Eventually(session).Should(Exit(0))
  1416  			})
  1417  		})
  1418  	})
  1419  
  1420  	Describe("Setting the identity provider to be used", func() {
  1421  		When("the user provides the --origin flag", func() {
  1422  			It("logs in successfully", func() {
  1423  				username, password := helpers.GetCredentials()
  1424  				session := helpers.CF("login", "-u", username, "-p", password, "--origin", "uaa")
  1425  				Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
  1426  				Eventually(session).Should(Say(`Authenticating\.\.\.`))
  1427  				Eventually(session).Should(Say(`OK`))
  1428  				Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
  1429  				Eventually(session).Should(Say("User:\\s+" + username))
  1430  				Eventually(session).Should(Exit(0))
  1431  			})
  1432  		})
  1433  
  1434  		When("the user provides the --origin flag and --sso flag", func() {
  1435  			It("rejects the command", func() {
  1436  				username, password := helpers.GetCredentials()
  1437  				session := helpers.CF("login", "-u", username, "-p", password, "--sso", "--origin", "uaa")
  1438  
  1439  				Eventually(session.Err).Should(Say(`Incorrect Usage: The following arguments cannot be used together: --sso, --origin`))
  1440  
  1441  				Eventually(session).Should(Exit(1))
  1442  			})
  1443  		})
  1444  		When("the user provides the --origin flag and --sso-passcode flag", func() {
  1445  			It("rejects the command", func() {
  1446  				username, password := helpers.GetCredentials()
  1447  				session := helpers.CF("login", "-u", username, "-p", password, "--sso-passcode", "some-passcode", "--origin", "uaa")
  1448  
  1449  				Eventually(session.Err).Should(Say(`Incorrect Usage: The following arguments cannot be used together: --sso-passcode, --origin`))
  1450  
  1451  				Eventually(session).Should(Exit(1))
  1452  			})
  1453  		})
  1454  	})
  1455  })