1
2
3
4
5
6
7
8
9
10
11
12
13 package chttp
14
15 import (
16 "context"
17 "encoding/json"
18 "net/http"
19 "net/http/cookiejar"
20 "net/url"
21 "testing"
22
23 "gitlab.com/flimzy/testy"
24 "golang.org/x/net/publicsuffix"
25
26 kivik "github.com/go-kivik/kivik/v4"
27 internal "github.com/go-kivik/kivik/v4/int/errors"
28 "github.com/go-kivik/kivik/v4/int/mock"
29 "github.com/go-kivik/kivik/v4/internal/nettest"
30 )
31
32 type mockRT struct {
33 resp *http.Response
34 err error
35 }
36
37 var _ http.RoundTripper = &mockRT{}
38
39 func (rt *mockRT) RoundTrip(_ *http.Request) (*http.Response, error) {
40 return rt.resp, rt.err
41 }
42
43 func TestAuthenticate(t *testing.T) {
44 s := nettest.NewHTTPTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45 defer r.Body.Close()
46 var authed bool
47 switch r.Header.Get("Authorization") {
48 case "Basic YWRtaW46YWJjMTIz", "Bearer tokennekot":
49 authed = true
50 }
51 if r.Method == http.MethodPost {
52 var result struct {
53 Name string
54 Password string
55 }
56 _ = json.NewDecoder(r.Body).Decode(&result)
57 if result.Name == "admin" && result.Password == "abc123" {
58 authed = true
59 http.SetCookie(w, &http.Cookie{
60 Name: kivik.SessionCookieName,
61 Value: "auth-token",
62 Path: "/",
63 HttpOnly: true,
64 })
65 }
66 }
67 if ses := r.Header.Get("Cookie"); ses == "AuthSession=auth-token" {
68 authed = true
69 }
70 if !authed {
71 w.WriteHeader(http.StatusUnauthorized)
72 return
73 }
74 w.WriteHeader(http.StatusOK)
75 if r.URL.Path == "/_session" {
76 _, _ = w.Write([]byte(`{"userCtx":{"name":"admin"}}`))
77 return
78 }
79 _, _ = w.Write([]byte(`{"foo":123}`))
80 }))
81
82 type authTest struct {
83 addr string
84 jar http.CookieJar
85 options kivik.Option
86 err string
87 status int
88 }
89 tests := testy.NewTable()
90 tests.Cleanup(s.Close)
91 tests.Add("unauthorized", authTest{
92 addr: s.URL,
93 err: "Unauthorized",
94 status: http.StatusUnauthorized,
95 })
96 tests.Add("basic auth", authTest{
97 addr: s.URL,
98 options: BasicAuth("admin", "abc123"),
99 })
100 tests.Add("cookie auth success", func(t *testing.T) interface{} {
101 sv := nettest.NewHTTPTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102 h := w.Header()
103 h.Set("Content-Type", "application/json")
104 h.Set("Date", "Sat, 08 Sep 2018 15:49:29 GMT")
105 h.Set("Server", "CouchDB/2.2.0 (Erlang OTP/19)")
106 if r.URL.Path == "/_session" {
107 h.Set("Set-Cookie", "AuthSession=YWRtaW46NUI5M0VGODk6eLUGqXf0HRSEV9PPLaZX86sBYes; Version=1; Path=/; HttpOnly")
108 w.WriteHeader(200)
109 _, _ = w.Write([]byte(`{"ok":true,"name":"admin","roles":["_admin"]}`))
110 } else {
111 w.WriteHeader(200)
112 _, _ = w.Write([]byte(`{"ok":true}`))
113 }
114 }))
115 return authTest{
116 addr: sv.URL,
117 options: CookieAuth("foo", "bar"),
118 }
119 })
120 tests.Add("failed basic auth", authTest{
121 addr: s.URL,
122 options: BasicAuth("foo", ""),
123 err: "Unauthorized",
124 status: http.StatusUnauthorized,
125 })
126 tests.Add("failed cookie auth", authTest{
127 addr: s.URL,
128 options: CookieAuth("foo", ""),
129 err: `Get "?` + s.URL + `/foo"?: Unauthorized`,
130 status: http.StatusUnauthorized,
131 })
132 tests.Add("already authenticated with cookie", func() interface{} {
133 jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
134 if err != nil {
135 t.Fatal(err)
136 }
137 u, _ := url.Parse(s.URL)
138 jar.SetCookies(u, []*http.Cookie{{
139 Name: kivik.SessionCookieName,
140 Value: "auth-token",
141 Path: "/",
142 HttpOnly: true,
143 }})
144 return authTest{
145 addr: s.URL,
146 jar: jar,
147 }
148 })
149 tests.Add("JWT auth", authTest{
150 addr: s.URL,
151 options: JWTAuth("tokennekot"),
152 })
153 tests.Add("failed JWT auth", authTest{
154 addr: s.URL,
155 options: JWTAuth("nekot"),
156 err: "Unauthorized",
157 status: http.StatusUnauthorized,
158 })
159
160 tests.Run(t, func(t *testing.T, test authTest) {
161 ctx := context.Background()
162 opts := test.options
163 if opts == nil {
164 opts = mock.NilOption
165 }
166 c, err := New(&http.Client{}, test.addr, opts)
167 if err != nil {
168 t.Fatal(err)
169 }
170 if test.jar != nil {
171 c.Client.Jar = test.jar
172 }
173 _, err = c.DoError(ctx, "GET", "/foo", nil)
174 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
175 t.Error(d)
176 }
177 })
178 }
179
View as plain text