Compare commits

..

35 commits

Author SHA1 Message Date
kristuff
d7b635875a v1.1
- Fixed: Update email pattern in `cleanMessage()` method to handle local addresses without TLD and to allow using the caracter `@` in custom message. Close #4
2022-10-06 00:16:16 +02:00
kristuff
af62240932 Update email pattern in cleanMessage() method
Update email pattern to handle local addresses without TLD and to allow using the caracter `@` in custom message. Close #4
2022-10-06 00:14:00 +02:00
kristuff
5639f1813a v1.0 2022-01-26 22:24:24 +01:00
kristuff
dc30007818 v0.9.15
**Changes**
- **New** `ApiHandler::setTimeout(int $timeout)` method allows to change default timeout before any API request. Timeout is expressed in milliseconds. Overwrites the value passed in constructor, useful when performing multiple queries with same handler but different timeout.
2021-12-08 20:42:33 +01:00
kristuff
d1d9cf9ad0 v0.9.14
**Changes**
- **New**: the `ApiHandler` (and `QuietApiHandler`) constructor takes now a **timeout** in third parameter. The timeout is expressed in **milliseconds** .  The timeout will apply to all API request methods (will raise exception if reached out). Default is **0** (no timeout).
- Formatting
2021-12-06 21:40:39 +01:00
kristuff
3ab67950a1 v0.9.13 2021-09-18 21:42:24 +02:00
kristuff
c4b5c65598 Fix #3
Check *properly* if property exists to prevent PHP notice.
2021-09-18 21:41:42 +02:00
kristuff
37b0a2eaa9 v0.9.12
**Fixed**
- Incorrect return type in `ApiResponse::getObject()` method (bug introduced in v0.9.8 with PHP strict types)
- cleaning, typos
2021-07-04 13:09:24 +02:00
kristuff
adff27d86d v0.9.11
**Fixed**
- getCategoryIdByName and getCategoryNameById now return string (instead array)
- IoT Targeted report category now named iot (instead of oit)
2021-02-23 20:31:53 +01:00
kristuff
77c2857eb8 v0.9.10
**Changed**
- report categories methods related now static
- `SilentApiHandler` class renamed `QuietApiHandler` **break change**
- new static method `ApiResponse::createErrorResponse`
2021-01-29 20:30:12 +01:00
kristuff
3c5c7155c2 v0.9.9
**Added**
- better exception handling with new class `SilentApiHandler`: to not raise any exception during API request but instead return an `ApiResponse` with errors
- version constant
- `ApiReponse` has new `errors()` and `hasError()` methods
2021-01-17 19:09:15 +01:00
kristuff
fa42dafc36 v0.9.8
**Added**
- ApiResponse class is now the return type for all API endpoint methods **break change**
- New parameter confidenceScore in blacklist() method (suscribers feature)

**Changed**
- method loadFromConfig and json helper removed (moved to client) **break change**
- field userId removed (not used). ApiHandler constructor changed **break change**
- `clear()` and `getBlacklist()` method renamed to `clearAddress()` and `blacklist()` (all method names match now api endpoint in camel case) **break change**
- scrict types
2021-01-16 18:28:11 +01:00
kristuff
1e7e1eb87b v0.9.7
**Added**
- support for bulk-report request (send cvs file)
2021-01-09 11:26:58 +01:00
kristuff
60d42db74c Update README.md 2021-01-08 19:33:24 +01:00
kristuff
cd48341f1b v0.9.6
**Fixed**
- clear method is broken in 0.9.5.
2021-01-08 19:12:48 +01:00
kristuff
1a882c0155 v0.9.6
**Fixed**
- clear method is broken in 0.9.5.
2021-01-08 16:31:43 +01:00
kristuff
ed9e4bd8fb v0.9.5
**Added**
- Support for clear address request (remove own report for a given IP address)

**Changed**
- internal refactoring
2021-01-08 15:51:29 +01:00
kristuff
2e21e7a490 v0.9.4 2021-01-08 10:15:18 +01:00
kristuff
6140da20ec v0.9.4
**Added**
- support for check-block request

**changed**
- internal refactoring
2021-01-08 10:02:09 +01:00
kristuff
9034c45536 v0.9.3
**Added**
- Blacklist request: set limit (depending on plan), optional plain text

**break change**
- rename class ApiManager to ApiHandler
- parameter day in check request is now type int
- commands name changes (l to L, g to G)
2021-01-07 22:00:18 +01:00
kristuff
c5811c73b6 v0.9.2 2020-06-28 22:02:10 +02:00
kristuff
a94c99fee2 v0.9.2 2020-06-28 21:43:49 +02:00
kristuff
368b7b4d75 v0.9.2 2020-06-28 21:42:34 +02:00
kristuff
3f5ea52b1e Handle comment max length 2020-06-28 20:45:54 +02:00
kristuff
2e8041d6d1 fix clean message process 2020-06-28 20:34:51 +02:00
kristuff
d3828776bb v0.9.1 2020-06-27 12:24:48 +02:00
kristuff
a3949e7916 fix 2020-06-27 12:23:32 +02:00
kristuff
5a3fc93491 v0.9.0 2020-06-27 11:42:55 +02:00
kristuff
bca2095bc6 Auto cleaning report message 2020-06-27 11:41:10 +02:00
kristuff
eb4e1d6d69 refactoring 2020-06-27 11:39:08 +02:00
kristuff
9a97e0eee7 fix hack cat status 2020-06-27 11:38:42 +02:00
kristuff
fa238f11a7 v0.2 2020-05-28 22:44:59 +02:00
kristuff
1c1fe7c58e initial commit 2020-05-15 15:51:24 +02:00
kristuff
d17b5e1739 initial commit 2020-05-15 11:29:55 +02:00
kristuff
eff0ac03cb initial commit 2020-05-14 21:23:51 +02:00
13 changed files with 1129 additions and 442 deletions

