github.com/rakutentech/cli@v6.12.5-0.20151006231303-24468b65536e+incompatible/cf/commands/login.go (about)

     1  package commands
     2  
     3  import (
     4  	"strconv"
     5  
     6  	"github.com/cloudfoundry/cli/cf/command_registry"
     7  	. "github.com/cloudfoundry/cli/cf/i18n"
     8  	"github.com/cloudfoundry/cli/flags"
     9  	"github.com/cloudfoundry/cli/flags/flag"
    10  
    11  	"github.com/cloudfoundry/cli/cf/api"
    12  	"github.com/cloudfoundry/cli/cf/api/authentication"
    13  	"github.com/cloudfoundry/cli/cf/api/organizations"
    14  	"github.com/cloudfoundry/cli/cf/api/spaces"
    15  	"github.com/cloudfoundry/cli/cf/configuration/core_config"
    16  	"github.com/cloudfoundry/cli/cf/models"
    17  	"github.com/cloudfoundry/cli/cf/requirements"
    18  	"github.com/cloudfoundry/cli/cf/terminal"
    19  )
    20  
    21  const maxLoginTries = 3
    22  const maxChoices = 50
    23  
    24  type Login struct {
    25  	ui            terminal.UI
    26  	config        core_config.ReadWriter
    27  	authenticator authentication.AuthenticationRepository
    28  	endpointRepo  api.EndpointRepository
    29  	orgRepo       organizations.OrganizationRepository
    30  	spaceRepo     spaces.SpaceRepository
    31  }
    32  
    33  func init() {
    34  	command_registry.Register(&Login{})
    35  }
    36  
    37  func (cmd *Login) MetaData() command_registry.CommandMetadata {
    38  	fs := make(map[string]flags.FlagSet)
    39  	fs["a"] = &cliFlags.StringFlag{Name: "a", Usage: T("API endpoint (e.g. https://api.example.com)")}
    40  	fs["u"] = &cliFlags.StringFlag{Name: "u", Usage: T("Username")}
    41  	fs["p"] = &cliFlags.StringFlag{Name: "p", Usage: T("Password")}
    42  	fs["o"] = &cliFlags.StringFlag{Name: "o", Usage: T("Org")}
    43  	fs["s"] = &cliFlags.StringFlag{Name: "s", Usage: T("Space")}
    44  	fs["sso"] = &cliFlags.BoolFlag{Name: "sso", Usage: T("Use a one-time password to login")}
    45  	fs["skip-ssl-validation"] = &cliFlags.BoolFlag{Name: "skip-ssl-validation", Usage: T("Please don't")}
    46  
    47  	return command_registry.CommandMetadata{
    48  		Name:        "login",
    49  		ShortName:   "l",
    50  		Description: T("Log user in"),
    51  		Usage: T("CF_NAME login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE]\n\n") +
    52  			terminal.WarningColor(T("WARNING:\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\n")) + T("EXAMPLE:\n") + T("   CF_NAME login (omit username and password to login interactively -- CF_NAME will prompt for both)\n") + T("   CF_NAME login -u name@example.com -p pa55woRD (specify username and password as arguments)\n") + T("   CF_NAME login -u name@example.com -p \"my password\" (use quotes for passwords with a space)\n") + T("   CF_NAME login -u name@example.com -p \"\\\"password\\\"\" (escape quotes if used in password)\n") + T("   CF_NAME login --sso (CF_NAME will provide a url to obtain a one-time password to login)"),
    53  		Flags: fs,
    54  	}
    55  }
    56  
    57  func (cmd *Login) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) {
    58  	return
    59  }
    60  
    61  func (cmd *Login) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command {
    62  	cmd.ui = deps.Ui
    63  	cmd.config = deps.Config
    64  	cmd.authenticator = deps.RepoLocator.GetAuthenticationRepository()
    65  	cmd.endpointRepo = deps.RepoLocator.GetEndpointRepository()
    66  	cmd.orgRepo = deps.RepoLocator.GetOrganizationRepository()
    67  	cmd.spaceRepo = deps.RepoLocator.GetSpaceRepository()
    68  	return cmd
    69  }
    70  
    71  func (cmd *Login) Execute(c flags.FlagContext) {
    72  	cmd.config.ClearSession()
    73  
    74  	endpoint, skipSSL := cmd.decideEndpoint(c)
    75  
    76  	Api{
    77  		ui:           cmd.ui,
    78  		config:       cmd.config,
    79  		endpointRepo: cmd.endpointRepo,
    80  	}.setApiEndpoint(endpoint, skipSSL, cmd.MetaData().Name)
    81  
    82  	defer func() {
    83  		cmd.ui.Say("")
    84  		cmd.ui.ShowConfiguration(cmd.config)
    85  	}()
    86  
    87  	// We thought we would never need to explicitly branch in this code
    88  	// for anything as simple as authentication, but it turns out that our
    89  	// assumptions did not match reality.
    90  
    91  	// When SAML is enabled (but not configured) then the UAA/Login server
    92  	// will always returns password prompts that includes the Passcode field.
    93  	// Users can authenticate with:
    94  	//   EITHER   username and password
    95  	//   OR       a one-time passcode
    96  
    97  	if c.Bool("sso") {
    98  		cmd.authenticateSSO(c)
    99  	} else {
   100  		cmd.authenticate(c)
   101  	}
   102  
   103  	orgIsSet := cmd.setOrganization(c)
   104  
   105  	if orgIsSet {
   106  		cmd.setSpace(c)
   107  	}
   108  	cmd.ui.NotifyUpdateIfNeeded(cmd.config)
   109  }
   110  
   111  func (cmd Login) decideEndpoint(c flags.FlagContext) (string, bool) {
   112  	endpoint := c.String("a")
   113  	skipSSL := c.Bool("skip-ssl-validation")
   114  	if endpoint == "" {
   115  		endpoint = cmd.config.ApiEndpoint()
   116  		skipSSL = cmd.config.IsSSLDisabled() || skipSSL
   117  	}
   118  
   119  	if endpoint == "" {
   120  		endpoint = cmd.ui.Ask(T("API endpoint"))
   121  	} else {
   122  		cmd.ui.Say(T("API endpoint: {{.Endpoint}}", map[string]interface{}{"Endpoint": terminal.EntityNameColor(endpoint)}))
   123  	}
   124  
   125  	return endpoint, skipSSL
   126  }
   127  
   128  func (cmd Login) authenticateSSO(c flags.FlagContext) {
   129  	prompts, err := cmd.authenticator.GetLoginPromptsAndSaveUAAServerURL()
   130  	if err != nil {
   131  		cmd.ui.Failed(err.Error())
   132  	}
   133  
   134  	credentials := make(map[string]string)
   135  	passcode := prompts["passcode"]
   136  
   137  	for i := 0; i < maxLoginTries; i++ {
   138  		credentials["passcode"] = cmd.ui.AskForPassword("%s", passcode.DisplayName)
   139  
   140  		cmd.ui.Say(T("Authenticating..."))
   141  		err = cmd.authenticator.Authenticate(credentials)
   142  
   143  		if err == nil {
   144  			cmd.ui.Ok()
   145  			cmd.ui.Say("")
   146  			break
   147  		}
   148  
   149  		cmd.ui.Say(err.Error())
   150  	}
   151  
   152  	if err != nil {
   153  		cmd.ui.Failed(T("Unable to authenticate."))
   154  	}
   155  }
   156  
   157  func (cmd Login) authenticate(c flags.FlagContext) {
   158  	usernameFlagValue := c.String("u")
   159  	passwordFlagValue := c.String("p")
   160  
   161  	prompts, err := cmd.authenticator.GetLoginPromptsAndSaveUAAServerURL()
   162  	if err != nil {
   163  		cmd.ui.Failed(err.Error())
   164  	}
   165  	passwordKeys := []string{}
   166  	credentials := make(map[string]string)
   167  
   168  	if value, ok := prompts["username"]; ok {
   169  		if prompts["username"].Type == core_config.AuthPromptTypeText && usernameFlagValue != "" {
   170  			credentials["username"] = usernameFlagValue
   171  		} else {
   172  			credentials["username"] = cmd.ui.Ask("%s", value.DisplayName)
   173  		}
   174  	}
   175  
   176  	for key, prompt := range prompts {
   177  		if prompt.Type == core_config.AuthPromptTypePassword {
   178  			if key == "passcode" {
   179  				continue
   180  			}
   181  
   182  			passwordKeys = append(passwordKeys, key)
   183  		} else if key == "username" {
   184  			continue
   185  		} else {
   186  			credentials[key] = cmd.ui.Ask("%s", prompt.DisplayName)
   187  		}
   188  	}
   189  
   190  	for i := 0; i < maxLoginTries; i++ {
   191  		for _, key := range passwordKeys {
   192  			if key == "password" && passwordFlagValue != "" {
   193  				credentials[key] = passwordFlagValue
   194  				passwordFlagValue = ""
   195  			} else {
   196  				credentials[key] = cmd.ui.AskForPassword("%s", prompts[key].DisplayName)
   197  			}
   198  		}
   199  
   200  		cmd.ui.Say(T("Authenticating..."))
   201  		err = cmd.authenticator.Authenticate(credentials)
   202  
   203  		if err == nil {
   204  			cmd.ui.Ok()
   205  			cmd.ui.Say("")
   206  			break
   207  		}
   208  
   209  		cmd.ui.Say(err.Error())
   210  	}
   211  
   212  	if err != nil {
   213  		cmd.ui.Failed(T("Unable to authenticate."))
   214  	}
   215  }
   216  
   217  func (cmd Login) setOrganization(c flags.FlagContext) (isOrgSet bool) {
   218  	orgName := c.String("o")
   219  
   220  	if orgName == "" {
   221  		availableOrgs := []models.Organization{}
   222  		orgs, apiErr := cmd.orgRepo.ListOrgs()
   223  		if apiErr != nil {
   224  			cmd.ui.Failed(T("Error finding available orgs\n{{.ApiErr}}",
   225  				map[string]interface{}{"ApiErr": apiErr.Error()}))
   226  		}
   227  		for _, org := range orgs {
   228  			if len(availableOrgs) < maxChoices {
   229  				availableOrgs = append(availableOrgs, org)
   230  			}
   231  		}
   232  
   233  		if len(availableOrgs) == 0 {
   234  			return false
   235  		} else if len(availableOrgs) == 1 {
   236  			cmd.targetOrganization(availableOrgs[0])
   237  			return true
   238  		} else {
   239  			orgName = cmd.promptForOrgName(availableOrgs)
   240  			if orgName == "" {
   241  				cmd.ui.Say("")
   242  				return false
   243  			}
   244  		}
   245  	}
   246  
   247  	org, err := cmd.orgRepo.FindByName(orgName)
   248  	if err != nil {
   249  		cmd.ui.Failed(T("Error finding org {{.OrgName}}\n{{.Err}}",
   250  			map[string]interface{}{"OrgName": terminal.EntityNameColor(orgName), "Err": err.Error()}))
   251  	}
   252  
   253  	cmd.targetOrganization(org)
   254  	return true
   255  }
   256  
   257  func (cmd Login) promptForOrgName(orgs []models.Organization) string {
   258  	orgNames := []string{}
   259  	for _, org := range orgs {
   260  		orgNames = append(orgNames, org.Name)
   261  	}
   262  
   263  	return cmd.promptForName(orgNames, T("Select an org (or press enter to skip):"), "Org")
   264  }
   265  
   266  func (cmd Login) targetOrganization(org models.Organization) {
   267  	cmd.config.SetOrganizationFields(org.OrganizationFields)
   268  	cmd.ui.Say(T("Targeted org {{.OrgName}}\n",
   269  		map[string]interface{}{"OrgName": terminal.EntityNameColor(org.Name)}))
   270  }
   271  
   272  func (cmd Login) setSpace(c flags.FlagContext) {
   273  	spaceName := c.String("s")
   274  
   275  	if spaceName == "" {
   276  		var availableSpaces []models.Space
   277  		err := cmd.spaceRepo.ListSpaces(func(space models.Space) bool {
   278  			availableSpaces = append(availableSpaces, space)
   279  			return (len(availableSpaces) < maxChoices)
   280  		})
   281  		if err != nil {
   282  			cmd.ui.Failed(T("Error finding available spaces\n{{.Err}}",
   283  				map[string]interface{}{"Err": err.Error()}))
   284  		}
   285  
   286  		if len(availableSpaces) == 0 {
   287  			return
   288  		} else if len(availableSpaces) == 1 {
   289  			cmd.targetSpace(availableSpaces[0])
   290  			return
   291  		} else {
   292  			spaceName = cmd.promptForSpaceName(availableSpaces)
   293  			if spaceName == "" {
   294  				cmd.ui.Say("")
   295  				return
   296  			}
   297  		}
   298  	}
   299  
   300  	space, err := cmd.spaceRepo.FindByName(spaceName)
   301  	if err != nil {
   302  		cmd.ui.Failed(T("Error finding space {{.SpaceName}}\n{{.Err}}",
   303  			map[string]interface{}{"SpaceName": terminal.EntityNameColor(spaceName), "Err": err.Error()}))
   304  	}
   305  
   306  	cmd.targetSpace(space)
   307  }
   308  
   309  func (cmd Login) promptForSpaceName(spaces []models.Space) string {
   310  	spaceNames := []string{}
   311  	for _, space := range spaces {
   312  		spaceNames = append(spaceNames, space.Name)
   313  	}
   314  
   315  	return cmd.promptForName(spaceNames, T("Select a space (or press enter to skip):"), "Space")
   316  }
   317  
   318  func (cmd Login) targetSpace(space models.Space) {
   319  	cmd.config.SetSpaceFields(space.SpaceFields)
   320  	cmd.ui.Say(T("Targeted space {{.SpaceName}}\n",
   321  		map[string]interface{}{"SpaceName": terminal.EntityNameColor(space.Name)}))
   322  }
   323  
   324  func (cmd Login) promptForName(names []string, listPrompt, itemPrompt string) string {
   325  	nameIndex := 0
   326  	var nameString string
   327  	for nameIndex < 1 || nameIndex > len(names) {
   328  		var err error
   329  
   330  		// list header
   331  		cmd.ui.Say(listPrompt)
   332  
   333  		// only display list if it is shorter than maxChoices
   334  		if len(names) < maxChoices {
   335  			for i, name := range names {
   336  				cmd.ui.Say("%d. %s", i+1, name)
   337  			}
   338  		} else {
   339  			cmd.ui.Say(T("There are too many options to display, please type in the name."))
   340  		}
   341  
   342  		nameString = cmd.ui.Ask("%s", itemPrompt)
   343  		if nameString == "" {
   344  			return ""
   345  		}
   346  
   347  		nameIndex, err = strconv.Atoi(nameString)
   348  
   349  		if err != nil {
   350  			nameIndex = 1
   351  			return nameString
   352  		}
   353  	}
   354  
   355  	return names[nameIndex-1]
   356  }