v.io/jiri@v0.0.0-20160715023856-abfb8b131290/gerrit/credentials.go (about) 1 // Copyright 2016 The Vanadium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gerrit 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "net/url" 13 "os" 14 "path/filepath" 15 "strings" 16 17 "v.io/jiri/collect" 18 "v.io/jiri/runutil" 19 ) 20 21 type credentials struct { 22 username string 23 password string 24 } 25 26 // hostCredentials returns credentials for the given Gerrit host. The 27 // function uses best effort to scan common locations where the 28 // credentials could exist. 29 func hostCredentials(seq runutil.Sequence, hostUrl *url.URL) (_ *credentials, e error) { 30 // Look for the host credentials in the .netrc file. 31 netrcPath := filepath.Join(os.Getenv("HOME"), ".netrc") 32 file, err := seq.Open(netrcPath) 33 if err != nil { 34 if !runutil.IsNotExist(err) { 35 return nil, err 36 } 37 } else { 38 defer collect.Error(func() error { return file.Close() }, &e) 39 credsMap, err := parseNetrcFile(file) 40 if err != nil { 41 return nil, err 42 } 43 creds, ok := credsMap[hostUrl.Host] 44 if ok { 45 return creds, nil 46 } 47 } 48 49 // Look for the host credentials in the git cookie file. 50 args := []string{"config", "--get", "http.cookiefile"} 51 var stdout, stderr bytes.Buffer 52 if err := seq.Capture(&stdout, &stderr).Last("git", args...); err == nil { 53 cookieFilePath := strings.TrimSpace(stdout.String()) 54 file, err := seq.Open(cookieFilePath) 55 if err != nil { 56 if !runutil.IsNotExist(err) { 57 return nil, err 58 } 59 } else { 60 defer collect.Error(func() error { return file.Close() }, &e) 61 credsMap, err := parseGitCookieFile(file) 62 if err != nil { 63 return nil, err 64 } 65 creds, ok := credsMap[hostUrl.Host] 66 if ok { 67 return creds, nil 68 } 69 // Account for site-wide credentials. Namely, the git cookie 70 // file can contain credentials of the form ".<name>", which 71 // should match any host "*.<name>". 72 for host, creds := range credsMap { 73 if strings.HasPrefix(host, ".") && strings.HasSuffix(hostUrl.Host, host) { 74 return creds, nil 75 } 76 } 77 } 78 } 79 80 return nil, fmt.Errorf("cannot find credentials for %q", hostUrl.String()) 81 } 82 83 // parseGitCookieFile parses the content of the given git cookie file 84 // and returns credentials stored in the file indexed by hosts. 85 func parseGitCookieFile(reader io.Reader) (map[string]*credentials, error) { 86 credsMap := map[string]*credentials{} 87 scanner := bufio.NewScanner(reader) 88 for scanner.Scan() { 89 line := scanner.Text() 90 parts := strings.Split(line, "\t") 91 if len(parts) != 7 { 92 continue 93 } 94 tokens := strings.Split(parts[6], "=") 95 if len(tokens) != 2 { 96 continue 97 } 98 credsMap[parts[0]] = &credentials{ 99 username: tokens[0], 100 password: tokens[1], 101 } 102 } 103 if err := scanner.Err(); err != nil { 104 return nil, fmt.Errorf("Scan() failed: %v", err) 105 } 106 return credsMap, nil 107 } 108 109 // parseNetrcFile parses the content of the given netrc file and 110 // returns credentials stored in the file indexed by hosts. 111 func parseNetrcFile(reader io.Reader) (map[string]*credentials, error) { 112 credsMap := map[string]*credentials{} 113 scanner := bufio.NewScanner(reader) 114 for scanner.Scan() { 115 line := scanner.Text() 116 parts := strings.Split(line, " ") 117 if len(parts) != 6 || parts[0] != "machine" || parts[2] != "login" || parts[4] != "password" { 118 continue 119 } 120 host := parts[1] 121 if _, present := credsMap[host]; present { 122 return nil, fmt.Errorf("multiple logins exist for %q, please ensure there is only one", host) 123 } 124 credsMap[host] = &credentials{ 125 username: parts[3], 126 password: parts[5], 127 } 128 } 129 if err := scanner.Err(); err != nil { 130 return nil, fmt.Errorf("Scan() failed: %v", err) 131 } 132 return credsMap, nil 133 }