github.com/arunkumar7540/cli@v6.45.0+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  	GetSpaceByNameAndOrganization(spaceName string, orgGUID string) (v3action.Space, v3action.Warnings, error)
    31  	GetOrganizations() ([]v3action.Organization, v3action.Warnings, error)
    32  	SetTarget(settings v3action.TargetSettings) (v3action.Warnings, error)
    33  }
    34  
    35  //go:generate counterfeiter . VersionChecker
    36  
    37  type VersionChecker interface {
    38  	MinCLIVersion() string
    39  	CloudControllerAPIVersion() string
    40  }
    41  
    42  //go:generate counterfeiter . ActorMaker
    43  
    44  type ActorMaker interface {
    45  	NewActor(command.Config, command.UI, bool) (LoginActor, error)
    46  }
    47  
    48  //go:generate counterfeiter . CheckerMaker
    49  
    50  type CheckerMaker interface {
    51  	NewVersionChecker(command.Config, command.UI, bool) (VersionChecker, error)
    52  }
    53  
    54  type ActorMakerFunc func(command.Config, command.UI, bool) (LoginActor, error)
    55  type CheckerMakerFunc func(command.Config, command.UI, bool) (VersionChecker, error)
    56  
    57  func (a ActorMakerFunc) NewActor(config command.Config, ui command.UI, targetCF bool) (LoginActor, error) {
    58  	return a(config, ui, targetCF)
    59  }
    60  
    61  func (c CheckerMakerFunc) NewVersionChecker(config command.Config, ui command.UI, targetCF bool) (VersionChecker, error) {
    62  	return c(config, ui, targetCF)
    63  }
    64  
    65  var actorMaker ActorMakerFunc = func(config command.Config, ui command.UI, targetCF bool) (LoginActor, error) {
    66  	client, uaa, err := shared.NewV3BasedClients(config, ui, targetCF, "")
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	v3Actor := v3action.NewActor(client, config, nil, uaa)
    72  	return v3Actor, nil
    73  }
    74  
    75  var checkerMaker CheckerMakerFunc = func(config command.Config, ui command.UI, targetCF bool) (VersionChecker, error) {
    76  	client, uaa, err := shared.NewClients(config, ui, targetCF)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	v2Actor := v2action.NewActor(client, uaa, config)
    82  	return v2Actor, nil
    83  }
    84  
    85  type LoginCommand struct {
    86  	APIEndpoint       string      `short:"a" description:"API endpoint (e.g. https://api.example.com)"`
    87  	Organization      string      `short:"o" description:"Org"`
    88  	Password          string      `short:"p" description:"Password"`
    89  	Space             string      `short:"s" description:"Space"`
    90  	SkipSSLValidation bool        `long:"skip-ssl-validation" description:"Skip verification of the API endpoint. Not recommended!"`
    91  	SSO               bool        `long:"sso" description:"Prompt for a one-time passcode to login"`
    92  	SSOPasscode       string      `long:"sso-passcode" description:"One-time passcode"`
    93  	Username          string      `short:"u" description:"Username"`
    94  	usage             interface{} `usage:"CF_NAME login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE] [--sso | --sso-passcode PASSCODE]\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)"`
    95  	relatedCommands   interface{} `related_commands:"api, auth, target"`
    96  
    97  	UI           command.UI
    98  	Actor        LoginActor
    99  	ActorMaker   ActorMaker
   100  	Checker      VersionChecker
   101  	CheckerMaker CheckerMaker
   102  	Config       command.Config
   103  }
   104  
   105  func (cmd *LoginCommand) Setup(config command.Config, ui command.UI) error {
   106  	cmd.ActorMaker = actorMaker
   107  	actor, err := cmd.ActorMaker.NewActor(config, ui, false)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	cmd.CheckerMaker = checkerMaker
   112  	cmd.Actor = actor
   113  	cmd.UI = ui
   114  	cmd.Config = config
   115  	return nil
   116  }
   117  
   118  func (cmd *LoginCommand) Execute(args []string) error {
   119  	if !cmd.Config.ExperimentalLogin() {
   120  		return translatableerror.UnrefactoredCommandError{}
   121  	}
   122  	cmd.UI.DisplayWarning("Using experimental login command, some behavior may be different")
   123  
   124  	if cmd.Config.UAAOAuthClient() != "cf" || cmd.Config.UAAOAuthClientSecret() != "" {
   125  		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.")
   126  	}
   127  
   128  	var err error
   129  
   130  	err = cmd.getAPI()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	cmd.UI.DisplayNewline()
   136  
   137  	err = cmd.retargetAPI()
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	defer cmd.showStatus()
   143  
   144  	if cmd.Config.UAAGrantType() == string(constant.GrantTypeClientCredentials) {
   145  		return errors.New("Service account currently logged in. Use 'cf logout' to log out service account and try again.")
   146  	}
   147  
   148  	var authErr error
   149  	if cmd.SSO == true || cmd.SSOPasscode != "" {
   150  		if cmd.SSO && cmd.SSOPasscode != "" {
   151  			return translatableerror.ArgumentCombinationError{Args: []string{"--sso-passcode", "--sso"}}
   152  		}
   153  		authErr = cmd.authenticateSSO()
   154  	} else {
   155  		authErr = cmd.authenticate()
   156  	}
   157  
   158  	if authErr != nil {
   159  		return errors.New("Unable to authenticate.")
   160  	}
   161  
   162  	if cmd.Organization != "" {
   163  		org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.Organization)
   164  		cmd.UI.DisplayWarnings(warnings)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		cmd.Config.SetOrganizationInformation(org.GUID, org.Name)
   169  	} else {
   170  		orgs, warnings, err := cmd.Actor.GetOrganizations()
   171  		cmd.UI.DisplayWarnings(warnings)
   172  		if err != nil {
   173  			return err
   174  		}
   175  		switch {
   176  		case len(orgs) == 1:
   177  			cmd.Config.SetOrganizationInformation(orgs[0].GUID, orgs[0].Name)
   178  		case len(orgs) > 1:
   179  			var emptyOrg v3action.Organization
   180  			chosenOrg, err := cmd.promptChosenOrg(orgs)
   181  			if err != nil {
   182  				return err
   183  			}
   184  			if chosenOrg != emptyOrg {
   185  				cmd.Config.SetOrganizationInformation(chosenOrg.GUID, chosenOrg.Name)
   186  			}
   187  		}
   188  	}
   189  
   190  	targetedOrg := cmd.Config.TargetedOrganization()
   191  
   192  	if targetedOrg.GUID != "" {
   193  
   194  		cmd.UI.DisplayTextWithFlavor("Targeted org: {{.Organization}}", map[string]interface{}{
   195  			"Organization": cmd.Config.TargetedOrganizationName(),
   196  		})
   197  
   198  		if cmd.Space != "" {
   199  			space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, targetedOrg.GUID)
   200  			cmd.UI.DisplayWarnings(warnings)
   201  			if err != nil {
   202  				return err
   203  			}
   204  			// the "AllowSSH" field is not returned by v3, and is never read from the config.
   205  			// persist `true` to maintain compatibility in the config file.
   206  			// TODO: this field should be removed entirely in v7
   207  			cmd.Config.SetSpaceInformation(space.GUID, space.Name, true)
   208  
   209  			cmd.UI.DisplayNewline()
   210  			cmd.UI.DisplayTextWithFlavor("Targeted space: {{.Space}}", map[string]interface{}{
   211  				"Space": space.Name,
   212  			})
   213  		}
   214  		cmd.UI.DisplayNewline()
   215  	}
   216  
   217  	err = cmd.checkMinCLIVersion()
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	cmd.UI.DisplayNewline()
   223  	cmd.UI.DisplayNewline()
   224  
   225  	return nil
   226  }
   227  
   228  func (cmd *LoginCommand) getAPI() error {
   229  	if cmd.APIEndpoint != "" {
   230  		cmd.UI.DisplayTextWithFlavor("API endpoint: {{.APIEndpoint}}", map[string]interface{}{
   231  			"APIEndpoint": cmd.APIEndpoint,
   232  		})
   233  	} else if cmd.Config.Target() != "" {
   234  		cmd.APIEndpoint = cmd.Config.Target()
   235  		cmd.UI.DisplayTextWithFlavor("API endpoint: {{.APIEndpoint}}", map[string]interface{}{
   236  			"APIEndpoint": cmd.APIEndpoint,
   237  		})
   238  	} else {
   239  		apiEndpoint, err := cmd.UI.DisplayTextPrompt("API endpoint")
   240  		if err != nil {
   241  			return err
   242  		}
   243  		cmd.APIEndpoint = apiEndpoint
   244  	}
   245  	return nil
   246  }
   247  
   248  func (cmd *LoginCommand) retargetAPI() error {
   249  	strippedEndpoint := strings.TrimRight(cmd.APIEndpoint, "/")
   250  	endpoint, _ := url.Parse(strippedEndpoint)
   251  	if endpoint.Scheme == "" {
   252  		endpoint.Scheme = "https"
   253  	}
   254  
   255  	settings := v3action.TargetSettings{
   256  		URL:               endpoint.String(),
   257  		SkipSSLValidation: cmd.Config.SkipSSLValidation() || cmd.SkipSSLValidation,
   258  	}
   259  	_, err := cmd.Actor.SetTarget(settings)
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	return cmd.reloadActor()
   265  }
   266  
   267  func (cmd *LoginCommand) authenticate() error {
   268  	prompts := cmd.Actor.GetLoginPrompts()
   269  	credentials := make(map[string]string)
   270  
   271  	if value, ok := prompts["username"]; ok {
   272  		if prompts["username"].Type == coreconfig.AuthPromptTypeText && cmd.Username != "" {
   273  			credentials["username"] = cmd.Username
   274  		} else {
   275  			var err error
   276  			credentials["username"], err = cmd.UI.DisplayTextPrompt(value.DisplayName)
   277  			if err != nil {
   278  				return err
   279  			}
   280  			cmd.UI.DisplayNewline()
   281  		}
   282  	}
   283  
   284  	passwordKeys := []string{}
   285  	for key, prompt := range prompts {
   286  		if prompt.Type == coreconfig.AuthPromptTypePassword {
   287  			if key == "passcode" || key == "password" {
   288  				continue
   289  			}
   290  
   291  			passwordKeys = append(passwordKeys, key)
   292  		} else if key == "username" {
   293  			continue
   294  		} else {
   295  			var err error
   296  			credentials[key], err = cmd.UI.DisplayTextPrompt(prompt.DisplayName)
   297  			if err != nil {
   298  				return err
   299  			}
   300  			cmd.UI.DisplayNewline()
   301  		}
   302  	}
   303  
   304  	var err error
   305  	for i := 0; i < maxLoginTries; i++ {
   306  		var promptedCredentials map[string]string
   307  		promptedCredentials, err = cmd.passwordPrompts(prompts, credentials, passwordKeys)
   308  		if err != nil {
   309  			return err
   310  		}
   311  
   312  		cmd.UI.DisplayText("Authenticating...")
   313  
   314  		err = cmd.Actor.Authenticate(promptedCredentials, "", constant.GrantTypePassword)
   315  
   316  		if err != nil {
   317  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   318  			cmd.UI.DisplayNewline()
   319  
   320  			if _, ok := err.(uaa.AccountLockedError); ok {
   321  				break
   322  			}
   323  		}
   324  
   325  		if err == nil {
   326  			cmd.UI.DisplayOK()
   327  			break
   328  		}
   329  	}
   330  	if err != nil {
   331  		return err
   332  	}
   333  	return nil
   334  }
   335  
   336  func (cmd *LoginCommand) authenticateSSO() error {
   337  	prompts := cmd.Actor.GetLoginPrompts()
   338  	credentials := make(map[string]string)
   339  
   340  	var err error
   341  	for i := 0; i < maxLoginTries; i++ {
   342  		if len(cmd.SSOPasscode) > 0 {
   343  			credentials["passcode"] = cmd.SSOPasscode
   344  			cmd.SSOPasscode = ""
   345  		} else {
   346  			credentials["passcode"], err = cmd.UI.DisplayPasswordPrompt(prompts["passcode"].DisplayName)
   347  			if err != nil {
   348  				return err
   349  			}
   350  		}
   351  
   352  		credentialsCopy := make(map[string]string, len(credentials))
   353  		for k, v := range credentials {
   354  			credentialsCopy[k] = v
   355  		}
   356  
   357  		cmd.UI.DisplayText("Authenticating...")
   358  		err = cmd.Actor.Authenticate(credentialsCopy, "", constant.GrantTypePassword)
   359  
   360  		if err != nil {
   361  			cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error())
   362  			cmd.UI.DisplayNewline()
   363  		}
   364  
   365  		if err == nil {
   366  			cmd.UI.DisplayOK()
   367  			cmd.UI.DisplayNewline()
   368  			break
   369  		}
   370  	}
   371  	if err != nil {
   372  		return err
   373  	}
   374  	return nil
   375  }
   376  
   377  func (cmd *LoginCommand) checkMinCLIVersion() error {
   378  	newChecker, err := cmd.CheckerMaker.NewVersionChecker(cmd.Config, cmd.UI, true)
   379  	if err != nil {
   380  		return err
   381  	}
   382  
   383  	cmd.Checker = newChecker
   384  	cmd.Config.SetMinCLIVersion(cmd.Checker.MinCLIVersion())
   385  	return command.WarnIfCLIVersionBelowAPIDefinedMinimum(cmd.Config, cmd.Checker.CloudControllerAPIVersion(), cmd.UI)
   386  }
   387  
   388  func (cmd *LoginCommand) passwordPrompts(prompts map[string]coreconfig.AuthPrompt, credentials map[string]string, passwordKeys []string) (map[string]string, error) {
   389  	// ensure that password gets prompted before other codes (eg. mfa code)
   390  	var err error
   391  	if passPrompt, ok := prompts["password"]; ok {
   392  		if cmd.Password != "" {
   393  			credentials["password"] = cmd.Password
   394  			cmd.Password = ""
   395  		} else {
   396  			credentials["password"], err = cmd.UI.DisplayPasswordPrompt(passPrompt.DisplayName)
   397  			if err != nil {
   398  				return nil, err
   399  			}
   400  		}
   401  	}
   402  
   403  	for _, key := range passwordKeys {
   404  		cmd.UI.DisplayNewline()
   405  		credentials[key], err = cmd.UI.DisplayPasswordPrompt(prompts[key].DisplayName)
   406  		if err != nil {
   407  			return nil, err
   408  		}
   409  	}
   410  
   411  	credentialsCopy := make(map[string]string, len(credentials))
   412  	for k, v := range credentials {
   413  		credentialsCopy[k] = v
   414  	}
   415  
   416  	return credentialsCopy, nil
   417  }
   418  
   419  func (cmd *LoginCommand) reloadActor() error {
   420  	newActor, err := cmd.ActorMaker.NewActor(cmd.Config, cmd.UI, true)
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	cmd.Actor = newActor
   426  
   427  	return nil
   428  }
   429  
   430  func (cmd *LoginCommand) showStatus() {
   431  	tableContent := [][]string{
   432  		{
   433  			cmd.UI.TranslateText("API endpoint:"),
   434  			cmd.UI.TranslateText("{{.APIEndpoint}} (API version: {{.APIVersion}})",
   435  				map[string]interface{}{
   436  					"APIEndpoint": strings.TrimRight(cmd.APIEndpoint, "/"),
   437  					"APIVersion":  cmd.Config.APIVersion(),
   438  				}),
   439  		},
   440  	}
   441  
   442  	user, err := cmd.Config.CurrentUserName()
   443  	if user == "" || err != nil {
   444  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   445  		cmd.displayNotLoggedIn()
   446  		return
   447  	}
   448  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("User:"), user})
   449  
   450  	orgName := cmd.Config.TargetedOrganizationName()
   451  	if orgName == "" {
   452  		cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   453  		cmd.displayNotTargetted()
   454  		return
   455  	}
   456  	tableContent = append(tableContent, []string{cmd.UI.TranslateText("Org:"), orgName})
   457  
   458  	spaceName := cmd.Config.TargetedSpace().Name
   459  	if spaceName == "" {
   460  		tableContent = append(tableContent, []string{cmd.UI.TranslateText("Space:"),
   461  			cmd.UI.TranslateText("No space targeted, use '{{.Command}}'", map[string]interface{}{
   462  				"Command": fmt.Sprintf("%s target -s SPACE", cmd.Config.BinaryName()),
   463  			})})
   464  	} else {
   465  		tableContent = append(tableContent, []string{cmd.UI.TranslateText("Space:"), spaceName})
   466  	}
   467  
   468  	cmd.UI.DisplayKeyValueTable("", tableContent, 3)
   469  }
   470  
   471  func (cmd *LoginCommand) displayNotLoggedIn() {
   472  	cmd.UI.DisplayText(
   473  		"Not logged in. Use '{{.CFLoginCommand}}' to log in.",
   474  		map[string]interface{}{
   475  			"CFLoginCommand": fmt.Sprintf("%s login", cmd.Config.BinaryName()),
   476  		},
   477  	)
   478  }
   479  
   480  func (cmd *LoginCommand) displayNotTargetted() {
   481  	cmd.UI.DisplayText("No org or space targeted, use '{{.CFTargetCommand}} -o ORG -s SPACE'",
   482  		map[string]interface{}{
   483  			"CFTargetCommand": fmt.Sprintf("%s target", cmd.Config.BinaryName()),
   484  		},
   485  	)
   486  }
   487  
   488  func (cmd *LoginCommand) promptChosenOrg(orgs []v3action.Organization) (v3action.Organization, error) {
   489  
   490  	var (
   491  		chosenOrgName string
   492  		err           error
   493  	)
   494  
   495  	if len(orgs) < 50 {
   496  		orgNames := make([]string, len(orgs))
   497  		for i, org := range orgs {
   498  			orgNames[i] = org.Name
   499  		}
   500  
   501  		for {
   502  			cmd.UI.DisplayText("Select an org:")
   503  			chosenOrgName, err = cmd.UI.DisplayTextMenu(orgNames, "Org")
   504  			if err != ui.ErrInvalidIndex {
   505  				break
   506  			}
   507  		}
   508  
   509  		if err != nil {
   510  			if invalidChoice, ok := err.(ui.InvalidChoiceError); ok {
   511  				return v3action.Organization{}, translatableerror.OrganizationNotFoundError{Name: invalidChoice.Choice}
   512  			} else if err == io.EOF {
   513  				return v3action.Organization{}, nil
   514  			}
   515  
   516  			return v3action.Organization{}, err
   517  		}
   518  
   519  		if chosenOrgName == "" {
   520  			return v3action.Organization{}, nil
   521  		}
   522  
   523  	} else {
   524  		cmd.UI.DisplayText("Select an org:")
   525  		cmd.UI.DisplayText("There are too many options to display; please type in the name.")
   526  		defaultChoice := "enter to skip"
   527  		chosenOrgName, err = cmd.UI.DisplayOptionalTextPrompt(defaultChoice, "Org")
   528  		if chosenOrgName == defaultChoice {
   529  			return v3action.Organization{}, nil
   530  		}
   531  	}
   532  
   533  	if err != nil {
   534  		return v3action.Organization{}, err
   535  	}
   536  
   537  	for _, org := range orgs {
   538  		if org.Name == chosenOrgName {
   539  			return org, nil
   540  		}
   541  	}
   542  
   543  	return v3action.Organization{}, translatableerror.OrganizationNotFoundError{Name: chosenOrgName}
   544  }