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