-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathicann-api.go
executable file
·219 lines (176 loc) · 5.54 KB
/
icann-api.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
// (c) Kamiar Bahri
package icannclient
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
// ICannAPI interface performs the basic funtions to interact
// with the ICANN's API.
type IIcannAPI interface {
Authenticate()
HTTPExec(method string, url string, hd http.Header, data []byte) HTTPResult
GetCommonHeaders() http.Header
Run()
accessTokenExpired() bool
waitForAuthAttemptTimeout()
writeAccessTokenToDisk()
getAccessTokenFromDisk()
}
// Run renews the access token every 23 hours
func (i *IcannAPI) Run() {
for {
acceessTokenExpired := i.accessTokenExpired()
if acceessTokenExpired {
i.Authenticated = false
i.Authenticate()
}
if i.isDirty {
// wait just over two minutes
time.Sleep(126 * time.Second)
} else {
i.isDirty = true
}
}
}
// waitForAuthAttemptTimeout halts the system until
// it reaches an appropiate time to make another auth attmept.
// The limit is 8 attmept in 5 minutes per IP address.
func (i *IcannAPI) waitForAuthAttemptTimeout() {
if mLastAuthenticationAttempt.Year() == 1 {
// first time since the app has started
return
}
// it's safer to wait the whole 2 minutes
time.Sleep(2 * time.Minute)
}
// GetCommonHeaders gets the headers required by icann api.
func (i *IcannAPI) GetCommonHeaders() http.Header {
hd := make(http.Header, 0)
hd.Add("Accept", "application/json")
hd.Add("Content-Type", "application/json")
hd.Add("User-Agent", i.UserAgent)
return hd
}
// accessTokenExpired returns true, if the issue-date of
// the access token a less than 24 hours.
func (i *IcannAPI) accessTokenExpired() bool {
if i.AccessToken.Token == "" || i.AccessToken.DateTimeIssued.Year() < 2000 {
i.getAccessTokenFromDisk()
}
// check for empty time
if i.AccessToken.DateTimeIssued.Year() < time.Now().Year() {
return true
}
return i.AccessToken.DateTimeIssued.Add(23 * time.Hour).Before(time.Now())
}
// writeAccessTokenToDisk transforms i.AccessToken into hex
// and saves it to disk (tokenFileName in i.AppDataDir directory).
func (i *IcannAPI) writeAccessTokenToDisk() {
if !FileOrDirExists(i.AppDataDir) {
log.Fatal("unable to access appdata path")
return
}
tokenFilePath := fmt.Sprintf("%s/%s", i.AppDataDir, tokenFileName)
b, _ := json.Marshal(i.AccessToken)
b = []byte(hex.EncodeToString(b))
os.WriteFile(tokenFilePath, b, os.ModePerm)
}
// getAccessTokenFromDisk reads the tokenFileName from i.AppDataDir
// (if exists) and transforms its content into i.AccessToken.
func (i *IcannAPI) getAccessTokenFromDisk() {
tokenFilePath := fmt.Sprintf("%s/%s", i.AppDataDir, tokenFileName)
if !FileOrDirExists(tokenFilePath) {
return
}
b, _ := os.ReadFile(tokenFilePath)
b, _ = hex.DecodeString(string(b))
json.Unmarshal(b, &i.AccessToken)
}
// Authenticate calls the authenticate and retreives an
// access code, which can be used by the ICzdsAPI interface.
func (i *IcannAPI) Authenticate() {
actExp := i.accessTokenExpired()
if !actExp {
// token still good? test the token
// hd := i.GetCommonHeaders()
// hd.Add("Authorization", fmt.Sprintf("Bearer %s", i.AccessToken.Token))
// res := i.HTTPExec(GET, czdsAPIDownloadLinksURL, hd, nil)
// if res.StatusCode == 200 {
i.Authenticated = true
// return
// }
}
i.waitForAuthAttemptTimeout()
data := []byte(fmt.Sprintf(`{"username":"%s", "password":"%s"}`, i.UserName, i.Password))
hd := i.GetCommonHeaders()
res := i.HTTPExec(POST, authenticateBaseURL, hd, data)
mLastAuthenticationAttempt = time.Now()
// too many authentication attempts from the same IP address
if res.StatusCode == http.StatusTooManyRequests {
// not much can be done until ~2 minutes has elapsed,
i.Authenticated = false
return
} else if res.StatusCode == 0 {
// status-code zero in this case does not necessarily mean
// that the authentication was rejected; it would rather mean
// the icann api could make sense of the information passed to
// it (i.e. hearders were not read). So, display the message
// and try again. This func will be called again in 2 minutes.
i.Authenticated = false
log.Println("authentication failed: status-code: 0; trying again in 2 min...")
return
}
if res.StatusCode != http.StatusOK {
// whether api site was unavailable or authenticaton failed, it's a
// good idea to bail out.
log.Fatal("authentication failed status-code:", res.StatusCode)
}
var autRes autResult
err := json.Unmarshal(res.ResponseBody, &autRes)
if err != nil {
// don't bail out; just display the error.
// as we could be in a middle of a long-running download
fmt.Println("")
log.Println(err)
return
}
if autRes.Message == "Authentication Successful" {
i.Authenticated = true
i.AccessToken.Token = autRes.AccessToken
i.AccessToken.DateTimeIssued = time.Now()
i.AccessToken.DateTimeExpires = time.Now().Add(23 * time.Hour)
i.writeAccessTokenToDisk()
} else {
// unlikely, but still account for this (status-cocde=200 and
// success message missing)
log.Fatal("authentication failed:", autRes.Message)
}
return
}
// HTTPExec is wrappter to make http calls.
func (i *IcannAPI) HTTPExec(method string, urlx string, hd http.Header, data []byte) HTTPResult {
var res HTTPResult
client := &http.Client{}
req, _ := http.NewRequest(method, urlx, bytes.NewBuffer([]byte(data)))
req.Header = hd
resp, err := client.Do(req)
if err != nil {
res.Error = err
return res
}
body, _ := io.ReadAll(resp.Body)
req.Body.Close()
resp.Body.Close()
client.CloseIdleConnections()
res.ResponseHeaders = resp.Header
res.StatusCode = resp.StatusCode
res.ResponseBody = body
return res
}