1
.gitattributes vendored
View file

@ -3,6 +3,5 @@ vendor/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
composer.* export-ignore
phpunit.xml export-ignore
README.* export-ignore

2
.gitignore vendored
View file

@ -1,3 +1 @@
_*
.old*
vendor/*

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 kristuff
Copyright (c) 2020-2022 kristuff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,11 +1,26 @@
# Kristuff\AbuseIPDB
> A mini library to work with the AbuseIPDB api V2
> A wrapper for AbuseIPDB API v2
see [kristuff/abuseipdb-cli](https://github.com/kristuff/abuseipdb-cli) for the CLI version
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/kristuff/abuseipdb/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/kristuff/abuseipdb/?branch=master)
[![Build Status](https://scrutinizer-ci.com/g/kristuff/abuseipdb/badges/build.png?b=master)](https://scrutinizer-ci.com/g/kristuff/abuseipdb/build-status/master)
[![Latest Stable Version](https://poser.pugx.org/kristuff/abuseipdb/v/stable)](https://packagist.org/packages/kristuff/abuseipdb)
[![License](https://poser.pugx.org/kristuff/abuseipdb/license)](https://packagist.org/packages/kristuff/abuseipdb)
***see also [kristuff/abuseipdb-cli](https://github.com/kristuff/abuseipdb-cli) for the `CLI` version***
Features
--------
- Single IP check request **✓**
- IP block check request **✓**
- Blacklist request **✓**
- Single IP report request **✓**
- Bulk report request (send `csv` file) **✓**
- Clear IP address request (remove your own reports) **✓**
- Auto cleaning report comments from sensitive data (email, custom ip/domain names list) **✓**
- Define timeout for cURL internal requests **✓**
Requirements
------------
------------
- PHP >= 7.1
- PHP's cURL
- A valid [abuseipdb.com](https://abuseipdb.com) account with an API key
@ -18,23 +33,24 @@ Deploy with composer:
```json
...
"require": {
"kristuff/abuseipdb": ">=0.1-stable"
"kristuff/abuseipdb": "^1.1-stable"
},
```
Usage
More infos
-----
```php
echo ('TODO');
```
- [Project website](https://kristuff.fr/projects/abuseipdb)
- [Api documentation](https://kristuff.fr/projects/abuseipdb/doc)
- CLI version: [github](https://github.com/kristuff/abuseipdb-cli) | [website](https://kristuff.fr/projects/abuseipdbcli)
License
-------
The MIT License (MIT)
Copyright (c) 2020 Kristuff
Copyright (c) 2020-2022 Kristuff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,12 +1,15 @@
{
"name": "kristuff/abuseipdb",
"description": "A library to work with the AbuseIPDB api V2",
"description": "A PHP wrapper for AbuseIPDB API v2",
"type": "library",
"keywords": ["abuseIPDB", "API"],
"license": "MIT",
"authors": [
{
"name": "Kristuff",
"homepage": "https://github.com/kristuff"
"homepage": "https://github.com/kristuff",
"email": "kristuff@kristuff.fr",
"role": "Developer"
}
],
"require": {

271
lib/ApiBase.php Normal file
View file

@ -0,0 +1,271 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiBase
*
* Abstract base class for ApiHanlder
* Contains main hard coded api settings
*/
abstract class ApiBase
{
/**
* AbuseIPDB API v2 Endpoint
* @var string
*/
protected $aipdbApiEndpoint = 'https://api.abuseipdb.com/api/v2/';
/**
* AbuseIPDB API key
*
* @access protected
* @var string $aipdbApiKey
*/
protected $aipdbApiKey = null;
/**
* AbuseIPDB API v2 categories
* shortname, id (string), long name
* last parameter is false when the category can't be used alone
*
* @static
* @var array
*/
protected static $aipdbApiCategories = [
// Altering DNS records resulting in improper redirection.
['dns-c' , '1', 'DNS Compromise', true],
// Falsifying domain server cache (cache poisoning).
['dns-p' , '2', 'DNS Poisoning', true],
// Fraudulent orders.
['fraud-orders' , '3', 'Fraud Orders', true],
// Participating in distributed denial-of-service (usually part of botnet).
['ddos' , '4', 'DDoS Attack', true],
//
['ftp-bf' , '5', 'FTP Brute-Force', true],
// Oversized IP packet.
['pingdeath' , '6', 'Ping of Death', true],
// Phishing websites and/or email.
['phishing' , '7', 'Phishing', true],
//
['fraudvoip' , '8', 'Fraud VoIP', true],
// Open proxy, open relay, or Tor exit node.
['openproxy' , '9', 'Open Proxy', true],
// Comment/forum spam, HTTP referer spam, or other CMS spam.
['webspam' , '10', 'Web Spam', true],
// Spam email content, infected attachments, and phishing emails. Note: Limit comments to only relevent
// information (instead of log dumps) and be sure to remove PII if you want to remain anonymous.
['emailspam' , '11', 'Email Spam', true],
// CMS blog comment spam.
['blogspam' , '12', 'Blog Spam', true],
// Conjunctive category.
['vpnip' , '13', 'VPN IP', false], // to check alone ??
// Scanning for open ports and vulnerable services.
['scan' , '14', 'Port Scan', true],
//
['hack' , '15', 'Hacking', true],
// Attempts at SQL injection.
['sql' , '16', 'SQL Injection', true],
// Email sender spoofing.
['spoof' , '17', 'Spoofing', true],
// Credential brute-force attacks on webpage logins and services like SSH, FTP, SIP, SMTP, RDP, etc.
// This category is seperate from DDoS attacks.
['brute' , '18', 'Brute-Force', true],
// Webpage scraping (for email addresses, content, etc) and crawlers that do not honor robots.txt.
// Excessive requests and user agent spoofing can also be reported here.
['badbot' , '19', 'Bad Web Bot', true],
// Host is likely infected with malware and being used for other attacks or to host malicious content.
// The host owner may not be aware of the compromise. This category is often used in combination
// with other attack categories.
['explhost' , '20', 'Exploited Host', true],
// Attempts to probe for or exploit installed web applications such as a CMS
// like WordPress/Drupal, e-commerce solutions, forum software, phpMyAdmin and
// various other software plugins/solutions.
['webattack' , '21', 'Web App Attack', true ],
// Secure Shell (SSH) abuse. Use this category in combination
// with more specific categories.
['ssh' , '22', 'SSH', false],
// Abuse was targeted at an "Internet of Things" type device. Include
// information about what type of device was targeted in the comments.
['iot' , '23', 'IoT Targeted', true],
];
/**
* Get the list of report categories
*
* @access public
* @static
*
* @return array
*/
public static function getCategories(): array
{
return self::$aipdbApiCategories;
}
/**
* Get the category id corresponding to given name
*
* @access public
* @static
* @param string $categoryName The report category name
*
* @return string|bool The category id in string format if found, otherwise false
*/
public static function getCategoryIdByName(string $categoryName)
{
foreach (self::$aipdbApiCategories as $cat){
if ($cat[0] === $categoryName) {
return $cat[1];
}
}
// not found
return false;
}
/**
* Get the category name corresponding to given id
*
* @access public
* @static
* @param string $categoryId The report category id
*
* @return string|bool The category name if found, otherwise false
*/
public static function getCategoryNameById(string $categoryId)
{
foreach (self::$aipdbApiCategories as $cat){
if ($cat[1] === $categoryId) {
return $cat[0];
}
}
// not found
return false;
}
/**
* Get the index of category corresponding to given value
*
* @access protected
* @static
* @param string $value The report category id or name
* @param string $index The index in value array
*
* @return int|bool The category index if found, otherwise false
*/
protected static function getCategoryIndex(string $value, int $index)
{
$i = 0;
foreach (self::$aipdbApiCategories as $cat){
if ($cat[$index] === $value) {
return $i;
}
$i++;
}
// not found
return false;
}
/**
* Check if the category(ies) given is/are valid
* Check for shortname or id, and categories that can't be used alone
*
* @access protected
* @param array $categories The report categories list
*
* @return string Formatted string id list ('18,2,3...')
* @throws \InvalidArgumentException
*/
protected function validateReportCategories(string $categories)
{
// the return categories string
$catsString = '';
// used when cat that can't be used alone
$needAnother = null;
// parse given categories
$cats = explode(',', $categories);
foreach ($cats as $cat) {
// get index on our array of categories
$catIndex = is_numeric($cat) ? self::getCategoryIndex($cat, 1) : self::getCategoryIndex($cat, 0);
// check if found
if ($catIndex === false ){
throw new \InvalidArgumentException('Invalid report category was given.');
}
// get Id
$catId = self::$aipdbApiCategories[$catIndex][1];
// need another ?
if ($needAnother !== false){
// is a standalone cat ?
if (self::$aipdbApiCategories[$catIndex][3] === false) {
$needAnother = true;
} else {
// ok, continue with other at least one given
// no need to reperform this check
$needAnother = false;
}
}
// set or add to cats list
$catsString = ($catsString === '') ? $catId : $catsString .','.$catId;
}
if ($needAnother !== false){
throw new \InvalidArgumentException('Invalid report category parameter given: this category can\'t be used alone.');
}
// if here that ok
return $catsString;
}
}

