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 }