# Git LFS Changelog
-### 2.4.1 (18 May, 2018)
+## 2.4.2 (28 May, 2018)
+
+### Bugs
+
+* lfsapi: re-authenticate HTTP redirects when needed #3028 (@ttaylorr)
+* lfsapi: allow unknown keywords in netrc file(s) #3027 (@ttaylorr)
+
+## 2.4.1 (18 May, 2018)
This release fixes a handful of bugs found and fixed since v2.4.0. In
particular, Git LFS no longer panic()'s after invalid API responses, can
"os"
"path/filepath"
- "github.com/bgentry/go-netrc/netrc"
+ "github.com/git-lfs/go-netrc/netrc"
)
type netrcfinder interface {
)
const (
- Version = "2.4.1"
+ Version = "2.4.2"
)
func init() {
+git-lfs (2.4.2) stable; urgency=low
+
+ * New upstream version
+
+ -- Taylor Blau <me@ttaylorr.com> Mon, 28 May 2018 14:29:00 +0000
+
git-lfs (2.4.1) stable; urgency=low
* New upstream version
-hash: bad2138ca7787101a7a23af2464319cc580f4285e90c07d11eb9f90ad3bb9604
-updated: 2018-02-27T14:39:39.133796-08:00
+hash: 5d2fbd8be4931b982d29c6ac8df833f139b28ffdb44ca062948a2386e2096a4d
+updated: 2018-05-28T18:35:44.591993-07:00
imports:
- name: github.com/alexbrainman/sspi
version: 4729b3d4d8581b2db83864d1018926e4154f9406
subpackages:
- ntlm
-- name: github.com/bgentry/go-netrc
- version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480
- subpackages:
- - netrc
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
+- name: github.com/git-lfs/go-netrc
+ version: e0e9ca483a183481412e6f5a700ff20a36177503
+ subpackages:
+ - netrc
- name: github.com/git-lfs/wildmatch
version: 8a0518641565a619e62a2738c7d4498fc345daf6
- name: github.com/inconshreveable/mousetrap
package: github.com/git-lfs/git-lfs
import:
-- package: github.com/bgentry/go-netrc
- version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480
+- package: github.com/git-lfs/go-netrc
+ version: e0e9ca483a183481412e6f5a700ff20a36177503
subpackages:
- netrc
- package: github.com/kr/pty
"os"
"strings"
- "github.com/bgentry/go-netrc/netrc"
"github.com/git-lfs/git-lfs/errors"
+ "github.com/git-lfs/go-netrc/netrc"
"github.com/rubyist/tracerx"
)
// authentication from netrc or git's credential helpers if necessary,
// supporting basic and ntlm authentication.
func (c *Client) DoWithAuth(remote string, req *http.Request) (*http.Response, error) {
+ return c.doWithAuth(remote, req, nil)
+}
+
+func (c *Client) doWithAuth(remote string, req *http.Request, via []*http.Request) (*http.Response, error) {
req.Header = c.extraHeadersFor(req)
apiEndpoint, access, credHelper, credsURL, creds, err := c.getCreds(remote, req)
return nil, err
}
- res, err := c.doWithCreds(req, credHelper, creds, credsURL, access)
+ res, err := c.doWithCreds(req, credHelper, creds, credsURL, access, via)
if err != nil {
if errors.IsAuthError(err) {
newAccess := getAuthAccess(res)
req.Header.Del("Authorization")
credHelper.Reject(creds)
}
+
+ // This case represents a rejected request that
+ // should have been authenticated but wasn't. Do
+ // not count this against our redirection
+ // maximum, so do not recur through doWithAuth
+ // and instead call DoWithAuth.
return c.DoWithAuth(remote, req)
}
}
return res, err
}
-func (c *Client) doWithCreds(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL, access Access) (*http.Response, error) {
+func (c *Client) doWithCreds(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL, access Access, via []*http.Request) (*http.Response, error) {
if access == NTLMAccess {
return c.doWithNTLM(req, credHelper, creds, credsURL)
}
- return c.do(req)
+ return c.do(req, "", via)
}
// getCreds fills the authorization header for the given request if possible,
func (c *Client) Do(req *http.Request) (*http.Response, error) {
req.Header = c.extraHeadersFor(req)
- return c.do(req)
+ return c.do(req, "", nil)
}
// do performs an *http.Request respecting redirects, and handles the response
// as defined in c.handleResponse. Notably, it does not alter the headers for
// the request argument in any way.
-func (c *Client) do(req *http.Request) (*http.Response, error) {
+func (c *Client) do(req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", UserAgent)
- res, err := c.doWithRedirects(c.httpClient(req.Host), req, nil)
+ res, err := c.doWithRedirects(c.httpClient(req.Host), req, remote, via)
if err != nil {
return res, err
}
return m
}
-func (c *Client) doWithRedirects(cli *http.Client, req *http.Request, via []*http.Request) (*http.Response, error) {
+func (c *Client) doWithRedirects(cli *http.Client, req *http.Request, remote string, via []*http.Request) (*http.Response, error) {
tracedReq, err := c.traceRequest(req)
if err != nil {
return nil, err
return res, err
}
- return c.doWithRedirects(cli, redirectedReq, via)
+ if len(req.Header.Get("Authorization")) > 0 {
+ // If the original request was authenticated (noted by the
+ // presence of the Authorization header), then recur through
+ // doWithAuth, retaining the requests via but only after
+ // authenticating the redirected request.
+ return c.doWithAuth(remote, redirectedReq, via)
+ }
+ return c.doWithRedirects(cli, redirectedReq, remote, via)
}
func (c *Client) httpClient(host string) *http.Client {
package lfsapi
import (
+ "encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httptest"
+ "strings"
"sync/atomic"
"testing"
assert.EqualError(t, err, "lfsapi/client: refusing insecure redirect, https->http")
}
+func TestClientRedirectReauthenticate(t *testing.T) {
+ var srv1, srv2 *httptest.Server
+ var called1, called2 uint32
+ var creds1, creds2 Creds
+
+ srv1 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ atomic.AddUint32(&called1, 1)
+
+ if hdr := r.Header.Get("Authorization"); len(hdr) > 0 {
+ parts := strings.SplitN(hdr, " ", 2)
+ typ, b64 := parts[0], parts[1]
+
+ auth, err := base64.URLEncoding.DecodeString(b64)
+ assert.Nil(t, err)
+ assert.Equal(t, "Basic", typ)
+ assert.Equal(t, "user1:pass1", string(auth))
+
+ http.Redirect(w, r, srv2.URL+r.URL.Path, http.StatusMovedPermanently)
+ return
+ }
+ w.WriteHeader(http.StatusUnauthorized)
+ }))
+
+ srv2 = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ atomic.AddUint32(&called2, 1)
+
+ parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
+ typ, b64 := parts[0], parts[1]
+
+ auth, err := base64.URLEncoding.DecodeString(b64)
+ assert.Nil(t, err)
+ assert.Equal(t, "Basic", typ)
+ assert.Equal(t, "user2:pass2", string(auth))
+ }))
+
+ // Change the URL of srv2 to make it appears as if it is a different
+ // host.
+ srv2.URL = strings.Replace(srv2.URL, "127.0.0.1", "0.0.0.0", 1)
+
+ creds1 = Creds(map[string]string{
+ "protocol": "http",
+ "host": strings.TrimPrefix(srv1.URL, "http://"),
+
+ "username": "user1",
+ "password": "pass1",
+ })
+ creds2 = Creds(map[string]string{
+ "protocol": "http",
+ "host": strings.TrimPrefix(srv2.URL, "http://"),
+
+ "username": "user2",
+ "password": "pass2",
+ })
+
+ defer srv1.Close()
+ defer srv2.Close()
+
+ c, err := NewClient(NewContext(nil, nil, nil))
+ creds := newCredentialCacher()
+ creds.Approve(creds1)
+ creds.Approve(creds2)
+ c.Credentials = creds
+
+ req, err := http.NewRequest("GET", srv1.URL, nil)
+ require.Nil(t, err)
+
+ _, err = c.DoWithAuth("", req)
+ assert.Nil(t, err)
+
+ // called1 is 2 since LFS tries an unauthenticated request first
+ assert.EqualValues(t, 2, called1)
+ assert.EqualValues(t, 1, called2)
+}
+
func TestNewClient(t *testing.T) {
c, err := NewClient(NewContext(nil, nil, map[string]string{
"lfs.dialtimeout": "151",
"os"
"path/filepath"
- "github.com/bgentry/go-netrc/netrc"
"github.com/git-lfs/git-lfs/config"
+ "github.com/git-lfs/go-netrc/netrc"
)
type NetrcFinder interface {
"strings"
"testing"
- "github.com/bgentry/go-netrc/netrc"
+ "github.com/git-lfs/go-netrc/netrc"
)
func TestNetrcWithHostAndPort(t *testing.T) {
}
func (c *Client) doWithNTLM(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL) (*http.Response, error) {
- res, err := c.do(req)
+ res, err := c.do(req, "", nil)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
msg := base64.StdEncoding.EncodeToString(message)
req.Header.Set("Authorization", "NTLM "+msg)
- return c.do(req)
+ return c.do(req, "", nil)
}
func parseChallengeResponse(res *http.Response) ([]byte, error) {
Name: git-lfs
-Version: 2.4.1
+Version: 2.4.2
Release: 1%{?dist}
Summary: Git extension for versioning large files
)
end_test
+begin_test "credentials from netrc with unknown keyword"
+(
+ set -e
+
+ printf "machine localhost\nlogin netrcuser\nnot-a-key something\npassword netrcpass\n" >> "$NETRCFILE"
+ echo $HOME
+ echo "GITSERVER $GITSERVER"
+ cat $NETRCFILE
+
+ # prevent prompts on Windows particularly
+ export SSH_ASKPASS=
+
+ reponame="netrctest"
+ setup_remote_repo "$reponame"
+
+ clone_repo "$reponame" repo2
+
+ # Need a remote named "localhost" or 127.0.0.1 in netrc will interfere with the other auth
+ git remote add "netrc" "$(echo $GITSERVER | sed s/127.0.0.1/localhost/)/netrctest"
+ git lfs env
+
+ git lfs track "*.dat"
+ echo "push a" > a.dat
+ git add .gitattributes a.dat
+ git commit -m "add a.dat"
+
+ GIT_TRACE=1 git lfs push netrc master 2>&1 | tee push.log
+ grep "Uploading LFS objects: 100% (1/1), 7 B" push.log
+ echo "any git credential calls:"
+ [ "0" -eq "$(cat push.log | grep "git credential" | wc -l)" ]
+)
+end_test
+
begin_test "credentials from netrc with bad password"
(
set -e
reponame="netrctest"
setup_remote_repo "$reponame"
- clone_repo "$reponame" repo2
+ clone_repo "$reponame" repo3
# Need a remote named "localhost" or 127.0.0.1 in netrc will interfere with the other auth
git remote add "netrc" "$(echo $GITSERVER | sed s/127.0.0.1/localhost/)/netrctest"
machine weirdlogin login uname password pass#pass
+machine google.com
+ login alice@google.com
+ not-a-keyword
+ password secure
+ also-not-a-keyword
+
default
login anonymous
password joe@example.com
tkMacdef
tkComment
tkWhitespace
+ tkUnknown
)
var keywords = map[string]tkType{
// TODO(bgentry): not safe for concurrency
for i := range n.tokens {
switch n.tokens[i].kind {
- case tkComment, tkDefault, tkWhitespace: // always append these types
+ case tkComment, tkDefault, tkWhitespace, tkUnknown: // always append these types
text = append(text, n.tokens[i].rawkind...)
default:
if n.tokens[i].value != "" { // skip empty-value tokens
t, err = newToken(rawb)
if err != nil {
if currentMacro == nil {
- return nil, &Error{pos, err.Error()}
+ t.kind = tkUnknown
+ nrc.tokens = append(nrc.tokens, t)
+ } else {
+ currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
}
- currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
continue
}
&Machine{Name: "mail.google.com", Login: "joe@gmail.com", Password: "somethingSecret", Account: "justagmail"},
&Machine{Name: "ray", Login: "demo", Password: "mypassword", Account: ""},
&Machine{Name: "weirdlogin", Login: "uname", Password: "pass#pass", Account: ""},
+ &Machine{Name: "google.com", Login: "alice@google.com", Password: "secure"},
&Machine{Name: "", Login: "anonymous", Password: "joe@example.com", Account: ""},
}
var expectedMacros = Macros{
if err != nil {
t.Fatal(err)
}
- if !eqMachine(m, expectedMachines[3]) {
+ if !eqMachine(m, expectedMachines[4]) {
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[3], m)
}
if !m.IsDefault() {
"FileVersion": {
"Major": 2,
"Minor": 4,
- "Patch": 1,
+ "Patch": 2,
"Build": 0
}
},
"FileDescription": "Git LFS",
"LegalCopyright": "GitHub, Inc. and Git LFS contributors",
"ProductName": "Git Large File Storage (LFS)",
- "ProductVersion": "2.4.1"
+ "ProductVersion": "2.4.2"
},
"IconPath": "script/windows-installer/git-lfs-logo.ico"
}