View file

@ -1,123 +0,0 @@
<?php
/**
* _ _ ___ ____ ____ ____
* / \ | |__ _ _ ___ ___|_ _| _ \| _ \| __ )
* / _ \ | '_ \| | | / __|/ _ \| || |_) | | | | _ \
* / ___ \| |_) | |_| \__ \ __/| || __/| |_| | |_) |
* /_/ \_\_.__/ \__,_|___/\___|___|_| |____/|____/
*
* This file is part of Kristuff\AbsuseIPDB.
*
* (c) Kristuff <contact@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 0.1.0
* @copyright 2020 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiDefintion
*
* Abstract base class for ApiManager
* Contains main hard coded api settings
*/
abstract class ApiDefintion
{
/**
* AbuseIPDB API v2 Endpoint
* @var string $api_endpoint
*/
protected $aipdbApiEndpoint = 'https://api.abuseipdb.com/api/v2/';
/**
* AbuseIPDB API v2 categories
* @var array $aipdbApiCategories
*/
protected $aipdbApiCategories = [
// Altering DNS records resulting in improper redirection.
'dns-c' => ['1', 'DNS Compromise', true],
// Falsifying domain server cache (cache poisoning).
'dns-p' => ['2', 'DNS Poisoning', true],
// Fraudulent orders.
'fraud-orders' => ['3', 'Fraud Orders', true],
// Participating in distributed denial-of-service (usually part of botnet).
'ddos' => ['4', 'DDoS Attack', true],
//
'ftp-bf' => ['5', 'FTP Brute-Force', true],
// Oversized IP packet.
'pingdeath' => ['6', 'Ping of Death', true],
// Phishing websites and/or email.
'phishing' => ['7', 'Phishing', true],
//
'fraudvoip' => ['8', 'Fraud VoIP', true],
// Open proxy, open relay, or Tor exit node.
'openproxy' => ['9', 'Open Proxy', true],
// Comment/forum spam, HTTP referer spam, or other CMS spam.
'webspam' => ['10', 'Web Spam', true],
// Spam email content, infected attachments, and phishing emails. Note: Limit comments to only relevent
// information (instead of log dumps) and be sure to remove PII if you want to remain anonymous.
'emailspam' => ['11', 'Email Spam', true],
// CMS blog comment spam.
'blogspam' => ['12', 'Blog Spam', true],
// Conjunctive category.
'vpnip' => ['13', 'VPN IP', false], // to check alone ??
// Scanning for open ports and vulnerable services.
'scan' => ['14', 'Port Scan', true],
// seems to can't be used alone
'hack' => ['15', 'Hacking', false],
// Attempts at SQL injection.
'sql' => ['16', 'SQL Injection'], true,
// Email sender spoofing.
'spoof' => ['17', 'Spoofing', true],
// Credential brute-force attacks on webpage logins and services like SSH, FTP, SIP, SMTP, RDP, etc.
// This category is seperate from DDoS attacks.
'brute' => ['18', 'Brute-Force', true],
// Webpage scraping (for email addresses, content, etc) and crawlers that do not honor robots.txt.
// Excessive requests and user agent spoofing can also be reported here.
'badbot' => ['19', 'Bad Web Bot', true],
// Host is likely infected with malware and being used for other attacks or to host malicious content.
// The host owner may not be aware of the compromise. This category is often used in combination
// with other attack categories.
'explhost' => ['20', 'Exploited Host', true],
// Attempts to probe for or exploit installed web applications such as a CMS
// like WordPress/Drupal, e-commerce solutions, forum software, phpMyAdmin and
// various other software plugins/solutions.
'webattack' => ['21', 'Web App Attack', true ],
// Secure Shell (SSH) abuse. Use this category in combination
// with more specific categories.
'ssh' => ['22', 'SSH', false],
// Abuse was targeted at an "Internet of Things" type device. Include
// information about what type of device was targeted in the comments.
'oit' => ['23', 'IoT Targeted', true],
];
}

