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  }