-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
breach.go
348 lines (279 loc) Β· 11.6 KB
/
breach.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
package hibp
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// BreachAPI is a HIBP breaches API client
type BreachAPI struct {
hibp *Client // References back to the parent HIBP client
domain string // Filter for a specific breach domain
disableTrunc bool // Controls the truncateResponse parameter for the breaches API (defaults to false)
noUnverified bool // Controls the includeUnverified parameter for the breaches API (defaults to false)
}
// Breach represents a JSON response structure of the breaches API
type Breach struct {
// Name is a pascal-cased name representing the breach which is unique across all other breaches.
// This value never changes and may be used to name dependent assets (such as images) but should not
// be shown directly to end users (see the "Title" attribute instead)
Name string `json:"Name"`
// Title is a descriptive title for the breach suitable for displaying to end users. It's unique across
// all breaches but individual values may change in the future (i.e. if another breach occurs against
// an organisation already in the system). If a stable value is required to reference the breach,
// refer to the "Name" attribute instead
Title string `json:"Title"`
// Domain of the primary website the breach occurred on. This may be used for identifying other
// assets external systems may have for the site
Domain string `json:"Domain"`
// BreachDate is the date (with no time) the breach originally occurred on in ISO 8601 format. This is not
// always accurate β frequently breaches are discovered and reported long after the original incident. Use
// this attribute as a guide only
BreachDate *APIDate `json:"BreachDate,omitempty"`
// AddedDate represents the date and time (precision to the minute) the breach was added to the system
// in ISO 8601 format
AddedDate time.Time `json:"AddedDate"`
// ModifiedDate is the date and time (precision to the minute) the breach was modified in ISO 8601 format.
// This will only differ from the AddedDate attribute if other attributes represented here are changed or
// data in the breach itself is changed (i.e. additional data is identified and loaded). It is always
// either equal to or greater then the AddedDate attribute, never less than
ModifiedDate time.Time `json:"ModifiedDate"`
// PwnCount is the total number of accounts loaded into the system. This is usually less than the total
// number reported by the media due to duplication or other data integrity issues in the source data
PwnCount int `json:"PwnCount"`
// Description contains an overview of the breach represented in HTML markup. The description may include
// markup such as emphasis and strong tags as well as hyperlinks
Description string `json:"Description"`
// DataClasses describes the nature of the data compromised in the breach and contains an alphabetically ordered
// string array of impacted data classes
DataClasses []string `json:"DataClasses"`
// IsVerified indicates that the breach is considered unverified. An unverified breach may not have
// been hacked from the indicated website. An unverified breach is still loaded into HIBP when there's
// sufficient confidence that a significant portion of the data is legitimate
IsVerified bool `json:"IsVerified"`
// IsFabricated indicates that the breach is considered fabricated. A fabricated breach is unlikely
// to have been hacked from the indicated website and usually contains a large amount of manufactured
// data. However, it still contains legitimate email addresses and asserts that the account owners
// were compromised in the alleged breach
IsFabricated bool `json:"IsFabricated"`
// IsSensitive indicates if the breach is considered sensitive. The public API will not return any
// accounts for a breach flagged as sensitive
IsSensitive bool `json:"IsSensitive"`
// IsRetired indicates if the breach has been retired. This data has been permanently removed and
// will not be returned by the API
IsRetired bool `json:"IsRetired"`
// IsSpamList indicates
IsSpamList bool `json:"IsSpamList"`
// LogoPath represents a URI that specifies where a logo for the breached service can be found.
// Logos are always in PNG format
LogoPath string `json:"LogoPath"`
}
type SubscribedDomains struct {
// DomainName is the full domain name that has been successfully verified.
DomainName string `json:"DomainName"`
// PwnCount is the total number of breached email addresses found on the domain at last search
// (will be null if no searches yet performed).
PwnCount *int `json:"PwnCount"`
// PwnCountExcludingSpamLists is the number of breached email addresses found on the domain
// at last search, excluding any breaches flagged as a spam list (will be null if no
// searches yet performed).
PwnCountExcludingSpamLists *int `json:"PwnCountExcludingSpamLists"`
// The total number of breached email addresses found on the domain when the current
// subscription was taken out (will be null if no searches yet performed). This number
// ensures the domain remains searchable throughout the subscription period even if the
// volume of breached accounts grows beyond the subscription's scope.
PwnCountExcludingSpamListsAtLastSubscriptionRenewal *int `json:"PwnCountExcludingSpamListsAtLastSubscriptionRenewal"`
// The date and time the current subscription ends in ISO 8601 format. The
// PwnCountExcludingSpamListsAtLastSubscriptionRenewal value is locked in until this time (will
// be null if there have been no subscriptions).
NextSubscriptionRenewal RenewalTime `json:"NextSubscriptionRenewal"`
}
// BreachOption is an additional option the can be set for the BreachApiClient
type BreachOption func(*BreachAPI)
// APIDate is a date string without time returned by the API represented as time.Time type
type APIDate time.Time
// RenewalTime is a timestamp returned by the API that doesn't have timezone information
type RenewalTime time.Time
// Breaches returns a list of all breaches in the HIBP system
func (b *BreachAPI) Breaches(options ...BreachOption) ([]*Breach, *http.Response, error) {
qp := b.setBreachOpts(options...)
au := fmt.Sprintf("%s/breaches", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, hr, err
}
var bl []*Breach
if err := json.Unmarshal(hb, &bl); err != nil {
return nil, hr, err
}
return bl, hr, nil
}
// BreachByName returns a single breached site based on its name
func (b *BreachAPI) BreachByName(n string, options ...BreachOption) (*Breach, *http.Response, error) {
qp := b.setBreachOpts(options...)
if n == "" {
return nil, nil, ErrNoName
}
au := fmt.Sprintf("%s/breach/%s", BaseURL, n)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, hr, err
}
var bd *Breach
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}
return bd, hr, nil
}
// LatestBreach returns the single most recent breach
func (b *BreachAPI) LatestBreach() (*Breach, *http.Response, error) {
au := fmt.Sprintf("%s/latestbreach", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, nil)
if err != nil {
return nil, hr, err
}
var bd *Breach
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}
return bd, hr, nil
}
// DataClasses are attribute of a record compromised in a breach. This method returns a list of strings
// with all registered data classes known to HIBP
func (b *BreachAPI) DataClasses() ([]string, *http.Response, error) {
au := fmt.Sprintf("%s/dataclasses", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, nil)
if err != nil {
return nil, hr, err
}
var dc []string
if err := json.Unmarshal(hb, &dc); err != nil {
return nil, hr, err
}
return dc, hr, nil
}
// BreachedAccount returns a single breached site based on its name
func (b *BreachAPI) BreachedAccount(a string, options ...BreachOption) ([]*Breach, *http.Response, error) {
qp := b.setBreachOpts(options...)
if a == "" {
return nil, nil, ErrNoAccountID
}
au := fmt.Sprintf("%s/breachedaccount/%s", BaseURL, a)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, qp)
if err != nil {
return nil, hr, err
}
var bd []*Breach
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}
return bd, hr, nil
}
// SubscribedDomains returns domains that have been successfully added to the domain
// search dashboard after verifying control are returned via this API. This is an
// authenticated API requiring an HIBP API key which will then return all domains associated with that key.
func (b *BreachAPI) SubscribedDomains() ([]SubscribedDomains, *http.Response, error) {
au := fmt.Sprintf("%s/subscribeddomains", BaseURL)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, nil)
if err != nil {
return nil, hr, err
}
var bd []SubscribedDomains
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}
return bd, hr, nil
}
// BreachedDomain returns all email addresses on a given domain and the breaches they've appeared
// in can be returned via the domain search API. Only domains that have been successfully added
// to the domain search dashboard after verifying control can be searched.
func (b *BreachAPI) BreachedDomain(domain string) (map[string][]string, *http.Response, error) {
au := fmt.Sprintf("%s/breacheddomain/%s", BaseURL, domain)
hb, hr, err := b.hibp.HTTPResBody(http.MethodGet, au, nil)
if err != nil {
return nil, hr, err
}
var bd map[string][]string
if err := json.Unmarshal(hb, &bd); err != nil {
return nil, hr, err
}
return bd, hr, nil
}
// WithDomain sets the domain filter for the breaches API
func WithDomain(d string) BreachOption {
return func(b *BreachAPI) {
b.domain = d
}
}
// WithoutTruncate disables the truncateResponse parameter in the breaches API
// This option only influences the BreachedAccount method
func WithoutTruncate() BreachOption {
return func(b *BreachAPI) {
b.disableTrunc = true
}
}
// WithoutUnverified suppress unverified breaches from the query
func WithoutUnverified() BreachOption {
return func(b *BreachAPI) {
b.noUnverified = true
}
}
// UnmarshalJSON for the APIDate type converts a give date string into a time.Time type
func (d *APIDate) UnmarshalJSON(s []byte) error {
ds := string(s[1 : len(s)-1])
if ds == "null" || ds == "" {
return nil
}
pd, err := time.Parse("2006-01-02", ds)
if err != nil {
return fmt.Errorf("convert API date string to time.Time type: %w", err)
}
*(*time.Time)(d) = pd
return nil
}
// Time adds a Time() method to the APIDate converted time.Time type
func (d *APIDate) Time() time.Time {
dp := *d
return time.Time(dp)
}
// UnmarshalJSON for the RenewalTime type converts a give date string into a time.Time type
func (d *RenewalTime) UnmarshalJSON(s []byte) error {
ds := string(s[1 : len(s)-1])
if ds == "null" || ds == "" {
return nil
}
pd, err := time.Parse("2006-01-02T15:04:05", ds)
if err != nil {
return fmt.Errorf("convert API date string to time.Time type: %w", err)
}
*(*time.Time)(d) = pd
return nil
}
// Time adds a Time() method to the RenewalTime converted time.Time type
func (d *RenewalTime) Time() time.Time {
dp := *d
return time.Time(dp)
}
// setBreachOpts returns a map of default settings and overridden values from different BreachOption
func (b *BreachAPI) setBreachOpts(options ...BreachOption) map[string]string {
qp := map[string]string{
"truncateResponse": "true",
"includeUnverified": "true",
}
for _, opt := range options {
if opt == nil {
continue
}
opt(b)
}
if b.domain != "" {
qp["domain"] = b.domain
}
if b.disableTrunc {
qp["truncateResponse"] = "false"
}
if b.noUnverified {
qp["includeUnverified"] = "false"
}
return qp
}