// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gerrit import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func md5str(text string) string { h := md5.Sum([]byte(text)) return hex.EncodeToString(h[:]) } func TestBasicAuth(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { expected := "User Password true" u, p, ok := r.BasicAuth() if expected != fmt.Sprintf("%s %s %t", u, p, ok) { t.Errorf("Expected %s, got %s %s %t", expected, u, p, ok) w.WriteHeader(http.StatusUnauthorized) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") // The JSON response begins with an XSRF-defeating header ")]}\n" fmt.Fprintln(w, ")]}") json.NewEncoder(w).Encode(AccountInfo{}) } })) defer ts.Close() _, err := NewClient( ts.URL, BasicAuth("User", "Password"), ).GetAccountInfo(context.Background(), "self") if err != nil { t.Error(err) } } func TestDigestAuth(t *testing.T) { const ( user = "User" pass = "Password" nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093" opaque = "5ccc069c403ebaf9f0171e9517f40e41" realm = "Gerrit Code Review" qop = "auth" ) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { header := r.Header.Get("Authorization") if header == "" { w.Header().Set("WWW-Authenticate", fmt.Sprintf( `Digest realm="%s", qop="%s", nonce="%s", opaque="%s"`, realm, qop, nonce, opaque, )) w.WriteHeader(http.StatusUnauthorized) } else { parts := strings.SplitN(header, " ", 2) parts = strings.Split(parts[1], ", ") opts := make(map[string]string) for _, part := range parts { vals := strings.SplitN(part, "=", 2) key := vals[0] val := strings.Trim(vals[1], "\",") opts[key] = val } // https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation // The "response" value is calculated in three steps, as follows. // Where values are combined, they are delimited by colons. // 1. The MD5 hash of the combined username, authentication realm and password is calculated. // The result is referred to as HA1. // 2. The MD5 hash of the combined method and digest URI is calculated, e.g. of "GET" and "/index.html". // The result is referred to as HA2. // 3. The MD5 hash of the combined HA1 result, server nonce (nonce), request counter (nc), // client nonce (cnonce), quality of protection code (qop) and HA2 result is calculated. // The result is the "response" value provided by the client. ha1 := md5str(fmt.Sprintf("%s:%s:%s", user, realm, pass)) ha2 := md5str("GET:/a/accounts/self") expected := md5str(fmt.Sprintf("%s:%s:%s:%s:%s:%s", ha1, nonce, opts["nc"], opts["cnonce"], qop, ha2)) if expected != opts["response"] { t.Errorf("Expected %s, got %s", expected, opts["response"]) w.WriteHeader(http.StatusUnauthorized) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") // The JSON response begins with an XSRF-defeating header ")]}\n" fmt.Fprintln(w, ")]}") json.NewEncoder(w).Encode(AccountInfo{}) } } })) defer ts.Close() _, err := NewClient( ts.URL, DigestAuth(user, pass), ).GetAccountInfo(context.Background(), "self") if err != nil { t.Error(err) } }