forked from go-kivik/couchdb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
156 lines (139 loc) · 3.92 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package chttp
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/cookiejar"
"github.com/flimzy/kivik"
"golang.org/x/net/publicsuffix"
)
// Authenticator is an interface that provides authentication to a server.
type Authenticator interface {
Authenticate(context.Context, *Client) error
Logout(context.Context, *Client) error
}
// BasicAuth provides HTTP Basic Auth for a client.
type BasicAuth struct {
Username string
Password string
// transport stores the original transport that is overridden by this auth
// mechanism
transport http.RoundTripper
}
var _ Authenticator = &BasicAuth{}
// RoundTrip fulfills the http.RoundTripper interface. It sets HTTP Basic Auth
// on outbound requests.
func (a *BasicAuth) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(a.Username, a.Password)
transport := a.transport
if transport == nil {
transport = http.DefaultTransport
}
return transport.RoundTrip(req)
}
// Authenticate sets HTTP Basic Auth headers for the client.
func (a *BasicAuth) Authenticate(ctx context.Context, c *Client) error {
// First see if the credentials seem good
req, err := c.NewRequest(ctx, kivik.MethodGet, "/_session", nil)
if err != nil {
return err
}
req.SetBasicAuth(a.Username, a.Password)
res, err := c.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if err = ResponseError(res); err != nil {
return err
}
result := struct {
Ctx struct {
Name string `json:"name"`
} `json:"userCtx"`
}{}
if err = json.NewDecoder(res.Body).Decode(&result); err != nil {
return err
}
if result.Ctx.Name != a.Username {
return errors.New("authentication failed")
}
// Everything looks good, lets make this official
a.transport = c.Transport
c.Transport = a
return nil
}
// Logout unsets BasicAuthentication
func (a *BasicAuth) Logout(_ context.Context, c *Client) error {
if c.Transport != a {
return errors.New("Not registered as authenticator")
}
c.Transport = a.transport
return nil
}
// CookieAuth provides CouchDB Cookie auth services as described at
// http://docs.couchdb.org/en/2.0.0/api/server/authn.html#cookie-authentication
type CookieAuth struct {
Username string `json:"name"`
Password string `json:"password"`
transport http.RoundTripper
// Set to true if the authenticator created the cookie jar; It will then
// also destroy it on logout.
setJar bool
}
var _ Authenticator = &CookieAuth{}
// Authenticate initiates a session with the CouchDB server.
func (a *CookieAuth) Authenticate(ctx context.Context, c *Client) error {
if err := a.setCookieJar(c); err != nil {
return err
}
buf := &bytes.Buffer{}
if err := json.NewEncoder(buf).Encode(a); err != nil {
return err
}
if _, err := c.DoError(ctx, kivik.MethodPost, "/_session", &Options{Body: buf}); err != nil {
return err
}
return ValidateAuth(ctx, a.Username, c)
}
// ValidateAuth validates that the requested username is authenticated.
func ValidateAuth(ctx context.Context, username string, client *Client) error {
// This does a final request to validate that auth was successful. Cookies
// may be filtered by a proxy, or a misconfigured client, so this check is
// necessary.
result := struct {
Ctx struct {
Name string `json:"name"`
} `json:"userCtx"`
}{}
if _, err := client.DoJSON(ctx, "GET", "/_session", nil, &result); err != nil {
return err
}
if result.Ctx.Name != username {
return errors.New("authentication failed")
}
return nil
}
func (a *CookieAuth) setCookieJar(c *Client) error {
// If a jar is already set, just use it
if c.Jar != nil {
return nil
}
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return err
}
c.Jar = jar
a.setJar = true
return nil
}
// Logout deletes the remote session.
func (a *CookieAuth) Logout(ctx context.Context, c *Client) error {
_, err := c.DoError(ctx, kivik.MethodDelete, "/_session", nil)
if a.setJar {
c.Jar = nil
}
return err
}