aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/gitlab/config.go
diff options
context:
space:
mode:
authorAmine Hilaly <hilalyamine@gmail.com>2019-07-10 00:41:43 +0200
committerAmine Hilaly <hilalyamine@gmail.com>2019-07-23 17:18:04 +0200
commit35a033c0f1f17fadc4525927aace4f4043038c7e (patch)
tree2a14f056747d133896fd1b0589df146bafdb93f4 /bridge/gitlab/config.go
parenta1a1d4868b7a32e90343b3c3b085fb523b20b8e2 (diff)
downloadgit-bug-35a033c0f1f17fadc4525927aace4f4043038c7e.tar.gz
bridge/gitlab: bridge project validation
bridge/gitlab: token generation
Diffstat (limited to 'bridge/gitlab/config.go')
-rw-r--r--bridge/gitlab/config.go230
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
- }
-}