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