github.com/thanhphan1147/cloudfoundry-cli@v7.1.0+incompatible/command/v7/login_command.go (about) 1 package v7 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/resources" 12 "code.cloudfoundry.org/cli/util/ui" 13 "code.cloudfoundry.org/clock" 14 15 "code.cloudfoundry.org/cli/actor/actionerror" 16 "code.cloudfoundry.org/cli/actor/v7action" 17 "code.cloudfoundry.org/cli/api/uaa/constant" 18 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 19 "code.cloudfoundry.org/cli/command" 20 "code.cloudfoundry.org/cli/command/translatableerror" 21 "code.cloudfoundry.org/cli/command/v7/shared" 22 ) 23 24 //go:generate counterfeiter . ActorReloader 25 26 type ActorReloader interface { 27 Reload(command.Config, command.UI) (Actor, error) 28 } 29 30 type ActualActorReloader struct{} 31 32 func (a ActualActorReloader) Reload(config command.Config, ui command.UI) (Actor, error) { 33 ccClient, uaaClient, routingClient, err := shared.GetNewClientsAndConnectToCF(config, ui, "") 34 if err != nil { 35 return nil, err 36 } 37 38 return v7action.NewActor(ccClient, config, nil, uaaClient, routingClient, clock.NewClock()), nil 39 } 40 41 const maxLoginTries = 3 42 43 type LoginCommand struct { 44 UI command.UI 45 Actor Actor 46 Config command.Config 47 ActorReloader ActorReloader 48 49 APIEndpoint string `short:"a" description:"API endpoint (e.g. https://api.example.com)"` 50 Organization string `short:"o" description:"Org"` 51 Password string `short:"p" description:"Password"` 52 Space string `short:"s" description:"Space"` 53 SkipSSLValidation bool `long:"skip-ssl-validation" description:"Skip verification of the API endpoint. Not recommended!"` 54 SSO bool `long:"sso" description:"Prompt for a one-time passcode to login"` 55 SSOPasscode string `long:"sso-passcode" description:"One-time passcode"` 56 Username string `short:"u" description:"Username"` 57 Origin string `long:"origin" description:"Indicates the identity provider to be used for login"` 58 usage interface{} `usage:"CF_NAME login [-a API_URL] [-u USERNAME] [-p PASSWORD] [-o ORG] [-s SPACE] [--sso | --sso-passcode PASSCODE] [--origin ORIGIN]\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)\n CF_NAME login --origin ldap"` 59 relatedCommands interface{} `related_commands:"api, auth, target"` 60 } 61 62 func (cmd *LoginCommand) Setup(config command.Config, ui command.UI) error { 63 ccClient, _ := shared.NewWrappedCloudControllerClient(config, ui) 64 cmd.Actor = v7action.NewActor(ccClient, config, nil, nil, nil, clock.NewClock()) 65 cmd.ActorReloader = ActualActorReloader{} 66 67 cmd.UI = ui 68 cmd.Config = config 69 return nil 70 } 71 72 func (cmd *LoginCommand) Execute(args []string) error { 73 if cmd.Config.UAAGrantType() == string(constant.GrantTypeClientCredentials) { 74 return translatableerror.PasswordGrantTypeLogoutRequiredError{} 75 } 76 77 if cmd.Config.UAAOAuthClient() != "cf" || cmd.Config.UAAOAuthClientSecret() != "" { 78 return translatableerror.ManualClientCredentialsError{} 79 } 80 81 err := cmd.validateFlags() 82 if err != nil { 83 return err 84 } 85 86 endpoint, err := cmd.determineAPIEndpoint() 87 if err != nil { 88 return err 89 } 90 91 err = cmd.targetAPI(endpoint) 92 if err != nil { 93 translatedErr := translatableerror.ConvertToTranslatableError(err) 94 if invalidSSLErr, ok := translatedErr.(translatableerror.InvalidSSLCertError); ok { 95 invalidSSLErr.SuggestedCommand = "login" 96 return invalidSSLErr 97 } 98 return err 99 } 100 101 versionWarning, err := shared.CheckCCAPIVersion(cmd.Config.APIVersion()) 102 if err != nil { 103 cmd.UI.DisplayWarning("Warning: unable to determine whether targeted API's version meets minimum supported.") 104 } 105 if versionWarning != "" { 106 cmd.UI.DisplayWarning(versionWarning) 107 } 108 109 cmd.UI.DisplayNewline() 110 111 cmd.Actor, err = cmd.ActorReloader.Reload(cmd.Config, cmd.UI) 112 if err != nil { 113 return err 114 } 115 116 defer cmd.showStatus() 117 118 var authErr error 119 if cmd.SSO || cmd.SSOPasscode != "" { 120 authErr = cmd.authenticateSSO() 121 } else { 122 authErr = cmd.authenticate() 123 } 124 125 if authErr != nil { 126 return errors.New("Unable to authenticate.") 127 } 128 129 err = cmd.Config.WriteConfig() 130 if err != nil { 131 return fmt.Errorf("Error writing config: %s", err.Error()) 132 } 133 134 if cmd.Organization != "" { 135 org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.Organization) 136 cmd.UI.DisplayWarnings(warnings) 137 if err != nil { 138 return err 139 } 140 141 cmd.Config.SetOrganizationInformation(org.GUID, org.Name) 142 } else { 143 orgs, warnings, err := cmd.Actor.GetOrganizations("") 144 cmd.UI.DisplayWarnings(warnings) 145 if err != nil { 146 return err 147 } 148 149 filteredOrgs, err := cmd.filterOrgsForSpace(orgs) 150 if err != nil { 151 return err 152 } 153 154 if len(filteredOrgs) == 1 { 155 cmd.Config.SetOrganizationInformation(filteredOrgs[0].GUID, filteredOrgs[0].Name) 156 } else if len(filteredOrgs) > 1 { 157 chosenOrg, err := cmd.promptChosenOrg(filteredOrgs) 158 if err != nil { 159 return err 160 } 161 162 if chosenOrg.GUID != "" { 163 cmd.Config.SetOrganizationInformation(chosenOrg.GUID, chosenOrg.Name) 164 } 165 } 166 } 167 168 targetedOrg := cmd.Config.TargetedOrganization() 169 170 if targetedOrg.GUID != "" { 171 cmd.UI.DisplayTextWithFlavor("Targeted org {{.Organization}}.", map[string]interface{}{ 172 "Organization": cmd.Config.TargetedOrganizationName(), 173 }) 174 cmd.UI.DisplayNewline() 175 176 if cmd.Space != "" { 177 space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, targetedOrg.GUID) 178 cmd.UI.DisplayWarnings(warnings) 179 if err != nil { 180 return err 181 } 182 cmd.targetSpace(space) 183 } else { 184 spaces, warnings, err := cmd.Actor.GetOrganizationSpaces(targetedOrg.GUID) 185 cmd.UI.DisplayWarnings(warnings) 186 if err != nil { 187 return err 188 } 189 190 if len(spaces) == 1 { 191 cmd.targetSpace(spaces[0]) 192 } else if len(spaces) > 1 { 193 chosenSpace, err := cmd.promptChosenSpace(spaces) 194 if err != nil { 195 return err 196 } 197 if chosenSpace.Name != "" { 198 cmd.targetSpace(chosenSpace) 199 } 200 } 201 } 202 } 203 204 return nil 205 } 206 207 func (cmd *LoginCommand) determineAPIEndpoint() (v7action.TargetSettings, error) { 208 endpoint := cmd.APIEndpoint 209 skipSSLValidation := cmd.SkipSSLValidation 210 211 var configTarget = cmd.Config.Target() 212 213 if endpoint == "" && configTarget != "" { 214 endpoint = configTarget 215 skipSSLValidation = cmd.Config.SkipSSLValidation() || cmd.SkipSSLValidation 216 } 217 218 if len(endpoint) > 0 { 219 cmd.UI.DisplayTextWithFlavor("API endpoint: {{.APIEndpoint}}", map[string]interface{}{ 220 "APIEndpoint": endpoint, 221 }) 222 } else { 223 userInput, err := cmd.UI.DisplayTextPrompt("API endpoint") 224 if err != nil { 225 return v7action.TargetSettings{}, err 226 } 227 endpoint = userInput 228 } 229 230 strippedEndpoint := strings.TrimRight(endpoint, "/") 231 parsedURL, err := url.Parse(strippedEndpoint) 232 if err != nil { 233 return v7action.TargetSettings{}, err 234 } 235 if parsedURL.Scheme == "" { 236 parsedURL.Scheme = "https" 237 } 238 239 return v7action.TargetSettings{URL: parsedURL.String(), SkipSSLValidation: skipSSLValidation}, nil 240 } 241 242 func (cmd *LoginCommand) targetAPI(settings v7action.TargetSettings) error { 243 warnings, err := cmd.Actor.SetTarget(settings) 244 cmd.UI.DisplayWarnings(warnings) 245 if err != nil { 246 return err 247 } 248 249 if strings.HasPrefix(settings.URL, "http:") { 250 cmd.UI.DisplayWarning("Warning: Insecure http API endpoint detected: secure https API endpoints are recommended") 251 } 252 253 return nil 254 } 255 256 func (cmd *LoginCommand) authenticate() error { 257 var err error 258 var credentials = make(map[string]string) 259 260 prompts := cmd.Actor.GetLoginPrompts() 261 nonPasswordPrompts, passwordPrompts := cmd.groupPrompts(prompts) 262 263 if value, ok := prompts["username"]; ok { 264 credentials["username"], err = cmd.getFlagValOrPrompt(&cmd.Username, value, true) 265 if err != nil { 266 return err 267 } 268 } 269 270 for key, prompt := range nonPasswordPrompts { 271 credentials[key], err = cmd.UI.DisplayTextPrompt(prompt.DisplayName) 272 if err != nil { 273 return err 274 } 275 } 276 277 for i := 0; i < maxLoginTries; i++ { 278 // ensure that password gets prompted before other codes (eg. mfa code) 279 if prompt, ok := prompts["password"]; ok { 280 credentials["password"], err = cmd.getFlagValOrPrompt(&cmd.Password, prompt, false) 281 if err != nil { 282 return err 283 } 284 } 285 286 for key, prompt := range passwordPrompts { 287 credentials[key], err = cmd.UI.DisplayPasswordPrompt(prompt.DisplayName) 288 if err != nil { 289 return err 290 } 291 } 292 293 cmd.UI.DisplayNewline() 294 cmd.UI.DisplayText("Authenticating...") 295 296 err = cmd.Actor.Authenticate(credentials, cmd.Origin, constant.GrantTypePassword) 297 298 if err != nil { 299 cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error()) 300 cmd.UI.DisplayNewline() 301 302 if _, ok := err.(uaa.AccountLockedError); ok { 303 break 304 } 305 } 306 307 if err == nil { 308 cmd.UI.DisplayOK() 309 break 310 } 311 } 312 313 return err 314 } 315 316 func (cmd *LoginCommand) authenticateSSO() error { 317 prompts := cmd.Actor.GetLoginPrompts() 318 319 var err error 320 for i := 0; i < maxLoginTries; i++ { 321 var passcode string 322 323 passcode, err = cmd.getFlagValOrPrompt(&cmd.SSOPasscode, prompts["passcode"], false) 324 if err != nil { 325 return err 326 } 327 328 credentials := map[string]string{"passcode": passcode} 329 330 cmd.UI.DisplayText("Authenticating...") 331 err = cmd.Actor.Authenticate(credentials, "", constant.GrantTypePassword) 332 333 if err != nil { 334 cmd.UI.DisplayWarning(translatableerror.ConvertToTranslatableError(err).Error()) 335 cmd.UI.DisplayNewline() 336 } else { 337 cmd.UI.DisplayOK() 338 cmd.UI.DisplayNewline() 339 break 340 } 341 } 342 return err 343 } 344 345 func (cmd *LoginCommand) groupPrompts(prompts map[string]coreconfig.AuthPrompt) (map[string]coreconfig.AuthPrompt, map[string]coreconfig.AuthPrompt) { 346 var ( 347 nonPasswordPrompts = make(map[string]coreconfig.AuthPrompt) 348 passwordPrompts = make(map[string]coreconfig.AuthPrompt) 349 ) 350 351 for key, prompt := range prompts { 352 if prompt.Type == coreconfig.AuthPromptTypePassword { 353 if key == "passcode" || key == "password" { 354 continue 355 } 356 357 passwordPrompts[key] = prompt 358 } else { 359 if key == "username" { 360 continue 361 } 362 363 nonPasswordPrompts[key] = prompt 364 } 365 } 366 367 return nonPasswordPrompts, passwordPrompts 368 } 369 370 func (cmd *LoginCommand) getFlagValOrPrompt(field *string, prompt coreconfig.AuthPrompt, isText bool) (string, error) { 371 if *field != "" { 372 value := *field 373 *field = "" 374 return value, nil 375 } else { 376 if isText { 377 return cmd.UI.DisplayTextPrompt(prompt.DisplayName) 378 } 379 return cmd.UI.DisplayPasswordPrompt(prompt.DisplayName) 380 } 381 } 382 383 func (cmd *LoginCommand) showStatus() { 384 tableContent := [][]string{ 385 { 386 cmd.UI.TranslateText("API endpoint:"), 387 strings.TrimRight(cmd.Config.Target(), "/"), 388 }, 389 { 390 cmd.UI.TranslateText("API version:"), 391 cmd.Config.APIVersion(), 392 }, 393 } 394 395 user, err := cmd.Config.CurrentUserName() 396 if user == "" || err != nil { 397 cmd.UI.DisplayKeyValueTable("", tableContent, 3) 398 command.DisplayNotLoggedInText(cmd.Config.BinaryName(), cmd.UI) 399 return 400 } 401 tableContent = append(tableContent, []string{cmd.UI.TranslateText("user:"), user}) 402 403 orgName := cmd.Config.TargetedOrganizationName() 404 if orgName == "" { 405 cmd.UI.DisplayKeyValueTable("", tableContent, 3) 406 cmd.UI.DisplayText("No org or space targeted, use '{{.CFTargetCommand}} -o ORG -s SPACE'", 407 map[string]interface{}{ 408 "CFTargetCommand": fmt.Sprintf("%s target", cmd.Config.BinaryName()), 409 }, 410 ) 411 return 412 } 413 tableContent = append(tableContent, []string{cmd.UI.TranslateText("org:"), orgName}) 414 415 spaceContent := cmd.Config.TargetedSpace().Name 416 if spaceContent == "" { 417 spaceContent = cmd.UI.TranslateText("No space targeted, use '{{.Command}}'", 418 map[string]interface{}{ 419 "Command": fmt.Sprintf("%s target -s SPACE", cmd.Config.BinaryName()), 420 }, 421 ) 422 } 423 tableContent = append(tableContent, []string{cmd.UI.TranslateText("space:"), spaceContent}) 424 425 cmd.UI.DisplayKeyValueTable("", tableContent, 3) 426 } 427 428 func (cmd *LoginCommand) filterOrgsForSpace(allOrgs []resources.Organization) ([]resources.Organization, error) { 429 if cmd.Space == "" { 430 return allOrgs, nil 431 } 432 433 var filteredOrgs []resources.Organization 434 for _, org := range allOrgs { 435 _, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.Space, org.GUID) 436 cmd.UI.DisplayWarnings(warnings) 437 if err == nil { 438 filteredOrgs = append(filteredOrgs, org) 439 continue 440 } 441 if _, ok := err.(actionerror.SpaceNotFoundError); !ok { 442 return []resources.Organization{}, err 443 } 444 } 445 446 return filteredOrgs, nil 447 } 448 449 func (cmd *LoginCommand) promptChosenOrg(orgs []resources.Organization) (resources.Organization, error) { 450 orgNames := make([]string, len(orgs)) 451 for i, org := range orgs { 452 orgNames[i] = org.Name 453 } 454 455 chosenOrgName, err := cmd.promptMenu(orgNames, "Select an org:", "Org") 456 457 if err != nil { 458 if invalidChoice, ok := err.(ui.InvalidChoiceError); ok { 459 if cmd.Space != "" { 460 return resources.Organization{}, translatableerror.OrganizationWithSpaceNotFoundError{Name: invalidChoice.Choice, SpaceName: cmd.Space} 461 } 462 return resources.Organization{}, translatableerror.OrganizationNotFoundError{Name: invalidChoice.Choice} 463 } 464 465 if err == io.EOF { 466 return resources.Organization{}, nil 467 } 468 469 return resources.Organization{}, err 470 } 471 472 for _, org := range orgs { 473 if org.Name == chosenOrgName { 474 return org, nil 475 } 476 } 477 478 return resources.Organization{}, nil 479 } 480 481 func (cmd *LoginCommand) promptChosenSpace(spaces []resources.Space) (resources.Space, error) { 482 spaceNames := make([]string, len(spaces)) 483 for i, space := range spaces { 484 spaceNames[i] = space.Name 485 } 486 487 chosenSpaceName, err := cmd.promptMenu(spaceNames, "Select a space:", "Space") 488 if err != nil { 489 if invalidChoice, ok := err.(ui.InvalidChoiceError); ok { 490 return resources.Space{}, translatableerror.SpaceNotFoundError{Name: invalidChoice.Choice} 491 } 492 493 if err == io.EOF { 494 return resources.Space{}, nil 495 } 496 497 return resources.Space{}, err 498 } 499 500 for _, space := range spaces { 501 if space.Name == chosenSpaceName { 502 return space, nil 503 } 504 } 505 return resources.Space{}, nil 506 } 507 508 func (cmd *LoginCommand) promptMenu(choices []string, text string, prompt string) (string, error) { 509 var choice string 510 var err error 511 512 if len(choices) < 50 { 513 for { 514 cmd.UI.DisplayText(text) 515 choice, err = cmd.UI.DisplayTextMenu(choices, prompt) 516 if err != ui.ErrInvalidIndex { 517 break 518 } 519 } 520 } else { 521 cmd.UI.DisplayText(text) 522 cmd.UI.DisplayText("There are too many options to display; please type in the name.") 523 cmd.UI.DisplayNewline() 524 defaultChoice := "enter to skip" 525 choice, err = cmd.UI.DisplayOptionalTextPrompt(defaultChoice, prompt) 526 527 if choice == defaultChoice { 528 return "", nil 529 } 530 if !contains(choices, choice) { 531 return "", ui.InvalidChoiceError{Choice: choice} 532 } 533 } 534 535 return choice, err 536 } 537 538 func (cmd *LoginCommand) targetSpace(space resources.Space) { 539 cmd.Config.SetSpaceInformation(space.GUID, space.Name, true) 540 541 cmd.UI.DisplayTextWithFlavor("Targeted space {{.Space}}.", map[string]interface{}{ 542 "Space": space.Name, 543 }) 544 cmd.UI.DisplayNewline() 545 } 546 547 func (cmd *LoginCommand) validateFlags() error { 548 if cmd.Origin != "" && cmd.SSO { 549 return translatableerror.ArgumentCombinationError{ 550 Args: []string{"--sso", "--origin"}, 551 } 552 } 553 554 if cmd.Origin != "" && cmd.SSOPasscode != "" { 555 return translatableerror.ArgumentCombinationError{ 556 Args: []string{"--sso-passcode", "--origin"}, 557 } 558 } 559 560 if cmd.SSO && cmd.SSOPasscode != "" { 561 return translatableerror.ArgumentCombinationError{ 562 Args: []string{"--sso-passcode", "--sso"}, 563 } 564 } 565 566 return nil 567 } 568 569 func contains(s []string, v string) bool { 570 for _, x := range s { 571 if x == v { 572 return true 573 } 574 } 575 return false 576 }