478
lib/ApiHandler.php Normal file
View file

@ -0,0 +1,478 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiHandler
*
* The main class to work with the AbuseIPDB API v2
*/
class ApiHandler extends ApiBase
{
/**
* Curl helper functions
*/
use CurlTrait;
/**
* @var string
*/
const VERSION = 'v1.1';
/**
* The ips to remove from report messages
* Generally you will add to this list yours ipv4 and ipv6, hostname, domain names
*
* @access protected
* @var array
*/
protected $selfIps = [];
/**
* The maximum number of milliseconds to allow cURL functions to execute. If libcurl is
* built to use the standard system name resolver, that portion of the connect will still
* use full-second resolution for timeouts with a minimum timeout allowed of one second.
*
* @access protected
* @var int
*/
protected $timeout = 0;
/**
* Constructor
*
* @access public
* @param string $apiKey The AbuseIPDB api key
* @param array $myIps The Ips/domain name you don't want to display in report messages
* @param int $timeout The maximum number of milliseconds to allow internal cURL functions
* to execute. Default is 0, no timeout
*
*/
public function __construct(string $apiKey, array $myIps = [], int $timeout = 0)
{
$this->aipdbApiKey = $apiKey;
$this->selfIps = $myIps;
$this->timeout = $timeout;
}
/**
* Sets the cURL timeout (apply then to any API request). Overwrites the value passed in
* constructor, useful when performing multiple queries with same handler but different timeout.
*
* @access public
* @param int $timeout The maximum number of milliseconds to allow internal cURL functions
* to execute.
*
* @return void
*/
public function setTimeout(int $timeout): void
{
$this->timeout = $timeout;
}
/**
* Get the current configuration in a indexed array
*
* @access public
*
* @return array
*/
public function getConfig(): array
{
return array(
'apiKey' => $this->aipdbApiKey,
'selfIps' => $this->selfIps,
'timeout' => $this->timeout,
);
}
/**
* Performs a 'report' api request
*
* Result, in json format will be something like this:
* {
* "data": {
* "ipAddress": "127.0.0.1",
* "abuseConfidenceScore": 52
* }
* }
*
* @access public
* @param string $ip The ip to report
* @param string $categories The report category(es)
* @param string $message The report message
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function report(string $ip, string $categories, string $message): ApiResponse
{
// ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('Ip was empty');
}
// categories must be set
if (empty($categories)){
throw new \InvalidArgumentException('Categories list was empty');
}
// message must be set
if (empty($message)){
throw new \InvalidArgumentException('Report message was empty');
}
// validates categories, clean message
$cats = $this->validateReportCategories($categories);
$msg = $this->cleanMessage($message);
// AbuseIPDB request
return $this->apiRequest(
'report', [
'ip' => $ip,
'categories' => $cats,
'comment' => $msg
],
'POST'
);
}
/**
* Performs a 'bulk-report' api request
*
* Result, in json format will be something like this:
* {
* "data": {
* "savedReports": 60,
* "invalidReports": [
* {
* "error": "Duplicate IP",
* "input": "41.188.138.68",
* "rowNumber": 5
* },
* {
* "error": "Invalid IP",
* "input": "127.0.foo.bar",
* "rowNumber": 6
* },
* {
* "error": "Invalid Category",
* "input": "189.87.146.50",
* "rowNumber": 8
* }
* ]
* }
* }
*
* @access public
* @param string $filePath The CSV file path. Could be an absolute or relative path.
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws InvalidPermissionException
*/
public function bulkReport(string $filePath): ApiResponse
{
// check file exists
if (!file_exists($filePath) || !is_file($filePath)){
throw new \InvalidArgumentException('The file [' . $filePath . '] does not exist.');
}
// check file is readable
if (!is_readable($filePath)){
throw new InvalidPermissionException('The file [' . $filePath . '] is not readable.');
}
return $this->apiRequest('bulk-report', [], 'POST', $filePath);
}
/**
* Perform a 'clear-address' api request
*
* Sample response:
*
* {
* "data": {
* "numReportsDeleted": 0
* }
* }
*
* @access public
* @param string $ip The IP to clear reports
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException When ip value was not set.
*/
public function clearAddress(string $ip): ApiResponse
{
// ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('IP argument must be set.');
}
return $this->apiRequest('clear-address', ['ipAddress' => $ip ], "DELETE") ;
}
/**
* Perform a 'check' api request
*
* @access public
* @param string $ip The ip to check
* @param int $maxAgeInDays Max age in days. Default is 30.
* @param bool $verbose True to get the full response (last reports and countryName). Default is false
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException when maxAge is less than 1 or greater than 365, or when ip value was not set.
*/
public function check(string $ip, int $maxAgeInDays = 30, bool $verbose = false): ApiResponse
{
// max age must be less or equal to 365
if ( $maxAgeInDays > 365 || $maxAgeInDays < 1 ){
throw new \InvalidArgumentException('maxAgeInDays must be between 1 and 365.');
}
// ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('ip argument must be set (empty value given)');
}
// minimal data
$data = [
'ipAddress' => $ip,
'maxAgeInDays' => $maxAgeInDays,
];
// option
if ($verbose){
$data['verbose'] = true;
}
return $this->apiRequest('check', $data, 'GET') ;
}
/**
* Perform a 'check-block' api request
*
*
* Sample json response for 127.0.0.1/24
*
* {
* "data": {
* "networkAddress": "127.0.0.0",
* "netmask": "255.255.255.0",
* "minAddress": "127.0.0.1",
* "maxAddress": "127.0.0.254",
* "numPossibleHosts": 254,
* "addressSpaceDesc": "Loopback",
* "reportedAddress": [
* {
* "ipAddress": "127.0.0.1",
* "numReports": 631,
* "mostRecentReport": "2019-03-21T16:35:16+00:00",
* "abuseConfidenceScore": 0,
* "countryCode": null
* },
* {
* "ipAddress": "127.0.0.2",
* "numReports": 16,
* "mostRecentReport": "2019-03-12T20:31:17+00:00",
* "abuseConfidenceScore": 0,
* "countryCode": null
* },
* ...
* ]
* }
* }
*
*
* @access public
* @param string $network The network to check
* @param int $maxAgeInDays The Max age in days, must
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException when $maxAgeInDays is less than 1 or greater than 365, or when $network value was not set.
*/
public function checkBlock(string $network, int $maxAgeInDays = 30): ApiResponse
{
// max age must be between 1 and 365
if ($maxAgeInDays > 365 || $maxAgeInDays < 1){
throw new \InvalidArgumentException('maxAgeInDays must be between 1 and 365 (' . $maxAgeInDays . ' was given)');
}
// ip must be set
if (empty($network)){
throw new \InvalidArgumentException('network argument must be set (empty value given)');
}
// minimal data
$data = [
'network' => $network,
'maxAgeInDays' => $maxAgeInDays,
];
return $this->apiRequest('check-block', $data, 'GET');
}
/**
* Perform a 'blacklist' api request
*
* @access public
* @param int $limit The blacklist limit. Default is 10000 (the api default limit)
* @param bool $plainText True to get the response in plaintext list. Default is false
* @param int $confidenceMinimum The abuse confidence score minimum (subscribers feature). Default is 100.
* The confidence minimum must be between 25 and 100.
* This parameter is a subscriber feature (not honored otherwise).
*
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException When maxAge is not a numeric value, when $limit is less than 1.
*/
public function blacklist(int $limit = 10000, bool $plainText = false, int $confidenceMinimum = 100): ApiResponse
{
if ($limit < 1){
throw new \InvalidArgumentException('limit must be at least 1 (' . $limit . ' was given)');
}
// minimal data
$data = [
'confidenceMinimum' => $confidenceMinimum,
'limit' => $limit,
];
// plaintext paremeter has no value and must be added only when true
// (set plaintext=false won't work)
if ($plainText){
$data['plaintext'] = $plainText;
}
return $this->apiRequest('blacklist', $data, 'GET');
}
/**
* Perform a cURL request
*
* @access protected
* @param string $path The api end path
* @param array $data The request data
* @param string $method The request method. Default is 'GET'
* @param string $csvFilePath The file path for csv file. When not empty, $data parameter is ignored and in place,
* the content of the given file if passed as csv. Default is empty string.
*
* @return ApiResponse
* @throws \RuntimeException
*/
protected function apiRequest(string $path, array $data, string $method = 'GET', string $csvFilePath = ''): ApiResponse
{
$curlErrorNumber = -1; // will be used later to check curl execution
$curlErrorMessage = '';
$url = $this->aipdbApiEndpoint . $path; // api url
// set the wanted format, JSON (required to prevent having full html page on error)
// and the AbuseIPDB API Key as a header
$headers = [
'Accept: application/json;',
'Key: ' . $this->aipdbApiKey,
];
// open curl connection
$ch = curl_init();
// for csv
if (!empty($csvFilePath)){
$cfile = new \CurlFile($csvFilePath, 'text/csv', 'csv');
//curl file itself return the realpath with prefix of @
$data = array('csv' => $cfile);
}
// set the method and data to send
if ($method == 'POST') {
$this->setCurlOption($ch, CURLOPT_POST, true);
$this->setCurlOption($ch, CURLOPT_POSTFIELDS, $data);
} else {
$this->setCurlOption($ch, CURLOPT_CUSTOMREQUEST, $method);
$url .= '?' . http_build_query($data);
}
// set url and options
$this->setCurlOption($ch, CURLOPT_URL, $url);
$this->setCurlOption($ch, CURLOPT_RETURNTRANSFER, 1);
$this->setCurlOption($ch, CURLOPT_HTTPHEADER, $headers);
/**
* set timeout
*
* @see https://curl.se/libcurl/c/CURLOPT_TIMEOUT_MS.html
* @see https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT_MS.html
* If libcurl is built to use the standard system name resolver, that portion of the transfer
* will still use full-second resolution for timeouts with a minimum timeout allowed of one second.
* In unix-like systems, this might cause signals to be used unless CURLOPT_NOSIGNAL is set.
*/
$this->setCurlOption($ch, CURLOPT_NOSIGNAL, 1);
$this->setCurlOption($ch, CURLOPT_TIMEOUT_MS, $this->timeout);
// execute curl call
$result = curl_exec($ch);
$curlErrorNumber = curl_errno($ch);
$curlErrorMessage = curl_error($ch);
// close connection
curl_close($ch);
if ($curlErrorNumber !== 0){
throw new \RuntimeException($curlErrorMessage);
}
return new ApiResponse($result !== false ? $result : '');
}
/**
* Clean message in case it comes from fail2ban <matches>
* Remove backslashes and sensitive information from the report
* @see https://wiki.shaunc.com/wikka.php?wakka=ReportingToAbuseIPDBWithFail2Ban
*
* @access public
* @param string $message The original message
*
* @return string
*/
public function cleanMessage(string $message): string
{
// Remove backslashes
$message = str_replace('\\', '', $message);
// Remove self ips
foreach ($this->selfIps as $ip){
$message = str_replace($ip, '*', $message);
}
// If we're reporting spam, further munge any email addresses in the report
$emailPattern = "/\b[A-Z0-9!#$%&'*`\/?^{|}~=+_.-]+@[A-Z0-9.-]+\b/i";
$message = preg_replace($emailPattern, "*", $message);
// Make sure message is less 1024 chars
return substr($message, 0, 1024);
}
}

