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