From 8552997694efc6434e8e14f2a1c1bd5707e64bcd Mon Sep 17 00:00:00 2001 From: Quentin Perez Date: Mon, 24 Aug 2015 13:53:40 +0200 Subject: [PATCH] fix #59 --- pkg/api/api.go | 106 ++++++++++++++++++++++++++---------- pkg/cli/cmd_login.go | 10 +++- pkg/commands/login.go | 123 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 198 insertions(+), 41 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 66b19a7bea..618fa88e71 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -24,6 +24,12 @@ import ( "github.com/scaleway/scaleway-cli/vendor/github.com/moul/anonuuid" ) +// Default values +var ( + ComputeAPI string = "https://api.scaleway.com/" + AccountAPI string = "https://account.scaleway.com/" +) + // ScalewayAPI is the interface used to communicate with the Scaleway API type ScalewayAPI struct { // ComputeAPI is the endpoint to the Scaleway API @@ -499,20 +505,20 @@ type ScalewayImageDefinition struct { Arch string `json:"arch"` } -// ScalewayTokenUserIDRoleDefinition represents a Scaleway Token UserId Role -type ScalewayTokenUserIDRoleDefinition struct { - Organization string `json:"organization,omitempty"` - Role string `json:"role,omitempty"` +// ScalewayRoleDefinition represents a Scaleway Token UserId Role +type ScalewayRoleDefinition struct { + Organization ScalewayOrganizationDefinition `json:"organization,omitempty"` + Role string `json:"role,omitempty"` } // ScalewayTokenDefinition represents a Scaleway Token type ScalewayTokenDefinition struct { - UserID string `json:"user_id"` - Description string `json:"description,omitempty"` - Roles ScalewayTokenUserIDRoleDefinition `json:"roles"` - Expires string `json:"expires"` - InheritsUsersPerms bool `json:"inherits_user_perms"` - ID string `json:"id"` + UserID string `json:"user_id"` + Description string `json:"description,omitempty"` + Roles ScalewayRoleDefinition `json:"roles"` + Expires string `json:"expires"` + InheritsUsersPerms bool `json:"inherits_user_perms"` + ID string `json:"id"` } // ScalewayTokensDefinition represents a Scaleway Tokens @@ -520,14 +526,51 @@ type ScalewayTokensDefinition struct { Tokens []ScalewayTokenDefinition `json:"tokens"` } -// ScalewayUserPatchKeyDefinition represents a key -type ScalewayUserPatchKeyDefinition struct { +// ScalewayConnectResponse represents the answer from POST /tokens +type ScalewayConnectResponse struct { + Token ScalewayTokenDefinition `json:"token"` +} + +// ScalewayConnect represents the data to connect +type ScalewayConnect struct { + Email string `json:"email"` + Password string `json:"password"` + Description string `json:"description"` + Expires bool `json:"expires"` +} + +// ScalewayOrganizationDefinition represents a Scaleway Organization +type ScalewayOrganizationDefinition struct { + ID string `json:"id"` + Name string `json:"name"` + Users []ScalewayUserDefinition `json:"users"` +} + +// ScalewayOrganizationsDefinition represents a Scaleway Organizations +type ScalewayOrganizationsDefinition struct { + Organizations []ScalewayOrganizationDefinition `json:"organizations"` +} + +// ScalewayUserDefinition represents a Scaleway User +type ScalewayUserDefinition struct { + Email string `json:"email"` + Firstname string `json:"firstname"` + Fullname string `json:"fullname"` + ID string `json:"id"` + Lastname string `json:"lastname"` + Organizations []ScalewayOrganizationDefinition `json:"organizations"` + Roles []ScalewayRoleDefinition `json:"roles"` + SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"` +} + +// ScalewayKeyDefinition represents a key +type ScalewayKeyDefinition struct { Key string `json:"key"` } -// ScalewayUserPatchDefinition represents a User Patch -type ScalewayUserPatchDefinition struct { - SSHPublicKeys []ScalewayUserPatchKeyDefinition `json:"ssh_public_keys"` +// ScalewayUserPatchSSHKeyDefinition represents a User Patch +type ScalewayUserPatchSSHKeyDefinition struct { + SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"` } // FuncMap used for json inspection @@ -821,10 +864,10 @@ func (s *ScalewayAPI) PostServer(definition ScalewayServerDefinition) (string, e return "", error } -// PatchUser updates a user -func (s *ScalewayAPI) PatchUser(UserID string, definition ScalewayUserPatchDefinition) error { - s.enableAccountAPI() - defer s.disableAccountAPI() +// PatchUserSSHKey updates a user +func (s *ScalewayAPI) PatchUserSSHKey(UserID string, definition ScalewayUserPatchSSHKeyDefinition) error { + s.EnableAccountAPI() + defer s.DisableAccountAPI() resp, err := s.PatchResponse(fmt.Sprintf("users/%s", UserID), definition) if err != nil { return err @@ -1267,8 +1310,8 @@ func (s *ScalewayAPI) GetTasks() (*[]ScalewayTask, error) { // CheckCredentials performs a dummy check to ensure we can contact the API func (s *ScalewayAPI) CheckCredentials() error { - s.enableAccountAPI() - defer s.disableAccountAPI() + s.EnableAccountAPI() + defer s.DisableAccountAPI() query := url.Values{} query.Set("token_id", s.Token) resp, err := s.GetResponse("tokens?" + query.Encode()) @@ -1283,8 +1326,8 @@ func (s *ScalewayAPI) CheckCredentials() error { // GetUserID returns the UserID func (s *ScalewayAPI) GetUserID() (string, error) { - s.enableAccountAPI() - defer s.disableAccountAPI() + s.EnableAccountAPI() + defer s.DisableAccountAPI() resp, err := s.GetResponse("tokens") if err != nil { return "", err @@ -1300,7 +1343,7 @@ func (s *ScalewayAPI) GetUserID() (string, error) { return "", err } if len(tokens.Tokens) == 0 { - return "", fmt.Errorf("Unable to get tokens") + return "", fmt.Errorf("unable to get tokens") } return tokens.Tokens[0].UserID, nil } @@ -1409,15 +1452,22 @@ func (s *ScalewayAPI) GetBootscriptID(needle string) string { // HideAPICredentials removes API credentials from a string func (s *ScalewayAPI) HideAPICredentials(input string) string { - output := strings.Replace(input, s.Token, s.anonuuid.FakeUUID(s.Token), -1) - output = strings.Replace(output, s.Organization, s.anonuuid.FakeUUID(s.Organization), -1) + output := input + if s.Token != "" { + output = strings.Replace(output, s.Token, s.anonuuid.FakeUUID(s.Token), -1) + } + if s.Organization != "" { + output = strings.Replace(output, s.Organization, s.anonuuid.FakeUUID(s.Organization), -1) + } return output } -func (s *ScalewayAPI) enableAccountAPI() { +// EnableAccountAPI enable accountAPI +func (s *ScalewayAPI) EnableAccountAPI() { s.APIUrl = s.AccountAPI } -func (s *ScalewayAPI) disableAccountAPI() { +// DisableAccountAPI disable accountAPI +func (s *ScalewayAPI) DisableAccountAPI() { s.APIUrl = s.ComputeAPI } diff --git a/pkg/cli/cmd_login.go b/pkg/cli/cmd_login.go index 6a0c7ca0e0..558e2fe60e 100644 --- a/pkg/cli/cmd_login.go +++ b/pkg/cli/cmd_login.go @@ -4,7 +4,11 @@ package cli -import "github.com/scaleway/scaleway-cli/pkg/commands" +import ( + "fmt" + + "github.com/scaleway/scaleway-cli/pkg/commands" +) var cmdLogin = &Command{ Exec: runLogin, @@ -36,7 +40,9 @@ func runLogin(cmd *Command, rawArgs []string) error { if len(rawArgs) != 0 { return cmd.PrintShortUsage() } - + if (organization != "" || token != "") && (organization == "" || token == "") { + return fmt.Errorf("you must define organization AND token") + } args := commands.LoginArgs{ Organization: organization, Token: token, diff --git a/pkg/commands/login.go b/pkg/commands/login.go index bd3f87fadd..659e658c1f 100644 --- a/pkg/commands/login.go +++ b/pkg/commands/login.go @@ -6,6 +6,7 @@ package commands import ( "bufio" + "encoding/json" "fmt" "io/ioutil" "os" @@ -78,19 +79,119 @@ func selectKey(args *LoginArgs) error { return nil } +func getToken(connect api.ScalewayConnect) (string, error) { + FakeConnection, err := api.NewScalewayAPI(api.ComputeAPI, api.AccountAPI, "", "") + if err != nil { + return "", fmt.Errorf("Unable to create a fake ScalewayAPI: %s", err) + } + FakeConnection.EnableAccountAPI() + + resp, err := FakeConnection.PostResponse("tokens", connect) + if err != nil { + return "", fmt.Errorf("unable to connect %v", err) + } + // Succeed POST code + if resp.StatusCode != 201 { + return "", fmt.Errorf("[%d] maybe your email or your password is not valid", resp.StatusCode) + } + var data api.ScalewayConnectResponse + + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&data) + if err != nil { + return "", err + } + return data.Token.ID, nil +} + +func getOrga(token string, email string) (string, error) { + FakeConnection, err := api.NewScalewayAPI(api.ComputeAPI, api.AccountAPI, "", token) + if err != nil { + return "", fmt.Errorf("Unable to create a fake ScalewayAPI: %s", err) + } + FakeConnection.EnableAccountAPI() + + resp, err := FakeConnection.GetResponse("organizations") + if err != nil { + return "", err + } + if resp.StatusCode != 200 { + return "", fmt.Errorf("[%d] unable to GET", resp.StatusCode) + } + + var data api.ScalewayOrganizationsDefinition + + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&data) + if err != nil { + return "", err + } + orgaID := "" + + for _, orga := range data.Organizations { + for _, user := range orga.Users { + if user.Email == email { + for i := range user.Organizations { + if user.Organizations[i].Name != "OCS" { + orgaID = user.Organizations[i].ID + goto exit + } + } + } + } + } + if orgaID == "" { + return "", fmt.Errorf("Unable to find your organization") + } +exit: + return orgaID, nil +} + +func connectAPI() (string, string, error) { + email := "" + password := "" + orga := "" + token := "" + hostname, err := os.Hostname() + if err != nil { + return "", "", fmt.Errorf("unable to get your Hostname %v", err) + } + promptUser("Login (cloud.scaleway.com): ", &email, true) + promptUser("Password: ", &password, false) + + connect := api.ScalewayConnect{ + Email: strings.Trim(email, "\n"), + Password: strings.Trim(password, "\n"), + Expires: false, + Description: strings.Join([]string{"scw", hostname}, "-"), + } + token, err = getToken(connect) + if err != nil { + return "", "", err + } + orga, err = getOrga(token, connect.Email) + if err != nil { + return "", "", err + } + return orga, token, nil +} + // RunLogin is the handler for 'scw login' func RunLogin(ctx CommandContext, args LoginArgs) error { - if args.Organization == "" { - fmt.Println("You can get your credentials on https://cloud.scaleway.com/#/credentials") - promptUser("Organization (access key): ", &args.Organization, true) - } - if args.Token == "" { - promptUser("Token: ", &args.Token, false) + if args.Organization == "" || args.Token == "" { + var err error + + args.Organization, args.Token, err = connectAPI() + if err != nil { + return err + } } cfg := &config.Config{ - ComputeAPI: "https://api.scaleway.com/", - AccountAPI: "https://account.scaleway.com/", + ComputeAPI: api.ComputeAPI, + AccountAPI: api.AccountAPI, Organization: strings.Trim(args.Organization, "\n"), Token: strings.Trim(args.Token, "\n"), } @@ -112,13 +213,13 @@ func RunLogin(ctx CommandContext, args LoginArgs) error { logrus.Errorf("Unable to contact ScalewayAPI: %s", err) } else { - SSHKey := api.ScalewayUserPatchDefinition{ - SSHPublicKeys: []api.ScalewayUserPatchKeyDefinition{{ + SSHKey := api.ScalewayUserPatchSSHKeyDefinition{ + SSHPublicKeys: []api.ScalewayKeyDefinition{{ Key: strings.Trim(args.SSHKey, "\n"), }}, } - if err = apiConnection.PatchUser(userID, SSHKey); err != nil { + if err = apiConnection.PatchUserSSHKey(userID, SSHKey); err != nil { logrus.Errorf("Unable to patch SSHkey: %v", err) } }