Skip to content

Commit b63100d

Browse files
authored
Merge pull request #39 from andygrunwald/fix-url-auth-parsing
NewClient() can't handle credentials containing characters such as /
2 parents 179c3f3 + 28a9265 commit b63100d

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ first. For more complete details see
2626
* Go 1.5 has been removed from testing on Travis. The linters introduced in
2727
0.4.0 do not support this version, Go 1.5 is lacking security updates and
2828
most Linux distros have moved beyond Go 1.5 now.
29+
* Add Go 1.9 to the Travis matrix.
30+
* Fixed an issue where urls containing certain characters in the credentials
31+
could cause NewClient() to use an invalid url. Something like `/`, which
32+
Gerrit could use for generated passwords, for example would break url.Parse's
33+
expectations.
2934

3035
### 0.3.0
3136

gerrit.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"net/url"
1212
"reflect"
13+
"regexp"
1314
"strings"
1415

1516
"github.com/google/go-querystring/query"
@@ -65,6 +66,12 @@ var (
6566
// credentials didn't allow us to query account information using digest, basic or cookie
6667
// auth.
6768
ErrAuthenticationFailed = errors.New("failed to authenticate using the provided credentials")
69+
70+
// ReParseURL is used to parse the url provided to NewClient(). This
71+
// regular expression contains five groups which capture the scheme,
72+
// username, password, hostname and port. If we parse the url with this
73+
// regular expression
74+
ReParseURL = regexp.MustCompile(`^(http|https)://(.+):(.+)@(.+):(\d+)(.*)$`)
6875
)
6976

7077
// NewClient returns a new Gerrit API client. The gerritURL argument has to be the
@@ -87,16 +94,35 @@ func NewClient(endpoint string, httpClient *http.Client) (*Client, error) {
8794
return nil, ErrNoInstanceGiven
8895
}
8996

97+
hasAuth := false
98+
username := ""
99+
password := ""
100+
101+
// Depending on the contents of the username and password the default
102+
// url.Parse may not work. The below is an example URL that
103+
// would end up being parsed incorrectly with url.Parse:
104+
// http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@localhost:38607
105+
// So instead of depending on url.Parse we'll try using a regular expression
106+
// first to match a specific pattern. If that ends up working we modify
107+
// the incoming endpoint to remove the username and password so the rest
108+
// of this function will run as expected.
109+
submatches := ReParseURL.FindAllStringSubmatch(endpoint, -1)
110+
if len(submatches) > 0 && len(submatches[0]) > 5 {
111+
submatch := submatches[0]
112+
username = submatch[2]
113+
password = submatch[3]
114+
endpoint = fmt.Sprintf(
115+
"%s://%s:%s%s", submatch[1], submatch[4], submatch[5], submatch[6])
116+
hasAuth = true
117+
}
118+
90119
baseURL, err := url.Parse(endpoint)
91120
if err != nil {
92121
return nil, err
93122
}
94123

95-
// Username and/or password provided as part of the url.
96-
97-
hasAuth := false
98-
username := ""
99-
password := ""
124+
// Note, if we retrieved the URL and password using the regular
125+
// expression above then the below code will do nothing.
100126
if baseURL.User != nil {
101127
username = baseURL.User.Username()
102128
parsedPassword, haspassword := baseURL.User.Password()

gerrit_test.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func testMethod(t *testing.T, r *http.Request, want string) {
8181
}
8282
}
8383

84-
func testRequestURL(t *testing.T, r *http.Request, want string) {
84+
func testRequestURL(t *testing.T, r *http.Request, want string) { // nolint: unparam
8585
if got := r.URL.String(); got != want {
8686
t.Errorf("Request URL: %v, want %v", got, want)
8787
}
@@ -264,6 +264,81 @@ func TestNewClient_BasicAuth(t *testing.T) {
264264
}
265265
}
266266

267+
func TestNewClient_ReParseURL(t *testing.T) {
268+
urls := map[string][]string{
269+
"http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@127.0.0.1:5000/": {
270+
"http://127.0.0.1:5000/", "admin", "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg",
271+
},
272+
"http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@127.0.0.1:5000/foo": {
273+
"http://127.0.0.1:5000/foo", "admin", "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg",
274+
},
275+
"http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@127.0.0.1:5000": {
276+
"http://127.0.0.1:5000", "admin", "ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg",
277+
},
278+
"https://admin:foo/bar@localhost:5": {
279+
"https://localhost:5", "admin", "foo/bar",
280+
},
281+
}
282+
for input, expectations := range urls {
283+
submatches := gerrit.ReParseURL.FindAllStringSubmatch(input, -1)
284+
submatch := submatches[0]
285+
username := submatch[2]
286+
password := submatch[3]
287+
endpoint := fmt.Sprintf(
288+
"%s://%s:%s%s", submatch[1], submatch[4], submatch[5], submatch[6])
289+
if endpoint != expectations[0] {
290+
t.Errorf("%s != %s", expectations[0], endpoint)
291+
}
292+
if username != expectations[1] {
293+
t.Errorf("%s != %s", expectations[1], username)
294+
}
295+
if password != expectations[2] {
296+
t.Errorf("%s != %s", expectations[2], password)
297+
}
298+
299+
}
300+
}
301+
302+
func TestNewClient_BasicAuth_PasswordWithSlashes(t *testing.T) {
303+
setup()
304+
defer teardown()
305+
306+
account := gerrit.AccountInfo{
307+
AccountID: 100000,
308+
Name: "test",
309+
Email: "test@localhost",
310+
Username: "test"}
311+
hits := 0
312+
313+
testMux.HandleFunc("/a/accounts/self", func(w http.ResponseWriter, r *http.Request) {
314+
hits++
315+
switch hits {
316+
case 1:
317+
writeresponse(t, w, nil, http.StatusUnauthorized)
318+
case 2:
319+
// The second request should be a basic auth request if the first request, which is for
320+
// digest based auth, fails.
321+
if !strings.HasPrefix(r.Header.Get("Authorization"), "Basic ") {
322+
t.Error("Missing 'Basic ' prefix")
323+
}
324+
writeresponse(t, w, account, http.StatusOK)
325+
case 3:
326+
t.Error("Did not expect another request")
327+
}
328+
})
329+
330+
serverURL := fmt.Sprintf(
331+
"http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@%s",
332+
testServer.Listener.Addr().String())
333+
client, err := gerrit.NewClient(serverURL, nil)
334+
if err != nil {
335+
t.Error(err)
336+
}
337+
if !client.Authentication.HasAuth() {
338+
t.Error("Expected HasAuth() == true")
339+
}
340+
}
341+
267342
func TestNewClient_CookieAuth(t *testing.T) {
268343
setup()
269344
defer teardown()

0 commit comments

Comments
 (0)