git.colasdn.top/scaleway/scaleway-cli@v1.11.1/pkg/commands/login.go (about) 1 // Copyright (C) 2015 Scaleway. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE.md file. 4 5 package commands 6 7 import ( 8 "bufio" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "strconv" 15 "strings" 16 17 "github.com/Sirupsen/logrus" 18 "golang.org/x/crypto/ssh/terminal" 19 20 "github.com/scaleway/scaleway-cli/pkg/api" 21 "github.com/scaleway/scaleway-cli/pkg/clilogger" 22 "github.com/scaleway/scaleway-cli/pkg/config" 23 "github.com/scaleway/scaleway-cli/pkg/scwversion" 24 ) 25 26 // LoginArgs are arguments passed to `RunLogin` 27 type LoginArgs struct { 28 Organization string 29 Token string 30 SSHKey string 31 SkipSSHKey bool 32 } 33 34 // selectKey allows to choice a key in ~/.ssh 35 func selectKey(args *LoginArgs) error { 36 home, err := config.GetHomeDir() 37 if err != nil { 38 return err 39 } 40 dir := filepath.Join(home, ".ssh") 41 files, err := ioutil.ReadDir(dir) 42 if err != nil { 43 return fmt.Errorf("Unable to open your ~/.ssh: %v", err) 44 } 45 var pubs []string 46 47 for i := range files { 48 if filepath.Ext(files[i].Name()) == ".pub" { 49 pubs = append(pubs, files[i].Name()) 50 } 51 } 52 if len(pubs) == 0 { 53 return nil 54 } 55 fmt.Println("Do you want to upload an SSH key ?") 56 fmt.Println("[0] I don't want to upload a key !") 57 for i := range pubs { 58 fmt.Printf("[%d] %s\n", i+1, pubs[i]) 59 } 60 for { 61 if err := promptUser("Which [id]: ", &args.SSHKey, true); err != nil { 62 return err 63 } 64 id, err := strconv.ParseUint(strings.TrimSpace(args.SSHKey), 10, 32) 65 if err != nil { 66 fmt.Println(err) 67 continue 68 } 69 if int(id) > len(pubs) { 70 fmt.Println("Out of range id must be lower than", len(pubs)) 71 continue 72 } 73 args.SSHKey = "" 74 if id == 0 { 75 break 76 } 77 buff, err := ioutil.ReadFile(filepath.Join(dir, pubs[id-1])) 78 if err != nil { 79 return fmt.Errorf("Unable to open your key: %v", err) 80 } 81 args.SSHKey = string(buff[:]) 82 break 83 } 84 return nil 85 } 86 87 func getToken(connect api.ScalewayConnect) (string, error) { 88 FakeConnection, err := api.NewScalewayAPI("", "", scwversion.UserAgent(), "", clilogger.SetupLogger) 89 if err != nil { 90 return "", fmt.Errorf("Unable to create a fake ScalewayAPI: %s", err) 91 } 92 FakeConnection.SetPassword(connect.Password) 93 94 resp, err := FakeConnection.PostResponse(api.AccountAPI, "tokens", connect) 95 if resp != nil { 96 defer resp.Body.Close() 97 } 98 if err != nil { 99 return "", fmt.Errorf("unable to connect %v", err) 100 } 101 102 // Succeed POST code 103 if resp.StatusCode != 201 { 104 return "", fmt.Errorf("[%d] maybe your email or your password is not valid", resp.StatusCode) 105 } 106 var data api.ScalewayConnectResponse 107 108 decoder := json.NewDecoder(resp.Body) 109 err = decoder.Decode(&data) 110 if err != nil { 111 return "", err 112 } 113 return data.Token.ID, nil 114 } 115 116 func getOrganization(token string, email string) (string, error) { 117 FakeConnection, err := api.NewScalewayAPI("", token, scwversion.UserAgent(), "", clilogger.SetupLogger) 118 if err != nil { 119 return "", fmt.Errorf("Unable to create a fake ScalewayAPI: %s", err) 120 } 121 data, err := FakeConnection.GetOrganization() 122 if err != nil { 123 return "", err 124 } 125 126 orgaID := "" 127 128 for _, orga := range data.Organizations { 129 for _, user := range orga.Users { 130 if user.Email == email { 131 for i := range user.Organizations { 132 if user.Organizations[i].Name != "OCS" { 133 orgaID = user.Organizations[i].ID 134 goto exit 135 } 136 } 137 } 138 } 139 } 140 if orgaID == "" { 141 return "", fmt.Errorf("Unable to find your organization") 142 } 143 exit: 144 return orgaID, nil 145 } 146 147 func connectAPI() (string, string, error) { 148 email := "" 149 password := "" 150 orga := "" 151 token := "" 152 hostname, err := os.Hostname() 153 if err != nil { 154 return "", "", fmt.Errorf("unable to get your Hostname %v", err) 155 } 156 if err = promptUser("Login (cloud.scaleway.com): ", &email, true); err != nil { 157 return "", "", err 158 } 159 if err = promptUser("Password: ", &password, false); err != nil { 160 return "", "", err 161 } 162 163 connect := api.ScalewayConnect{ 164 Email: strings.Trim(email, "\r\n"), 165 Password: strings.Trim(password, "\r\n"), 166 Expires: false, 167 Description: strings.Join([]string{"scw", hostname}, "-"), 168 } 169 token, err = getToken(connect) 170 if err != nil { 171 return "", "", err 172 } 173 orga, err = getOrganization(token, connect.Email) 174 if err != nil { 175 return "", "", err 176 } 177 return orga, token, nil 178 } 179 180 // uploadSSHKeys uploads an SSH Key 181 func uploadSSHKeys(apiConnection *api.ScalewayAPI, newKey string) { 182 user, err := apiConnection.GetUser() 183 if err != nil { 184 logrus.Errorf("Unable to contact ScalewayAPI: %s", err) 185 } else { 186 user.SSHPublicKeys = append(user.SSHPublicKeys, api.ScalewayKeyDefinition{Key: strings.Trim(newKey, "\n")}) 187 188 SSHKeys := api.ScalewayUserPatchSSHKeyDefinition{ 189 SSHPublicKeys: user.SSHPublicKeys, 190 } 191 for i := range SSHKeys.SSHPublicKeys { 192 SSHKeys.SSHPublicKeys[i].Fingerprint = "" 193 } 194 195 userID, err := apiConnection.GetUserID() 196 if err != nil { 197 logrus.Errorf("Unable to get userID: %s", err) 198 } else { 199 if err = apiConnection.PatchUserSSHKey(userID, SSHKeys); err != nil { 200 logrus.Errorf("Unable to patch SSHkey: %v", err) 201 } 202 } 203 } 204 } 205 206 // RunLogin is the handler for 'scw login' 207 func RunLogin(ctx CommandContext, args LoginArgs) error { 208 if config, cfgErr := config.GetConfig(); cfgErr == nil { 209 if TestConnection, err := api.NewScalewayAPI(config.Organization, config.Token, scwversion.UserAgent(), "", clilogger.SetupLogger); err == nil { 210 if user, err := TestConnection.GetUser(); err == nil { 211 fmt.Println("You are already logged as", user.Fullname) 212 } 213 } 214 } 215 216 if args.Organization == "" || args.Token == "" { 217 var err error 218 219 args.Organization, args.Token, err = connectAPI() 220 if err != nil { 221 return err 222 } 223 } 224 225 cfg := &config.Config{ 226 Organization: strings.Trim(args.Organization, "\n"), 227 Token: strings.Trim(args.Token, "\n"), 228 } 229 230 apiConnection, err := api.NewScalewayAPI(cfg.Organization, cfg.Token, scwversion.UserAgent(), "", clilogger.SetupLogger) 231 if err != nil { 232 return fmt.Errorf("Unable to create ScalewayAPI: %s", err) 233 } 234 err = apiConnection.CheckCredentials() 235 if err != nil { 236 return fmt.Errorf("Unable to contact ScalewayAPI: %s", err) 237 } 238 if !args.SkipSSHKey { 239 if err = selectKey(&args); err != nil { 240 logrus.Errorf("Unable to select a key: %v", err) 241 } else { 242 if args.SSHKey != "" { 243 uploadSSHKeys(apiConnection, args.SSHKey) 244 } 245 } 246 } 247 name := "." 248 user, err := apiConnection.GetUser() 249 if err == nil { 250 name = "as " + user.Fullname + "." 251 } 252 fmt.Println("") 253 fmt.Println("You are now authenticated on Scaleway.com", name) 254 fmt.Println("You can list your existing servers using `scw ps` or create a new one using `scw run ubuntu-xenial`.") 255 fmt.Println("You can get a list of all available commands using `scw -h` and get more usage examples on github.com/scaleway/scaleway-cli.") 256 fmt.Println("Happy cloud riding.") 257 return cfg.Save() 258 } 259 260 func promptUser(prompt string, output *string, echo bool) error { 261 // FIXME: should use stdin/stdout from command context 262 fmt.Fprintf(os.Stdout, prompt) 263 os.Stdout.Sync() 264 265 if !echo { 266 b, err := terminal.ReadPassword(int(os.Stdin.Fd())) 267 if err != nil { 268 return fmt.Errorf("Unable to prompt for password: %s", err) 269 } 270 *output = string(b) 271 fmt.Fprintf(os.Stdout, "\n") 272 } else { 273 reader := bufio.NewReader(os.Stdin) 274 *output, _ = reader.ReadString('\n') 275 } 276 return nil 277 }