From a1a1d4868b7a32e90343b3c3b085fb523b20b8e2 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Tue, 9 Jul 2019 22:58:47 +0200 Subject: bridge/gitlab: add bridge configure --- bridge/gitlab/config.go | 518 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 bridge/gitlab/config.go (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go new file mode 100644 index 00000000..997494dd --- /dev/null +++ b/bridge/gitlab/config.go @@ -0,0 +1,518 @@ +package gitlab + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + neturl "net/url" + "os" + "regexp" + "strconv" + "strings" + "syscall" + "time" + + "github.com/pkg/errors" + "github.com/xanzy/go-gitlab" + "golang.org/x/crypto/ssh/terminal" + + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/repository" +) + +const ( + target = "gitlab" + gitlabV4Url = "https://gitlab.com/api/v4" + keyID = "id" + keyTarget = "target" + keyToken = "token" + + defaultTimeout = 60 * time.Second +) + +//note to my self: bridge configure --target=gitlab --url=$URL + +var ( + ErrBadProjectURL = errors.New("bad project url") +) + +func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { + if params.Project != "" { + fmt.Println("warning: --project is ineffective for a gitlab bridge") + } + if params.Owner != "" { + fmt.Println("warning: --owner is ineffective for a gitlab bridge") + } + + conf := make(core.Configuration) + var err error + var url string + var token string + var projectID string + + // get project url + if params.URL != "" { + url = params.URL + + } else { + // remote suggestions + remotes, err := repo.GetRemotes() + if err != nil { + return nil, err + } + + // terminal prompt + url, err = promptURL(remotes) + if err != nil { + return nil, err + } + } + + // get user token + if params.Token != "" { + token = params.Token + } else { + token, err = promptTokenOptions(url) + if err != nil { + return nil, err + } + } + + var ok bool + // validate project url and get it ID + ok, projectID, err = validateProjectURL(url, token) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("invalid project id or wrong token scope") + } + + conf[keyID] = projectID + conf[keyToken] = token + conf[keyTarget] = target + + return conf, nil +} + +func (*Gitlab) ValidateConfig(conf core.Configuration) error { + if v, ok := conf[keyTarget]; !ok { + return fmt.Errorf("missing %s key", keyTarget) + } else if v != target { + return fmt.Errorf("unexpected target name: %v", v) + } + + if _, ok := conf[keyToken]; !ok { + return fmt.Errorf("missing %s key", keyToken) + } + + if _, ok := conf[keyID]; !ok { + return fmt.Errorf("missing %s key", keyID) + } + + return nil +} + +func requestToken(note, username, password string, scope string) (*http.Response, error) { + return requestTokenWith2FA(note, username, password, "", scope) +} + +//TODO: FIX THIS ONE +func requestTokenWith2FA(note, username, password, otpCode string, scope string) (*http.Response, error) { + url := fmt.Sprintf("%s/authorizations", gitlabV4Url) + params := struct { + Scopes []string `json:"scopes"` + Note string `json:"note"` + Fingerprint string `json:"fingerprint"` + }{ + Scopes: []string{scope}, + Note: note, + Fingerprint: randomFingerprint(), + } + + data, err := json.Marshal(params) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + + req.SetBasicAuth(username, password) + req.Header.Set("Content-Type", "application/json") + + if otpCode != "" { + req.Header.Set("X-GitHub-OTP", otpCode) + } + + client := &http.Client{ + Timeout: defaultTimeout, + } + + return client.Do(req) +} + +func decodeBody(body io.ReadCloser) (string, error) { + data, _ := ioutil.ReadAll(body) + + aux := struct { + Token string `json:"token"` + }{} + + err := json.Unmarshal(data, &aux) + if err != nil { + return "", err + } + + if aux.Token == "" { + return "", fmt.Errorf("no token found in response: %s", string(data)) + } + + return aux.Token, nil +} + +func randomFingerprint() string { + // Doesn't have to be crypto secure, it's just to avoid token collision + rand.Seed(time.Now().UnixNano()) + var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, 32) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func promptTokenOptions(url string) (string, error) { + for { + fmt.Println() + fmt.Println("[1]: user provided token") + fmt.Println("[2]: interactive token creation") + fmt.Print("Select option: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + if err != nil { + return "", err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index != 1 && index != 2) { + fmt.Println("invalid input") + continue + } + + if index == 1 { + return promptToken() + } + + return loginAndRequestToken(url) + } +} + +func promptToken() (string, error) { + fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.") + fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") + fmt.Println() + fmt.Println("The access scope depend on the type of repository.") + fmt.Println("Public:") + fmt.Println(" - 'public_repo': to be able to read public repositories") + fmt.Println("Private:") + fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println() + + re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`) + if err != nil { + panic("regexp compile:" + err.Error()) + } + + for { + fmt.Print("Enter token: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", err + } + + token := strings.TrimRight(line, "\n") + if re.MatchString(token) { + return token, nil + } + + fmt.Println("token is invalid") + } +} + +// TODO: FIX THIS ONE TOO +func loginAndRequestToken(url string) (string, error) { + + // prompt project visibility to know the token scope needed for the repository + isPublic, err := promptProjectVisibility() + if err != nil { + return "", err + } + + username, err := promptUsername() + if err != nil { + return "", err + } + + password, err := promptPassword() + if err != nil { + return "", err + } + + var scope string + //TODO: Gitlab scopes + if isPublic { + // public_repo is requested to be able to read public repositories + scope = "public_repo" + } else { + // 'repo' is request to be able to read private repositories + // /!\ token will have read/write rights on every private repository you have access to + scope = "repo" + } + + // Attempt to authenticate and create a token + + note := fmt.Sprintf("git-bug - %s/%s", url) + + resp, err := requestToken(note, username, password, scope) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + // Handle 2FA is needed + OTPHeader := resp.Header.Get("X-GitHub-OTP") + if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { + otpCode, err := prompt2FA() + if err != nil { + return "", err + } + + resp, err = requestTokenWith2FA(note, username, password, otpCode, scope) + if err != nil { + return "", err + } + + defer resp.Body.Close() + } + + if resp.StatusCode == http.StatusCreated { + return decodeBody(resp.Body) + } + + b, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b)) +} + +func promptUsername() (string, error) { + for { + fmt.Print("username: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", err + } + + line = strings.TrimRight(line, "\n") + + ok, err := validateUsername(line) + if err != nil { + return "", err + } + if ok { + return line, nil + } + + fmt.Println("invalid username") + } +} + +func promptURL(remotes map[string]string) (string, error) { + validRemotes := getValidGitlabRemoteURLs(remotes) + if len(validRemotes) > 0 { + for { + fmt.Println("\nDetected projects:") + + // print valid remote gitlab urls + for i, remote := range validRemotes { + fmt.Printf("[%d]: %v\n", i+1, remote) + } + + fmt.Printf("\n[0]: Another project\n\n") + fmt.Printf("Select option: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index < 0 && index >= len(validRemotes)) { + fmt.Println("invalid input") + continue + } + + // if user want to enter another project url break this loop + if index == 0 { + break + } + + return validRemotes[index-1], nil + } + } + + // manually enter gitlab url + for { + fmt.Print("Gitlab project URL: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", err + } + + url := strings.TrimRight(line, "\n") + if line == "" { + fmt.Println("URL is empty") + continue + } + + return url, nil + } +} + +func splitURL(url string) (string, string, error) { + cleanUrl := strings.TrimSuffix(url, ".git") + objectUrl, err := neturl.Parse(cleanUrl) + if err != nil { + return "", "", nil + } + + return fmt.Sprintf("%s%s", objectUrl.Host, objectUrl.Path), objectUrl.Path, nil +} + +func getValidGitlabRemoteURLs(remotes map[string]string) []string { + urls := make([]string, 0, len(remotes)) + for _, u := range remotes { + url, _, err := splitURL(u) + if err != nil { + continue + } + + urls = append(urls, url) + } + + return urls +} + +func validateUsername(username string) (bool, error) { + // no need for a token for this action + client := buildClient("") + + users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: &username}) + if err != nil { + return false, err + } + + if len(users) == 0 { + return false, fmt.Errorf("username not found") + } else if len(users) > 1 { + return false, fmt.Errorf("found multiple matches") + } + + return users[0].Username == username, nil +} + +func validateProjectURL(url, token string) (bool, string, error) { + client := buildClient(token) + + _, projectPath, err := splitURL(url) + if err != nil { + return false, "", err + } + + project, _, err := client.Projects.GetProject(projectPath[1:], &gitlab.GetProjectOptions{}) + if err != nil { + return false, "", err + } + projectID := strconv.Itoa(project.ID) + + return true, projectID, nil +} + +func promptPassword() (string, error) { + for { + fmt.Print("password: ") + + bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) + // new line for coherent formatting, ReadPassword clip the normal new line + // entered by the user + fmt.Println() + + if err != nil { + return "", err + } + + if len(bytePassword) > 0 { + return string(bytePassword), nil + } + + fmt.Println("password is empty") + } +} + +func prompt2FA() (string, error) { + for { + fmt.Print("two-factor authentication code: ") + + byte2fa, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Println() + if err != nil { + return "", err + } + + if len(byte2fa) > 0 { + return string(byte2fa), nil + } + + fmt.Println("code is empty") + } +} + +func promptProjectVisibility() (bool, error) { + for { + fmt.Println("[1]: public") + fmt.Println("[2]: private") + fmt.Print("repository visibility: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + if err != nil { + return false, err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index != 0 && index != 1) { + fmt.Println("invalid input") + continue + } + + // return true for public repositories, false for private + return index == 0, nil + } +} -- cgit From 35a033c0f1f17fadc4525927aace4f4043038c7e Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 10 Jul 2019 00:41:43 +0200 Subject: bridge/gitlab: bridge project validation bridge/gitlab: token generation --- bridge/gitlab/config.go | 230 ++++++++++-------------------------------------- 1 file changed, 45 insertions(+), 185 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index 997494dd..a6fd6622 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -2,13 +2,7 @@ package gitlab import ( "bufio" - "bytes" - "encoding/json" "fmt" - "io" - "io/ioutil" - "math/rand" - "net/http" neturl "net/url" "os" "regexp" @@ -28,15 +22,13 @@ import ( const ( target = "gitlab" gitlabV4Url = "https://gitlab.com/api/v4" - keyID = "id" + keyID = "project-id" keyTarget = "target" keyToken = "token" defaultTimeout = 60 * time.Second ) -//note to my self: bridge configure --target=gitlab --url=$URL - var ( ErrBadProjectURL = errors.New("bad project url") ) @@ -53,7 +45,6 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( var err error var url string var token string - var projectID string // get project url if params.URL != "" { @@ -85,7 +76,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( var ok bool // validate project url and get it ID - ok, projectID, err = validateProjectURL(url, token) + ok, id, err := validateProjectURL(url, token) if err != nil { return nil, err } @@ -93,7 +84,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, fmt.Errorf("invalid project id or wrong token scope") } - conf[keyID] = projectID + conf[keyID] = strconv.Itoa(id) conf[keyToken] = token conf[keyTarget] = target @@ -118,75 +109,19 @@ func (*Gitlab) ValidateConfig(conf core.Configuration) error { return nil } -func requestToken(note, username, password string, scope string) (*http.Response, error) { - return requestTokenWith2FA(note, username, password, "", scope) -} - -//TODO: FIX THIS ONE -func requestTokenWith2FA(note, username, password, otpCode string, scope string) (*http.Response, error) { - url := fmt.Sprintf("%s/authorizations", gitlabV4Url) - params := struct { - Scopes []string `json:"scopes"` - Note string `json:"note"` - Fingerprint string `json:"fingerprint"` - }{ - Scopes: []string{scope}, - Note: note, - Fingerprint: randomFingerprint(), - } - - data, err := json.Marshal(params) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - return nil, err - } - - req.SetBasicAuth(username, password) - req.Header.Set("Content-Type", "application/json") - - if otpCode != "" { - req.Header.Set("X-GitHub-OTP", otpCode) - } - - client := &http.Client{ - Timeout: defaultTimeout, - } - - return client.Do(req) -} - -func decodeBody(body io.ReadCloser) (string, error) { - data, _ := ioutil.ReadAll(body) - - aux := struct { - Token string `json:"token"` - }{} - - err := json.Unmarshal(data, &aux) +func requestToken(client *gitlab.Client, userID int, name string, scopes ...string) (string, error) { + impToken, _, err := client.Users.CreateImpersonationToken( + userID, + &gitlab.CreateImpersonationTokenOptions{ + Name: &name, + Scopes: &scopes, + }, + ) if err != nil { return "", err } - if aux.Token == "" { - return "", fmt.Errorf("no token found in response: %s", string(data)) - } - - return aux.Token, nil -} - -func randomFingerprint() string { - // Doesn't have to be crypto secure, it's just to avoid token collision - rand.Seed(time.Now().UnixNano()) - var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]rune, 32) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] - } - return string(b) + return impToken.Token, nil } func promptTokenOptions(url string) (string, error) { @@ -222,11 +157,7 @@ func promptToken() (string, error) { fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.") fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") fmt.Println() - fmt.Println("The access scope depend on the type of repository.") - fmt.Println("Public:") - fmt.Println(" - 'public_repo': to be able to read public repositories") - fmt.Println("Private:") - fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println("'api' scope access : access scope: to be able to make api calls") fmt.Println() re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`) @@ -251,15 +182,7 @@ func promptToken() (string, error) { } } -// TODO: FIX THIS ONE TOO func loginAndRequestToken(url string) (string, error) { - - // prompt project visibility to know the token scope needed for the repository - isPublic, err := promptProjectVisibility() - if err != nil { - return "", err - } - username, err := promptUsername() if err != nil { return "", err @@ -270,50 +193,26 @@ func loginAndRequestToken(url string) (string, error) { return "", err } - var scope string - //TODO: Gitlab scopes - if isPublic { - // public_repo is requested to be able to read public repositories - scope = "public_repo" - } else { - // 'repo' is request to be able to read private repositories - // /!\ token will have read/write rights on every private repository you have access to - scope = "repo" - } - // Attempt to authenticate and create a token - note := fmt.Sprintf("git-bug - %s/%s", url) + note := fmt.Sprintf("git-bug - %s", url) - resp, err := requestToken(note, username, password, scope) + ok, id, err := validateUsername(username) if err != nil { return "", err } - - defer resp.Body.Close() - - // Handle 2FA is needed - OTPHeader := resp.Header.Get("X-GitHub-OTP") - if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { - otpCode, err := prompt2FA() - if err != nil { - return "", err - } - - resp, err = requestTokenWith2FA(note, username, password, otpCode, scope) - if err != nil { - return "", err - } - - defer resp.Body.Close() + if !ok { + return "", fmt.Errorf("invalid username") } - if resp.StatusCode == http.StatusCreated { - return decodeBody(resp.Body) + client, err := buildClientFromUsernameAndPassword(username, password) + if err != nil { + return "", err } - b, _ := ioutil.ReadAll(resp.Body) - return "", fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b)) + fmt.Println(username, password) + + return requestToken(client, id, note, "api") } func promptUsername() (string, error) { @@ -327,7 +226,7 @@ func promptUsername() (string, error) { line = strings.TrimRight(line, "\n") - ok, err := validateUsername(line) + ok, _, err := validateUsername(line) if err != nil { return "", err } @@ -394,63 +293,67 @@ func promptURL(remotes map[string]string) (string, error) { } } -func splitURL(url string) (string, string, error) { +func getProjectPath(url string) (string, error) { + cleanUrl := strings.TrimSuffix(url, ".git") objectUrl, err := neturl.Parse(cleanUrl) if err != nil { - return "", "", nil + return "", nil } - return fmt.Sprintf("%s%s", objectUrl.Host, objectUrl.Path), objectUrl.Path, nil + return objectUrl.Path[1:], nil } func getValidGitlabRemoteURLs(remotes map[string]string) []string { urls := make([]string, 0, len(remotes)) for _, u := range remotes { - url, _, err := splitURL(u) + path, err := getProjectPath(u) if err != nil { continue } - urls = append(urls, url) + urls = append(urls, fmt.Sprintf("%s%s", "gitlab.com", path)) } return urls } -func validateUsername(username string) (bool, error) { +func validateUsername(username string) (bool, int, error) { // no need for a token for this action client := buildClient("") users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: &username}) if err != nil { - return false, err + return false, 0, err } if len(users) == 0 { - return false, fmt.Errorf("username not found") + return false, 0, fmt.Errorf("username not found") } else if len(users) > 1 { - return false, fmt.Errorf("found multiple matches") + return false, 0, fmt.Errorf("found multiple matches") + } + + if users[0].Username == username { + return true, users[0].ID, nil } - return users[0].Username == username, nil + return false, 0, nil } -func validateProjectURL(url, token string) (bool, string, error) { +func validateProjectURL(url, token string) (bool, int, error) { client := buildClient(token) - _, projectPath, err := splitURL(url) + projectPath, err := getProjectPath(url) if err != nil { - return false, "", err + return false, 0, err } - project, _, err := client.Projects.GetProject(projectPath[1:], &gitlab.GetProjectOptions{}) + project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{}) if err != nil { - return false, "", err + return false, 0, err } - projectID := strconv.Itoa(project.ID) - return true, projectID, nil + return true, project.ID, nil } func promptPassword() (string, error) { @@ -473,46 +376,3 @@ func promptPassword() (string, error) { fmt.Println("password is empty") } } - -func prompt2FA() (string, error) { - for { - fmt.Print("two-factor authentication code: ") - - byte2fa, err := terminal.ReadPassword(int(syscall.Stdin)) - fmt.Println() - if err != nil { - return "", err - } - - if len(byte2fa) > 0 { - return string(byte2fa), nil - } - - fmt.Println("code is empty") - } -} - -func promptProjectVisibility() (bool, error) { - for { - fmt.Println("[1]: public") - fmt.Println("[2]: private") - fmt.Print("repository visibility: ") - - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - fmt.Println() - if err != nil { - return false, err - } - - line = strings.TrimRight(line, "\n") - - index, err := strconv.Atoi(line) - if err != nil || (index != 0 && index != 1) { - fmt.Println("invalid input") - continue - } - - // return true for public repositories, false for private - return index == 0, nil - } -} -- cgit From 51445256496f2eb629f62c638faa6199356eb8e6 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 12 Jul 2019 17:31:01 +0200 Subject: bridge/gitlab: remove request token methodes --- bridge/gitlab/config.go | 98 +++++-------------------------------------------- 1 file changed, 9 insertions(+), 89 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index a6fd6622..392452c6 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -8,23 +8,21 @@ import ( "regexp" "strconv" "strings" - "syscall" "time" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" - "golang.org/x/crypto/ssh/terminal" "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/repository" ) const ( - target = "gitlab" - gitlabV4Url = "https://gitlab.com/api/v4" - keyID = "project-id" - keyTarget = "target" - keyToken = "token" + target = "gitlab" + gitlabV4Url = "https://gitlab.com/api/v4" + keyProjectID = "project-id" + keyTarget = "target" + keyToken = "token" defaultTimeout = 60 * time.Second ) @@ -84,7 +82,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, fmt.Errorf("invalid project id or wrong token scope") } - conf[keyID] = strconv.Itoa(id) + conf[keyProjectID] = strconv.Itoa(id) conf[keyToken] = token conf[keyTarget] = target @@ -102,8 +100,8 @@ func (*Gitlab) ValidateConfig(conf core.Configuration) error { return fmt.Errorf("missing %s key", keyToken) } - if _, ok := conf[keyID]; !ok { - return fmt.Errorf("missing %s key", keyID) + if _, ok := conf[keyProjectID]; !ok { + return fmt.Errorf("missing %s key", keyProjectID) } return nil @@ -124,6 +122,7 @@ func requestToken(client *gitlab.Client, userID int, name string, scopes ...stri return impToken.Token, nil } +//TODO fix this func promptTokenOptions(url string) (string, error) { for { fmt.Println() @@ -148,8 +147,6 @@ func promptTokenOptions(url string) (string, error) { if index == 1 { return promptToken() } - - return loginAndRequestToken(url) } } @@ -182,62 +179,6 @@ func promptToken() (string, error) { } } -func loginAndRequestToken(url string) (string, error) { - username, err := promptUsername() - if err != nil { - return "", err - } - - password, err := promptPassword() - if err != nil { - return "", err - } - - // Attempt to authenticate and create a token - - note := fmt.Sprintf("git-bug - %s", url) - - ok, id, err := validateUsername(username) - if err != nil { - return "", err - } - if !ok { - return "", fmt.Errorf("invalid username") - } - - client, err := buildClientFromUsernameAndPassword(username, password) - if err != nil { - return "", err - } - - fmt.Println(username, password) - - return requestToken(client, id, note, "api") -} - -func promptUsername() (string, error) { - for { - fmt.Print("username: ") - - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return "", err - } - - line = strings.TrimRight(line, "\n") - - ok, _, err := validateUsername(line) - if err != nil { - return "", err - } - if ok { - return line, nil - } - - fmt.Println("invalid username") - } -} - func promptURL(remotes map[string]string) (string, error) { validRemotes := getValidGitlabRemoteURLs(remotes) if len(validRemotes) > 0 { @@ -355,24 +296,3 @@ func validateProjectURL(url, token string) (bool, int, error) { return true, project.ID, nil } - -func promptPassword() (string, error) { - for { - fmt.Print("password: ") - - bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) - // new line for coherent formatting, ReadPassword clip the normal new line - // entered by the user - fmt.Println() - - if err != nil { - return "", err - } - - if len(bytePassword) > 0 { - return string(bytePassword), nil - } - - fmt.Println("password is empty") - } -} -- cgit From 6c02f0951da7962ccdcc50628887ef4eda2eb3b7 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 12 Jul 2019 17:37:49 +0200 Subject: bridge/gitlab: prompt only for user provided token --- bridge/gitlab/config.go | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index 392452c6..7faddd6d 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -66,7 +66,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( if params.Token != "" { token = params.Token } else { - token, err = promptTokenOptions(url) + token, err = promptToken() if err != nil { return nil, err } @@ -122,34 +122,6 @@ func requestToken(client *gitlab.Client, userID int, name string, scopes ...stri return impToken.Token, nil } -//TODO fix this -func promptTokenOptions(url string) (string, error) { - for { - fmt.Println() - fmt.Println("[1]: user provided token") - fmt.Println("[2]: interactive token creation") - fmt.Print("Select option: ") - - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - fmt.Println() - if err != nil { - return "", err - } - - line = strings.TrimRight(line, "\n") - - index, err := strconv.Atoi(line) - if err != nil || (index != 1 && index != 2) { - fmt.Println("invalid input") - continue - } - - if index == 1 { - return promptToken() - } - } -} - func promptToken() (string, error) { fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.") fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") -- cgit From ce3a2788ab3bc9f205bcc27b03355155d641c2f4 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 17 Jul 2019 22:41:42 +0200 Subject: bridge/gitlab: fix note error handling bug bridge/gitlab: remove unused functions --- bridge/gitlab/config.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index 7faddd6d..cac2d91e 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -107,21 +107,6 @@ func (*Gitlab) ValidateConfig(conf core.Configuration) error { return nil } -func requestToken(client *gitlab.Client, userID int, name string, scopes ...string) (string, error) { - impToken, _, err := client.Users.CreateImpersonationToken( - userID, - &gitlab.CreateImpersonationTokenOptions{ - Name: &name, - Scopes: &scopes, - }, - ) - if err != nil { - return "", err - } - - return impToken.Token, nil -} - func promptToken() (string, error) { fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.") fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") @@ -231,28 +216,6 @@ func getValidGitlabRemoteURLs(remotes map[string]string) []string { return urls } -func validateUsername(username string) (bool, int, error) { - // no need for a token for this action - client := buildClient("") - - users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{Username: &username}) - if err != nil { - return false, 0, err - } - - if len(users) == 0 { - return false, 0, fmt.Errorf("username not found") - } else if len(users) > 1 { - return false, 0, fmt.Errorf("found multiple matches") - } - - if users[0].Username == username { - return true, users[0].ID, nil - } - - return false, 0, nil -} - func validateProjectURL(url, token string) (bool, int, error) { client := buildClient(token) -- cgit From 7726bbdbcf8aa49548ecbcfc323b47b0ec034f54 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 19 Jul 2019 18:49:28 +0200 Subject: bridge/gitlab: add bridge config tests --- bridge/gitlab/config.go | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index cac2d91e..efef5993 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -8,7 +8,6 @@ import ( "regexp" "strconv" "strings" - "time" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" @@ -17,16 +16,6 @@ import ( "github.com/MichaelMure/git-bug/repository" ) -const ( - target = "gitlab" - gitlabV4Url = "https://gitlab.com/api/v4" - keyProjectID = "project-id" - keyTarget = "target" - keyToken = "token" - - defaultTimeout = 60 * time.Second -) - var ( ErrBadProjectURL = errors.New("bad project url") ) @@ -108,10 +97,10 @@ func (*Gitlab) ValidateConfig(conf core.Configuration) error { } func promptToken() (string, error) { - fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.") - fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") + fmt.Println("You can generate a new token by visiting https://gitlab.com/profile/personal_access_tokens.") + fmt.Println("Choose 'Create personal access token' and set the necessary access scope for your repository.") fmt.Println() - fmt.Println("'api' scope access : access scope: to be able to make api calls") + fmt.Println("'api' access scope: to be able to make api calls") fmt.Println() re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`) @@ -192,13 +181,14 @@ func promptURL(remotes map[string]string) (string, error) { } func getProjectPath(url string) (string, error) { - cleanUrl := strings.TrimSuffix(url, ".git") + cleanUrl = strings.Replace(cleanUrl, "git@", "https://", 1) objectUrl, err := neturl.Parse(cleanUrl) if err != nil { - return "", nil + return "", err } + fmt.Println(objectUrl.Path) return objectUrl.Path[1:], nil } -- cgit From ece2cb126293361212d7673fea976876af7b811b Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Mon, 22 Jul 2019 18:56:14 +0200 Subject: bridge/gitlab: improve tests and errors bridge/gitlab: global fixes --- bridge/gitlab/config.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index efef5993..a375bab2 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -3,7 +3,7 @@ package gitlab import ( "bufio" "fmt" - neturl "net/url" + "net/url" "os" "regexp" "strconv" @@ -41,13 +41,13 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( // remote suggestions remotes, err := repo.GetRemotes() if err != nil { - return nil, err + return nil, errors.Wrap(err, "getting remotes") } // terminal prompt url, err = promptURL(remotes) if err != nil { - return nil, err + return nil, errors.Wrap(err, "url prompt") } } @@ -57,7 +57,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( } else { token, err = promptToken() if err != nil { - return nil, err + return nil, errors.Wrap(err, "token prompt") } } @@ -65,7 +65,7 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( // validate project url and get it ID ok, id, err := validateProjectURL(url, token) if err != nil { - return nil, err + return nil, errors.Wrap(err, "project validation") } if !ok { return nil, fmt.Errorf("invalid project id or wrong token scope") @@ -180,15 +180,14 @@ func promptURL(remotes map[string]string) (string, error) { } } -func getProjectPath(url string) (string, error) { - cleanUrl := strings.TrimSuffix(url, ".git") +func getProjectPath(projectUrl string) (string, error) { + cleanUrl := strings.TrimSuffix(projectUrl, ".git") cleanUrl = strings.Replace(cleanUrl, "git@", "https://", 1) - objectUrl, err := neturl.Parse(cleanUrl) + objectUrl, err := url.Parse(cleanUrl) if err != nil { - return "", err + return "", ErrBadProjectURL } - fmt.Println(objectUrl.Path) return objectUrl.Path[1:], nil } -- cgit From d098a96407c55281c28bfdea9925df587b4d4400 Mon Sep 17 00:00:00 2001 From: Amine Date: Tue, 23 Jul 2019 17:10:07 +0200 Subject: bridge/gitlab: global code and comment updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Michael Muré --- bridge/gitlab/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index a375bab2..dbbd1bd9 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -62,13 +62,13 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( } var ok bool - // validate project url and get it ID + // validate project url and get its ID ok, id, err := validateProjectURL(url, token) if err != nil { return nil, errors.Wrap(err, "project validation") } if !ok { - return nil, fmt.Errorf("invalid project id or wrong token scope") + return nil, fmt.Errorf("invalid project id or incorrect token scope") } conf[keyProjectID] = strconv.Itoa(id) @@ -121,7 +121,7 @@ func promptToken() (string, error) { return token, nil } - fmt.Println("token is invalid") + fmt.Println("token format is invalid") } } @@ -147,7 +147,7 @@ func promptURL(remotes map[string]string) (string, error) { line = strings.TrimRight(line, "\n") index, err := strconv.Atoi(line) - if err != nil || (index < 0 && index >= len(validRemotes)) { + if err != nil || (index < 0 && index > len(validRemotes)) { fmt.Println("invalid input") continue } -- cgit From 0329bfdf440ec48c5c5c5c6dbe2ca8519d99b706 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Tue, 23 Jul 2019 17:29:53 +0200 Subject: bridge/gitlab: change validateProjectURL signature bridge/gitlab: code cleanup --- bridge/gitlab/config.go | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) (limited to 'bridge/gitlab/config.go') diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index dbbd1bd9..15172871 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -61,26 +61,22 @@ func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) ( } } - var ok bool // validate project url and get its ID - ok, id, err := validateProjectURL(url, token) + id, err := validateProjectURL(url, token) if err != nil { return nil, errors.Wrap(err, "project validation") } - if !ok { - return nil, fmt.Errorf("invalid project id or incorrect token scope") - } conf[keyProjectID] = strconv.Itoa(id) conf[keyToken] = token - conf[keyTarget] = target + conf[core.KeyTarget] = target return conf, nil } func (*Gitlab) ValidateConfig(conf core.Configuration) error { - if v, ok := conf[keyTarget]; !ok { - return fmt.Errorf("missing %s key", keyTarget) + if v, ok := conf[core.KeyTarget]; !ok { + return fmt.Errorf("missing %s key", core.KeyTarget) } else if v != target { return fmt.Errorf("unexpected target name: %v", v) } @@ -147,7 +143,7 @@ func promptURL(remotes map[string]string) (string, error) { line = strings.TrimRight(line, "\n") index, err := strconv.Atoi(line) - if err != nil || (index < 0 && index > len(validRemotes)) { + if err != nil || index < 0 || index > len(validRemotes) { fmt.Println("invalid input") continue } @@ -205,18 +201,18 @@ func getValidGitlabRemoteURLs(remotes map[string]string) []string { return urls } -func validateProjectURL(url, token string) (bool, int, error) { - client := buildClient(token) - +func validateProjectURL(url, token string) (int, error) { projectPath, err := getProjectPath(url) if err != nil { - return false, 0, err + return 0, err } + client := buildClient(token) + project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{}) if err != nil { - return false, 0, err + return 0, err } - return true, project.ID, nil + return project.ID, nil } -- cgit