This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git
The following commit(s) were added to refs/heads/master by this push: new d0b58be client: implement login based API calls, simplify config d0b58be is described below commit d0b58bee3a7d223c2b521c870c741c0183c65022 Author: Rohit Yadav <ro...@apache.org> AuthorDate: Mon Apr 16 07:37:41 2018 +0530 client: implement login based API calls, simplify config Signed-off-by: Rohit Yadav <ro...@apache.org> --- cmd/api.go | 6 +++- cmd/login.go | 27 +++++++++++++++--- cmd/network.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++--------- config/config.go | 67 ++++++++++++++++++++++--------------------- 4 files changed, 136 insertions(+), 51 deletions(-) diff --git a/cmd/api.go b/cmd/api.go index dd4400a..47a0f0f 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -77,7 +77,11 @@ func init() { return nil } - b, _ := NewAPIRequest(r, api.Name, apiArgs) + b, err := NewAPIRequest(r, api.Name, apiArgs) + if err != nil { + return err + } + response, _ := json.MarshalIndent(b, "", " ") // Implement various output formats diff --git a/cmd/login.go b/cmd/login.go index a2ace48..36b54e8 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -40,33 +40,52 @@ func init() { return nil } + // username prompt := promptui.Prompt{ Label: "Username", Validate: validate, Default: "", } - username, err := prompt.Run() if err != nil { fmt.Printf("Prompt failed %v\n", err) return nil } + //password prompt = promptui.Prompt{ Label: "Password", Validate: validate, Mask: '*', } - password, err := prompt.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return nil + } + // domain + prompt = promptui.Prompt{ + Label: "Domain", + Validate: validate, + Default: "/", + } + domain, err := prompt.Run() if err != nil { fmt.Printf("Prompt failed %v\n", err) return nil } - // TODO: implement login based key setup workflow - fmt.Println("Trying to log in using", username, password) + r.Config.ActiveProfile.Username = username + r.Config.ActiveProfile.Password = password + r.Config.ActiveProfile.Domain = domain + + client, _, err := Login(r) + if client == nil || err != nil { + fmt.Println("Failed to login, check credentials") + } else { + fmt.Println("Success!") + } return nil }, diff --git a/cmd/network.go b/cmd/network.go index d40b7bd..1a57494 100644 --- a/cmd/network.go +++ b/cmd/network.go @@ -21,15 +21,18 @@ import ( "bytes" "crypto/hmac" "crypto/sha1" + "crypto/tls" "encoding/base64" "encoding/json" "errors" "fmt" "io/ioutil" "net/http" + "net/http/cookiejar" "net/url" "sort" "strings" + "time" ) func encodeRequestParams(params url.Values) string { @@ -56,6 +59,42 @@ func encodeRequestParams(params url.Values) string { return buf.String() } +// Login logs in a user based on provided request and returns http client and session key +func Login(r *Request) (*http.Client, string, error) { + params := make(url.Values) + params.Add("command", "login") + params.Add("username", r.Config.ActiveProfile.Username) + params.Add("password", r.Config.ActiveProfile.Password) + params.Add("domain", r.Config.ActiveProfile.Domain) + params.Add("response", "json") + + jar, _ := cookiejar.New(nil) + client := &http.Client{ + Jar: jar, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: !r.Config.ActiveProfile.VerifyCert}, + }, + } + + sessionKey := "" + resp, err := client.PostForm(r.Config.ActiveProfile.URL, params) + if resp.StatusCode != http.StatusOK { + e := errors.New("failed to log in") + if err != nil { + e = errors.New("failed to log in due to:" + err.Error()) + } + return client, sessionKey, e + } + + for _, cookie := range resp.Cookies() { + if cookie.Name == "sessionkey" { + sessionKey = cookie.Value + break + } + } + return client, sessionKey, nil +} + // NewAPIRequest makes an API request to configured management server func NewAPIRequest(r *Request, api string, args []string) (map[string]interface{}, error) { params := make(url.Values) @@ -66,25 +105,47 @@ func NewAPIRequest(r *Request, api string, args []string) (map[string]interface{ params.Add(parts[0], parts[1]) } } + params.Add("response", "json") - apiKey := r.Config.Core.ActiveProfile.APIKey - secretKey := r.Config.Core.ActiveProfile.SecretKey + var client *http.Client + var encodedParams string + var err error + if len(r.Config.ActiveProfile.APIKey) > 0 && len(r.Config.ActiveProfile.SecretKey) > 0 { + apiKey := r.Config.ActiveProfile.APIKey + secretKey := r.Config.ActiveProfile.SecretKey - if len(apiKey) > 0 { - params.Add("apiKey", apiKey) - } + if len(apiKey) > 0 { + params.Add("apiKey", apiKey) + } - params.Add("response", "json") - encodedParams := encodeRequestParams(params) + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: !r.Config.ActiveProfile.VerifyCert}, + }, + } + encodedParams = encodeRequestParams(params) - mac := hmac.New(sha1.New, []byte(secretKey)) - mac.Write([]byte(strings.Replace(strings.ToLower(encodedParams), "+", "%20", -1))) - signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - encodedParams = encodedParams + fmt.Sprintf("&signature=%s", url.QueryEscape(signature)) + mac := hmac.New(sha1.New, []byte(secretKey)) + mac.Write([]byte(strings.Replace(strings.ToLower(encodedParams), "+", "%20", -1))) + signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + encodedParams = encodedParams + fmt.Sprintf("&signature=%s", url.QueryEscape(signature)) + } else if len(r.Config.ActiveProfile.Username) > 0 && len(r.Config.ActiveProfile.Password) > 0 { + var sessionKey string + client, sessionKey, err = Login(r) + if err != nil { + return nil, err + } + params.Add("sessionkey", sessionKey) + encodedParams = encodeRequestParams(params) + } else { + fmt.Println("Please provide either apikey/secretkey or username/password to make an API call") + return nil, errors.New("failed to authenticate to make API call") + } - apiURL := fmt.Sprintf("%s?%s", r.Config.Core.ActiveProfile.URL, encodedParams) + apiURL := fmt.Sprintf("%s?%s", r.Config.ActiveProfile.URL, encodedParams) - response, err := http.Get(apiURL) + client.Timeout = time.Duration(time.Duration(r.Config.Core.Timeout) * time.Second) + response, err := client.Get(apiURL) if err != nil { fmt.Println("Error:", err) return nil, err diff --git a/config/config.go b/config/config.go index 16b2b64..97fa6da 100644 --- a/config/config.go +++ b/config/config.go @@ -47,21 +47,21 @@ type ServerProfile struct { // Core block describes common options for the CLI type Core struct { - AsyncBlock bool `ini:"asyncblock"` - Timeout int `ini:"timeout"` - Output string `ini:"output"` - ProfileName string `ini:"profile"` - ActiveProfile *ServerProfile `ini:"-"` + AsyncBlock bool `ini:"asyncblock"` + Timeout int `ini:"timeout"` + Output string `ini:"output"` + ProfileName string `ini:"profile"` } // Config describes CLI config file and default options type Config struct { - Dir string - ConfigFile string - HistoryFile string - CacheFile string - LogFile string - Core *Core + Dir string + ConfigFile string + HistoryFile string + CacheFile string + LogFile string + Core *Core + ActiveProfile *ServerProfile } func getDefaultConfigDir() string { @@ -86,15 +86,15 @@ func defaultConfig() *Config { Timeout: 1800, Output: JSON, ProfileName: "local", - ActiveProfile: &ServerProfile{ - URL: "http://localhost:8080/client/api", - Username: "admin", - Password: "password", - Domain: "/", - APIKey: "", - SecretKey: "", - VerifyCert: false, - }, + }, + ActiveProfile: &ServerProfile{ + URL: "http://localhost:8080/client/api", + Username: "admin", + Password: "password", + Domain: "/", + APIKey: "", + SecretKey: "", + VerifyCert: false, }, } } @@ -117,7 +117,7 @@ func reloadConfig(cfg *Config) *Config { defaultConf := defaultConfig() conf := ini.Empty() conf.Section(ini.DEFAULT_SECTION).ReflectFrom(defaultConf.Core) - conf.Section(cfg.Core.ProfileName).ReflectFrom(defaultConf.Core.ActiveProfile) + conf.Section(cfg.Core.ProfileName).ReflectFrom(defaultConf.ActiveProfile) conf.SaveTo(cfg.ConfigFile) } @@ -149,16 +149,16 @@ func reloadConfig(cfg *Config) *Config { profile, err := conf.GetSection(cfg.Core.ProfileName) if profile == nil { section, _ := conf.NewSection(cfg.Core.ProfileName) - section.ReflectFrom(&defaultConfig().Core.ActiveProfile) + section.ReflectFrom(&defaultConfig().ActiveProfile) } else { // Write - if cfg.Core.ActiveProfile != nil { - conf.Section(cfg.Core.ProfileName).ReflectFrom(&cfg.Core.ActiveProfile) + if cfg.ActiveProfile != nil { + conf.Section(cfg.Core.ProfileName).ReflectFrom(&cfg.ActiveProfile) } // Update profile := new(ServerProfile) conf.Section(cfg.Core.ProfileName).MapTo(profile) - cfg.Core.ActiveProfile = profile + cfg.ActiveProfile = profile } // Save conf.SaveTo(cfg.ConfigFile) @@ -187,21 +187,21 @@ func (c *Config) UpdateConfig(key string, value string) { c.Core.Timeout = intValue case "profile": c.Core.ProfileName = value - c.Core.ActiveProfile = nil + c.ActiveProfile = nil case "url": - c.Core.ActiveProfile.URL = value + c.ActiveProfile.URL = value case "username": - c.Core.ActiveProfile.Username = value + c.ActiveProfile.Username = value case "password": - c.Core.ActiveProfile.Password = value + c.ActiveProfile.Password = value case "domain": - c.Core.ActiveProfile.Domain = value + c.ActiveProfile.Domain = value case "apikey": - c.Core.ActiveProfile.APIKey = value + c.ActiveProfile.APIKey = value case "secretkey": - c.Core.ActiveProfile.SecretKey = value + c.ActiveProfile.SecretKey = value case "verifycert": - c.Core.ActiveProfile.VerifyCert = value == "true" + c.ActiveProfile.VerifyCert = value == "true" default: return } @@ -213,6 +213,7 @@ func (c *Config) UpdateConfig(key string, value string) { func NewConfig() *Config { defaultConf := defaultConfig() defaultConf.Core = nil + defaultConf.ActiveProfile = nil cfg := reloadConfig(defaultConf) LoadCache(cfg) return cfg -- To stop receiving notification emails like this one, please contact ro...@apache.org.