blob: fcff2d5d8ac0d509fabaa39de7783101e9e8118a [file] [log] [blame]
Pavlo Sumkinff20af92016-10-19 16:34:28 +03001// 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
5package gerrit
6
7import (
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
19func md5str(text string) string {
20 h := md5.Sum([]byte(text))
21 return hex.EncodeToString(h[:])
22}
23
24func 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
49func 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}