golang.org/x/build@v0.0.0-20240506185731-218518f32b70/gerrit/auth_test.go (about) 1 // Copyright 2016 The Go 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 "context" 9 "crypto/md5" 10 "encoding/hex" 11 "encoding/json" 12 "fmt" 13 "net/http" 14 "net/http/httptest" 15 "strings" 16 "testing" 17 ) 18 19 func md5str(text string) string { 20 h := md5.Sum([]byte(text)) 21 return hex.EncodeToString(h[:]) 22 } 23 24 func TestBasicAuth(t *testing.T) { 25 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 expected := "User Password true" 27 u, p, ok := r.BasicAuth() 28 if expected != fmt.Sprintf("%s %s %t", u, p, ok) { 29 t.Errorf("Expected %s, got %s %s %t", expected, u, p, ok) 30 w.WriteHeader(http.StatusUnauthorized) 31 } else { 32 w.Header().Set("Content-Type", "application/json; charset=UTF-8") 33 // The JSON response begins with an XSRF-defeating header ")]}\n" 34 fmt.Fprintln(w, ")]}") 35 json.NewEncoder(w).Encode(AccountInfo{}) 36 } 37 })) 38 defer ts.Close() 39 40 _, err := NewClient( 41 ts.URL, 42 BasicAuth("User", "Password"), 43 ).GetAccountInfo(context.Background(), "self") 44 if err != nil { 45 t.Error(err) 46 } 47 } 48 49 func TestDigestAuth(t *testing.T) { 50 const ( 51 user = "User" 52 pass = "Password" 53 nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093" 54 opaque = "5ccc069c403ebaf9f0171e9517f40e41" 55 realm = "Gerrit Code Review" 56 qop = "auth" 57 ) 58 59 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 header := r.Header.Get("Authorization") 61 if header == "" { 62 w.Header().Set("WWW-Authenticate", fmt.Sprintf( 63 `Digest realm="%s", qop="%s", nonce="%s", opaque="%s"`, 64 realm, qop, nonce, opaque, 65 )) 66 w.WriteHeader(http.StatusUnauthorized) 67 } else { 68 parts := strings.SplitN(header, " ", 2) 69 parts = strings.Split(parts[1], ", ") 70 opts := make(map[string]string) 71 72 for _, part := range parts { 73 vals := strings.SplitN(part, "=", 2) 74 key := vals[0] 75 val := strings.Trim(vals[1], "\",") 76 opts[key] = val 77 } 78 79 // https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation 80 // The "response" value is calculated in three steps, as follows. 81 // Where values are combined, they are delimited by colons. 82 // 1. The MD5 hash of the combined username, authentication realm and password is calculated. 83 // The result is referred to as HA1. 84 // 2. The MD5 hash of the combined method and digest URI is calculated, e.g. of "GET" and "/index.html". 85 // The result is referred to as HA2. 86 // 3. The MD5 hash of the combined HA1 result, server nonce (nonce), request counter (nc), 87 // client nonce (cnonce), quality of protection code (qop) and HA2 result is calculated. 88 // The result is the "response" value provided by the client. 89 ha1 := md5str(fmt.Sprintf("%s:%s:%s", user, realm, pass)) 90 ha2 := md5str("GET:/a/accounts/self") 91 expected := md5str(fmt.Sprintf("%s:%s:%s:%s:%s:%s", ha1, nonce, opts["nc"], opts["cnonce"], qop, ha2)) 92 93 if expected != opts["response"] { 94 t.Errorf("Expected %s, got %s", expected, opts["response"]) 95 w.WriteHeader(http.StatusUnauthorized) 96 } else { 97 w.Header().Set("Content-Type", "application/json; charset=UTF-8") 98 // The JSON response begins with an XSRF-defeating header ")]}\n" 99 fmt.Fprintln(w, ")]}") 100 json.NewEncoder(w).Encode(AccountInfo{}) 101 } 102 } 103 })) 104 defer ts.Close() 105 106 _, err := NewClient( 107 ts.URL, 108 DigestAuth(user, pass), 109 ).GetAccountInfo(context.Background(), "self") 110 if err != nil { 111 t.Error(err) 112 } 113 }