-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.go
More file actions
232 lines (210 loc) · 6.92 KB
/
client.go
File metadata and controls
232 lines (210 loc) · 6.92 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package peasant
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
// Transport defines the interface for handling nonce generation and directory
// listing.
type Transport interface {
// NewNonce generates a new nonce.
NewNonce() (string, error)
// Directory returns the directory map.
Directory() (map[string]any, error)
}
// HttpTransport implements the Transport interface for HTTP communications.
type HttpTransport struct {
// DirectoryProvider is embedded to provide directory-related functionality.
DirectoryProvider
// DirectoryKey is the key used to retrieve the directory-related url.
DirectoryKey string
// DirectoryMethod specifies the HTTP method used for directory operations.
DirectoryMethod string
// Client is the HTTP Client used for making requests.
http.Client
// NonceKey is the header key used to retrieve the nonce from responses.
NonceKey string
}
// NewHttpTransport initializes and returns a new HttpTransport using the
// provided DirectoryProvider. It sets up the transport in the provider and
// returns the configured HttpTransport. Returns an error if setting the
// transport fails.
func NewHttpTransport(p DirectoryProvider) (*HttpTransport, error) {
ht := &HttpTransport{
DirectoryProvider: p,
DirectoryKey: "newNonce",
DirectoryMethod: http.MethodHead,
Client: http.Client{},
NonceKey: "Nonce",
}
if err := p.SetTransport(ht); err != nil {
return nil, err
}
return ht, nil
}
// NewNonceUrl returns the URL for generating a new nonce. Developers should
// override this method if the new nonce URL needs to be resolved differently.
func (ht *HttpTransport) NewNonceUrl() (string, error) {
d, err := ht.Directory()
if err != nil {
return "", err
}
val, ok := d[ht.DirectoryKey]
if !ok {
return "", fmt.Errorf(
"the transport wasn't able to find the nonce key %s",
ht.DirectoryKey)
}
return val.(string), nil
}
// ResolveNonce extracts the nonce from the response headers using the
// predefined nonceKey. Developers should override this method if the nonce
// needs to be resolved in a different way.
func (ht *HttpTransport) ResolveNonce(res *http.Response) string {
return res.Header.Get(ht.NonceKey)
}
// NewNonce generates a new nonce by making an HTTP HEAD request to the new
// nonce URL. This method depends on NewNonceUrl and ResolveNonce. The basic
// implementation is provided, but customization should be done in the
// dependent methods. If further customization is needed, developers can use
// this method as a template.
func (ht *HttpTransport) NewNonce() (string, error) {
url, err := ht.NewNonceUrl()
if err != nil {
return "", err
}
req, err := http.NewRequest(ht.DirectoryMethod, url, nil)
if err != nil {
return "", err
}
res, err := ht.Client.Do(req)
if err != nil {
return "", err
}
if res.StatusCode > 299 {
return "", errors.New(res.Status)
}
return ht.ResolveNonce(res), nil
}
// DirectoryProvider defines the interface for objects that provide directory
// information. Implementations should support retrieving a directory map,
// getting the URL, and setting the transport.
type DirectoryProvider interface {
// Directory fetches the directory map from the provider.
Directory() (map[string]any, error)
// GetUrl returns the provider's URL.
GetUrl() string
// SetTransport sets the transport mechanism for the provider.
SetTransport(Transport) error
}
// MemoryDirectoryProvider is an in-memory implementation of DirectoryProvider.
// It holds a URL and returns predefined directory data.
type MemoryDirectoryProvider struct {
url string
}
// Directory returns a static map containing the directory endpoints.
// It constructs the "newNonce" endpoint using the provider's URL.
func (p *MemoryDirectoryProvider) Directory() (map[string]any, error) {
return map[string]any{
"newNonce": p.GetUrl() + "/nonce/new-nonce",
}, nil
}
// GetUrl returns the URL configured for the memory provider.
func (p *MemoryDirectoryProvider) GetUrl() string {
return p.url
}
// SetTransport is a no-op for MemoryDirectoryProvider, as it does not use a
// transport. It always returns nil.
func (p *MemoryDirectoryProvider) SetTransport(_ Transport) error {
return nil
}
// HttpDirectoryProvider provides access to a remote directory via HTTP.
// It uses an embedded HttpTransport to make requests to the given URL.
type HttpDirectoryProvider struct {
Url string
*HttpTransport
}
// NewHttpDirectoryProvider creates and returns a new HttpDirectoryProvider
// with the given URL.
func NewHttpDirectoryProvider(url string) *HttpDirectoryProvider {
return &HttpDirectoryProvider{
Url: url,
}
}
// Directory fetches the remote directory from the provider's URL.
// It returns a map representing the directory, or an error if the request or
// decoding fails.
func (p *HttpDirectoryProvider) Directory() (map[string]any, error) {
r, err := p.HttpTransport.Get(p.GetUrl())
if err != nil {
return nil, err
}
dir := map[string]any{}
err = BodyAsJson(r, &dir)
if err != nil {
return nil, err
}
return dir, nil
}
// GetUrl returns the URL configured for the HTTP directory provider.
func (p *HttpDirectoryProvider) GetUrl() string {
return p.Url
}
// SetTransport sets the HTTP transport for the provider. It attempts to cast
// the given Transport to *HttpTransport. Returns an error if the cast fails.
func (p *HttpDirectoryProvider) SetTransport(t Transport) error {
var ok bool
p.HttpTransport, ok = t.(*HttpTransport)
if !ok {
return errors.New("was not able to cast Transport to HttpTransport")
}
return nil
}
// Peasant represents an agent in the Peasant protocol, which communicates with
// a bastion.
// It wraps a Transport for handling nonce generation and other communication
// aspects.
type Peasant struct {
Transport
}
// NewPeasant initializes a new Peasant with the provided Transport.
func NewPeasant(tr Transport) *Peasant {
return &Peasant{tr}
}
// NewNonce generates a new nonce by delegating the call to the underlying
// Transport.
// This method allows the Peasant to obtain a new nonce for communication with
// a bastion.
func (p *Peasant) NewNonce() (string, error) {
return p.Transport.NewNonce()
}
// BodyAsString reads the entire body of an HTTP response and returns it as a
// string.
// It consumes the response body, so the caller should not attempt to read from
// it again.
func BodyAsString(res *http.Response) (string, error) {
body, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(body), nil
}
// BodyAsJson reads the entire body of an HTTP response and unmarshals it into
// the provided jsonBody.
// It consumes the response body, so the caller should not attempt to read from
// it again.
// The jsonBody parameter should be a pointer to a struct or a slice where JSON
// data will be unmarshaled.
func BodyAsJson(res *http.Response, jsonBody any) error {
b, err := io.ReadAll(res.Body)
if err != nil {
return err
}
err = json.Unmarshal(b, jsonBody)
if err != nil {
return err
}
return nil
}