merge master

This commit is contained in:
risk danger olson 2015-09-01 16:04:17 -06:00
commit ce18a1d11f
10 changed files with 581 additions and 443 deletions

@ -55,18 +55,18 @@ type objectResource struct {
Error *objectError `json:"error,omitempty"` Error *objectError `json:"error,omitempty"`
} }
func (o *objectResource) NewRequest(relation, method string) (*http.Request, Creds, error) { func (o *objectResource) NewRequest(relation, method string) (*http.Request, error) {
rel, ok := o.Rel(relation) rel, ok := o.Rel(relation)
if !ok { if !ok {
return nil, nil, errors.New("relation does not exist") return nil, errors.New("relation does not exist")
} }
req, creds, err := newClientRequest(method, rel.Href, rel.Header) req, err := newClientRequest(method, rel.Href, rel.Header)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
return req, creds, nil return req, nil
} }
func (o *objectResource) Rel(name string) (*linkRelation, bool) { func (o *objectResource) Rel(name string) (*linkRelation, bool) {
@ -105,23 +105,22 @@ func (e *ClientError) Error() string {
} }
func Download(oid string) (io.ReadCloser, int64, error) { func Download(oid string) (io.ReadCloser, int64, error) {
req, creds, err := newApiRequest("GET", oid) req, err := newApiRequest("GET", oid)
if err != nil { if err != nil {
return nil, 0, Error(err) return nil, 0, Error(err)
} }
res, obj, err := doApiRequest(req, creds) res, obj, err := doLegacyApiRequest(req)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
LogTransfer("lfs.api.download", res) LogTransfer("lfs.api.download", res)
req, err = obj.NewRequest("download", "GET")
req, creds, err = obj.NewRequest("download", "GET")
if err != nil { if err != nil {
return nil, 0, Error(err) return nil, 0, Error(err)
} }
res, err = doHttpRequest(req, creds) res, err = doStorageRequest(req)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -135,18 +134,18 @@ type byteCloser struct {
} }
func DownloadCheck(oid string) (*objectResource, error) { func DownloadCheck(oid string) (*objectResource, error) {
req, creds, err := newApiRequest("GET", oid) req, err := newApiRequest("GET", oid)
if err != nil { if err != nil {
return nil, Error(err) return nil, Error(err)
} }
res, obj, err := doApiRequest(req, creds) res, obj, err := doLegacyApiRequest(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
LogTransfer("lfs.api.download", res) LogTransfer("lfs.api.download", res)
_, _, err = obj.NewRequest("download", "GET") _, err = obj.NewRequest("download", "GET")
if err != nil { if err != nil {
return nil, Error(err) return nil, Error(err)
} }
@ -155,12 +154,12 @@ func DownloadCheck(oid string) (*objectResource, error) {
} }
func DownloadObject(obj *objectResource) (io.ReadCloser, int64, error) { func DownloadObject(obj *objectResource) (io.ReadCloser, int64, error) {
req, creds, err := obj.NewRequest("download", "GET") req, err := obj.NewRequest("download", "GET")
if err != nil { if err != nil {
return nil, 0, Error(err) return nil, 0, Error(err)
} }
res, err := doHttpRequest(req, creds) res, err := doStorageRequest(req)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -185,7 +184,7 @@ func Batch(objects []*objectResource, operation string) ([]*objectResource, erro
return nil, Error(err) return nil, Error(err)
} }
req, creds, err := newBatchApiRequest() req, err := newBatchApiRequest()
if err != nil { if err != nil {
return nil, Error(err) return nil, Error(err)
} }
@ -196,7 +195,8 @@ func Batch(objects []*objectResource, operation string) ([]*objectResource, erro
req.Body = &byteCloser{bytes.NewReader(by)} req.Body = &byteCloser{bytes.NewReader(by)}
tracerx.Printf("api: batch %d files", len(objects)) tracerx.Printf("api: batch %d files", len(objects))
res, objs, err := doApiBatchRequest(req, creds)
res, objs, err := doApiBatchRequest(req)
if err != nil { if err != nil {
if res == nil { if res == nil {
return nil, err return nil, err
@ -241,7 +241,7 @@ func UploadCheck(oidPath string) (*objectResource, error) {
return nil, Error(err) return nil, Error(err)
} }
req, creds, err := newApiRequest("POST", oid) req, err := newApiRequest("POST", oid)
if err != nil { if err != nil {
return nil, Error(err) return nil, Error(err)
} }
@ -252,7 +252,7 @@ func UploadCheck(oidPath string) (*objectResource, error) {
req.Body = &byteCloser{bytes.NewReader(by)} req.Body = &byteCloser{bytes.NewReader(by)}
tracerx.Printf("api: uploading (%s)", oid) tracerx.Printf("api: uploading (%s)", oid)
res, obj, err := doApiRequest(req, creds) res, obj, err := doLegacyApiRequest(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -290,7 +290,7 @@ func UploadObject(o *objectResource, cb CopyCallback) error {
Reader: file, Reader: file,
} }
req, creds, err := o.NewRequest("upload", "PUT") req, err := o.NewRequest("upload", "PUT")
if err != nil { if err != nil {
return Error(err) return Error(err)
} }
@ -298,16 +298,17 @@ func UploadObject(o *objectResource, cb CopyCallback) error {
if len(req.Header.Get("Content-Type")) == 0 { if len(req.Header.Get("Content-Type")) == 0 {
req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Content-Type", "application/octet-stream")
} }
if req.Header.Get("Transfer-Encoding") == "chunked" { if req.Header.Get("Transfer-Encoding") == "chunked" {
req.TransferEncoding = []string{"chunked"} req.TransferEncoding = []string{"chunked"}
} else { } else {
req.Header.Set("Content-Length", strconv.FormatInt(o.Size, 10)) req.Header.Set("Content-Length", strconv.FormatInt(o.Size, 10))
} }
req.ContentLength = o.Size
req.ContentLength = o.Size
req.Body = ioutil.NopCloser(reader) req.Body = ioutil.NopCloser(reader)
res, err := doHttpRequest(req, creds) res, err := doStorageRequest(req)
if err != nil { if err != nil {
return err return err
} }
@ -324,7 +325,7 @@ func UploadObject(o *objectResource, cb CopyCallback) error {
return nil return nil
} }
req, creds, err = o.NewRequest("verify", "POST") req, err = o.NewRequest("verify", "POST")
if err != nil { if err != nil {
return Error(err) return Error(err)
} }
@ -338,7 +339,7 @@ func UploadObject(o *objectResource, cb CopyCallback) error {
req.Header.Set("Content-Length", strconv.Itoa(len(by))) req.Header.Set("Content-Length", strconv.Itoa(len(by)))
req.ContentLength = int64(len(by)) req.ContentLength = int64(len(by))
req.Body = ioutil.NopCloser(bytes.NewReader(by)) req.Body = ioutil.NopCloser(bytes.NewReader(by))
res, err = doHttpRequest(req, creds) res, err = doAPIRequest(req)
if err != nil { if err != nil {
return err return err
} }
@ -350,6 +351,72 @@ func UploadObject(o *objectResource, cb CopyCallback) error {
return err return err
} }
// doLegacyApiRequest runs the request to the LFS legacy API.
func doLegacyApiRequest(req *http.Request) (*http.Response, *objectResource, error) {
via := make([]*http.Request, 0, 4)
res, err := doApiRequestWithRedirects(req, via, true)
if err != nil {
return res, nil, err
}
obj := &objectResource{}
err = decodeApiResponse(res, obj)
if err != nil {
setErrorResponseContext(err, res)
return nil, nil, err
}
return res, obj, nil
}
// doApiBatchRequest runs the request to the LFS batch API. If the API returns a
// 401, the repo will be marked as having private access and the request will be
// re-run. When the repo is marked as having private access, credentials will
// be retrieved.
func doApiBatchRequest(req *http.Request) (*http.Response, []*objectResource, error) {
res, err := doAPIRequest(req)
if err != nil {
return res, nil, err
}
var objs map[string][]*objectResource
err = decodeApiResponse(res, &objs)
if err != nil {
setErrorResponseContext(err, res)
}
return res, objs["objects"], err
}
// doStorageREquest runs the request to the storage API from a link provided by
// the "actions" or "_links" properties an LFS API response.
func doStorageRequest(req *http.Request) (*http.Response, error) {
creds, err := getCreds(req)
if err != nil {
return nil, err
}
return doHttpRequest(req, creds)
}
// doAPIRequest runs the request to the LFS API, without parsing the response
// body. If the API returns a 401, the repo will be marked as having private
// access and the request will be re-run. When the repo is marked as having
// private access, credentials will be retrieved.
func doAPIRequest(req *http.Request) (*http.Response, error) {
via := make([]*http.Request, 0, 4)
useCreds := true
if req.Method == "GET" || req.Method == "HEAD" {
useCreds = Config.PrivateAccess()
}
return doApiRequestWithRedirects(req, via, useCreds)
}
// doHttpRequest runs the given HTTP request. LFS or Storage API requests should
// use doApiBatchRequest() or doStorageRequest() instead.
func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) { func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
res, err := Config.HttpClient().Do(req) res, err := Config.HttpClient().Do(req)
if res == nil { if res == nil {
@ -364,8 +431,7 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
if err != nil { if err != nil {
err = Errorf(err, "Error for %s %s", res.Request.Method, res.Request.URL) err = Errorf(err, "Error for %s %s", res.Request.Method, res.Request.URL)
} else { } else {
saveCredentials(creds, res) err = handleResponse(res, creds)
err = handleResponse(res)
} }
if err != nil { if err != nil {
@ -379,7 +445,16 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
return res, err return res, err
} }
func doApiRequestWithRedirects(req *http.Request, creds Creds, via []*http.Request) (*http.Response, error) { func doApiRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) {
var creds Creds
if useCreds {
c, err := getCredsForAPI(req)
if err != nil {
return nil, err
}
creds = c
}
res, err := doHttpRequest(req, creds) res, err := doHttpRequest(req, creds)
if err != nil { if err != nil {
return res, err return res, err
@ -393,7 +468,7 @@ func doApiRequestWithRedirects(req *http.Request, creds Creds, via []*http.Reque
redirectTo = locurl.String() redirectTo = locurl.String()
} }
redirectedReq, redirectedCreds, err := newClientRequest(req.Method, redirectTo, nil) redirectedReq, err := newClientRequest(req.Method, redirectTo, nil)
if err != nil { if err != nil {
return res, Errorf(err, err.Error()) return res, Errorf(err, err.Error())
} }
@ -421,53 +496,15 @@ func doApiRequestWithRedirects(req *http.Request, creds Creds, via []*http.Reque
return res, Errorf(err, err.Error()) return res, Errorf(err, err.Error())
} }
return doApiRequestWithRedirects(redirectedReq, redirectedCreds, via) return doApiRequestWithRedirects(redirectedReq, via, useCreds)
} }
return res, nil return res, nil
} }
func doApiRequest(req *http.Request, creds Creds) (*http.Response, *objectResource, error) { func handleResponse(res *http.Response, creds Creds) error {
via := make([]*http.Request, 0, 4) saveCredentials(creds, res)
res, err := doApiRequestWithRedirects(req, creds, via)
if err != nil {
return res, nil, err
}
obj := &objectResource{}
err = decodeApiResponse(res, obj)
if err != nil {
setErrorResponseContext(err, res)
return nil, nil, err
}
return res, obj, nil
}
// doApiBatchRequest runs the request to the batch API. If the API returns a 401,
// the repo will be marked as having private access and the request will be
// re-run. When the repo is marked as having private access, credentials will
// be retrieved.
func doApiBatchRequest(req *http.Request, creds Creds) (*http.Response, []*objectResource, error) {
via := make([]*http.Request, 0, 4)
res, err := doApiRequestWithRedirects(req, creds, via)
if err != nil {
return res, nil, err
}
var objs map[string][]*objectResource
err = decodeApiResponse(res, &objs)
if err != nil {
setErrorResponseContext(err, res)
}
return res, objs["objects"], err
}
func handleResponse(res *http.Response) error {
if res.StatusCode < 400 { if res.StatusCode < 400 {
return nil return nil
} }
@ -525,19 +562,7 @@ func defaultError(res *http.Response) error {
return Error(fmt.Errorf(msgFmt, res.Request.URL)) return Error(fmt.Errorf(msgFmt, res.Request.URL))
} }
func saveCredentials(creds Creds, res *http.Response) { func newApiRequest(method, oid string) (*http.Request, error) {
if creds == nil {
return
}
if res.StatusCode < 300 {
execCreds(creds, "approve")
} else if res.StatusCode == 401 {
execCreds(creds, "reject")
}
}
func newApiRequest(method, oid string) (*http.Request, Creds, error) {
endpoint := Config.Endpoint() endpoint := Config.Endpoint()
objectOid := oid objectOid := oid
operation := "download" operation := "download"
@ -561,22 +586,22 @@ func newApiRequest(method, oid string) (*http.Request, Creds, error) {
u, err := ObjectUrl(endpoint, objectOid) u, err := ObjectUrl(endpoint, objectOid)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
req, creds, err := newClientRequest(method, u.String(), res.Header) req, err := newClientRequest(method, u.String(), res.Header)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
req.Header.Set("Accept", mediaType) req.Header.Set("Accept", mediaType)
return req, creds, nil return req, nil
} }
func newClientRequest(method, rawurl string, header map[string]string) (*http.Request, Creds, error) { func newClientRequest(method, rawurl string, header map[string]string) (*http.Request, error) {
req, err := http.NewRequest(method, rawurl, nil) req, err := http.NewRequest(method, rawurl, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
for key, value := range header { for key, value := range header {
@ -584,15 +609,11 @@ func newClientRequest(method, rawurl string, header map[string]string) (*http.Re
} }
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
creds, err := getCreds(req)
if err != nil {
return nil, nil, err
}
return req, creds, nil return req, nil
} }
func newBatchApiRequest() (*http.Request, Creds, error) { func newBatchApiRequest() (*http.Request, error) {
endpoint := Config.Endpoint() endpoint := Config.Endpoint()
res, err := sshAuthenticate(endpoint, "download", "") res, err := sshAuthenticate(endpoint, "download", "")
@ -608,12 +629,12 @@ func newBatchApiRequest() (*http.Request, Creds, error) {
u, err := ObjectUrl(endpoint, "batch") u, err := ObjectUrl(endpoint, "batch")
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
req, creds, err := newBatchClientRequest("POST", u.String()) req, err := newBatchClientRequest("POST", u.String())
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
req.Header.Set("Accept", mediaType) req.Header.Set("Accept", mediaType)
@ -623,78 +644,18 @@ func newBatchApiRequest() (*http.Request, Creds, error) {
} }
} }
return req, creds, nil return req, nil
} }
func newBatchClientRequest(method, rawurl string) (*http.Request, Creds, error) { func newBatchClientRequest(method, rawurl string) (*http.Request, error) {
req, err := http.NewRequest(method, rawurl, nil) req, err := http.NewRequest(method, rawurl, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
// Get the creds if we're private return req, nil
if Config.PrivateAccess() {
// The PrivateAccess() check can be pushed down and this block simplified
// once everything goes through the batch endpoint.
creds, err := getCreds(req)
if err != nil {
return nil, nil, err
}
return req, creds, nil
}
return req, nil, nil
}
func getCreds(req *http.Request) (Creds, error) {
if len(req.Header.Get("Authorization")) > 0 {
return nil, nil
}
apiUrl, err := Config.ObjectUrl("")
if err != nil {
return nil, err
}
if req.URL.Scheme != apiUrl.Scheme ||
req.URL.Host != apiUrl.Host {
return nil, nil
}
if setRequestAuthFromUrl(req, apiUrl) {
return nil, nil
}
credsUrl := apiUrl
if len(Config.CurrentRemote) > 0 {
if u, ok := Config.GitConfig("remote." + Config.CurrentRemote + ".url"); ok {
gitRemoteUrl, err := url.Parse(u)
if err != nil {
return nil, err
}
if gitRemoteUrl.Scheme == apiUrl.Scheme &&
gitRemoteUrl.Host == apiUrl.Host {
if setRequestAuthFromUrl(req, gitRemoteUrl) {
return nil, nil
}
credsUrl = gitRemoteUrl
}
}
}
creds, err := credentials(credsUrl)
if err != nil {
return nil, err
}
setRequestAuth(req, creds["username"], creds["password"])
return creds, nil
} }
func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool { func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool {
@ -710,6 +671,10 @@ func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool {
} }
func setRequestAuth(req *http.Request, user, pass string) { func setRequestAuth(req *http.Request, user, pass string) {
if len(user) == 0 && len(pass) == 0 {
return
}
token := fmt.Sprintf("%s:%s", user, pass) token := fmt.Sprintf("%s:%s", user, pass)
auth := "Basic " + base64.URLEncoding.EncodeToString([]byte(token)) auth := "Basic " + base64.URLEncoding.EncodeToString([]byte(token))
req.Header.Set("Authorization", auth) req.Header.Set("Authorization", auth)

@ -13,7 +13,7 @@ import (
func TestSuccessStatus(t *testing.T) { func TestSuccessStatus(t *testing.T) {
for _, status := range []int{200, 201, 202} { for _, status := range []int{200, 201, 202} {
res := &http.Response{StatusCode: status} res := &http.Response{StatusCode: status}
if err := handleResponse(res); err != nil { if err := handleResponse(res, nil); err != nil {
t.Errorf("Unexpected error for HTTP %d: %s", status, err.Error()) t.Errorf("Unexpected error for HTTP %d: %s", status, err.Error())
} }
} }
@ -59,7 +59,7 @@ func TestErrorStatusWithCustomMessage(t *testing.T) {
} }
res.Header.Set("Content-Type", "application/vnd.git-lfs+json; charset=utf-8") res.Header.Set("Content-Type", "application/vnd.git-lfs+json; charset=utf-8")
err = handleResponse(res) err = handleResponse(res, nil)
if err == nil { if err == nil {
t.Errorf("No error from HTTP %d", status) t.Errorf("No error from HTTP %d", status)
continue continue
@ -121,7 +121,7 @@ func TestErrorStatusWithDefaultMessage(t *testing.T) {
// purposely wrong content type so it falls back to default // purposely wrong content type so it falls back to default
res.Header.Set("Content-Type", "application/vnd.git-lfs+json2") res.Header.Set("Content-Type", "application/vnd.git-lfs+json2")
err = handleResponse(res) err = handleResponse(res, nil)
if err == nil { if err == nil {
t.Errorf("No error from HTTP %d", status) t.Errorf("No error from HTTP %d", status)
continue continue

@ -53,6 +53,7 @@ type Configuration struct {
loading sync.Mutex // guards initialization of gitConfig and remotes loading sync.Mutex // guards initialization of gitConfig and remotes
gitConfig map[string]string gitConfig map[string]string
origConfig map[string]string
remotes []string remotes []string
extensions map[string]Extension extensions map[string]Extension
fetchIncludePaths []string fetchIncludePaths []string
@ -234,11 +235,6 @@ func (c *Configuration) GitConfig(key string) (string, bool) {
return value, ok return value, ok
} }
func (c *Configuration) SetConfig(key, value string) {
c.loadGitConfig()
c.gitConfig[key] = value
}
func (c *Configuration) ObjectUrl(oid string) (*url.URL, error) { func (c *Configuration) ObjectUrl(oid string) (*url.URL, error) {
return ObjectUrl(c.Endpoint(), oid) return ObjectUrl(c.Endpoint(), oid)
} }
@ -290,12 +286,12 @@ func (c *Configuration) FetchPruneConfig() *FetchPruneConfig {
return c.fetchPruneConfig return c.fetchPruneConfig
} }
func (c *Configuration) loadGitConfig() { func (c *Configuration) loadGitConfig() bool {
c.loading.Lock() c.loading.Lock()
defer c.loading.Unlock() defer c.loading.Unlock()
if c.gitConfig != nil { if c.gitConfig != nil {
return return false
} }
uniqRemotes := make(map[string]bool) uniqRemotes := make(map[string]bool)
@ -373,4 +369,6 @@ func (c *Configuration) loadGitConfig() {
} }
c.remotes = append(c.remotes, remote) c.remotes = append(c.remotes, remote)
} }
return true
} }

@ -184,6 +184,7 @@ func TestBareHTTPEndpointAddsLfsSuffix(t *testing.T) {
} }
func TestObjectUrl(t *testing.T) { func TestObjectUrl(t *testing.T) {
defer Config.ResetConfig()
tests := map[string]string{ tests := map[string]string{
"http://example.com": "http://example.com/objects/oid", "http://example.com": "http://example.com/objects/oid",
"http://example.com/": "http://example.com/objects/oid", "http://example.com/": "http://example.com/objects/oid",
@ -205,6 +206,8 @@ func TestObjectUrl(t *testing.T) {
} }
func TestObjectsUrl(t *testing.T) { func TestObjectsUrl(t *testing.T) {
defer Config.ResetConfig()
tests := map[string]string{ tests := map[string]string{
"http://example.com": "http://example.com/objects", "http://example.com": "http://example.com/objects",
"http://example.com/": "http://example.com/objects", "http://example.com/": "http://example.com/objects",
@ -391,6 +394,7 @@ func TestLoadInvalidExtension(t *testing.T) {
assert.Equal(t, "", ext.Smudge) assert.Equal(t, "", ext.Smudge)
assert.Equal(t, 0, ext.Priority) assert.Equal(t, 0, ext.Priority)
} }
func TestFetchPruneConfigDefault(t *testing.T) { func TestFetchPruneConfigDefault(t *testing.T) {
config := &Configuration{} config := &Configuration{}
fp := config.FetchPruneConfig() fp := config.FetchPruneConfig()
@ -416,5 +420,27 @@ func TestFetchPruneConfigCustom(t *testing.T) {
assert.Equal(t, 9, fp.FetchRecentCommitsDays) assert.Equal(t, 9, fp.FetchRecentCommitsDays)
assert.Equal(t, 30, fp.PruneOffsetDays) assert.Equal(t, 30, fp.PruneOffsetDays)
assert.Equal(t, false, fp.FetchRecentRefsIncludeRemotes) assert.Equal(t, false, fp.FetchRecentRefsIncludeRemotes)
}
// only used for tests
func (c *Configuration) SetConfig(key, value string) {
if c.loadGitConfig() {
c.loading.Lock()
c.origConfig = make(map[string]string)
for k, v := range c.gitConfig {
c.origConfig[k] = v
}
c.loading.Unlock()
}
c.gitConfig[key] = value
}
func (c *Configuration) ResetConfig() {
c.loading.Lock()
c.gitConfig = make(map[string]string)
for k, v := range c.origConfig {
c.gitConfig[k] = v
}
c.loading.Unlock()
} }

@ -3,69 +3,124 @@ package lfs
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"os/exec" "os/exec"
"strings" "strings"
) )
type credentialFetcher interface { // getCreds gets the credentials for the given request's URL, and sets its
Credentials() Creds // Authorization header with them using Basic Authentication. This is like
// getCredsForAPI(), but skips checking the LFS url or git remote.
func getCreds(req *http.Request) (Creds, error) {
if len(req.Header.Get("Authorization")) > 0 {
return nil, nil
}
creds, err := fillCredentials(req.URL)
if err != nil {
return nil, Error(err)
}
setRequestAuth(req, creds["username"], creds["password"])
return creds, nil
} }
type credentialFunc func(Creds, string) (credentialFetcher, error) // getCredsForAPI gets the credentials for LFS API requests and sets the given
// request's Authorization header with them using Basic Authentication.
// 1. Check the LFS URL for authentication. Ex: http://user:pass@example.com
// 2. Check the Git remote URL for authentication IF it's the same scheme and
// host of the LFS URL.
// 3. Ask 'git credential' to fill in the password from one of the above URLs.
//
// This prefers the Git remote URL for checking credentials so that users only
// have to enter their passwords once for Git and Git LFS. It uses the same
// URL path that Git does, in case 'useHttpPath' is enabled in the Git config.
func getCredsForAPI(req *http.Request) (Creds, error) {
if len(req.Header.Get("Authorization")) > 0 {
return nil, nil
}
var execCreds credentialFunc credsUrl, err := getCredURLForAPI(req)
if err != nil {
return nil, Error(err)
}
func credentials(u *url.URL) (Creds, error) { if credsUrl == nil {
path := strings.TrimPrefix(u.Path, "/") return nil, nil
creds := Creds{"protocol": u.Scheme, "host": u.Host, "path": path} }
cmd, err := execCreds(creds, "fill")
creds, err := fillCredentials(credsUrl)
if err != nil {
return nil, Error(err)
}
if creds != nil {
setRequestAuth(req, creds["username"], creds["password"])
}
return creds, nil
}
func getCredURLForAPI(req *http.Request) (*url.URL, error) {
apiUrl, err := Config.ObjectUrl("")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return cmd.Credentials(), nil
}
type CredentialCmd struct { // if the LFS request doesn't match the current LFS url, don't bother
output *bytes.Buffer // attempting to set the Authorization header from the LFS or Git remote URLs.
SubCommand string if req.URL.Scheme != apiUrl.Scheme ||
*exec.Cmd req.URL.Host != apiUrl.Host {
} return req.URL, nil
func NewCommand(input Creds, subCommand string) *CredentialCmd {
buf1 := new(bytes.Buffer)
cmd := exec.Command("git", "credential", subCommand)
cmd.Stdin = input.Buffer()
cmd.Stdout = buf1
/*
There is a reason we don't hook up stderr here:
Git's credential cache daemon helper does not close its stderr, so if this
process is the process that fires up the daemon, it will wait forever
(until the daemon exits, really) trying to read from stderr.
See https://github.com/github/git-lfs/issues/117 for more details.
*/
return &CredentialCmd{buf1, subCommand, cmd}
}
func (c *CredentialCmd) StdoutString() string {
return c.output.String()
}
func (c *CredentialCmd) Credentials() Creds {
creds := make(Creds)
for _, line := range strings.Split(c.StdoutString(), "\n") {
pieces := strings.SplitN(line, "=", 2)
if len(pieces) < 2 {
continue
}
creds[pieces[0]] = pieces[1]
} }
return creds if setRequestAuthFromUrl(req, apiUrl) {
return nil, nil
}
credsUrl := apiUrl
if len(Config.CurrentRemote) > 0 {
if u, ok := Config.GitConfig("remote." + Config.CurrentRemote + ".url"); ok {
gitRemoteUrl, err := url.Parse(u)
if err != nil {
return nil, err
}
if gitRemoteUrl.Scheme == apiUrl.Scheme &&
gitRemoteUrl.Host == apiUrl.Host {
if setRequestAuthFromUrl(req, gitRemoteUrl) {
return nil, nil
}
credsUrl = gitRemoteUrl
}
}
}
return credsUrl, nil
}
func fillCredentials(u *url.URL) (Creds, error) {
path := strings.TrimPrefix(u.Path, "/")
creds := Creds{"protocol": u.Scheme, "host": u.Host, "path": path}
return execCreds(creds, "fill")
}
func saveCredentials(creds Creds, res *http.Response) {
if creds == nil {
return
}
switch res.StatusCode {
case 401, 403:
execCreds(creds, "reject")
default:
if res.StatusCode < 300 {
execCreds(creds, "approve")
}
}
} }
type Creds map[string]string type Creds map[string]string
@ -83,25 +138,54 @@ func (c Creds) Buffer() *bytes.Buffer {
return buf return buf
} }
func init() { type credentialFunc func(Creds, string) (Creds, error)
execCreds = func(input Creds, subCommand string) (credentialFetcher, error) {
cmd := NewCommand(input, subCommand) func execCredsCommand(input Creds, subCommand string) (Creds, error) {
output := new(bytes.Buffer)
cmd := exec.Command("git", "credential", subCommand)
cmd.Stdin = input.Buffer()
cmd.Stdout = output
/*
There is a reason we don't hook up stderr here:
Git's credential cache daemon helper does not close its stderr, so if this
process is the process that fires up the daemon, it will wait forever
(until the daemon exits, really) trying to read from stderr.
See https://github.com/github/git-lfs/issues/117 for more details.
*/
err := cmd.Start() err := cmd.Start()
if err == nil { if err == nil {
err = cmd.Wait() err = cmd.Wait()
} }
if exitErr, ok := err.(*exec.ExitError); ok { if _, ok := err.(*exec.ExitError); ok {
if exitErr.ProcessState.Success() == false && !Config.GetenvBool("GIT_TERMINAL_PROMPT", true) { if !Config.GetenvBool("GIT_TERMINAL_PROMPT", true) {
return nil, fmt.Errorf("Change the GIT_TERMINAL_PROMPT env var to be prompted to enter your credentials for %s://%s.", return nil, fmt.Errorf("Change the GIT_TERMINAL_PROMPT env var to be prompted to enter your credentials for %s://%s.",
input["protocol"], input["host"]) input["protocol"], input["host"])
} }
// 'git credential' exits with 128 if the helper doesn't fill the username
// and password values.
if subCommand == "fill" && err.Error() == "exit status 128" {
return input, nil
}
} }
if err != nil { if err != nil {
return cmd, fmt.Errorf("'git credential %s' error: %s\n", cmd.SubCommand, err.Error()) return nil, fmt.Errorf("'git credential %s' error: %s\n", subCommand, err.Error())
} }
return cmd, nil creds := make(Creds)
for _, line := range strings.Split(output.String(), "\n") {
pieces := strings.SplitN(line, "=", 2)
if len(pieces) < 2 {
continue
} }
creds[pieces[0]] = pieces[1]
}
return creds, nil
} }
var execCreds credentialFunc = execCredsCommand

@ -2,156 +2,222 @@ package lfs
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"net/http" "net/http"
"testing" "testing"
) )
func TestGetCredentialsForApi(t *testing.T) {
checkGetCredentials(t, getCredsForAPI, []*getCredentialCheck{
{
Desc: "simple",
Config: map[string]string{"lfs.url": "https://git-server.com"},
Method: "GET",
Href: "https://git-server.com/foo",
Protocol: "https",
Host: "git-server.com",
Username: "git-server.com",
Password: "monkey",
},
{
Desc: "auth header",
Config: map[string]string{"lfs.url": "https://git-server.com"},
Header: map[string]string{"Authorization": "Test monkey"},
Method: "GET",
Href: "https://git-server.com/foo",
Authorization: "Test monkey",
},
{
Desc: "scheme mismatch",
Config: map[string]string{"lfs.url": "https://git-server.com"},
Method: "GET",
Href: "http://git-server.com/foo",
Protocol: "http",
Host: "git-server.com",
Path: "foo",
Username: "git-server.com",
Password: "monkey",
},
{
Desc: "host mismatch",
Config: map[string]string{"lfs.url": "https://git-server.com"},
Method: "GET",
Href: "https://git-server2.com/foo",
Protocol: "https",
Host: "git-server2.com",
Path: "foo",
Username: "git-server2.com",
Password: "monkey",
},
{
Desc: "port mismatch",
Config: map[string]string{"lfs.url": "https://git-server.com"},
Method: "GET",
Href: "https://git-server.com:8080/foo",
Protocol: "https",
Host: "git-server.com:8080",
Path: "foo",
Username: "git-server.com:8080",
Password: "monkey",
},
{
Desc: "api url auth",
Config: map[string]string{"lfs.url": "https://testuser:testpass@git-server.com"},
Method: "GET",
Href: "https://git-server.com/foo",
Authorization: "Basic " + base64.URLEncoding.EncodeToString([]byte("testuser:testpass")),
},
{
Desc: "git url auth",
CurrentRemote: "origin",
Config: map[string]string{
"lfs.url": "https://git-server.com",
"remote.origin.url": "https://gituser:gitpass@git-server.com",
},
Method: "GET",
Href: "https://git-server.com/foo",
Authorization: "Basic " + base64.URLEncoding.EncodeToString([]byte("gituser:gitpass")),
},
})
}
func TestGetCredentials(t *testing.T) { func TestGetCredentials(t *testing.T) {
Config.SetConfig("lfs.url", "https://lfs-server.com") checks := []*getCredentialCheck{
req, err := http.NewRequest("GET", "https://lfs-server.com/foo", nil) {
Desc: "git server",
Method: "GET",
Href: "https://git-server.com/foo",
Protocol: "https",
Host: "git-server.com",
Username: "git-server.com",
Password: "monkey",
},
{
Desc: "separate lfs server",
Method: "GET",
Href: "https://lfs-server.com/foo",
Protocol: "https",
Host: "lfs-server.com",
Username: "lfs-server.com",
Password: "monkey",
},
}
// these properties should not change the outcome
for _, check := range checks {
check.CurrentRemote = "origin"
check.Config = map[string]string{
"lfs.url": "https://testuser:testuser@git-server.com",
"remote.origin.url": "https://gituser:gitpass@git-server.com",
}
}
checkGetCredentials(t, getCreds, checks)
}
func checkGetCredentials(t *testing.T, getCredsFunc func(*http.Request) (Creds, error), checks []*getCredentialCheck) {
existingRemote := Config.CurrentRemote
for _, check := range checks {
t.Logf("Checking %q", check.Desc)
Config.CurrentRemote = check.CurrentRemote
for key, value := range check.Config {
Config.SetConfig(key, value)
}
req, err := http.NewRequest(check.Method, check.Href, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Errorf("[%s] %s", check.Desc, err)
continue
} }
creds, err := getCreds(req) for key, value := range check.Header {
req.Header.Set(key, value)
}
creds, err := getCredsFunc(req)
if err != nil { if err != nil {
t.Fatal(err) t.Errorf("[%s] %s", check.Desc, err)
continue
} }
if value := creds["username"]; value != "lfs-server.com" { if check.ExpectCreds() {
t.Errorf("bad username: %s", value) if creds == nil {
t.Errorf("[%s], no credentials returned", check.Desc)
continue
} }
if value := creds["password"]; value != "monkey" { if value := creds["protocol"]; len(check.Protocol) > 0 && value != check.Protocol {
t.Errorf("bad password: %s", value) t.Errorf("[%s] bad protocol: %q, expected: %q", check.Desc, value, check.Protocol)
} }
expected := "Basic " + base64.URLEncoding.EncodeToString([]byte("lfs-server.com:monkey")) if value := creds["host"]; len(check.Host) > 0 && value != check.Host {
t.Errorf("[%s] bad host: %q, expected: %q", check.Desc, value, check.Host)
}
if value := creds["username"]; len(check.Username) > 0 && value != check.Username {
t.Errorf("[%s] bad username: %q, expected: %q", check.Desc, value, check.Username)
}
if value := creds["password"]; len(check.Password) > 0 && value != check.Password {
t.Errorf("[%s] bad password: %q, expected: %q", check.Desc, value, check.Password)
}
if value := creds["path"]; len(check.Path) > 0 && value != check.Path {
t.Errorf("[%s] bad path: %q, expected: %q", check.Desc, value, check.Path)
}
} else {
if creds != nil {
t.Errorf("[%s], unexpected credentials: %v // %v", check.Desc, creds, check)
continue
}
}
if len(check.Authorization) > 0 {
if actual := req.Header.Get("Authorization"); actual != check.Authorization {
t.Errorf("[%s] Unexpected Authorization header: %s", check.Desc, actual)
}
} else {
rawtoken := fmt.Sprintf("%s:%s", check.Username, check.Password)
expected := "Basic " + base64.URLEncoding.EncodeToString([]byte(rawtoken))
if value := req.Header.Get("Authorization"); value != expected { if value := req.Header.Get("Authorization"); value != expected {
t.Errorf("Bad Authorization. Expected '%s', got '%s'", expected, value) t.Errorf("[%s] Bad Authorization. Expected '%s', got '%s'", check.Desc, expected, value)
}
}
Config.ResetConfig()
Config.CurrentRemote = existingRemote
} }
} }
func TestGetCredentialsWithExistingAuthorization(t *testing.T) { type getCredentialCheck struct {
Config.SetConfig("lfs.url", "https://lfs-server.com") Desc string
req, err := http.NewRequest("GET", "http://lfs-server.com/foo", nil) Config map[string]string
if err != nil { Header map[string]string
t.Fatal(err) Method string
} Href string
Protocol string
req.Header.Set("Authorization", "Test monkey") Host string
Username string
creds, err := getCreds(req) Password string
if err != nil { Path string
t.Fatal(err) Authorization string
} CurrentRemote string
if creds != nil {
t.Errorf("Unexpected creds: %v", creds)
}
if actual := req.Header.Get("Authorization"); actual != "Test monkey" {
t.Errorf("Unexpected Authorization header: %s", actual)
}
} }
func TestGetCredentialsWithSchemeMismatch(t *testing.T) { func (c *getCredentialCheck) ExpectCreds() bool {
Config.SetConfig("lfs.url", "https://lfs-server.com") return len(c.Protocol) > 0 || len(c.Host) > 0 || len(c.Username) > 0 ||
req, err := http.NewRequest("GET", "http://lfs-server.com/foo", nil) len(c.Password) > 0 || len(c.Path) > 0
if err != nil {
t.Fatal(err)
}
creds, err := getCreds(req)
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("Unexpected creds: %v", creds)
}
if actual := req.Header.Get("Authorization"); actual != "" {
t.Errorf("Unexpected Authorization header: %s", actual)
}
}
func TestGetCredentialsWithHostMismatch(t *testing.T) {
Config.SetConfig("lfs.url", "https://lfs-server.com")
req, err := http.NewRequest("GET", "https://lfs-server2.com/foo", nil)
if err != nil {
t.Fatal(err)
}
creds, err := getCreds(req)
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("Unexpected creds: %v", creds)
}
if actual := req.Header.Get("Authorization"); actual != "" {
t.Errorf("Unexpected Authorization header: %s", actual)
}
}
func TestGetCredentialsWithPortMismatch(t *testing.T) {
Config.SetConfig("lfs.url", "https://lfs-server.com")
req, err := http.NewRequest("GET", "https://lfs-server:8080.com/foo", nil)
if err != nil {
t.Fatal(err)
}
creds, err := getCreds(req)
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("Unexpected creds: %v", creds)
}
if actual := req.Header.Get("Authorization"); actual != "" {
t.Errorf("Unexpected Authorization header: %s", actual)
}
}
func TestGetCredentialsWithRfc1738UsernameAndPassword(t *testing.T) {
Config.SetConfig("lfs.url", "https://testuser:testpass@lfs-server.com")
req, err := http.NewRequest("GET", "https://lfs-server.com/foo", nil)
if err != nil {
t.Fatal(err)
}
creds, err := getCreds(req)
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("unexpected creds: %v", creds)
}
expected := "Basic " + base64.URLEncoding.EncodeToString([]byte("testuser:testpass"))
if value := req.Header.Get("Authorization"); value != expected {
t.Errorf("Bad Authorization. Expected '%s', got '%s'", expected, value)
}
} }
func init() { func init() {
execCreds = func(input Creds, subCommand string) (credentialFetcher, error) { execCreds = func(input Creds, subCommand string) (Creds, error) {
return &testCredentialFetcher{input}, nil output := make(Creds)
for key, value := range input {
output[key] = value
}
output["username"] = input["host"]
output["password"] = "monkey"
return output, nil
} }
} }
type testCredentialFetcher struct {
Creds Creds
}
func (c *testCredentialFetcher) Credentials() Creds {
c.Creds["username"] = c.Creds["host"]
c.Creds["password"] = "monkey"
return c.Creds
}

@ -72,10 +72,6 @@ func TestSuccessfulDownload(t *testing.T) {
t.Error("Invalid Accept") t.Error("Invalid Accept")
} }
if r.Header.Get("Authorization") != expectedAuth(t, server) {
t.Error("Invalid Authorization")
}
if r.Header.Get("A") != "1" { if r.Header.Get("A") != "1" {
t.Error("invalid A") t.Error("invalid A")
} }
@ -87,6 +83,7 @@ func TestSuccessfulDownload(t *testing.T) {
w.Write([]byte("test")) w.Write([]byte("test"))
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
@ -204,10 +201,6 @@ func TestSuccessfulDownloadWithRedirects(t *testing.T) {
t.Error("Invalid Accept") t.Error("Invalid Accept")
} }
if r.Header.Get("Authorization") != expectedAuth(t, server) {
t.Error("Invalid Authorization")
}
if r.Header.Get("A") != "1" { if r.Header.Get("A") != "1" {
t.Error("invalid A") t.Error("invalid A")
} }
@ -219,6 +212,7 @@ func TestSuccessfulDownloadWithRedirects(t *testing.T) {
w.Write([]byte("test")) w.Write([]byte("test"))
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/redirect") Config.SetConfig("lfs.url", server.URL+"/redirect")
for _, redirect := range redirectCodes { for _, redirect := range redirectCodes {
@ -324,6 +318,7 @@ func TestSuccessfulDownloadWithAuthorization(t *testing.T) {
w.Write([]byte("test")) w.Write([]byte("test"))
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
@ -412,10 +407,6 @@ func TestSuccessfulDownloadFromSeparateHost(t *testing.T) {
t.Error("Invalid Accept") t.Error("Invalid Accept")
} }
if r.Header.Get("Authorization") != "" {
t.Error("Invalid Authorization")
}
if r.Header.Get("A") != "1" { if r.Header.Get("A") != "1" {
t.Error("invalid A") t.Error("invalid A")
} }
@ -427,6 +418,7 @@ func TestSuccessfulDownloadFromSeparateHost(t *testing.T) {
w.Write([]byte("test")) w.Write([]byte("test"))
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
reader, size, err := Download("oid") reader, size, err := Download("oid")
if err != nil { if err != nil {
@ -546,10 +538,6 @@ func TestSuccessfulDownloadFromSeparateRedirectedHost(t *testing.T) {
t.Error("Invalid Accept") t.Error("Invalid Accept")
} }
if r.Header.Get("Authorization") != "" {
t.Error("Invalid Authorization")
}
if r.Header.Get("A") != "1" { if r.Header.Get("A") != "1" {
t.Error("invalid A") t.Error("invalid A")
} }
@ -561,6 +549,7 @@ func TestSuccessfulDownloadFromSeparateRedirectedHost(t *testing.T) {
w.Write([]byte("test")) w.Write([]byte("test"))
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
for _, redirect := range redirectCodes { for _, redirect := range redirectCodes {
@ -597,6 +586,7 @@ func TestDownloadAPIError(t *testing.T) {
w.WriteHeader(404) w.WriteHeader(404)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid") _, _, err := Download("oid")
if err == nil { if err == nil {
@ -664,6 +654,7 @@ func TestDownloadStorageError(t *testing.T) {
w.WriteHeader(500) w.WriteHeader(500)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
_, _, err := Download("oid") _, _, err := Download("oid")
if err == nil { if err == nil {

@ -103,6 +103,7 @@ func TestExistingUpload(t *testing.T) {
w.WriteHeader(200) w.WriteHeader(200)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -226,6 +227,7 @@ func TestUploadWithRedirect(t *testing.T) {
w.Write(by) w.Write(by)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/redirect") Config.SetConfig("lfs.url", server.URL+"/redirect")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -399,6 +401,7 @@ func TestSuccessfulUploadWithVerify(t *testing.T) {
w.WriteHeader(200) w.WriteHeader(200)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -559,6 +562,7 @@ func TestSuccessfulUploadWithoutVerify(t *testing.T) {
w.WriteHeader(200) w.WriteHeader(200)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -603,6 +607,7 @@ func TestUploadApiError(t *testing.T) {
w.WriteHeader(404) w.WriteHeader(404)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -710,6 +715,7 @@ func TestUploadStorageError(t *testing.T) {
w.WriteHeader(404) w.WriteHeader(404)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")
@ -858,6 +864,7 @@ func TestUploadVerifyError(t *testing.T) {
w.WriteHeader(404) w.WriteHeader(404)
}) })
defer Config.ResetConfig()
Config.SetConfig("lfs.url", server.URL+"/media") Config.SetConfig("lfs.url", server.URL+"/media")
oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11") oidPath, _ := LocalMediaPath("988881adc9fc3655077dc2d4d757d480b5ea0e11")

@ -6,7 +6,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
) )
@ -18,9 +17,6 @@ var (
} }
delim = '\n' delim = '\n'
hostRE = regexp.MustCompile(`\A127.0.0.1:\d+\z`)
credsDir = "" credsDir = ""
) )
@ -74,6 +70,7 @@ func fill() {
os.Exit(1) os.Exit(1)
} }
if user != "skip" {
if _, ok := creds["username"]; !ok { if _, ok := creds["username"]; !ok {
creds["username"] = user creds["username"] = user
} }
@ -81,6 +78,7 @@ func fill() {
if _, ok := creds["password"]; !ok { if _, ok := creds["password"]; !ok {
creds["password"] = pass creds["password"] = pass
} }
}
for key, value := range creds { for key, value := range creds {
fmt.Fprintf(os.Stderr, "CREDS SEND: %s=%s\n", key, value) fmt.Fprintf(os.Stderr, "CREDS SEND: %s=%s\n", key, value)

@ -2,54 +2,7 @@
. "test/testlib.sh" . "test/testlib.sh"
begin_test "git credential" begin_test "credentials without useHttpPath, with wrong path password"
(
set -e
printf "git:server" > "$CREDSDIR/credential-test.com"
printf "git:path" > "$CREDSDIR/credential-test.com--some-path"
mkdir empty
cd empty
git init
echo "protocol=http
host=credential-test.com" | GIT_TERMINAL_PROMPT=0 git credential fill | tee cred.log
expected="protocol=http
host=credential-test.com
username=git
password=server"
[ "$expected" = "$(cat cred.log)" ]
echo "protocol=http
host=credential-test.com
path=some/path" | GIT_TERMINAL_PROMPT=0 git credential fill | tee cred.log
expected="protocol=http
host=credential-test.com
username=git
password=server"
[ "$expected" = "$(cat cred.log)" ]
git config credential.useHttpPath true
echo "protocol=http
host=credential-test.com
path=some/path" | GIT_TERMINAL_PROMPT=0 git credential fill | tee cred.log
expected="protocol=http
host=credential-test.com
path=some/path
username=git
password=path"
[ "$expected" = "$(cat cred.log)" ]
)
end_test
begin_test "credentials without useHttpPath, with wrong password"
( (
set -e set -e
@ -136,3 +89,53 @@ begin_test "credentials with useHttpPath, with correct password"
grep "(1 of 1 files)" push.log grep "(1 of 1 files)" push.log
) )
end_test end_test
begin_test "git credential"
(
set -e
printf "git:server" > "$CREDSDIR/credential-test.com"
printf "git:path" > "$CREDSDIR/credential-test.com--some-path"
mkdir empty
cd empty
git init
echo "protocol=http
host=credential-test.com" | GIT_TERMINAL_PROMPT=0 git credential fill > cred.log
cat cred.log
expected="protocol=http
host=credential-test.com
username=git
password=server"
[ "$expected" = "$(cat cred.log)" ]
echo "protocol=http
host=credential-test.com
path=some/path" | GIT_TERMINAL_PROMPT=0 git credential fill > cred.log
cat cred.log
expected="protocol=http
host=credential-test.com
username=git
password=server"
[ "$expected" = "$(cat cred.log)" ]
git config credential.useHttpPath true
echo "protocol=http
host=credential-test.com
path=some/path" | GIT_TERMINAL_PROMPT=0 git credential fill > cred.log
cat cred.log
expected="protocol=http
host=credential-test.com
path=some/path
username=git
password=path"
[ "$expected" = "$(cat cred.log)" ]
)
end_test