1
0
Fork 0

feature: documentation, category enum and cached check-block

This commit is contained in:
Seraphim Strub 2024-08-08 17:57:27 +00:00
parent d456ceac98
commit 4f1fe4840e

View file

@ -27,6 +27,45 @@ const (
defaultUserAgent = "go-abuseipdb/" + Version
)
const (
endpointCheck = "check"
endpointReports = "reports"
endpointBlacklist = "blacklist"
endpointReport = "report"
endpointCheckBlock = "check-block"
endpointBulkReport = "bulk-report"
endpointClearAddress = "clear-address"
)
// ReportCategory is documented at https://www.abuseipdb.com/categories
type ReportCategory int
const (
DnsCompromise ReportCategory = iota + 1
DnsPoisoning
FraudOrders
DDoSAttack
FtpBruteForce
PingOfDeath
Phishing
FraudVoIp
OpenProxy
WebSpam
EmailSpam
BlogSpam
VpnIp
PortScan
Hacking
SqlInjection
Spoofing
BruteForce
BadWebBot
ExploitedHost
WebAppAttack
Ssh
IotTargeted
)
type Client struct {
client *http.Client
BaseURL *url.URL
@ -51,6 +90,7 @@ func NewClient(client *http.Client) *Client {
return c
}
// AddApiKey pass api key from https://www.abuseipdb.com/account/api
func (c *Client) AddApiKey(key string) {
c.APIKey = key
}
@ -126,6 +166,9 @@ func (c *Client) do(ctx context.Context, req *http.Request, v any) error {
return err
}
defer resp.Body.Close()
if err := handleError(resp); err != nil {
return err
}
switch v := v.(type) {
case nil:
@ -170,16 +213,15 @@ type CheckResult struct {
}
type CheckResultReport struct {
ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"`
Categories []int `json:"categories"`
ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"`
ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"`
Categories []ReportCategory `json:"categories"`
ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"`
}
var checkEndpoint = "check"
// Check an IP for abuse and additional information
func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) {
ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{
@ -195,7 +237,7 @@ func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*Che
}
}
req, err := c.newRequest(http.MethodGet, checkEndpoint, parameters, nil)
req, err := c.newRequest(http.MethodGet, endpointCheck, parameters, nil)
if err != nil {
return nil, err
}
@ -208,13 +250,15 @@ func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*Che
return &result, nil
}
// CheckCached uses the client cache and the ip as a key
// CheckCached same as Check but uses the client cache and the ip as a key (cache has to be added to client for this to work)
func (c *Client) CheckCached(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) {
key := fmt.Sprintf("%s:%s", checkEndpoint, ip.String())
if c.cache == nil {
return nil, errors.New("cache is missing")
}
key := fmt.Sprintf("%s:%s", endpointCheck, ip.String())
if r, err := c.cache.Get(key); err == nil {
result := CheckResult{}
if jsonErr := json.NewDecoder(bytes.NewReader(r)).Decode(&result); jsonErr == nil {
fmt.Println("return cached")
return &result, nil
}
}
@ -251,16 +295,16 @@ type ReportsResult struct {
} `json:"data"`
}
type ReportResultReport struct {
ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"`
Categories []int `json:"categories"`
ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"`
ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"`
Categories []ReportCategory `json:"categories"`
ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"`
}
// Reports returns reports for a certain IP
func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (*ReportsResult, error) {
var endpoint = "reports"
ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{
"ipAddress": ipAddress,
@ -278,7 +322,7 @@ func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (
}
}
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodGet, endpointReports, parameters, nil)
if err != nil {
return nil, err
}
@ -332,12 +376,11 @@ func handleBlacklistOptions(opts *BlacklistOptions) map[string]string {
return parameters
}
// Blacklist will generate a blacklist based on the Options
func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*BlacklistResult, error) {
var endpoint = "blacklist"
parameters := handleBlacklistOptions(opts)
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodGet, endpointBlacklist, parameters, nil)
if err != nil {
return nil, err
}
@ -350,12 +393,11 @@ func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*Blackl
return &result, nil
}
// BlacklistPlain same as Blacklist but returns a newline separated string
func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io.Reader, error) {
var endpoint = "blacklist"
parameters := handleBlacklistOptions(opts)
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodGet, endpointBlacklist, parameters, nil)
if err != nil {
return nil, err
}
@ -374,10 +416,11 @@ func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io
return &result, nil
}
// ReportOptions at least Categories is required
type ReportOptions struct {
Categories []int `json:"categories"`
Comment string `json:"comment,omitempty"`
Time *time.Time `json:"timestamp,omitempty"`
Categories []ReportCategory `json:"categories"`
Comment string `json:"comment,omitempty"`
Time *time.Time `json:"timestamp,omitempty"`
}
type ReportResult struct {
@ -387,9 +430,8 @@ type ReportResult struct {
} `json:"data"`
}
// Report sends a report for an IP with additional Information
func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*ReportResult, error) {
var endpoint = "report"
ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{
"ip": ipAddress,
@ -399,7 +441,7 @@ func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*R
if opts.Categories != nil {
categories := make([]string, len(opts.Categories))
for i, category := range opts.Categories {
categories[i] = strconv.Itoa(category)
categories[i] = strconv.Itoa(int(category))
}
parameters["categories"] = strings.Join(categories, ",")
}
@ -411,7 +453,7 @@ func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*R
}
}
req, err := c.newRequest(http.MethodPost, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodPost, endpointReport, parameters, nil)
if err != nil {
return nil, err
}
@ -445,13 +487,15 @@ type CheckBlockResult struct {
} `json:"data"`
}
// CheckBlock test an IPNet of up to /16 and returns all addresses with reports, it is recommended to check /24 networks
func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) {
var endpoint = "check-block"
network := html.EscapeString(ipnNet.String())
parameters := map[string]string{
"network": network,
}
if cidr, _ := ipnNet.Mask.Size(); cidr < 16 {
return nil, fmt.Errorf("CIDR must be less than or equal to 16")
}
if opts != nil {
if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 {
@ -459,7 +503,7 @@ func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckB
}
}
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodGet, endpointCheckBlock, parameters, nil)
if err != nil {
return nil, err
}
@ -472,11 +516,37 @@ func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckB
return &result, nil
}
// CheckBlockCached same as CheckBlock but uses the client cache and the ipnet as a key (cache has to be added to client for this to work)
func (c *Client) CheckBlockCached(ctx context.Context, ipNet *net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) {
if c.cache == nil {
return nil, errors.New("cache is missing")
}
key := fmt.Sprintf("%s:%s", endpointCheck, ipNet.String())
if r, err := c.cache.Get(key); err == nil {
result := CheckBlockResult{}
if jsonErr := json.NewDecoder(bytes.NewReader(r)).Decode(&result); jsonErr == nil {
return &result, nil
}
}
if result, err := c.CheckBlock(ctx, ipNet, opts); err == nil {
resultEncoded := bytes.Buffer{}
if jsonErr := json.NewEncoder(&resultEncoded).Encode(result); jsonErr != nil {
return nil, jsonErr
}
if re := resultEncoded.Bytes(); re != nil {
c.cache.Set(key, re)
}
return result, nil
} else {
return nil, err
}
}
type BulkReportData struct {
IpAddress string `csv:"IP"`
Categories []int `csv:"Categories"`
ReportDate time.Time `csv:"ReportDate"`
Comment string `csv:"Comment"`
IpAddress string `csv:"IP"`
Categories []ReportCategory `csv:"Categories"`
ReportDate time.Time `csv:"ReportDate"`
Comment string `csv:"Comment"`
}
type BulkReportDatas []BulkReportData
@ -510,7 +580,7 @@ func (b *BulkReportDatas) toSlice() [][]string {
for _, data := range *b {
categories := make([]string, len(data.Categories))
for i, category := range data.Categories {
categories[i] = strconv.Itoa(category)
categories[i] = strconv.Itoa(int(category))
}
slice = append(slice, []string{
data.IpAddress,
@ -578,9 +648,8 @@ type BulkReportResult struct {
} `json:"data"`
}
// BulkReport takes a list of reports the success for ich reported entry
func (c *Client) BulkReport(ctx context.Context, data *BulkReportDatas) (*BulkReportResult, error) {
var endpoint = "bulk-report"
if data == nil || len(*data) == 0 {
return nil, errors.New("bulk report: no data")
}
@ -593,7 +662,7 @@ func (c *Client) BulkReport(ctx context.Context, data *BulkReportDatas) (*BulkRe
}
requestBody, contentType := toMultipartCsv(csvData)
req, err := c.newRequest(http.MethodPost, endpoint, nil, requestBody)
req, err := c.newRequest(http.MethodPost, endpointBulkReport, nil, requestBody)
if err != nil {
return nil, err
}
@ -613,14 +682,14 @@ type ClearAddressData struct {
} `json:"data"`
}
// ClearAddress allow one to delete all reports made from your account for a certain address
func (c *Client) ClearAddress(ctx context.Context, ip net.IP) (*ClearAddressData, error) {
var endpoint = "clear-address"
ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{
"ipAddress": ipAddress,
}
req, err := c.newRequest(http.MethodDelete, endpoint, parameters, nil)
req, err := c.newRequest(http.MethodDelete, endpointClearAddress, parameters, nil)
if err != nil {
return nil, err
}