github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/integration/shared/isolated/login_command_test.go (about)

     1  package isolated
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"regexp"
     8  	"runtime"
     9  
    10  	"code.cloudfoundry.org/cli/integration/helpers"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	. "github.com/onsi/gomega/gbytes"
    14  	. "github.com/onsi/gomega/gexec"
    15  	"github.com/onsi/gomega/ghttp"
    16  )
    17  
    18  var _ = Describe("login command", func() {
    19  	Describe("Help Text", func() {
    20  		When("--help flag is set", func() {
    21  			It("displays the command usage", func() {
    22  				session := helpers.CF("login", "--help")
    23  				Eventually(session).Should(Exit(0))
    24  
    25  				Expect(session).Should(Say("NAME:\n"))
    26  				Expect(session).Should(Say("login - Log user in"))
    27  
    28  				Expect(session).Should(Say("USAGE:\n"))
    29  				Expect(session).Should(Say(`cf login \[-a API_URL\] \[-u USERNAME\] \[-p PASSWORD\] \[-o ORG\] \[-s SPACE\] \[--sso | --sso-passcode PASSCODE\]`))
    30  
    31  				Expect(session).Should(Say("WARNING:\n"))
    32  				Expect(session).Should(Say("Providing your password as a command line option is highly discouraged\n"))
    33  				Expect(session).Should(Say("Your password may be visible to others and may be recorded in your shell history\n"))
    34  
    35  				Expect(session).Should(Say("EXAMPLES:\n"))
    36  				Expect(session).Should(Say(regexp.QuoteMeta("cf login (omit username and password to login interactively -- cf will prompt for both)")))
    37  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p pa55woRD (specify username and password as arguments)")))
    38  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p \"my password\" (use quotes for passwords with a space)")))
    39  				Expect(session).Should(Say(regexp.QuoteMeta("cf login -u name@example.com -p \"\\\"password\\\"\" (escape quotes if used in password)")))
    40  				Expect(session).Should(Say(regexp.QuoteMeta("cf login --sso (cf will provide a url to obtain a one-time passcode to login)")))
    41  
    42  				Expect(session).Should(Say("ALIAS:\n"))
    43  				Expect(session).Should(Say("l"))
    44  
    45  				Expect(session).Should(Say("OPTIONS:\n"))
    46  				Expect(session).Should(Say(`-a\s+API endpoint \(e.g. https://api\.example\.com\)`))
    47  				Expect(session).Should(Say(`-o\s+Org`))
    48  				Expect(session).Should(Say(`-p\s+Password`))
    49  				Expect(session).Should(Say(`-s\s+Space`))
    50  				Expect(session).Should(Say(`--skip-ssl-validation\s+Skip verification of the API endpoint\. Not recommended\!`))
    51  				Expect(session).Should(Say(`--sso\s+Prompt for a one-time passcode to login`))
    52  				Expect(session).Should(Say(`--sso-passcode\s+One-time passcode`))
    53  				Expect(session).Should(Say(`-u\s+Username`))
    54  
    55  				Expect(session).Should(Say("SEE ALSO:\n"))
    56  				Expect(session).Should(Say("api, auth, target"))
    57  			})
    58  		})
    59  	})
    60  
    61  	Describe("Minimum Version Check", func() {
    62  		When("the api version is less than the minimum supported version", func() {
    63  			var server *ghttp.Server
    64  
    65  			BeforeEach(func() {
    66  				server = helpers.StartServerWithAPIVersions("1.0.0", "")
    67  				server.RouteToHandler(http.MethodPost, "/oauth/token",
    68  					ghttp.RespondWithJSONEncoded(http.StatusOK, struct{}{}))
    69  				server.RouteToHandler(http.MethodGet, "/v2/organizations",
    70  					ghttp.RespondWith(http.StatusOK, `{
    71  					 "total_results": 0,
    72  					 "total_pages": 1,
    73  					 "resources": []}`))
    74  			})
    75  
    76  			AfterEach(func() {
    77  				server.Close()
    78  			})
    79  
    80  			It("displays the warning and exits successfully", func() {
    81  				session := helpers.CF("login", "-a", server.URL(), "--skip-ssl-validation")
    82  				Eventually(session).Should(Say("Your API version is no longer supported. Upgrade to a newer version of the API."))
    83  				Eventually(session).Should(Exit(0))
    84  			})
    85  		})
    86  
    87  		When("the CLI version is lower than the minimum supported version by the CC", func() {
    88  			var server *ghttp.Server
    89  
    90  			BeforeEach(func() {
    91  				server = helpers.StartServerWithMinimumCLIVersion("9000.0.0")
    92  				server.RouteToHandler(http.MethodPost, "/oauth/token",
    93  					ghttp.RespondWithJSONEncoded(http.StatusOK, struct{}{}))
    94  				server.RouteToHandler(http.MethodGet, "/v2/organizations",
    95  					ghttp.RespondWith(http.StatusOK, `{
    96  					 "total_results": 0,
    97  					 "total_pages": 1,
    98  					 "resources": []}`))
    99  			})
   100  
   101  			AfterEach(func() {
   102  				server.Close()
   103  			})
   104  
   105  			It("displays the warning and exits successfully", func() {
   106  				session := helpers.CF("login", "-a", server.URL(), "--skip-ssl-validation")
   107  				Eventually(session).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`))
   108  				Eventually(session).Should(Exit(0))
   109  			})
   110  		})
   111  	})
   112  
   113  	Describe("API Endpoint", func() {
   114  		BeforeEach(func() {
   115  			helpers.TurnOnExperimental()
   116  		})
   117  
   118  		AfterEach(func() {
   119  			helpers.TurnOffExperimental()
   120  		})
   121  
   122  		When("the API endpoint is not set", func() {
   123  			BeforeEach(func() {
   124  				helpers.UnsetAPI()
   125  			})
   126  
   127  			When("the user does not provide the -a flag", func() {
   128  				It("prompts the user for an endpoint", func() {
   129  					input := NewBuffer()
   130  					input.Write([]byte("\n"))
   131  					session := helpers.CFWithStdin(input, "login")
   132  					Eventually(session).Should(Say("API endpoint:"))
   133  					session.Interrupt()
   134  					Eventually(session).Should(Exit())
   135  				})
   136  
   137  				When("the API endpoint provided at the prompt is unreachable", func() {
   138  					It("returns an error", func() {
   139  						input := NewBuffer()
   140  						input.Write([]byte("does.not.exist\n"))
   141  						session := helpers.CFWithStdin(input, "login")
   142  						Eventually(session).Should(Say("API endpoint:"))
   143  						Eventually(session).Should(Say("FAILED"))
   144  						Eventually(session.Err).Should(Say("Request error: "))
   145  						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."))
   146  						Eventually(session).Should(Exit(1))
   147  					})
   148  				})
   149  			})
   150  
   151  			When("the user provides the -a flag", func() {
   152  				It("sets the API endpoint and does not prompt the user for the API endpoint", func() {
   153  					session := helpers.CF("login", "-a", apiURL, "--skip-ssl-validation")
   154  					Eventually(session).Should(Say("API endpoint: %s", apiURL))
   155  					// TODO This currently because we dont have the user/password prompt implemented. Uncomment this line after we implement the other prompts
   156  					// Consistently(session).ShouldNot(Say("API endpoint:"))
   157  					// session.Interrupt()
   158  					Eventually(session).Should(Exit())
   159  
   160  					session = helpers.CF("api")
   161  					Eventually(session).Should(Exit(0))
   162  					Expect(session).Should(Say("api endpoint:   %s", apiURL))
   163  				})
   164  
   165  				When("the provided API endpoint is unreachable", func() {
   166  					It("displays an error and fails", func() {
   167  						session := helpers.CF("login", "-a", "does.not.exist", "--skip-ssl-validation")
   168  						Eventually(session).Should(Say("API endpoint: does.not.exist"))
   169  						Eventually(session).Should(Say("FAILED"))
   170  						Eventually(session.Err).Should(Say("Request error: "))
   171  						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."))
   172  						Eventually(session).Should(Exit(1))
   173  					})
   174  				})
   175  			})
   176  		})
   177  
   178  		When("the API endpoint is already set", func() {
   179  			It("does not prompt the user for API endpoint", func() {
   180  				session := helpers.CF("login")
   181  				Consistently(session).ShouldNot(Say("API endpoint>"))
   182  				session.Interrupt()
   183  				Eventually(session).Should(Exit())
   184  			})
   185  
   186  			When("the user provides a new API endpoint with the -a flag", func() {
   187  				When("the provided API endpoint is unreachable", func() {
   188  					It("displays an error and does not change the API endpoint", func() {
   189  						session := helpers.CF("login", "-a", "does.not.exist", "--skip-ssl-validation")
   190  						Eventually(session).Should(Say("API endpoint: does.not.exist"))
   191  						Eventually(session).Should(Say("FAILED"))
   192  						Eventually(session.Err).Should(Say("Request error: "))
   193  						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."))
   194  						Eventually(session).Should(Exit(1))
   195  
   196  						apiSession := helpers.CF("api")
   197  						Eventually(apiSession).Should(Exit(0))
   198  						Eventually(apiSession).Should(Say("api endpoint:   %s", apiURL))
   199  					})
   200  				})
   201  			})
   202  		})
   203  	})
   204  
   205  	Describe("SSL Validation", func() {
   206  		When("no scheme is included in the API endpoint", func() {
   207  			var (
   208  				hostname  string
   209  				serverURL *url.URL
   210  				err       error
   211  			)
   212  
   213  			BeforeEach(func() {
   214  				serverURL, err = url.Parse(helpers.GetAPI())
   215  				Expect(err).NotTo(HaveOccurred())
   216  
   217  				hostname = serverURL.Hostname()
   218  			})
   219  
   220  			It("defaults to https", func() {
   221  				username, password := helpers.GetCredentials()
   222  				session := helpers.CF("login", "-u", username, "-p", password, "-a", hostname, "--skip-ssl-validation")
   223  
   224  				Eventually(session).Should(Say("API endpoint: %s", hostname))
   225  				Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   226  				Eventually(session).Should(Exit(0))
   227  			})
   228  		})
   229  
   230  		When("the API endpoint's scheme is http", func() {
   231  			var httpURL string
   232  
   233  			BeforeEach(func() {
   234  				apiURL, err := url.Parse(helpers.GetAPI())
   235  				Expect(err).NotTo(HaveOccurred())
   236  				apiURL.Scheme = "http"
   237  
   238  				httpURL = apiURL.String()
   239  			})
   240  
   241  			It("shows a warning to the user", func() {
   242  				username, password := helpers.GetCredentials()
   243  				session := helpers.CF("login", "-u", username, "-p", password, "-a", httpURL, "--skip-ssl-validation")
   244  
   245  				Eventually(session).Should(Say("API endpoint: %s", httpURL))
   246  				Eventually(session).Should(Say("Warning: Insecure http API endpoint detected: secure https API endpoints are recommended"))
   247  				Eventually(session).Should(Exit(0))
   248  			})
   249  		})
   250  
   251  		When("the OS provides a valid SSL Certificate (Unix: SSL_CERT_FILE or SSL_CERT_DIR Environment variables) (Windows: Import-Certificate call)", func() {
   252  			BeforeEach(func() {
   253  				if skipSSLValidation != "" {
   254  					Skip("SKIP_SSL_VALIDATION is enabled and is " + skipSSLValidation)
   255  				}
   256  			})
   257  
   258  			It("trusts the cert and allows the users to log in", func() {
   259  				username, password := helpers.GetCredentials()
   260  				session := helpers.CF("login", "-u", username, "-p", password, "-a", helpers.GetAPI())
   261  				Eventually(session).Should(Say("API endpoint: %s", apiURL))
   262  				Eventually(session).Should(Exit())
   263  
   264  				session = helpers.CF("api")
   265  				Eventually(session).Should(Exit(0))
   266  				Expect(session).Should(Say("api endpoint:   %s", apiURL))
   267  			})
   268  		})
   269  
   270  		When("the SSL Certificate is invalid", func() {
   271  			var (
   272  				server    *ghttp.Server
   273  				serverURL *url.URL
   274  				err       error
   275  			)
   276  
   277  			BeforeEach(func() {
   278  				cliVersion := "1.0.0"
   279  				server = helpers.StartServerWithMinimumCLIVersion(cliVersion)
   280  				serverURL, err = url.Parse(server.URL())
   281  				Expect(err).NotTo(HaveOccurred())
   282  			})
   283  
   284  			AfterEach(func() {
   285  				server.Close()
   286  			})
   287  
   288  			It("displays a helpful error message and exits 1", func() {
   289  				session := helpers.CF("login", "-a", serverURL.String())
   290  				Eventually(session).Should(Say("API endpoint: %s", serverURL))
   291  				Eventually(session).Should(Say("FAILED"))
   292  				Eventually(session).Should(Say("Invalid SSL Cert for %s:%s", serverURL.Hostname(), serverURL.Port()))
   293  				Eventually(session).Should(Say("TIP: Use 'cf login --skip-ssl-validation' to continue with an insecure API endpoint"))
   294  				Eventually(session).Should(Exit(1))
   295  			})
   296  		})
   297  	})
   298  
   299  	Describe("SSO", func() {
   300  		When("--sso-passcode is provided", func() {
   301  			Context("and --sso is also passed", func() {
   302  				It("fails with a useful error message", func() {
   303  					session := helpers.CF("login", "--sso-passcode", "some-passcode", "--sso")
   304  					Eventually(session).Should(Say("Incorrect usage: --sso-passcode flag cannot be used with --sso"))
   305  					Eventually(session).Should(Exit(1))
   306  				})
   307  			})
   308  
   309  			Context("and the provided passcode is incorrect", func() {
   310  				It("prompts twice, displays an error and fails", func() {
   311  					session := helpers.CF("login", "--sso-passcode", "some-passcode")
   312  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   313  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   314  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   315  
   316  					// Leaving out expectation of prompt text, since it comes from UAA (and doesn't show up on Windows)
   317  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   318  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   319  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   320  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   321  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   322  					Eventually(session).Should(Say(`Not logged in. Use 'cf login' to log in.`))
   323  					Eventually(session).Should(Say(`FAILED`))
   324  					Eventually(session).Should(Say(`Unable to authenticate`))
   325  
   326  					Eventually(session).Should(Exit(1))
   327  				})
   328  			})
   329  
   330  			When("a passcode isn't provided", func() {
   331  				It("prompts the user to try again", func() {
   332  					session := helpers.CF("login", "--sso-passcode")
   333  					Eventually(session.Err).Should(Say("Incorrect Usage: expected argument for flag `--sso-passcode'"))
   334  					Eventually(session).Should(Exit(1))
   335  				})
   336  			})
   337  		})
   338  
   339  		When("a user authenticates with valid client credentials", func() {
   340  			BeforeEach(func() {
   341  				clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet()
   342  				session := helpers.CF("auth", clientID, clientSecret, "--client-credentials")
   343  				Eventually(session).Should(Exit(0))
   344  			})
   345  
   346  			When("a different user logs in with valid sso passcode", func() {
   347  				// Following test is desired, but not current behavior.
   348  				XIt("should fail log in and display an error informing the user they need to log out", func() {
   349  					session := helpers.CF("login", "--sso-passcode", "my-fancy-sso")
   350  
   351  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   352  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   353  					// The following message is a bit strange in the output. Consider removing?
   354  					Eventually(session).Should(Say("Not logged in. Use 'cf login' to log in."))
   355  					Eventually(session).Should(Say("FAILED"))
   356  					Eventually(session).Should(Say(`Service account currently logged in\. Use 'cf logout' to log out service account and try again\.`))
   357  					Eventually(session).Should(Exit(1))
   358  				})
   359  			})
   360  		})
   361  	})
   362  
   363  	Describe("User Credentials", func() {
   364  		// TODO: Figure out a way to pass in password when we don't have a tty
   365  		XIt("prompts the user for email and password", func() {
   366  			username, password := helpers.GetCredentials()
   367  			buffer := NewBuffer()
   368  			buffer.Write([]byte(fmt.Sprintf("%s\n%s\n", username, password)))
   369  			session := helpers.CFWithStdin(buffer, "login")
   370  			Eventually(session).Should(Say("Email> "))
   371  			Eventually(session).Should(Say("Password> "))
   372  			Eventually(session).Should(Exit(0))
   373  		})
   374  
   375  		When("the user provides the -p flag", func() {
   376  			It("prompts the user for their email and logs in successfully", func() {
   377  				username, password := helpers.GetCredentials()
   378  				input := NewBuffer()
   379  				input.Write([]byte(username + "\n"))
   380  				session := helpers.CFWithStdin(input, "login", "-p", password)
   381  				Eventually(session).Should(Say("Email> "))
   382  				Eventually(session).Should(Exit(0))
   383  			})
   384  		})
   385  
   386  		When("the user provides the -p and -u flags", func() {
   387  			Context("and the credentials are correct", func() {
   388  				It("logs in successfully", func() {
   389  					username, password := helpers.GetCredentials()
   390  					session := helpers.CF("login", "-p", password, "-u", username)
   391  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   392  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   393  					Eventually(session).Should(Say(`OK`))
   394  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   395  					Eventually(session).Should(Say("User:\\s+" + username))
   396  					Eventually(session).Should(Exit(0))
   397  				})
   398  			})
   399  
   400  			Context("and the credentials are incorrect", func() {
   401  				BeforeEach(func() {
   402  					if runtime.GOOS == "windows" {
   403  						// TODO: Don't skip this test on windows.
   404  						Skip("Skipping on Windows until refactor of cf login.")
   405  					}
   406  				})
   407  
   408  				It("prompts twice, displays an error and fails", func() {
   409  					username, password := helpers.GetCredentials()
   410  					badPassword := password + "_wrong"
   411  					session := helpers.CF("login", "-p", badPassword, "-u", username)
   412  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   413  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   414  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   415  					Eventually(session).Should(Say(`Password>`))
   416  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   417  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   418  					Eventually(session).Should(Say(`Password>`))
   419  					Eventually(session).Should(Say(`Authenticating\.\.\.`))
   420  					Eventually(session).Should(Say(`Credentials were rejected, please try again.`))
   421  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   422  					Eventually(session).Should(Say(`Not logged in. Use 'cf login' to log in.`))
   423  					Eventually(session).Should(Say(`FAILED`))
   424  					Eventually(session).Should(Say(`Unable to authenticate`))
   425  
   426  					Eventually(session).Should(Exit(1))
   427  				})
   428  
   429  				Context("and the user was previously logged in", func() {
   430  					BeforeEach(func() {
   431  						helpers.LoginCF()
   432  					})
   433  
   434  					It("logs them out", func() {
   435  						username, password := helpers.GetCredentials()
   436  						badPassword := password + "_wrong"
   437  						session := helpers.CF("login", "-p", badPassword, "-u", username)
   438  						Eventually(session).Should(Say(`Not logged in. Use 'cf login' to log in.`))
   439  						Eventually(session).Should(Exit())
   440  
   441  						orgsSession := helpers.CF("orgs")
   442  						Eventually(orgsSession.Err).Should(Say(`Not logged in. Use 'cf login' to log in.`))
   443  						Eventually(orgsSession).Should(Exit())
   444  					})
   445  				})
   446  			})
   447  
   448  			When("already logged in with client credentials", func() {
   449  				BeforeEach(func() {
   450  					clientID, clientSecret := helpers.SkipIfClientCredentialsNotSet()
   451  					session := helpers.CF("auth", clientID, clientSecret, "--client-credentials")
   452  					Eventually(session).Should(Exit(0))
   453  				})
   454  
   455  				It("should fail log in and display an error informing the user they need to log out", func() {
   456  					username, password := helpers.GetCredentials()
   457  					session := helpers.CF("login", "-p", password, "-u", username)
   458  					Eventually(session).Should(Say("API endpoint:\\s+" + helpers.GetAPI()))
   459  					Eventually(session).Should(Say(`API endpoint:\s+` + helpers.GetAPI() + `\s+\(API version: \d\.\d{1,3}\.\d{1,3}\)`))
   460  					// The following message is a bit strange in the output. Consider removing?
   461  					Eventually(session).Should(Say("Not logged in. Use 'cf login' to log in."))
   462  					Eventually(session).Should(Say("FAILED"))
   463  					Eventually(session).Should(Say("Service account currently logged in. Use 'cf logout' to log out service account and try again."))
   464  					Eventually(session).Should(Exit(1))
   465  				})
   466  			})
   467  		})
   468  	})
   469  })