View file

@ -1,304 +0,0 @@
<?php
/**
* _ _ ___ ____ ____ ____
* / \ | |__ _ _ ___ ___|_ _| _ \| _ \| __ )
* / _ \ | '_ \| | | / __|/ _ \| || |_) | | | | _ \
* / ___ \| |_) | |_| \__ \ __/| || __/| |_| | |_) |
* /_/ \_\_.__/ \__,_|___/\___|___|_| |____/|____/
*
* This file is part of Kristuff\AbsuseIPDB.
*
* (c) Kristuff <contact@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 0.1.0
* @copyright 2020 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiManager
*
* The main class to work with the AbuseIPDB API v2
*/
class ApiManager extends ApiDefintion
{
/**
* AbuseIPDB API key
*
* @access protected
* @var string $aipdbApiKey
*/
protected $aipdbApiKey = null;
/**
* AbuseIPDB user id
*
* @access protected
* @var string $aipdbUserId
*/
protected $aipdbUserId = null;
/**
* The ips to remove from message
* Generally you will add to this list yours ipv4 and ipv6, and the hostname
*
* @access protected
* @var array $selfIps
*/
protected $selfIps = [];
/**
* Constructor
*
* @access public
* @param string $apiKey The AbuseIPDB api key
* @param string $userId The AbuseIPDB user's id
* @param array $myIps The Ips you dont want to report
*
*/
public function __construct(string $apiKey, string $userId, array $myIps = [])
{
$this->aipdbApiKey = $apiKey;
$this->aipdbUserId = $userId;
$this->selfIps = $myIps;
}
/**
* Get the current configuration in a indexed array
*
* @access public
* @return array
*/
public function getConfig()
{
return array(
'userId' => $this->aipdbUserId,
'apiKey' => $this->aipdbApiKey,
'selfIps' => $this->selfIps,
);
}
/**
* Get a new instance of ApiManager with config stored in a Json file
*
* @access public
* @static
* @param string $configPath The configuration file path
*
* @return \Kristuff\AbuseIPDB\ApiManager
*/
public static function fromConfigstring(string $configPath)
{
//todo check file exist
$config = self::loadJsonFile($configPath);
return new ApiManager($config->api_key, $config->user_id, $config->self_ips);
}
/**
* Get the list of report categories
*
* @access public
* @return array
*/
public function getCategories()
{
return $this->aipdbApiCategories;
}
/**
* Performs a 'report' api request
*
* Result, in json format will be something like this:
* {
* "data": {
* "ipAddress": "127.0.0.1",
* "abuseConfidenceScore": 52
* }
* }
*
* @access public
* @param string $ip The ip to report
* @param array $categories The report categories
* @param string $message The report message
*
* @return stdClass|array
* @throws \InvalidArgumentException
*/
public function report(string $ip = '', array $categories = [], $message = '')
{
// ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('Ip was empty');
}
// categories must be set
if (empty($categories)){
throw new \InvalidArgumentException('categories list was empty');
}
// message must be set
if (empty($message)){
throw new \InvalidArgumentException('report message was empty');
}
// TODO valider les cat / seules pas seules...
// TODO clean message ? selfips list
$cats = $this->validateCategories($categories);
// report AbuseIPDB request
//TODO
return $this->apiRequest('report', 'POST', [
'ip' => $ip,
'categories' => 'TODO', '21,15',
'comment' => $message
]);
}
/**
* Check if the category(ies) given is/are valid
* Check for shortname or id, and categories that can't be used alone
*
* @access public
* @param array $categories The report categories list
*
* @return string Formatted string id list ('18,2,3...')
* @throws \InvalidArgumentException
*/
public function validateCategories(array $categories = [])
{
$newList = [];
$needAnother = false;
foreach ($categories as $cat){
}
//todo
}
/**
* Perform a 'check' api request
*
*
* TODO OPTION POUR VERBOSE ;;;
* force $maxAge int as parameter ?
*
* @access public
* @param string $ip The ip to check
* @param string $maxAge Max age in days
*
* @return stdObj
* @throws \InvalidArgumentException
*/
public function check(string $ip = null, string $maxAge = '30')
{
$maxAge = intval($maxAge);
// max age must less or equal to 365
if ($maxAge > 365 || $maxAge < 1){
throw new \InvalidArgumentException('maxAge must be at least 1 and less than 365 (' . $maxAge . ' was given)');
}
//ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('ip argument must be set (null given)');
}
// check AbuseIPDB request
return $this->apiRequest('check', 'GET', [
'ipAddress' => $ip,
'maxAgeInDays' => $maxAge,
'verbose' => true
]);
}
/**
* Perform a cURL request TODO: option as array
*
* @access protected
* @param string $path The api end path
* @param string $method The request method. Default is 'GET'
* @param array $data The request data
*
* @return stdObj TODO object ARRAY ;;;
*/
protected function apiRequest(string $path, string $method = 'GET', array $data)
{
// set api url
$url = $this->aipdbApiEndpoint . $path;
// open curl connection
$ch = curl_init();
// set the method and data to send
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
} else {
$url .= '?' . http_build_query($data);
}
// set the url to call
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// set the AbuseIPDB API Key as a header
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json;',
'Key: ' . $this->aipdbApiKey,
]);
// execute curl call
$result = curl_exec($ch);
// close connection
curl_close($ch);
// return response as json object
return json_decode($result);
}
/**
* Load and returns decoded Json from given file
*
* @access public
* @static
* @param string $filePath The file's full path
* @param bool [$trowError] Throw error on true or silent process. Default is true
*
* @return string|null
* @throws \Exception
* @throws \LogicException
*/
protected static function loadJsonFile(string $filePath, bool $throwError = true)
{
// check file exists
if (!file_exists($filePath) || !is_file($filePath)){
if ($throwError) {
throw new \Exception('Config file not found');
}
return null;
}
// get and parse content
$content = file_get_contents($filePath);
$json = json_decode(utf8_encode($content));
// check for errors
if ($json == null && json_last_error() != JSON_ERROR_NONE){
if ($throwError) {
throw new \LogicException(sprintf("Failed to parse config file Error: '%s'", json_last_error_msg()));
}
}
return $json;
}
}

