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  }