-
Notifications
You must be signed in to change notification settings - Fork 14
/
requests.go
421 lines (373 loc) · 13.4 KB
/
requests.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
package czds
import (
"encoding/json"
"fmt"
"io"
"time"
)
// Filters for RequestsFilter.Status
// Statuses for RequestStatus.Status
const (
RequestAll = ""
RequestSubmitted = "Submitted"
RequestPending = "Pending"
RequestApproved = "Approved"
RequestDenied = "Denied"
RequestRevoked = "Revoked"
RequestExpired = "Expired"
RequestCanceled = "Canceled"
)
// Filters for RequestsSort.Direction
const (
SortAsc = "asc"
SortDesc = "desc"
)
// Filters for RequestsSort.Field
const (
SortByTLD = "tld"
SortByStatus = "status"
SortByLastUpdated = "last_updated"
SortByExpiration = "expired"
SortByCreated = "created"
SortByAutoRenew = "auto_renew"
)
// Status from TLDStatus.CurrentStatus and RequestsInfo.Status
const (
StatusAvailable = "available"
StatusSubmitted = "submitted"
StatusPending = "pending"
StatusApproved = "approved"
StatusDenied = "denied"
StatusExpired = "expired"
StatusCanceled = "canceled"
StatusRevoked = "revoked" // unverified
)
// number of days into the future to check zones for expiration extensions.
// 0 disables the check
const expiryDateThreshold = 120
// used in RequestExtension
var emptyStruct, _ = json.Marshal(make(map[int]int))
// RequestsFilter is used to set what results should be returned by GetRequests
type RequestsFilter struct {
Status string `json:"status"` // should be set to one of the Request* constants
Filter string `json:"filter"` // zone name search
Pagination RequestsPagination `json:"pagination"`
Sort RequestsSort `json:"sort"`
}
// RequestsSort sets which field and direction the results for the RequestsFilter request should be returned with
type RequestsSort struct {
Field string `json:"field"`
Direction string `json:"direction"`
}
// RequestsPagination sets the page size and offset for paginated results for RequestsFilter
type RequestsPagination struct {
Size int `json:"size"`
Page int `json:"page"`
}
// Request holds information about a request in RequestsResponse from GetRequests()
type Request struct {
RequestID string `json:"requestId"`
TLD string `json:"tld"`
ULabel string `json:"ulable"` // UTF-8 decoded punycode, looks like API has a typo
Status string `json:"status"` // should be set to one of the Request* constants
Created time.Time `json:"created"`
LastUpdated time.Time `json:"last_updated"`
Expired time.Time `json:"expired"` // Note: epoch 0 means no expiration set
SFTP bool `json:"sftp"`
AutoRenew bool `json:"auto_renew"`
}
// RequestsResponse holds Requests from from GetRequests() and total number of requests that match the query but may not be returned due to pagination
type RequestsResponse struct {
Requests []Request `json:"requests"`
TotalRequests int64 `json:"totalRequests"`
}
// TLDStatus is information about a particular TLD returned from GetTLDStatus() or included in RequestsInfo
type TLDStatus struct {
TLD string `json:"tld"`
ULabel string `json:"ulable"` // UTF-8 decoded punycode, looks like API has a typo
CurrentStatus string `json:"currentStatus"` // should be set to one of the Status* constants
SFTP bool `json:"sftp"`
}
// HistoryEntry contains a timestamp and description of action that happened for a RequestsInfo
// For example: requested, expired, approved, etc..
type HistoryEntry struct {
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
Comment string `json:"comment"`
}
// FtpDetails contains FTP information for RequestsInfo
type FtpDetails struct {
PrivateDataError bool `json:"privateDataError"`
}
// RequestsInfo contains the detailed information about a particular zone request returned by GetRequestInfo()
type RequestsInfo struct {
RequestID string `json:"requestId"`
TLD *TLDStatus `json:"tld"`
FtpIps []string `json:"ftpips"`
Status string `json:"status"` // should be set to one of the Status* constants
TcVersion string `json:"tcVersion"`
Created time.Time `json:"created"`
RequestIP string `json:"requestIp"`
Reason string `json:"reason"`
LastUpdated time.Time `json:"last_updated"`
Cancellable bool `json:"cancellable"`
Extensible bool `json:"extensible"`
ExtensionInProcess bool `json:"extensionInProcess"`
AutoRenew bool `json:"auto_renew"`
Expired time.Time `json:"expired"` // Note: epoch 0 means no expiration set
History []HistoryEntry `json:"history"`
FtpDetails *FtpDetails `json:"ftpDetails"`
PrivateDataError bool `json:"privateDataError"`
}
// RequestSubmission contains the information required to submit a new request with SubmitRequest()
type RequestSubmission struct {
AllTLDs bool `json:"allTlds"`
TLDNames []string `json:"tldNames"`
Reason string `json:"reason"`
TcVersion string `json:"tcVersion"` // terms and conditions revision version
AdditionalFTPIps []string `json:"additionalFtfIps,omitempty"`
}
// Terms holds the terms and conditions details from GetTerms()
type Terms struct {
Version string `json:"version"`
Content string `json:"content"`
ContentURL string `json:"contentUrl"`
Created time.Time `json:"created"`
}
// CancelRequestSubmission Request cancellation arguments passed to CancelRequest()
type CancelRequestSubmission struct {
RequestID string `json:"integrationId"` // This is effectively 'requestId'
TLDName string `json:"tldName"`
}
// GetRequests searches for the status of zones requests as seen on the
// CZDS dashboard page "https://czds.icann.org/zone-requests/all"
func (c *Client) GetRequests(filter *RequestsFilter) (*RequestsResponse, error) {
c.v("GetRequests filter: %+v", filter)
requests := new(RequestsResponse)
err := c.jsonAPI("POST", "/czds/requests/all", filter, requests)
return requests, err
}
// GetRequestInfo gets detailed information about a particular request and its timeline
// as seen on the CZDS dashboard page "https://czds.icann.org/zone-requests/{ID}"
func (c *Client) GetRequestInfo(requestID string) (*RequestsInfo, error) {
c.v("GetRequestInfo request ID: %s", requestID)
request := new(RequestsInfo)
err := c.jsonAPI("GET", "/czds/requests/"+requestID, nil, request)
return request, err
}
// GetTLDStatus gets the current status of all TLDs and their ability to be requested
func (c *Client) GetTLDStatus() ([]TLDStatus, error) {
c.v("GetTLDStatus")
requests := make([]TLDStatus, 0, 20)
err := c.jsonAPI("GET", "/czds/tlds", nil, &requests)
return requests, err
}
// GetTerms gets the current terms and conditions from the CZDS portal
// page "https://czds.icann.org/terms-and-conditions"
// this is required to accept the terms and conditions when submitting a new request
func (c *Client) GetTerms() (*Terms, error) {
c.v("GetTerms")
terms := new(Terms)
// this does not appear to need auth, but we auth regardless
err := c.jsonAPI("GET", "/czds/terms/condition", nil, terms)
return terms, err
}
// SubmitRequest submits a new request for access to new zones
func (c *Client) SubmitRequest(request *RequestSubmission) error {
c.v("SubmitRequest request: %+v", request)
err := c.jsonAPI("POST", "/czds/requests/create", request, nil)
return err
}
// CancelRequest cancels a pre-existing request.
// Can only cancel pending requests.
func (c *Client) CancelRequest(cancel *CancelRequestSubmission) (*RequestsInfo, error) {
c.v("CancelRequest request: %+v", cancel)
request := new(RequestsInfo)
err := c.jsonAPI("POST", "/czds/requests/cancel", cancel, request)
return request, err
}
// RequestExtension submits a request to have the access extended.
// Can only request extensions for requests expiring within 30 days.
func (c *Client) RequestExtension(requestID string) (*RequestsInfo, error) {
c.v("RequestExtension request ID: %s", requestID)
request := new(RequestsInfo)
err := c.jsonAPI("POST", "/czds/requests/extension/"+requestID, emptyStruct, request)
return request, err
}
// DownloadAllRequests outputs the contents of the csv file downloaded by
// the "Download All Requests" button on the CZDS portal to the provided output
func (c *Client) DownloadAllRequests(output io.Writer) error {
c.v("DownloadAllRequests")
url := c.BaseURL + "/czds/requests/report"
resp, err := c.apiRequest(true, "GET", url, nil)
if err != nil {
return err
}
defer resp.Body.Close()
n, err := io.Copy(output, resp.Body)
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("%s was empty", url)
}
return nil
}
// RequestTLDs is a helper function that requests access to the provided tlds with the provided reason
// TLDs provided should be marked as able to request from GetTLDStatus()
func (c *Client) RequestTLDs(tlds []string, reason string) error {
c.v("RequestTLDs TLDS: %+v", tlds)
// get terms
terms, err := c.GetTerms()
if err != nil {
return err
}
// submit request
request := &RequestSubmission{
TLDNames: tlds,
Reason: reason,
TcVersion: terms.Version,
}
err = c.SubmitRequest(request)
return err
}
// RequestAllTLDs is a helper function to request access to all available TLDs with the provided reason
func (c *Client) RequestAllTLDs(reason string) ([]string, error) {
return c.RequestAllTLDsExcept(reason, nil)
}
// RequestAllTLDsExcept is a helper function to request access to all available TLDs with the provided reason skipping over the TLDs in except
func (c *Client) RequestAllTLDsExcept(reason string, except []string) ([]string, error) {
c.v("RequestAllTLDs")
exceptMap := slice2LowerMap(except)
// get available to request
status, err := c.GetTLDStatus()
if err != nil {
return nil, err
}
// check to see if any available to request
requestTLDs := make([]string, 0, 10)
for _, tld := range status {
if exceptMap[tld.TLD] {
// skip over excluded TLDs
continue
}
switch tld.CurrentStatus {
case StatusAvailable, StatusCanceled, StatusDenied, StatusExpired, StatusRevoked:
requestTLDs = append(requestTLDs, tld.TLD)
}
}
// if none, return now
if len(requestTLDs) == 0 {
c.v("no TLDs to request")
return requestTLDs, nil
}
// get terms
terms, err := c.GetTerms()
if err != nil {
return nil, err
}
// submit request
request := &RequestSubmission{
AllTLDs: true,
TLDNames: requestTLDs,
Reason: reason,
TcVersion: terms.Version,
}
c.v("Requesting %d TLDs %+v", len(requestTLDs), requestTLDs)
err = c.SubmitRequest(request)
return requestTLDs, err
}
// ExtendTLD is a helper function that requests extensions to the provided tld
// TLDs provided should be marked as Extensible from GetRequestInfo()
func (c *Client) ExtendTLD(tld string) error {
c.v("ExtendTLD: %q", tld)
requestID, err := c.GetZoneRequestID(tld)
if err != nil {
return fmt.Errorf("error GetZoneRequestID(%q): %w", tld, err)
}
c.v("ExtendTLD: tld: %q requestID: %q", tld, requestID)
info, err := c.RequestExtension(requestID)
if err != nil {
return fmt.Errorf("RequestExtension(%q): %w", tld, err)
}
if !info.ExtensionInProcess {
return fmt.Errorf("error, zone request %q, %q: extension already in progress", tld, requestID)
}
return nil
}
// ExtendAllTLDs is a helper function to request extensions to all TLDs that are extendable
func (c *Client) ExtendAllTLDs() ([]string, error) {
return c.ExtendAllTLDsExcept(nil)
}
// ExtendAllTLDsExcept is a helper function to request extensions to all TLDs that are extendable excluding any in except
func (c *Client) ExtendAllTLDsExcept(except []string) ([]string, error) {
c.v("ExtendAllTLDs")
tlds := make([]string, 0, 10)
toExtend := make([]Request, 0, 10)
exceptMap := slice2LowerMap(except)
// get all TLDs to extend
const pageSize = 100
filter := RequestsFilter{
Status: RequestApproved,
Filter: "",
Pagination: RequestsPagination{
Size: pageSize,
Page: 0,
},
Sort: RequestsSort{
Field: SortByExpiration,
Direction: SortAsc,
},
}
// test if a request is extensible
isExtensible := func(id string) (bool, error) {
info, err := c.GetRequestInfo(id)
return info.Extensible, err
}
// get all pages of requests and check which ones are extendable
morePages := true
for morePages {
c.v("ExtendAllTLDs requesting %d requests on page %d", filter.Pagination.Size, filter.Pagination.Page)
req, err := c.GetRequests(&filter)
if err != nil {
return tlds, err
}
for _, r := range req.Requests {
// check for break early
if expiryDateThreshold > 0 && r.Expired.After(time.Now().AddDate(0, 0, expiryDateThreshold)) {
c.v("request %q: %q expires on %s, > %d days threshold, looking no further", r.TLD, r.RequestID, r.Expired.Format(time.ANSIC), expiryDateThreshold)
morePages = false
break
}
// get request info
ext, err := isExtensible(r.RequestID)
if err != nil {
return tlds, err
}
if ext {
toExtend = append(toExtend, r)
}
}
filter.Pagination.Page++
if len(req.Requests) == 0 {
morePages = false
}
}
// perform extend
c.v("requesting extensions for %d tlds: %+v", len(toExtend), toExtend)
for _, r := range toExtend {
if exceptMap[r.TLD] {
// skip over excluded TLDs
continue
}
_, err := c.RequestExtension(r.RequestID)
if err != nil {
return tlds, err
}
tlds = append(tlds, r.TLD)
}
if len(tlds) != len(toExtend) {
return tlds, fmt.Errorf("expected to extend %d TLDs but only extended %d", len(toExtend), len(tlds))
}
return tlds, nil
}