github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/command/v6/login_command.go (about)

     1  package v6
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"code.cloudfoundry.org/cli/api/uaa"
    11  	"code.cloudfoundry.org/cli/util/ui"
    12  
    13  	"code.cloudfoundry.org/cli/actor/v2action"
    14  	"code.cloudfoundry.org/cli/actor/v3action"
    15  	"code.cloudfoundry.org/cli/api/uaa/constant"
    16  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    17  	"code.cloudfoundry.org/cli/command"
    18  	"code.cloudfoundry.org/cli/command/translatableerror"
    19  	"code.cloudfoundry.org/cli/command/v6/shared"
    20  )
    21  
    22  //go:generate counterfeiter . LoginActor
    23  
    24  const maxLoginTries = 3
    25  
    26  type LoginActor interface {
    27  	Authenticate(credentials map[string]string, origin string, grantType constant.GrantType) error
    28  	GetLoginPrompts() map[string]coreconfig.AuthPrompt
    29  	GetOrganizationByName(orgName string) (v3action.Organization, v3action.Warnings, error)
    30  	GetOrganizationSpaces(orgName string) ([]v3action.Space, v3action.Warnings, error)
    31  	GetSpaceByNameAndOrganization(spaceName string, orgGUID string) (v3action.Space, v3action.Warnings, error)
    32  	GetOrganizations() ([]v3action.Organization, v3action.Warnings, error)
    33  	SetTarget(settings v3action.TargetSettings) (v3action.Warnings, error)
    34  }
    35  
    36  //go:generate counterfeiter . V2LoginActor
    37  
    38  type V2LoginActor interface {
    39  	MinCLIVersion() string
    40  	CloudControllerAPIVersion() string
    41  	AuthorizationEndpoint() string
    42  }
    43  
    44  //go:generate counterfeiter . ActorMaker
    45  
    46  type ActorMaker interface {
    47  	NewActor(command.Config, command.UI, bool, string) (LoginActor, error)
    48  }
    49  
    50  //go:generate counterfeiter . V2ActorMaker
    51  
    52  type V2ActorMaker interface {
    53  	NewV2Actor(command.Config, command.UI, bool) (V2LoginActor, error)
    54  }
    55  
    56  type ActorMakerFunc func(command.Config, command.UI, bool, string) (LoginActor, error)
    57  type V2ActorMakerFunc func(command.Config, command.UI, bool) (V2LoginActor, error)
    58  
    59  func (a ActorMakerFunc) NewActor(config command.Config, ui command.UI, targetCF bool, authorizationEndpoint string) (LoginActor, error) {
    60  	return a(config, ui, targetCF, authorizationEndpoint)
    61  }
    62  
    63  func (a V2ActorMakerFunc) NewV2Actor(config command.Config, ui command.UI, targetCF bool) (V2LoginActor, error) {
    64  	return a(config, ui, targetCF)
    65  }
    66  
    67  var actorMaker ActorMakerFunc = func(config command.Config, ui command.UI, targetCF bool, authorizationEndpoint string) (LoginActor, error) {
    68  	client, uaa, err := shared.NewV3BasedClientsWithAuthorizationEndpoint(config, ui, targetCF, authorizationEndpoint)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	v3Actor := v3action.NewActor(client, config, nil, uaa)
    74  	return v3Actor, nil
    75  }
    76  
    77  var v2ActorMaker V2ActorMakerFunc = func(config command.Config, ui command.UI, targetCF bool) (V2LoginActor, error) {
    78  	client, uaa, err := shared.GetNewClientsAndConnectToCF(config, ui)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	v2Actor := v2action.NewActor(client, uaa, config)
    84  	return v2Actor, nil
    85  }
    86  
    87  type LoginCommand struct {
    88  	APIEndpoint       string      `short:"a" description:"API endpoint (e.g. https://api.example.com)"`
    89  	Organization      string      `short:"o" description:"Org"`
    90  	Password          string      `short:"p" description:"Password"`
    91  	Space             string      `short:"s" description:"Space"`
    92  	SkipSSLValidation bool        `long:"skip-ssl-validation" description:"Skip verification of the API endpoint. Not recommended!"`
    93  	SSO               bool        `long:"sso" description:"Prompt for a one-time passcode to login"`
    94  	SSOPasscode       string      `long:"sso-passcode" description:"One-time passcode"`
    95  	Username          string      `short:"u" description:"Username"`
    96  	Origin            string      `long:"origin" description:"Indicates the identity provider to be used for login"`
    97  	usage             interface{} `usage:"CF_NAME login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE] [--sso | --sso-passcode PASSCODE] [--origin ORIGIN]\n\nWARNING:\n   Providing your password as a command line option is highly discouraged\n   Your password may be visible to others and may be recorded in your shell history\n\nEXAMPLES:\n   CF_NAME login (omit username and password to login interactively -- CF_NAME will prompt for both)\n   CF_NAME login -u name@example.com -p pa55woRD (specify username and password as arguments)\n   CF_NAME login -u name@example.com -p \"my password\" (use quotes for passwords with a space)\n   CF_NAME login -u name@example.com -p \"\\\"password\\\"\" (escape quotes if used in password)\n   CF_NAME login --sso (CF_NAME will provide a url to obtain a one-time passcode to login)\n   CF_NAME login --origin ldap"`
    98  	relatedCommands   interface{} `related_commands:"api, auth, target"`
    99  
   100  	UI           command.UI
   101  	Actor        LoginActor
   102  	ActorMaker   ActorMaker
   103  	V2Actor      V2LoginActor
   104  	V2ActorMaker V2ActorMaker
   105  	Config       command.Config
   106  }
   107  
   108  func (cmd *LoginCommand) Setup(config command.Config, ui command.UI) error {
   109  	cmd.ActorMaker = actorMaker
   110  	actor, err := cmd.ActorMaker.NewActor(config, ui, false, "")
   111  	if err != nil {
   112  		return err
   113  	}
   114  	cmd.V2ActorMaker = v2ActorMaker
   115  	cmd.Actor = actor
   116  	cmd.UI = ui
   117  	cmd.Config = config
   118  	return nil
   119  }
   120  
   121  func (cmd *LoginCommand) Execute(args []string) error {
   122  	err := cmd.validateFlags()
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	endpoint, err := cmd.determineAPIEndpoint()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	cmd.UI.DisplayNewline()
   133  
   134  	err = cmd.targetAPI(endpoint)
   135  	if err != nil {
   136  		te := translatableerror.ConvertToTranslatableError(err)
   137  		if ise, ok := te.(translatableerror.InvalidSSLCertError); ok {
   138  			ise.SuggestedCommand = "login"
   139  			return ise
   140  		}
   141  		return err
   142  	}
   143  
   144  	err = cmd.checkMinCLIVersion()
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	defer cmd.showStatus()
   150  
   151  	if cmd.Config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
   152  		return translatableerror.PasswordGrantTypeLogoutRequiredError{}
   153  	} else if cmd.Config.UAAOAuthClient() != "cf" || cmd.Config.UAAOAuthClientSecret() != "" {
   154  		cmd.UI.DisplayWarning("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.")
   155  	}
   156  
   157  	var authErr error
   158  	if cmd.SSO || cmd.SSOPasscode != "" {
   159  		authErr = cmd.authenticateSSO()
   160  	} else {
   161  		authErr = cmd.authenticate()
   162  	}
   163  
   164  	if authErr != nil {
   165  		return errors.New("Unable to authenticate.")
   166  	}
   167  
   168  	err = cmd.Config.WriteConfig()
   169  	if err != nil {
   170  		return fmt.Errorf("Error writing config: %s", err.Error())
   171  	}
   172  
   173  	if cmd.Organization != "" {
   174  		org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.Organization)
   175  		cmd.UI.DisplayWarnings(warnings)
   176  		if err != nil {
   177  			return err
   178  		}
   179  		cmd.Config.SetOrganizationInformation(org.GUID, org.Name)
   180  	} else {
   181  		orgs, warnings, err := cmd.Actor.GetOrganizations()
   182  		cmd.UI.DisplayWarnings(warnings)
   183  		if err != nil {
   184  			return err
   185  		}
   186  		if len(orgs) == 1 {
   187  			cmd.Config.SetOrganizationInformation(orgs[0].GUID, orgs[0].Name)
   188  		} else if len(orgs) > 1 {
   189  			var emptyOrg v3action.Organization
   190  			chosenOrg, err := cmd.promptChosenOrg(orgs)
   191  			if err != nil {
   192  				return err
   193  			}
   194  			if chosenOrg != emptyOrg {
   195  				cmd.Config.SetOrganizationInformation(chosenOrg.GUID, chosenOrg.Name)
   196  			}
   197  		}
   198  	}
   199  
   200  	targetedOrg := cmd.Config.TargetedOrganization()
   201  
   202  	if targetedOrg.GUID != "" {
   203  		cmd.UI.DisplayTextWithFlavor("Targeted org {{.Organization}}", map[string]interface{}{
   204  			"Organization": cmd.Config.TargetedOrganizationName(),
   205  		})
   206  		cmd.UI.DisplayNewline()
   207  
   208  		if cmd.Space != "" {
   209  			space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, targetedOrg.GUID)
   210  			cmd.UI.DisplayWarnings(warnings)
   211  			if err != nil {
   212  				return err
   213  			}
   214  			cmd.targetSpace(space)
   215  		} else {
   216  			spaces, warnings, err := cmd.Actor.GetOrganizationSpaces(targetedOrg.GUID)
   217  			cmd.UI.DisplayWarnings(warnings)
   218  			if err != nil {
   219  				return err
   220  			}
   221  
   222  			if len(spaces) == 1 {
   223  				cmd.targetSpace(spaces[0])
   224  			} else if len(spaces) > 1 {
   225  				var emptySpace v3action.Space
   226  				chosenSpace, err := cmd.promptChosenSpace(spaces)
   227  				if err != nil {
   228  					return err
   229  				}
   230  				if chosenSpace != emptySpace {
   231  					cmd.targetSpace(chosenSpace)
   232  				}
   233  			}
   234  		}
   235  	}
   236  
   237  	cmd.UI.DisplayNewline()
   238  	cmd.UI.DisplayNewline()
   239  	cmd.UI.DisplayNewline()
   240  
   241  	return nil
   242  }
   243  
   244  func (cmd *LoginCommand) targetSpace(space v3action.Space) {
   245  	cmd.Config.SetSpaceInformation(space.GUID, space.Name, true)
   246  
   247  	cmd.UI.DisplayTextWithFlavor("Targeted space {{.Space}}", map[string]interface{}{
   248  		"Space": space.Name,
   249  	})
   250  }
   251  
   252  func (cmd *LoginCommand) determineAPIEndpoint() (v3action.TargetSettings, error) {
   253  	var (
   254  		endpoint          string
   255  		skipSSLValidation bool
   256  	)
   257  
   258  	if cmd.APIEndpoint != "" {
   259  		endpoint = cmd.APIEndpoint
   260  		skipSSLValidation = cmd.SkipSSLValidation
   261  	} else if cmd.Config.Target() != "" {
   262  		endpoint = cmd.Config.Target()
   263  		skipSSLValidation = cmd.Config.SkipSSLValidation() || cmd.SkipSSLValidation
   264  	} else {
   265  		endpoint = ""
   266  		skipSSLValidation = cmd.SkipSSLValidation
   267  	}
   268  
   269  	if len(endpoint) > 0 {
   270  		cmd.UI.DisplayTextWithFlavor("API endpoint: {{.APIEndpoint}}", map[string]interface{}{
   271  			"APIEndpoint": endpoint,
   272  		})
   273  	} else {
   274  		var err error
   275  		endpoint, err = cmd.UI.DisplayTextPrompt("API endpoint")
   276  		if err != nil {
   277  			return v3action.TargetSettings{}, err
   278  		}
   279  	}
   280  
   281  	strippedEndpoint := strings.TrimRight(endpoint, "/")
   282  	u, _ := url.Parse(strippedEndpoint)
   283  	if u.Scheme == "" {
   284  		u.Scheme = "https"
   285  	}
   286  	return v3action.TargetSettings{
   287  		URL:               u.String(),
   288  		SkipSSLValidation: skipSSLValidation,
   289  	}, nil
   290  }
   291  
   292  func (cmd *LoginCommand) targetAPI(settings v3action.TargetSettings) error {
   293  	warnings, err := cmd.Actor.SetTarget(settings)
   294  	cmd.UI.DisplayWarnings(warnings)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	if strings.HasPrefix(settings.URL, "http:") {
   299  		cmd.UI.DisplayWarning("Warning: Insecure http API endpoint detected: secure https API endpoints are recommended")
   300  	}
   301  
   302  	return cmd.reloadActors()
   303  }
   304  
   305  func (cmd *LoginCommand) authenticate() error {
   306  	prompts := cmd.Actor.GetLoginPrompts()
   307  	credentials := make(map[string]string)
   308  
   309  	if value, ok := prompts["username"]; ok {
   310  		if prompts["username"].Type == coreconfig.AuthPromptTypeText && cmd.Username != "" {
   311  			credentials["username"] = cmd.Username
   312  		} else {
   313  			var err error
   314  			credentials["username"], err = cmd.UI.DisplayTextPrompt(value.DisplayName)
   315  			if err != nil {
   316  				return err
   317  			}
   318  			cmd.UI.DisplayNewline()
   319  		}
   320  	}
   321  
   322  	passwordKeys := []string{}
   323  	for key, prompt := range prompts {
   324  		if prompt.Type == coreconfig.AuthPromptTypePassword {
   325  			if key == "passcode" || key == "password" {
   326  				continue
   327  			}
   328  
   329  			passwordKeys = append(passwordKeys, key)
   330  		} else if key == "username" {
   331  			continue
   332  		} else {
   333  			var err error
   334  			credentials[key], err = cmd.UI.DisplayTextPrompt(prompt.DisplayName)
   335  			if err != nil {
   336  				return err
   337  			}
   338  			cmd.UI.DisplayNewline()
   339  		}
   340  	}
   341  
   342  	var err error
   343  	for i := 0; i < maxLoginTries; i++ {
   344  		var promptedCredentials map[string]string
   345  		promptedCredentials, err = cmd.passwordPrompts(prompts, credentials, passwordKeys)
   346  		if err != nil {
   347  			return err
   348  		}
   349  
   350  		cmd.UI.DisplayText("Authenticating...")
   351  
   352  		err = cmd.Actor.Authenticate(promptedCredentials, cmd.Origin, constant.GrantTypePassword)
   353  
   354  		if err != nil {
   355  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   356  			cmd.UI.DisplayNewline()
   357  
   358  			if _, ok := err.(uaa.AccountLockedError); ok {
   359  				break
   360  			}
   361  		}
   362  
   363  		if err == nil {
   364  			cmd.UI.DisplayOK()
   365  			break
   366  		}
   367  	}
   368  	if err != nil {
   369  		return err
   370  	}
   371  	return nil
   372  }
   373  
   374  func (cmd *LoginCommand) authenticateSSO() error {
   375  	prompts := cmd.Actor.GetLoginPrompts()
   376  	credentials := make(map[string]string)
   377  
   378  	var err error
   379  	for i := 0; i < maxLoginTries; i++ {
   380  		if len(cmd.SSOPasscode) > 0 {
   381  			credentials["passcode"] = cmd.SSOPasscode
   382  			cmd.SSOPasscode = ""
   383  		} else {
   384  			credentials["passcode"], err = cmd.UI.DisplayPasswordPrompt(prompts["passcode"].DisplayName)
   385  			if err != nil {
   386  				return err
   387  			}
   388  		}
   389  
   390  		credentialsCopy := make(map[string]string, len(credentials))
   391  		for k, v := range credentials {
   392  			credentialsCopy[k] = v
   393  		}
   394  
   395  		cmd.UI.DisplayText("Authenticating...")
   396  		err = cmd.Actor.Authenticate(credentialsCopy, "", constant.GrantTypePassword)
   397  
   398  		if err != nil {
   399  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   400  			cmd.UI.DisplayNewline()
   401  		}
   402  
   403  		if err == nil {
   404  			cmd.UI.DisplayOK()
   405  			cmd.UI.DisplayNewline()
   406  			break
   407  		}
   408  	}
   409  	if err != nil {
   410  		return err
   411  	}
   412  	return nil
   413  }
   414  
   415  func (cmd *LoginCommand) checkMinCLIVersion() error {
   416  	cmd.Config.SetMinCLIVersion(cmd.V2Actor.MinCLIVersion())
   417  	return command.WarnIfCLIVersionBelowAPIDefinedMinimum(cmd.Config, cmd.V2Actor.CloudControllerAPIVersion(), cmd.UI)
   418  }
   419  
   420  func (cmd *LoginCommand) passwordPrompts(prompts map[string]coreconfig.AuthPrompt, credentials map[string]string, passwordKeys []string) (map[string]string, error) {
   421  	// ensure that password gets prompted before other codes (eg. mfa code)
   422  	var err error
   423  	if passPrompt, ok := prompts["password"]; ok {
   424  		if cmd.Password != "" {
   425  			credentials["password"] = cmd.Password
   426  			cmd.Password = ""
   427  		} else {
   428  			credentials["password"], err = cmd.UI.DisplayPasswordPrompt(passPrompt.DisplayName)
   429  			if err != nil {
   430  				return nil, err
   431  			}
   432  		}
   433  	}
   434  
   435  	for _, key := range passwordKeys {
   436  		cmd.UI.DisplayNewline()
   437  		credentials[key], err = cmd.UI.DisplayPasswordPrompt(prompts[key].DisplayName)
   438  		if err != nil {
   439  			return nil, err
   440  		}
   441  	}
   442  
   443  	credentialsCopy := make(map[string]string, len(credentials))
   444  	for k, v := range credentials {
   445  		credentialsCopy[k] = v
   446  	}
   447  
   448  	return credentialsCopy, nil
   449  }
   450  
   451  func (cmd *LoginCommand) reloadActors() error {
   452  	newV2Actor, err := cmd.V2ActorMaker.NewV2Actor(cmd.Config, cmd.UI, true)
   453  	if err != nil {
   454  		return err
   455  	}
   456  
   457  	cmd.V2Actor = newV2Actor
   458  
   459  	newActor, err := cmd.ActorMaker.NewActor(cmd.Config, cmd.UI, true, cmd.V2Actor.AuthorizationEndpoint())
   460  	if err != nil {
   461  		return err
   462  	}
   463  
   464  	cmd.Actor = newActor
   465  
   466  	return nil
   467  }
   468  
   469  func (cmd *LoginCommand) showStatus() {
   470  	tableContent := [][]string{
   471  		{
   472  			cmd.UI.TranslateText("API endpoint:"),
   473  			cmd.UI.TranslateText("{{.APIEndpoint}} (API version: {{.APIVersion}})",
   474  				map[string]interface{}{
   475  					"APIEndpoint": strings.TrimRight(cmd.Config.Target(), "/"),
   476  					"APIVersion":  cmd.Config.APIVersion(),
   477  				}),
   478  		},
   479  	}
   480  
   481  	user, err := cmd.Config.CurrentUserName()
   482  	if user == "" || err != nil {
   483  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   484  		command.DisplayNotLoggedInText(cmd.Config.BinaryName(), cmd.UI)
   485  		return
   486  	}
   487  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("User:"), user})
   488  
   489  	orgName := cmd.Config.TargetedOrganizationName()
   490  	if orgName == "" {
   491  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   492  		cmd.displayNotTargetted()
   493  		return
   494  	}
   495  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("Org:"), orgName})
   496  
   497  	spaceName := cmd.Config.TargetedSpace().Name
   498  	if spaceName == "" {
   499  		tableContent = append(tableContent, []string{cmd.UI.TranslateText("Space:"),
   500  			cmd.UI.TranslateText("No space targeted, use '{{.Command}}'", map[string]interface{}{
   501  				"Command": fmt.Sprintf("%s target -s SPACE", cmd.Config.BinaryName()),
   502  			})})
   503  	} else {
   504  		tableContent = append(tableContent, []string{cmd.UI.TranslateText("Space:"), spaceName})
   505  	}
   506  
   507  	cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   508  }
   509  
   510  func (cmd *LoginCommand) displayNotTargetted() {
   511  	cmd.UI.DisplayText("No org or space targeted, use '{{.CFTargetCommand}} -o ORG -s SPACE'",
   512  		map[string]interface{}{
   513  			"CFTargetCommand": fmt.Sprintf("%s target", cmd.Config.BinaryName()),
   514  		},
   515  	)
   516  }
   517  
   518  func (cmd *LoginCommand) promptChosenOrg(orgs []v3action.Organization) (v3action.Organization, error) {
   519  	orgNames := make([]string, len(orgs))
   520  	for i, org := range orgs {
   521  		orgNames[i] = org.Name
   522  	}
   523  
   524  	chosenOrgName, err := cmd.promptMenu(orgNames, "Select an org:", "Org")
   525  
   526  	if err != nil {
   527  		if invalidChoice, ok := err.(ui.InvalidChoiceError); ok {
   528  			return v3action.Organization{}, translatableerror.OrganizationNotFoundError{Name: invalidChoice.Choice}
   529  		} else if err == io.EOF {
   530  			return v3action.Organization{}, nil
   531  		} else {
   532  			return v3action.Organization{}, err
   533  		}
   534  	}
   535  
   536  	for _, org := range orgs {
   537  		if org.Name == chosenOrgName {
   538  			return org, nil
   539  		}
   540  	}
   541  
   542  	return v3action.Organization{}, nil
   543  }
   544  
   545  func (cmd *LoginCommand) promptChosenSpace(spaces []v3action.Space) (v3action.Space, error) {
   546  	spaceNames := make([]string, len(spaces))
   547  	for i, space := range spaces {
   548  		spaceNames[i] = space.Name
   549  	}
   550  
   551  	chosenSpaceName, err := cmd.promptMenu(spaceNames, "Select a space:", "Space")
   552  	if err != nil {
   553  		if invalidChoice, ok := err.(ui.InvalidChoiceError); ok {
   554  			return v3action.Space{}, translatableerror.SpaceNotFoundError{Name: invalidChoice.Choice}
   555  		} else if err == io.EOF {
   556  			return v3action.Space{}, nil
   557  		} else {
   558  			return v3action.Space{}, err
   559  		}
   560  	}
   561  
   562  	for _, space := range spaces {
   563  		if space.Name == chosenSpaceName {
   564  			return space, nil
   565  		}
   566  	}
   567  	return v3action.Space{}, nil
   568  }
   569  
   570  func (cmd *LoginCommand) promptMenu(choices []string, text string, prompt string) (string, error) {
   571  	var (
   572  		choice string
   573  		err    error
   574  	)
   575  
   576  	if len(choices) < 50 {
   577  		for {
   578  			cmd.UI.DisplayText(text)
   579  			choice, err = cmd.UI.DisplayTextMenu(choices, prompt)
   580  			if err == ui.ErrInvalidIndex {
   581  				continue
   582  			} else {
   583  				break
   584  			}
   585  		}
   586  	} else {
   587  		cmd.UI.DisplayText(text)
   588  		cmd.UI.DisplayText("There are too many options to display; please type in the name.")
   589  		cmd.UI.DisplayNewline()
   590  		defaultChoice := "enter to skip"
   591  		choice, err = cmd.UI.DisplayOptionalTextPrompt(defaultChoice, prompt)
   592  
   593  		switch {
   594  		case choice == defaultChoice:
   595  			return "", nil
   596  		case !contains(choices, choice):
   597  			return "", ui.InvalidChoiceError{Choice: choice}
   598  		}
   599  	}
   600  
   601  	return choice, err
   602  }
   603  
   604  func (cmd *LoginCommand) validateFlags() error {
   605  	if cmd.Origin != "" {
   606  		if cmd.SSO {
   607  			return translatableerror.ArgumentCombinationError{
   608  				Args: []string{"--sso", "--origin"},
   609  			}
   610  		}
   611  		if cmd.SSOPasscode != "" {
   612  			return translatableerror.ArgumentCombinationError{
   613  				Args: []string{"--sso-passcode", "--origin"},
   614  			}
   615  		}
   616  	}
   617  	if cmd.SSO && cmd.SSOPasscode != "" {
   618  		return translatableerror.ArgumentCombinationError{
   619  			Args: []string{"--sso-passcode", "--sso"},
   620  		}
   621  	}
   622  	return nil
   623  }
   624  
   625  func contains(s []string, v string) bool {
   626  	for _, x := range s {
   627  		if x == v {
   628  			return true
   629  		}
   630  	}
   631  	return false
   632  }