github.com/willmadison/cli@v6.40.1-0.20181018160101-29d5937903ff+incompatible/cf/commands/login_test.go (about)

     1  package commands_test
     2  
     3  import (
     4  	"strconv"
     5  
     6  	"code.cloudfoundry.org/cli/cf/api/authentication/authenticationfakes"
     7  	"code.cloudfoundry.org/cli/cf/api/organizations/organizationsfakes"
     8  	"code.cloudfoundry.org/cli/cf/api/spaces/spacesfakes"
     9  	"code.cloudfoundry.org/cli/cf/commandregistry"
    10  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    11  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig/coreconfigfakes"
    12  	"code.cloudfoundry.org/cli/cf/errors"
    13  	"code.cloudfoundry.org/cli/cf/models"
    14  	testcmd "code.cloudfoundry.org/cli/cf/util/testhelpers/commands"
    15  	testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration"
    16  	testterm "code.cloudfoundry.org/cli/cf/util/testhelpers/terminal"
    17  	. "github.com/onsi/ginkgo"
    18  	. "github.com/onsi/gomega"
    19  
    20  	. "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers"
    21  )
    22  
    23  var _ = Describe("Login Command", func() {
    24  	var (
    25  		Flags        []string
    26  		Config       coreconfig.Repository
    27  		ui           *testterm.FakeUI
    28  		authRepo     *authenticationfakes.FakeRepository
    29  		endpointRepo *coreconfigfakes.FakeEndpointRepository
    30  		orgRepo      *organizationsfakes.FakeOrganizationRepository
    31  		spaceRepo    *spacesfakes.FakeSpaceRepository
    32  
    33  		org  models.Organization
    34  		deps commandregistry.Dependency
    35  
    36  		minCLIVersion            string
    37  		minRecommendedCLIVersion string
    38  		apiVersion               string
    39  	)
    40  
    41  	updateCommandDependency := func(pluginCall bool) {
    42  		deps.UI = ui
    43  		deps.Config = Config
    44  		deps.RepoLocator = deps.RepoLocator.SetEndpointRepository(endpointRepo)
    45  		deps.RepoLocator = deps.RepoLocator.SetAuthenticationRepository(authRepo)
    46  		deps.RepoLocator = deps.RepoLocator.SetOrganizationRepository(orgRepo)
    47  		deps.RepoLocator = deps.RepoLocator.SetSpaceRepository(spaceRepo)
    48  		commandregistry.Commands.SetCommand(commandregistry.Commands.FindCommand("login").SetDependency(deps, pluginCall))
    49  	}
    50  
    51  	listSpacesStub := func(spaces []models.Space) func(func(models.Space) bool) error {
    52  		return func(cb func(models.Space) bool) error {
    53  			var keepGoing bool
    54  			for _, s := range spaces {
    55  				keepGoing = cb(s)
    56  				if !keepGoing {
    57  					return nil
    58  				}
    59  			}
    60  			return nil
    61  		}
    62  	}
    63  
    64  	BeforeEach(func() {
    65  		Flags = []string{}
    66  		Config = testconfig.NewRepository()
    67  		ui = &testterm.FakeUI{}
    68  		authRepo = new(authenticationfakes.FakeRepository)
    69  		authRepo.AuthenticateStub = func(credentials map[string]string) error {
    70  			Config.SetAccessToken("my_access_token")
    71  			Config.SetRefreshToken("my_refresh_token")
    72  			return nil
    73  		}
    74  		endpointRepo = new(coreconfigfakes.FakeEndpointRepository)
    75  		minCLIVersion = "1.0.0"
    76  		minRecommendedCLIVersion = "1.0.0"
    77  		apiVersion = "100.200.300"
    78  
    79  		org = models.Organization{}
    80  		org.Name = "my-new-org"
    81  		org.GUID = "my-new-org-guid"
    82  
    83  		orgRepo = &organizationsfakes.FakeOrganizationRepository{}
    84  		orgRepo.ListOrgsReturns([]models.Organization{org}, nil)
    85  
    86  		space := models.Space{}
    87  		space.GUID = "my-space-guid"
    88  		space.Name = "my-space"
    89  
    90  		spaceRepo = new(spacesfakes.FakeSpaceRepository)
    91  		spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{space})
    92  
    93  		authRepo.GetLoginPromptsAndSaveUAAServerURLReturns(map[string]coreconfig.AuthPrompt{
    94  			"username": {
    95  				DisplayName: "Username",
    96  				Type:        coreconfig.AuthPromptTypeText,
    97  			},
    98  			"password": {
    99  				DisplayName: "Password",
   100  				Type:        coreconfig.AuthPromptTypePassword,
   101  			},
   102  		}, nil)
   103  	})
   104  
   105  	Context("interactive usage", func() {
   106  		JustBeforeEach(func() {
   107  			endpointRepo.GetCCInfoStub = func(endpoint string) (*coreconfig.CCInfo, string, error) {
   108  				return &coreconfig.CCInfo{
   109  					APIVersion:               apiVersion,
   110  					AuthorizationEndpoint:    "auth/endpoint",
   111  					DopplerEndpoint:          "doppler/endpoint",
   112  					MinCLIVersion:            minCLIVersion,
   113  					MinRecommendedCLIVersion: minRecommendedCLIVersion,
   114  					SSHOAuthClient:           "some-client",
   115  					RoutingAPIEndpoint:       "routing/endpoint",
   116  				}, endpoint, nil
   117  			}
   118  		})
   119  
   120  		Describe("when there are a small number of organizations and spaces", func() {
   121  			var org2 models.Organization
   122  			var space2 models.Space
   123  
   124  			BeforeEach(func() {
   125  				org1 := models.Organization{}
   126  				org1.GUID = "some-org-guid"
   127  				org1.Name = "some-org"
   128  
   129  				org2 = models.Organization{}
   130  				org2.GUID = "my-new-org-guid"
   131  				org2.Name = "my-new-org"
   132  
   133  				space1 := models.Space{}
   134  				space1.GUID = "my-space-guid"
   135  				space1.Name = "my-space"
   136  
   137  				space2 = models.Space{}
   138  				space2.GUID = "some-space-guid"
   139  				space2.Name = "some-space"
   140  
   141  				orgRepo.ListOrgsReturns([]models.Organization{org1, org2}, nil)
   142  				spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{space1, space2})
   143  				spaceRepo.FindByNameStub = func(name string) (models.Space, error) {
   144  					m := map[string]models.Space{
   145  						space1.Name: space1,
   146  						space2.Name: space2,
   147  					}
   148  					return m[name], nil
   149  				}
   150  			})
   151  
   152  			It("lets the user select an org and space by number", func() {
   153  				orgRepo.FindByNameReturns(org2, nil)
   154  				OUT_OF_RANGE_CHOICE := "3"
   155  				ui.Inputs = []string{"api.example.com", "user@example.com", "password", OUT_OF_RANGE_CHOICE, "2", OUT_OF_RANGE_CHOICE, "1"}
   156  
   157  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   158  
   159  				Expect(ui.Outputs()).To(ContainSubstrings(
   160  					[]string{"Select an org"},
   161  					[]string{"1. some-org"},
   162  					[]string{"2. my-new-org"},
   163  					[]string{"Select a space"},
   164  					[]string{"1. my-space"},
   165  					[]string{"2. some-space"},
   166  				))
   167  
   168  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   169  				Expect(Config.SpaceFields().GUID).To(Equal("my-space-guid"))
   170  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   171  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   172  
   173  				Expect(Config.APIEndpoint()).To(Equal("api.example.com"))
   174  				Expect(Config.APIVersion()).To(Equal("100.200.300"))
   175  				Expect(Config.AuthenticationEndpoint()).To(Equal("auth/endpoint"))
   176  				Expect(Config.SSHOAuthClient()).To(Equal("some-client"))
   177  				Expect(Config.MinCLIVersion()).To(Equal("1.0.0"))
   178  				Expect(Config.MinRecommendedCLIVersion()).To(Equal("1.0.0"))
   179  				Expect(Config.DopplerEndpoint()).To(Equal("doppler/endpoint"))
   180  				Expect(Config.RoutingAPIEndpoint()).To(Equal("routing/endpoint"))
   181  
   182  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   183  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("api.example.com"))
   184  
   185  				Expect(orgRepo.FindByNameArgsForCall(0)).To(Equal("my-new-org"))
   186  				Expect(spaceRepo.FindByNameArgsForCall(0)).To(Equal("my-space"))
   187  
   188  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   189  			})
   190  
   191  			It("lets the user select an org and space by name", func() {
   192  				ui.Inputs = []string{"api.example.com", "user@example.com", "password", "my-new-org", "my-space"}
   193  				orgRepo.FindByNameReturns(org2, nil)
   194  
   195  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   196  
   197  				Expect(ui.Outputs()).To(ContainSubstrings(
   198  					[]string{"Select an org"},
   199  					[]string{"1. some-org"},
   200  					[]string{"2. my-new-org"},
   201  					[]string{"Select a space"},
   202  					[]string{"1. my-space"},
   203  					[]string{"2. some-space"},
   204  				))
   205  
   206  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   207  				Expect(Config.SpaceFields().GUID).To(Equal("my-space-guid"))
   208  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   209  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   210  
   211  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   212  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("api.example.com"))
   213  
   214  				Expect(orgRepo.FindByNameArgsForCall(0)).To(Equal("my-new-org"))
   215  				Expect(spaceRepo.FindByNameArgsForCall(0)).To(Equal("my-space"))
   216  
   217  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   218  			})
   219  
   220  			It("lets the user specify an org and space using flags", func() {
   221  				Flags = []string{"-a", "api.example.com", "-u", "user@example.com", "-p", "password", "-o", "my-new-org", "-s", "my-space"}
   222  
   223  				orgRepo.FindByNameReturns(org2, nil)
   224  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   225  
   226  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   227  				Expect(Config.SpaceFields().GUID).To(Equal("my-space-guid"))
   228  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   229  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   230  
   231  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   232  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("api.example.com"))
   233  				Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   234  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   235  					"username": "user@example.com",
   236  					"password": "password",
   237  				}))
   238  
   239  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   240  			})
   241  
   242  			It("doesn't ask the user for the API url if they have it in their config", func() {
   243  				orgRepo.FindByNameReturns(org, nil)
   244  				Config.SetAPIEndpoint("http://api.example.com")
   245  
   246  				Flags = []string{"-o", "my-new-org", "-s", "my-space"}
   247  				ui.Inputs = []string{"user@example.com", "password"}
   248  
   249  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   250  
   251  				Expect(Config.APIEndpoint()).To(Equal("http://api.example.com"))
   252  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   253  				Expect(Config.SpaceFields().GUID).To(Equal("my-space-guid"))
   254  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   255  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   256  
   257  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   258  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("http://api.example.com"))
   259  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   260  			})
   261  		})
   262  
   263  		It("displays an update notification", func() {
   264  			ui.Inputs = []string{"http://api.example.com", "user@example.com", "password"}
   265  			testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   266  			Expect(ui.NotifyUpdateIfNeededCallCount).To(Equal(1))
   267  		})
   268  
   269  		It("tries to get the organizations", func() {
   270  			Flags = []string{}
   271  			ui.Inputs = []string{"api.example.com", "user@example.com", "password", "my-org-1", "my-space"}
   272  			testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   273  			Expect(orgRepo.ListOrgsCallCount()).To(Equal(1))
   274  			Expect(orgRepo.ListOrgsArgsForCall(0)).To(Equal(50))
   275  		})
   276  
   277  		Describe("when there are too many orgs to show", func() {
   278  			BeforeEach(func() {
   279  				organizations := []models.Organization{}
   280  				for i := 0; i < 60; i++ {
   281  					id := strconv.Itoa(i)
   282  					org := models.Organization{}
   283  					org.GUID = "my-org-guid-" + id
   284  					org.Name = "my-org-" + id
   285  					organizations = append(organizations, org)
   286  				}
   287  				orgRepo.ListOrgsReturns(organizations, nil)
   288  				orgRepo.FindByNameReturns(organizations[1], nil)
   289  
   290  				space1 := models.Space{}
   291  				space1.GUID = "my-space-guid"
   292  				space1.Name = "my-space"
   293  
   294  				space2 := models.Space{}
   295  				space2.GUID = "some-space-guid"
   296  				space2.Name = "some-space"
   297  
   298  				spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{space1, space2})
   299  			})
   300  
   301  			It("doesn't display a list of orgs (the user must type the name)", func() {
   302  				ui.Inputs = []string{"api.example.com", "user@example.com", "password", "my-org-1", "my-space"}
   303  
   304  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   305  
   306  				Expect(ui.Outputs()).ToNot(ContainSubstrings([]string{"my-org-2"}))
   307  				Expect(orgRepo.FindByNameArgsForCall(0)).To(Equal("my-org-1"))
   308  				Expect(Config.OrganizationFields().GUID).To(Equal("my-org-guid-1"))
   309  			})
   310  		})
   311  
   312  		Describe("when there is only a single org and space", func() {
   313  			It("does not ask the user to select an org/space", func() {
   314  				ui.Inputs = []string{"http://api.example.com", "user@example.com", "password"}
   315  
   316  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   317  
   318  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   319  				Expect(Config.SpaceFields().GUID).To(Equal("my-space-guid"))
   320  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   321  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   322  
   323  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   324  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("http://api.example.com"))
   325  				Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   326  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   327  					"username": "user@example.com",
   328  					"password": "password",
   329  				}))
   330  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   331  			})
   332  		})
   333  
   334  		Describe("where there are no available orgs", func() {
   335  			BeforeEach(func() {
   336  				orgRepo.ListOrgsReturns([]models.Organization{}, nil)
   337  				spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{})
   338  			})
   339  
   340  			It("does not as the user to select an org", func() {
   341  				ui.Inputs = []string{"http://api.example.com", "user@example.com", "password"}
   342  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   343  
   344  				Expect(Config.OrganizationFields().GUID).To(Equal(""))
   345  				Expect(Config.SpaceFields().GUID).To(Equal(""))
   346  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   347  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   348  
   349  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   350  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("http://api.example.com"))
   351  				Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   352  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   353  					"username": "user@example.com",
   354  					"password": "password",
   355  				}))
   356  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   357  			})
   358  		})
   359  
   360  		Describe("when there is only a single org and no spaces", func() {
   361  			BeforeEach(func() {
   362  				orgRepo.ListOrgsReturns([]models.Organization{org}, nil)
   363  				spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{})
   364  			})
   365  
   366  			It("does not ask the user to select a space", func() {
   367  				ui.Inputs = []string{"http://api.example.com", "user@example.com", "password"}
   368  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   369  
   370  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   371  				Expect(Config.SpaceFields().GUID).To(Equal(""))
   372  				Expect(Config.AccessToken()).To(Equal("my_access_token"))
   373  				Expect(Config.RefreshToken()).To(Equal("my_refresh_token"))
   374  
   375  				Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   376  				Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("http://api.example.com"))
   377  				Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   378  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   379  					"username": "user@example.com",
   380  					"password": "password",
   381  				}))
   382  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   383  			})
   384  		})
   385  
   386  		Describe("login prompts", func() {
   387  			BeforeEach(func() {
   388  				authRepo.GetLoginPromptsAndSaveUAAServerURLReturns(map[string]coreconfig.AuthPrompt{
   389  					"account_number": {
   390  						DisplayName: "Account Number",
   391  						Type:        coreconfig.AuthPromptTypeText,
   392  					},
   393  					"username": {
   394  						DisplayName: "Username",
   395  						Type:        coreconfig.AuthPromptTypeText,
   396  					},
   397  					"passcode": {
   398  						DisplayName: "It's a passcode, what you want it to be???",
   399  						Type:        coreconfig.AuthPromptTypePassword,
   400  					},
   401  					"password": {
   402  						DisplayName: "Your Password",
   403  						Type:        coreconfig.AuthPromptTypePassword,
   404  					},
   405  				}, nil)
   406  			})
   407  
   408  			Context("when the user does not provide the --sso flag", func() {
   409  				It("prompts the user for 'password' prompt and any text type prompt", func() {
   410  					ui.Inputs = []string{"api.example.com", "the-username", "the-account-number", "the-password"}
   411  
   412  					testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   413  
   414  					Expect(ui.Prompts).To(ContainSubstrings(
   415  						[]string{"API endpoint"},
   416  						[]string{"Account Number"},
   417  						[]string{"Username"},
   418  					))
   419  					Expect(ui.PasswordPrompts).To(ContainSubstrings([]string{"Your Password"}))
   420  					Expect(ui.PasswordPrompts).ToNot(ContainSubstrings(
   421  						[]string{"passcode"},
   422  					))
   423  
   424  					Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   425  					Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   426  						"account_number": "the-account-number",
   427  						"username":       "the-username",
   428  						"password":       "the-password",
   429  					}))
   430  				})
   431  			})
   432  
   433  			Context("when the user does provide the --sso flag", func() {
   434  				It("only prompts the user for the passcode type prompts", func() {
   435  					Flags = []string{"--sso", "-a", "api.example.com"}
   436  					ui.Inputs = []string{"the-one-time-code"}
   437  
   438  					testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   439  
   440  					Expect(ui.Prompts).To(BeEmpty())
   441  					Expect(ui.PasswordPrompts).To(ContainSubstrings([]string{"passcode"}))
   442  					Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   443  					Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   444  						"passcode": "the-one-time-code",
   445  					}))
   446  				})
   447  			})
   448  
   449  			Context("when the user provides the --sso-passcode flag", func() {
   450  				It("does not prompt the user for the passcode type prompts", func() {
   451  					Flags = []string{"--sso-passcode", "the-one-time-code", "-a", "api.example.com"}
   452  
   453  					testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   454  
   455  					Expect(ui.Prompts).To(BeEmpty())
   456  					Expect(ui.PasswordPrompts).To(BeEmpty())
   457  					Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   458  					Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   459  						"passcode": "the-one-time-code",
   460  					}))
   461  				})
   462  			})
   463  
   464  			Context("when the user does provides both the --sso and --sso-passcode flags", func() {
   465  				It("errors with usage error and does not try to authenticate", func() {
   466  					Flags = []string{"--sso", "-sso-passcode", "the-one-time-code", "-a", "api.example.com"}
   467  					ui.Inputs = []string{"the-one-time-code"}
   468  
   469  					execution := testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   470  					Expect(execution).To(BeFalse())
   471  
   472  					Expect(authRepo.AuthenticateCallCount()).To(Equal(0))
   473  				})
   474  			})
   475  
   476  			Context("when multiple prompts of password type are given", func() {
   477  				BeforeEach(func() {
   478  					authRepo.GetLoginPromptsAndSaveUAAServerURLReturns(map[string]coreconfig.AuthPrompt{
   479  						"username": {
   480  							DisplayName: "Username",
   481  							Type:        coreconfig.AuthPromptTypeText,
   482  						},
   483  						"otherpass1": {
   484  							DisplayName: "some secure thing like mfa",
   485  							Type:        coreconfig.AuthPromptTypePassword,
   486  						},
   487  						"password": {
   488  							DisplayName: "Your Password",
   489  							Type:        coreconfig.AuthPromptTypePassword,
   490  						},
   491  					}, nil)
   492  				})
   493  
   494  				It("prompts for the password first", func() {
   495  					ui.Inputs = []string{"api.example.com", "the-username", "the-password", "other-secret"}
   496  
   497  					testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   498  
   499  					Expect(ui.PasswordPrompts).To(Equal([]string{
   500  						"Your Password",
   501  						"some secure thing like mfa",
   502  					}))
   503  				})
   504  			})
   505  
   506  			It("takes the password from the -p flag", func() {
   507  				Flags = []string{"-p", "the-password"}
   508  				ui.Inputs = []string{"api.example.com", "the-username", "the-account-number", "the-pin"}
   509  
   510  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   511  
   512  				Expect(ui.PasswordPrompts).ToNot(ContainSubstrings([]string{"Your Password"}))
   513  				Expect(authRepo.AuthenticateCallCount()).To(Equal(1))
   514  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   515  					"account_number": "the-account-number",
   516  					"username":       "the-username",
   517  					"password":       "the-password",
   518  				}))
   519  			})
   520  
   521  			It("tries 3 times for the password-type prompts", func() {
   522  				authRepo.AuthenticateReturns(errors.New("Error authenticating."))
   523  				ui.Inputs = []string{"api.example.com", "the-username", "the-account-number",
   524  					"the-password-1", "the-password-2", "the-password-3"}
   525  
   526  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   527  
   528  				Expect(authRepo.AuthenticateCallCount()).To(Equal(3))
   529  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   530  					"username":       "the-username",
   531  					"account_number": "the-account-number",
   532  					"password":       "the-password-1",
   533  				}))
   534  				Expect(authRepo.AuthenticateArgsForCall(1)).To(Equal(map[string]string{
   535  					"username":       "the-username",
   536  					"account_number": "the-account-number",
   537  					"password":       "the-password-2",
   538  				}))
   539  				Expect(authRepo.AuthenticateArgsForCall(2)).To(Equal(map[string]string{
   540  					"username":       "the-username",
   541  					"account_number": "the-account-number",
   542  					"password":       "the-password-3",
   543  				}))
   544  
   545  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   546  			})
   547  
   548  			It("prompts user for password again if password given on the cmd line fails", func() {
   549  				authRepo.AuthenticateReturns(errors.New("Error authenticating."))
   550  
   551  				Flags = []string{"-p", "the-password-1"}
   552  
   553  				ui.Inputs = []string{"api.example.com", "the-username", "the-account-number",
   554  					"the-password-2", "the-password-3"}
   555  
   556  				testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   557  
   558  				Expect(authRepo.AuthenticateCallCount()).To(Equal(3))
   559  				Expect(authRepo.AuthenticateArgsForCall(0)).To(Equal(map[string]string{
   560  					"username":       "the-username",
   561  					"account_number": "the-account-number",
   562  					"password":       "the-password-1",
   563  				}))
   564  				Expect(authRepo.AuthenticateArgsForCall(1)).To(Equal(map[string]string{
   565  					"username":       "the-username",
   566  					"account_number": "the-account-number",
   567  					"password":       "the-password-2",
   568  				}))
   569  				Expect(authRepo.AuthenticateArgsForCall(2)).To(Equal(map[string]string{
   570  					"username":       "the-username",
   571  					"account_number": "the-account-number",
   572  					"password":       "the-password-3",
   573  				}))
   574  
   575  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   576  			})
   577  		})
   578  	})
   579  
   580  	Describe("updates to the config", func() {
   581  		BeforeEach(func() {
   582  			Config.SetAPIEndpoint("api.the-old-endpoint.com")
   583  			Config.SetAccessToken("the-old-access-token")
   584  			Config.SetRefreshToken("the-old-refresh-token")
   585  			endpointRepo.GetCCInfoStub = func(endpoint string) (*coreconfig.CCInfo, string, error) {
   586  				return &coreconfig.CCInfo{
   587  					APIVersion:               apiVersion,
   588  					AuthorizationEndpoint:    "auth/endpoint",
   589  					DopplerEndpoint:          "doppler/endpoint",
   590  					MinCLIVersion:            minCLIVersion,
   591  					MinRecommendedCLIVersion: minRecommendedCLIVersion,
   592  					SSHOAuthClient:           "some-client",
   593  					RoutingAPIEndpoint:       "routing/endpoint",
   594  				}, endpoint, nil
   595  			}
   596  
   597  		})
   598  
   599  		JustBeforeEach(func() {
   600  			testcmd.RunCLICommand("login", Flags, nil, updateCommandDependency, false, ui)
   601  		})
   602  
   603  		var ItShowsTheTarget = func() {
   604  			It("shows the target", func() {
   605  				Expect(ui.ShowConfigurationCalled).To(BeTrue())
   606  			})
   607  		}
   608  
   609  		var ItDoesntShowTheTarget = func() {
   610  			It("does not show the target info", func() {
   611  				Expect(ui.ShowConfigurationCalled).To(BeFalse())
   612  			})
   613  		}
   614  
   615  		var ItFails = func() {
   616  			It("fails", func() {
   617  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   618  			})
   619  		}
   620  
   621  		var ItSucceeds = func() {
   622  			It("runs successfully", func() {
   623  				Expect(ui.Outputs()).ToNot(ContainSubstrings([]string{"FAILED"}))
   624  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"OK"}))
   625  			})
   626  		}
   627  
   628  		Describe("when the user is setting an API", func() {
   629  			BeforeEach(func() {
   630  				Flags = []string{"-a", "https://api.the-server.com", "-u", "the-user-name", "-p", "the-password"}
   631  			})
   632  
   633  			Describe("when the --skip-ssl-validation flag is provided", func() {
   634  				BeforeEach(func() {
   635  					Flags = append(Flags, "--skip-ssl-validation")
   636  				})
   637  
   638  				Describe("setting api endpoint is successful", func() {
   639  					BeforeEach(func() {
   640  						Config.SetSSLDisabled(false)
   641  					})
   642  
   643  					ItSucceeds()
   644  					ItShowsTheTarget()
   645  
   646  					It("stores the API endpoint and the skip-ssl flag", func() {
   647  						Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   648  						Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("https://api.the-server.com"))
   649  						Expect(Config.IsSSLDisabled()).To(BeTrue())
   650  					})
   651  				})
   652  
   653  				Describe("setting api endpoint failed", func() {
   654  					BeforeEach(func() {
   655  						Config.SetSSLDisabled(true)
   656  						endpointRepo.GetCCInfoReturns(nil, "", errors.New("API endpoint not found"))
   657  					})
   658  
   659  					ItFails()
   660  					ItDoesntShowTheTarget()
   661  
   662  					It("clears the entire config", func() {
   663  						Expect(Config.APIEndpoint()).To(BeEmpty())
   664  						Expect(Config.IsSSLDisabled()).To(BeFalse())
   665  						Expect(Config.AccessToken()).To(BeEmpty())
   666  						Expect(Config.RefreshToken()).To(BeEmpty())
   667  						Expect(Config.OrganizationFields().GUID).To(BeEmpty())
   668  						Expect(Config.SpaceFields().GUID).To(BeEmpty())
   669  					})
   670  				})
   671  			})
   672  
   673  			Describe("when the --skip-ssl-validation flag is not provided", func() {
   674  				Describe("setting api endpoint is successful", func() {
   675  					BeforeEach(func() {
   676  						Config.SetSSLDisabled(true)
   677  					})
   678  
   679  					ItSucceeds()
   680  					ItShowsTheTarget()
   681  
   682  					It("updates the API endpoint and enables SSL validation", func() {
   683  						Expect(endpointRepo.GetCCInfoCallCount()).To(Equal(1))
   684  						Expect(endpointRepo.GetCCInfoArgsForCall(0)).To(Equal("https://api.the-server.com"))
   685  						Expect(Config.IsSSLDisabled()).To(BeFalse())
   686  					})
   687  				})
   688  
   689  				Describe("setting api endpoint failed", func() {
   690  					BeforeEach(func() {
   691  						Config.SetSSLDisabled(true)
   692  						endpointRepo.GetCCInfoReturns(nil, "", errors.New("API endpoint not found"))
   693  					})
   694  
   695  					ItFails()
   696  					ItDoesntShowTheTarget()
   697  
   698  					It("clears the entire config", func() {
   699  						Expect(Config.APIEndpoint()).To(BeEmpty())
   700  						Expect(Config.IsSSLDisabled()).To(BeFalse())
   701  						Expect(Config.AccessToken()).To(BeEmpty())
   702  						Expect(Config.RefreshToken()).To(BeEmpty())
   703  						Expect(Config.OrganizationFields().GUID).To(BeEmpty())
   704  						Expect(Config.SpaceFields().GUID).To(BeEmpty())
   705  					})
   706  				})
   707  			})
   708  
   709  			Describe("when there is an invalid SSL cert", func() {
   710  				BeforeEach(func() {
   711  					endpointRepo.GetCCInfoReturns(nil, "", errors.NewInvalidSSLCert("https://bobs-burgers.com", "SELF SIGNED SADNESS"))
   712  					ui.Inputs = []string{"bobs-burgers.com"}
   713  				})
   714  
   715  				It("fails and suggests the user skip SSL validation", func() {
   716  					Expect(ui.Outputs()).To(ContainSubstrings(
   717  						[]string{"FAILED"},
   718  						[]string{"SSL Cert", "https://bobs-burgers.com"},
   719  						[]string{"TIP", "login", "--skip-ssl-validation"},
   720  					))
   721  				})
   722  
   723  				ItDoesntShowTheTarget()
   724  			})
   725  
   726  			Describe("when the api version is older than the minimum version", func() {
   727  				BeforeEach(func() {
   728  					apiVersion = "2.68.0"
   729  				})
   730  
   731  				It("prints a warning", func() {
   732  					Expect(ui.WarnOutputs[0]).To(ContainSubstring("Your API version is no longer supported. Upgrade to a newer version of the API."))
   733  				})
   734  			})
   735  		})
   736  
   737  		Describe("when user is logging in and not setting the api endpoint", func() {
   738  			BeforeEach(func() {
   739  				Flags = []string{"-u", "the-user-name", "-p", "the-password"}
   740  			})
   741  
   742  			Describe("when the --skip-ssl-validation flag is provided", func() {
   743  				BeforeEach(func() {
   744  					Flags = append(Flags, "--skip-ssl-validation")
   745  					Config.SetSSLDisabled(false)
   746  				})
   747  
   748  				It("disables SSL validation", func() {
   749  					Expect(Config.IsSSLDisabled()).To(BeTrue())
   750  				})
   751  			})
   752  
   753  			Describe("when the --skip-ssl-validation flag is not provided", func() {
   754  				BeforeEach(func() {
   755  					Config.SetSSLDisabled(true)
   756  				})
   757  
   758  				It("should not change config's SSLDisabled flag", func() {
   759  					Expect(Config.IsSSLDisabled()).To(BeTrue())
   760  				})
   761  			})
   762  
   763  			Describe("and the login fails authenticaton", func() {
   764  				BeforeEach(func() {
   765  					authRepo.AuthenticateReturns(errors.New("Error authenticating."))
   766  
   767  					Config.SetSSLDisabled(true)
   768  
   769  					Flags = []string{"-u", "user@example.com"}
   770  					ui.Inputs = []string{"password", "password2", "password3", "password4"}
   771  				})
   772  
   773  				ItFails()
   774  				ItShowsTheTarget()
   775  
   776  				It("does not change the api endpoint or SSL setting in the config", func() {
   777  					Expect(Config.APIEndpoint()).To(Equal("api.the-old-endpoint.com"))
   778  					Expect(Config.IsSSLDisabled()).To(BeTrue())
   779  				})
   780  
   781  				It("clears Access Token, Refresh Token, Org, and Space in the config", func() {
   782  					Expect(Config.AccessToken()).To(BeEmpty())
   783  					Expect(Config.RefreshToken()).To(BeEmpty())
   784  					Expect(Config.OrganizationFields().GUID).To(BeEmpty())
   785  					Expect(Config.SpaceFields().GUID).To(BeEmpty())
   786  				})
   787  			})
   788  		})
   789  
   790  		Describe("and the login fails to target an org", func() {
   791  			BeforeEach(func() {
   792  				Flags = []string{"-u", "user@example.com", "-p", "password", "-o", "nonexistentorg", "-s", "my-space"}
   793  				orgRepo.FindByNameReturns(models.Organization{}, errors.New("No org"))
   794  				Config.SetSSLDisabled(true)
   795  			})
   796  
   797  			ItFails()
   798  			ItShowsTheTarget()
   799  
   800  			It("does not update the api endpoint or ssl setting in the config", func() {
   801  				Expect(Config.APIEndpoint()).To(Equal("api.the-old-endpoint.com"))
   802  				Expect(Config.IsSSLDisabled()).To(BeTrue())
   803  			})
   804  
   805  			It("clears Org, and Space in the config", func() {
   806  				Expect(Config.OrganizationFields().GUID).To(BeEmpty())
   807  				Expect(Config.SpaceFields().GUID).To(BeEmpty())
   808  			})
   809  		})
   810  
   811  		Describe("and the login fails to target a space", func() {
   812  			BeforeEach(func() {
   813  				Flags = []string{"-u", "user@example.com", "-p", "password", "-o", "my-new-org", "-s", "nonexistent"}
   814  				orgRepo.FindByNameReturns(org, nil)
   815  				spaceRepo.FindByNameReturns(models.Space{}, errors.New("find-by-name-err"))
   816  
   817  				Config.SetSSLDisabled(true)
   818  			})
   819  
   820  			ItFails()
   821  			ItShowsTheTarget()
   822  
   823  			It("does not update the api endpoint or ssl setting in the config", func() {
   824  				Expect(Config.APIEndpoint()).To(Equal("api.the-old-endpoint.com"))
   825  				Expect(Config.IsSSLDisabled()).To(BeTrue())
   826  			})
   827  
   828  			It("updates the org in the config", func() {
   829  				Expect(Config.OrganizationFields().GUID).To(Equal("my-new-org-guid"))
   830  			})
   831  
   832  			It("clears the space in the config", func() {
   833  				Expect(Config.SpaceFields().GUID).To(BeEmpty())
   834  			})
   835  		})
   836  
   837  		Describe("and the login succeeds", func() {
   838  			BeforeEach(func() {
   839  				orgRepo.FindByNameReturns(models.Organization{
   840  					OrganizationFields: models.OrganizationFields{
   841  						Name: "new-org",
   842  						GUID: "new-org-guid",
   843  					},
   844  				}, nil)
   845  
   846  				space1 := models.Space{}
   847  				space1.GUID = "new-space-guid"
   848  				space1.Name = "new-space-name"
   849  				spaceRepo.ListSpacesStub = listSpacesStub([]models.Space{space1})
   850  				spaceRepo.FindByNameReturns(space1, nil)
   851  
   852  				authRepo.AuthenticateStub = func(credentials map[string]string) error {
   853  					Config.SetAccessToken("new_access_token")
   854  					Config.SetRefreshToken("new_refresh_token")
   855  					return nil
   856  				}
   857  
   858  				Flags = []string{"-u", "user@example.com", "-p", "password", "-o", "new-org", "-s", "new-space"}
   859  
   860  				Config.SetAPIEndpoint("api.the-old-endpoint.com")
   861  				Config.SetSSLDisabled(true)
   862  			})
   863  
   864  			ItSucceeds()
   865  			ItShowsTheTarget()
   866  
   867  			It("does not update the api endpoint or SSL setting", func() {
   868  				Expect(Config.APIEndpoint()).To(Equal("api.the-old-endpoint.com"))
   869  				Expect(Config.IsSSLDisabled()).To(BeTrue())
   870  			})
   871  
   872  			It("updates the config", func() {
   873  				Expect(Config.AccessToken()).To(Equal("new_access_token"))
   874  				Expect(Config.RefreshToken()).To(Equal("new_refresh_token"))
   875  				Expect(Config.OrganizationFields().GUID).To(Equal("new-org-guid"))
   876  				Expect(Config.SpaceFields().GUID).To(Equal("new-space-guid"))
   877  
   878  				Expect(Config.APIVersion()).To(Equal("100.200.300"))
   879  				Expect(Config.AuthenticationEndpoint()).To(Equal("auth/endpoint"))
   880  				Expect(Config.SSHOAuthClient()).To(Equal("some-client"))
   881  				Expect(Config.MinCLIVersion()).To(Equal("1.0.0"))
   882  				Expect(Config.MinRecommendedCLIVersion()).To(Equal("1.0.0"))
   883  				Expect(Config.DopplerEndpoint()).To(Equal("doppler/endpoint"))
   884  				Expect(Config.RoutingAPIEndpoint()).To(Equal("routing/endpoint"))
   885  
   886  			})
   887  		})
   888  
   889  		Describe("when a previous user authenticated with a client grant type", func() {
   890  			BeforeEach(func() {
   891  				Config.SetUAAGrantType("client_credentials")
   892  			})
   893  
   894  			Context("when the current user logs in with a password grant type", func() {
   895  				BeforeEach(func() {
   896  					Flags = []string{"-u", "the-user-name", "-p", "the-password"}
   897  				})
   898  
   899  				It("displays an error informing the user to log out and returns an error", func() {
   900  					Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   901  					Expect(ui.Outputs()).To(ContainSubstrings([]string{"Service account currently logged in. Use 'cf logout' to log out service account and try again."}))
   902  				})
   903  			})
   904  		})
   905  	})
   906  })