github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/commands/login.go (about)

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