diff options
Diffstat (limited to 'bridge/gitlab/config.go')
-rw-r--r-- | bridge/gitlab/config.go | 230 |
1 files changed, 45 insertions, 185 deletions
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 - } -} |