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