-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrestclient.go
221 lines (187 loc) · 6.82 KB
/
restclient.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
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
package xcarestclient
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
// RESTClient encapsulates the actual HTTP client that communicates with XCA.
// Use New() to obtain an usable instance. All fields should be treated as read-only; functions are provided where changes shall be possible.
type RESTClient struct {
httpClient http.Client
HTTPHost string
HTTPPort uint
HTTPTimeout uint
UserAgent string
xcaUserID string
xcaSecret string
OAuth OAuthToken
}
// New is used to create an usable instance of RESTClient.
// By default a new instance will use HTTPS to port 5825 with strict certificate checking. The HTTP timeout is set to 5 seconds. Authentication must be set manually before trying to send a query to XCA.
func New(host string) RESTClient {
var c RESTClient
c.httpClient = http.Client{}
c.HTTPHost = host
c.SetPort(5825)
c.SetTimeout(5)
c.UseSecureHTTPS()
c.SetUserAgent(fmt.Sprintf("%s/%s", moduleName, moduleVersion))
return c
}
// SetPort sets the TCP port where XCA is listening for the RESTClient instance.
func (c *RESTClient) SetPort(port uint) error {
if httpMinPort <= port && httpMaxPort >= port {
c.HTTPPort = port
return nil
}
return fmt.Errorf("port out of range (%d - %d)", httpMinPort, httpMaxPort)
}
// SetTimeout sets the HTTP timeout in seconds for the RESTClient instance.
func (c *RESTClient) SetTimeout(seconds uint) error {
if httpMinTimeout <= seconds && httpMaxTimeout >= seconds {
c.httpClient.Timeout = time.Second * time.Duration(seconds)
return nil
}
return fmt.Errorf("timeout out of range (%d - %d)", httpMinTimeout, httpMaxTimeout)
}
// UseSecureHTTPS enforces strict HTTPS certificate checking.
func (c *RESTClient) UseSecureHTTPS() {
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
}
c.httpClient.Transport = httpTransport
}
// UseInsecureHTTPS disables strict HTTPS certificate checking.
func (c *RESTClient) UseInsecureHTTPS() {
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
c.httpClient.Transport = httpTransport
}
// SetUserAgent sets the User-Agent HTTP header.
func (c *RESTClient) SetUserAgent(ua string) {
c.UserAgent = ua
}
// SetAuth sets the authentication credentials.
func (c *RESTClient) SetAuth(userID string, secret string) {
c.xcaUserID = userID
c.xcaSecret = secret
}
// SanitizeEndpoint prepares the provided API endpoint for concatenation.
func SanitizeEndpoint(endpoint *string) {
if !strings.HasPrefix(*endpoint, "/") {
*endpoint = fmt.Sprintf("/%s", *endpoint)
}
if !strings.HasPrefix(*endpoint, "/management") {
*endpoint = fmt.Sprintf("/management%s", *endpoint)
}
}
// SetRequestHeaders sets the usual headers required for requests to XCA.
func SetRequestHeaders(client *RESTClient, req *http.Request, payload *[]byte) {
req.Header.Set("User-Agent", client.UserAgent)
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Accept", jsonMimeType)
if payload != nil {
req.Header.Set("Content-Type", jsonMimeType)
}
if client.OAuth.AccessToken != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.OAuth.AccessToken))
}
}
// PostRequest returns a prepared HTTP POST request instance.
func (c *RESTClient) PostRequest(endpoint string, payload []byte) (*http.Request, error) {
SanitizeEndpoint(&endpoint)
endpointURL := fmt.Sprintf("https://%s:%d%s", c.HTTPHost, c.HTTPPort, endpoint)
req, reqErr := http.NewRequest(http.MethodPost, endpointURL, bytes.NewBuffer(payload))
if reqErr != nil {
return req, fmt.Errorf("could not create request: %s", reqErr)
}
SetRequestHeaders(c, req, &payload)
return req, nil
}
// PutRequest returns a prepared HTTP PUT request instance.
func (c *RESTClient) PutRequest(endpoint string, payload []byte) (*http.Request, error) {
SanitizeEndpoint(&endpoint)
endpointURL := fmt.Sprintf("https://%s:%d%s", c.HTTPHost, c.HTTPPort, endpoint)
req, reqErr := http.NewRequest(http.MethodPut, endpointURL, bytes.NewBuffer(payload))
if reqErr != nil {
return req, fmt.Errorf("could not create request: %s", reqErr)
}
SetRequestHeaders(c, req, &payload)
return req, nil
}
// GetRequest returns a prepared HTTP GET request instance.
func (c *RESTClient) GetRequest(endpoint string) (*http.Request, error) {
SanitizeEndpoint(&endpoint)
endpointURL := fmt.Sprintf("https://%s:%d%s", c.HTTPHost, c.HTTPPort, endpoint)
req, reqErr := http.NewRequest(http.MethodGet, endpointURL, nil)
if reqErr != nil {
return req, fmt.Errorf("could not create request: %s", reqErr)
}
SetRequestHeaders(c, req, nil)
return req, nil
}
// DeleteRequest returns a prepared HTTP DELETE request instance.
func (c *RESTClient) DeleteRequest(endpoint string) (*http.Request, error) {
SanitizeEndpoint(&endpoint)
endpointURL := fmt.Sprintf("https://%s:%d%s", c.HTTPHost, c.HTTPPort, endpoint)
req, reqErr := http.NewRequest(http.MethodDelete, endpointURL, nil)
if reqErr != nil {
return req, fmt.Errorf("could not create request: %s", reqErr)
}
SetRequestHeaders(c, req, nil)
return req, nil
}
// PerformRequest sends a request to XCA and returns the result.
func (c *RESTClient) PerformRequest(req *http.Request) (*http.Response, error) {
return c.httpClient.Do(req)
}
// Authenticate performs authentication against XCA and stores the OAuth token.
func (c *RESTClient) Authenticate() error {
// Empty token structure to start with.
var tokenData OAuthToken
var tokenRequest TokenRequest
tokenRequest.GrantType = "password"
tokenRequest.UserID = c.xcaUserID
tokenRequest.Password = c.xcaSecret
tokenRequest.Scope = ""
// Generate an actual HTTP request.
payload, _ := json.Marshal(tokenRequest)
req, reqErr := c.PostRequest("/management/v1/oauth2/token", payload)
if reqErr != nil {
return fmt.Errorf("could not create HTTP(S) request: %s", reqErr)
}
// Try to get a result from the API.
res, resErr := c.httpClient.Do(req)
if resErr != nil {
return fmt.Errorf("could not connect to XMC: %s", resErr)
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("got status code %d instead of %d", res.StatusCode, http.StatusOK)
}
defer res.Body.Close()
// Check if the HTTP response has yielded the expected content type.
resContentType := res.Header.Get("Content-Type")
if strings.Index(resContentType, jsonMimeType) != 0 {
return fmt.Errorf("Content-Type %s returned instead of %s", resContentType, jsonMimeType)
}
// Read and parse the body of the HTTP response.
body, bodyErr := io.ReadAll(res.Body)
if bodyErr != nil {
return fmt.Errorf("could not read server response: %s", bodyErr)
}
jsonErr := json.Unmarshal(body, &tokenData)
if jsonErr != nil {
return fmt.Errorf("could not read server response: %s", jsonErr)
}
c.OAuth = tokenData
if decodeErr := c.OAuth.Decode(); decodeErr != nil {
return fmt.Errorf("error decoding token: %s", decodeErr)
}
return nil
}