137
lib/ApiResponse.php Normal file
View file

@ -0,0 +1,137 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiResponse
*
*/
class ApiResponse
{
/**
*
* @access protected
* @var string
*/
protected $curlResponse;
/**
*
* @access protected
* @var object
*/
protected $decodedResponse;
/**
* Constructor
*
* @access public
* @param string $plaintext AbuseIPDB response in plaintext
*
*/
public function __construct(?string $plaintext = null)
{
$this->curlResponse = $plaintext;
$this->decodedResponse = !empty($plaintext) ? json_decode($plaintext, false) : null;
}
/**
* Get response as array. May return null
*
* @access public
*
* @return array|null
*/
public function getArray(): ?array
{
return json_decode($this->curlResponse, true);
}
/**
* Get response as object. May return null
*
* @access public
*
* @return \stdClass|null
*/
public function getObject(): ?\stdClass
{
return $this->decodedResponse;
}
/**
* Get response as plaintext. May return null
*
* @access public
*
* @return string|null
*/
public function getPlaintext(): ?string
{
return $this->curlResponse;
}
/**
* Get whether the response contains error(s)
*
* @access public
*
* @return bool
*/
public function hasError(): bool
{
return count($this->errors()) > 0;
}
/**
* Get an array of errors (object) contained is response
*
* @access public
*
* @return array
*/
public function errors(): array
{
return ($this->decodedResponse && property_exists($this->decodedResponse, 'errors')) ? $this->decodedResponse->errors : [];
}
/**
* Get an internal error message in an ApiResponse object
*
* @access public
* @static
* @param string $message The error message
*
* @return ApiResponse
*/
public static function createErrorResponse(string $message): ApiResponse
{
$response = [
"errors" => [
[
"title" => "Internal Error",
"detail" => $message
]
]
];
return new ApiResponse(json_encode($response));
}
}

44
lib/CurlTrait.php Normal file
View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* cURL helper functions
*/
trait CurlTrait
{
/**
* helper to configure cURL option
*
* @access protected
* @param resource $ch
* @param int $option
* @param mixed $value
*
* @return void
* @throws \RuntimeException
*/
protected function setCurlOption($ch, int $option, $value): void
{
if(!curl_setopt($ch,$option,$value)){
throw new \RuntimeException('curl_setopt failed! '.curl_error($ch));
}
}
}

View file

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Custom Exception for not readable file
*/
class InvalidPermissionException extends \Exception
{
}

141
lib/QuietApiHandler.php Normal file
View file

@ -0,0 +1,141 @@
<?php declare(strict_types=1);
/**
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <kristuff@kristuff.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class QuietApiHandler
*
* Overwrite ApiHandler with Exception handling
* Instead of Exception, all methods return an ApiResponse that may
* contains errors from the AbuseIPDB API, or internal errors
*/
class QuietApiHandler extends ApiHandler
{
/**
* Performs a 'report' api request, with Exception handling
*
* @access public
* @param string $ip The ip to report
* @param string $categories The report category(es)
* @param string $message The report message
*
* @return ApiResponse
*/
public function report(string $ip, string $categories, string $message): ApiResponse
{
try {
return parent::report($ip,$categories,$message);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
/**
* Performs a 'bulk-report' api request, with Exception handling
*
* @access public
* @param string $filePath The CSV file path. Could be an absolute or relative path.
*
* @return ApiResponse
*/
public function bulkReport(string $filePath): ApiResponse
{
try {
return parent::bulkReport($filePath);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
/**
* Perform a 'clear-address' api request, with Exception handling
*
* @access public
* @param string $ip The IP to clear reports
*
* @return ApiResponse
*/
public function clearAddress(string $ip): ApiResponse
{
try {
return parent::clearAddress($ip);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
/**
* Perform a 'check' api request, with Exception handling
*
* @access public
* @param string $ip The ip to check
* @param int $maxAgeInDays Max age in days. Default is 30.
* @param bool $verbose True to get the full response (last reports and countryName). Default is false
*
* @return ApiResponse
*/
public function check(string $ip, int $maxAgeInDays = 30, bool $verbose = false): ApiResponse
{
try {
return parent::check($ip, $maxAgeInDays, $verbose);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
/**
* Perform a 'check-block' api request, with Exception handling
*
* @access public
* @param string $network The network to check
* @param int $maxAgeInDays The Max age in days, must
*
* @return ApiResponse
*/
public function checkBlock(string $network, int $maxAgeInDays = 30): ApiResponse
{
try {
return parent::checkBlock($network, $maxAgeInDays);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
/**
* Perform a 'blacklist' api request, with Exception handling
*
* @access public
* @param int $limit The blacklist limit. Default is 10000 (the api default limit)
* @param bool $plainText True to get the response in plaintext list. Default is false
* @param int $confidenceMinimum The abuse confidence score minimum (subscribers feature). Default is 100.
* The confidence minimum must be between 25 and 100.
* This parameter is a subscriber feature (not honored otherwise).
*
* @return ApiResponse
*/
public function blacklist(int $limit = 10000, bool $plainText = false, int $confidenceMinimum = 100): ApiResponse
{
try {
return parent::blacklist($limit, $plainText, $confidenceMinimum);
} catch (\Exception $e) {
return ApiResponse::createErrorResponse($e->getMessage());
}
}
}