Compare commits

..

16 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
10 changed files with 719 additions and 383 deletions

2
.gitignore vendored
View file

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

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2021 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

@ -13,12 +13,11 @@ Features
- Single IP check request **✓**
- IP block check request **✓**
- Blacklist request **✓**
- Single report request **✓**
- Clear address request (remove own reports) **✓**
- Auto cleaning report comment from sensitive data **✓**
**Not implemented:**
- *\[TODO\] Bulk report Api 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
------------
@ -34,25 +33,24 @@ Deploy with composer:
```json
...
"require": {
"kristuff/abuseipdb": ">=0.9.5-stable"
"kristuff/abuseipdb": "^1.1-stable"
},
```
Usage
More infos
-----
```php
<?php
- [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)
echo ('TODO');
```
License
-------
The MIT License (MIT)
Copyright (c) 2020-2021 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 wrapper for 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": {

View file

@ -1,32 +1,31 @@
<?php
<?php declare(strict_types=1);
/**
* _ _ ___ ____ ____ ____
* / \ | |__ _ _ ___ ___|_ _| _ \| _ \| __ )
* / _ \ | '_ \| | | / __|/ _ \| || |_) | | | | _ \
* / ___ \| |_) | |_| \__ \ __/| || __/| |_| | |_) |
* /_/ \_\_.__/ \__,_|___/\___|___|_| |____/|____/
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbsuseIPDB.
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <contact@kristuff.fr>
* (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 0.9.5
* @copyright 2020-2021 Kristuff
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Class ApiDefintion
* Class ApiBase
*
* Abstract base class for ApiManager
* Abstract base class for ApiHanlder
* Contains main hard coded api settings
*/
abstract class ApiDefintion
abstract class ApiBase
{
/**
* AbuseIPDB API v2 Endpoint
@ -35,13 +34,22 @@ abstract class ApiDefintion
protected $aipdbApiEndpoint = 'https://api.abuseipdb.com/api/v2/';
/**
* AbuseIPDB API v2 categories
* shorname, id (string), long name
* last paramter is false when the category cant' be used alone
* 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 $aipdbApiCategories = [
protected static $aipdbApiCategories = [
// Altering DNS records resulting in improper redirection.
['dns-c' , '1', 'DNS Compromise', true],
@ -119,33 +127,36 @@ abstract class ApiDefintion
// 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],
['iot' , '23', 'IoT Targeted', true],
];
/**
* Get the list of report categories
*
* @access public
* @static
*
* @return array
*/
public function getCategories()
public static function getCategories(): array
{
return $this->aipdbApiCategories;
return self::$aipdbApiCategories;
}
/**
* Get the category id corresponding to given name
*
* @access public
* @param string $categoryName The report categoriy name
* @static
* @param string $categoryName The report category name
*
* @return string|bool The category id in string format if found, otherwise false
*/
public function getCategoryIdbyName(string $categoryName)
public static function getCategoryIdByName(string $categoryName)
{
foreach ($this->aipdbApiCategories as $cat){
foreach (self::$aipdbApiCategories as $cat){
if ($cat[0] === $categoryName) {
return $cat;
return $cat[1];
}
}
@ -157,15 +168,16 @@ abstract class ApiDefintion
* 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 function getCategoryNameById(string $categoryId)
public static function getCategoryNameById(string $categoryId)
{
foreach ($this->aipdbApiCategories as $cat){
foreach (self::$aipdbApiCategories as $cat){
if ($cat[1] === $categoryId) {
return $cat;
return $cat[0];
}
}
@ -177,15 +189,16 @@ abstract class ApiDefintion
* 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 function getCategoryIndex(string $value, int $index)
protected static function getCategoryIndex(string $value, int $index)
{
$i = 0;
foreach ($this->aipdbApiCategories as $cat){
foreach (self::$aipdbApiCategories as $cat){
if ($cat[$index] === $value) {
return $i;
}
@ -196,4 +209,63 @@ abstract class ApiDefintion
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,21 +1,20 @@
<?php
<?php declare(strict_types=1);
/**
* _ _ ___ ____ ____ ____
* / \ | |__ _ _ ___ ___|_ _| _ \| _ \| __ )
* / _ \ | '_ \| | | / __|/ _ \| || |_) | | | | _ \
* / ___ \| |_) | |_| \__ \ __/| || __/| |_| | |_) |
* /_/ \_\_.__/ \__,_|___/\___|___|_| |____/|____/
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbsuseIPDB.
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <contact@kristuff.fr>
* (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 0.9.5
* @copyright 2020-2021 Kristuff
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
@ -25,104 +24,85 @@ namespace Kristuff\AbuseIPDB;
*
* The main class to work with the AbuseIPDB API v2
*/
class ApiHandler extends ApiDefintion
class ApiHandler extends ApiBase
{
/**
* AbuseIPDB API key
*
* @access protected
* @var string $aipdbApiKey
* Curl helper functions
*/
protected $aipdbApiKey = null;
use CurlTrait;
/**
* AbuseIPDB user id
*
* @access protected
* @var string $aipdbUserId
* @var string
*/
protected $aipdbUserId = null;
const VERSION = 'v1.1';
/**
* The ips to remove from message
* Generally you will add to this list yours ipv4 and ipv6, and the hostname
* 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 $selfIps
* @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 string $userId The AbuseIPDB user's id
* @param array $myIps The Ips/domain name you dont want to display in report messages
* @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, string $userId, array $myIps = [])
public function __construct(string $apiKey, array $myIps = [], int $timeout = 0)
{
$this->aipdbApiKey = $apiKey;
$this->aipdbUserId = $userId;
$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()
public function getConfig(): array
{
return array(
'userId' => $this->aipdbUserId,
'apiKey' => $this->aipdbApiKey,
'selfIps' => $this->selfIps,
// TODO default report cat
'timeout' => $this->timeout,
);
}
/**
* Get a new instance of ApiHandler with config stored in a Json file
*
* @access public
* @static
* @param string $configPath The configuration file path
*
* @return \Kristuff\AbuseIPDB\ApiHandler
* @throws \InvalidArgumentException If the given file does not exist
* @throws \Kristuff\AbuseIPDB\InvalidPermissionException If the given file is not readable
*/
public static function fromConfigFile(string $configPath)
{
// check file exists
if (!file_exists($configPath) || !is_file($configPath)){
throw new \InvalidArgumentException('The file [' . $configPath . '] does not exist.');
}
// check file is readable
if (!is_readable($configPath)){
throw new InvalidPermissionException('The file [' . $configPath . '] is not readable.');
}
$keyConfig = self::loadJsonFile($configPath);
$selfIps = [];
// Look for other optional config files in the same directory
$selfIpsConfigPath = pathinfo($configPath, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . 'self_ips.json';
if (file_exists($selfIpsConfigPath)){
$selfIps = self::loadJsonFile($selfIpsConfigPath)->self_ips;
}
$app = new self($keyConfig->api_key, $keyConfig->user_id, $selfIps);
return $app;
}
/**
* Performs a 'report' api request
*
@ -136,14 +116,14 @@ class ApiHandler extends ApiDefintion
*
* @access public
* @param string $ip The ip to report
* @param string $categories The report categories
* @param string $categories The report category(es)
* @param string $message The report message
* @param bool $returnArray True to return an indexed array instead of object. Default is false.
*
* @return object|array
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function report(string $ip = '', string $categories = '', string $message = '', bool $returnArray = false)
public function report(string $ip, string $categories, string $message): ApiResponse
{
// ip must be set
if (empty($ip)){
@ -152,29 +132,143 @@ class ApiHandler extends ApiDefintion
// categories must be set
if (empty($categories)){
throw new \InvalidArgumentException('categories list was empty');
throw new \InvalidArgumentException('Categories list was empty');
}
// message must be set
if (empty($message)){
throw new \InvalidArgumentException('report message was empty');
throw new \InvalidArgumentException('Report message was empty');
}
// validates categories, clean message
$cats = $this->validateReportCategories($categories);
$msg = $this->cleanMessage($message);
// report AbuseIPDB request
$response = $this->apiRequest(
// AbuseIPDB request
return $this->apiRequest(
'report', [
'ip' => $ip,
'categories' => $cats,
'comment' => $msg
],
'POST', $returnArray
'POST'
);
}
return json_decode($response, $returnArray);
/**
* 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') ;
}
/**
@ -214,133 +308,56 @@ class ApiHandler extends ApiDefintion
*
* @access public
* @param string $network The network to check
* @param int $maxAge Max age in days
* @param bool $returnArray True to return an indexed array instead of object. Default is false.
* @param int $maxAgeInDays The Max age in days, must
*
* @return object|array
* @throws \InvalidArgumentException when maxAge is less than 1 or greater than 365, or when network value was not set.
* @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 = null, int $maxAge = 30, bool $returnArray = false)
public function checkBlock(string $network, int $maxAgeInDays = 30): ApiResponse
{
// max age must be 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)');
// 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 (null given)');
throw new \InvalidArgumentException('network argument must be set (empty value given)');
}
// minimal data
$data = [
'network' => $network,
'maxAgeInDays' => $maxAge,
'maxAgeInDays' => $maxAgeInDays,
];
$response = $this->apiRequest('check-block', $data, 'GET', $returnArray) ;
return json_decode($response, $returnArray);
}
/**
* Perform a 'clear-address' api request
*
* Sample response:
*
* {
* "data": {
* "numReportsDeleted": 0
* }
* }
*
*
* @access public
* @param string $ip The ip to check
* @param bool $returnArray True to return an indexed array instead of object. Default is false.
*
* @return object|array
* @throws \InvalidArgumentException When ip value was not set.
*/
public function clear(string $ip = null, bool $returnArray = false)
{
// ip must be set
if (empty($ip)){
throw new \InvalidArgumentException('ip argument must be set (null given)');
}
// minimal data
$data = [
'ipAddress' => $ip,
];
$response = $this->apiRequest('check', $data, 'DELETE', $returnArray) ;
return json_decode($response, $returnArray);
}
/**
* Perform a 'check' api request
*
* @access public
* @param string $ip The ip to check
* @param int $maxAge Max age in days
* @param bool $verbose True to get the full response. Default is false
* @param bool $returnArray True to return an indexed array instead of object. Default is false.
*
* @return object|array
* @throws \InvalidArgumentException when maxAge is less than 1 or greater than 365, or when ip value was not set.
*/
public function check(string $ip = null, int $maxAge = 30, bool $verbose = false, bool $returnArray = false)
{
// max age must be 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)');
}
// minimal data
$data = [
'ipAddress' => $ip,
'maxAgeInDays' => $maxAge,
];
// option
if ($verbose){
$data['verbose'] = true;
}
$response = $this->apiRequest('check', $data, 'GET', $returnArray) ;
return json_decode($response, $returnArray);
return $this->apiRequest('check-block', $data, 'GET');
}
/**
* Perform a 'blacklist' api request
*
* @access public
* @param int $limit The blacklist limit. Default is TODO (the api default limit)
* @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 bool $returnArray True to return an indexed array instead of object (when $plainText is set to false). 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 object|array
* @throws \InvalidArgumentException When maxAge is not a numeric value, when maxAge is less than 1 or
* greater than 365, or when ip value was not set.
* @return ApiResponse
* @throws \RuntimeException
* @throws \InvalidArgumentException When maxAge is not a numeric value, when $limit is less than 1.
*/
public function getBlacklist(int $limit = 10000, bool $plainText = false, bool $returnArray = false)
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' => 100, // The abuseConfidenceScore parameter is a subscriber feature.
'confidenceMinimum' => $confidenceMinimum,
'limit' => $limit,
];
@ -350,73 +367,7 @@ class ApiHandler extends ApiDefintion
$data['plaintext'] = $plainText;
}
$response = $this->apiRequest('blacklist', $data, 'GET');
if ($plainText){
return $response;
}
return json_decode($response, $returnArray);
}
/**
* 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) ? $this->getCategoryIndex($cat, 1) : $this->getCategoryIndex($cat, 0);
// check if found
if ($catIndex === false ){
throw new \InvalidArgumentException('Invalid report category was given : ['. $cat . ']');
}
// get Id
$catId = $this->aipdbApiCategories[$catIndex][1];
// need another ?
if ($needAnother !== false){
// is a standalone cat ?
if ($this->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 paremeter given: some categories can\'t be used alone');
}
// if here that ok
return $catsString;
return $this->apiRequest('blacklist', $data, 'GET');
}
/**
@ -426,45 +377,75 @@ class ApiHandler extends ApiDefintion
* @param string $path The api end path
* @param array $data The request data
* @param string $method The request method. Default is 'GET'
* @param bool $returnArray True to return an indexed array instead of an object. Default is false.
* @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 mixed
* @return ApiResponse
* @throws \RuntimeException
*/
protected function apiRequest(string $path, array $data, string $method = 'GET', bool $returnArray = false)
protected function apiRequest(string $path, array $data, string $method = 'GET', string $csvFilePath = ''): ApiResponse
{
// set api url
$url = $this->aipdbApiEndpoint . $path;
$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') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$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 the url to call
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// set url and options
$this->setCurlOption($ch, CURLOPT_URL, $url);
$this->setCurlOption($ch, CURLOPT_RETURNTRANSFER, 1);
$this->setCurlOption($ch, CURLOPT_HTTPHEADER, $headers);
// set the wanted format, JSON (required to prevent having full html page on error)
// and the AbuseIPDB API Key as a header
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json;',
'Key: ' . $this->aipdbApiKey,
]);
/**
* 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);
// return response as JSON data
return $result;
if ($curlErrorNumber !== 0){
throw new \RuntimeException($curlErrorMessage);
}
return new ApiResponse($result !== false ? $result : '');
}
/**
@ -477,7 +458,7 @@ class ApiHandler extends ApiDefintion
*
* @return string
*/
protected function cleanMessage(string $message)
public function cleanMessage(string $message): string
{
// Remove backslashes
$message = str_replace('\\', '', $message);
@ -488,46 +469,10 @@ class ApiHandler extends ApiDefintion
}
// If we're reporting spam, further munge any email addresses in the report
$emailPattern = "/[^@\s]*@[^@\s]*\.[^@\s]*/";
$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);
}
/**
* 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 object|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

@ -1,29 +1,27 @@
<?php
<?php declare(strict_types=1);
/**
* _ _ ___ ____ ____ ____
* / \ | |__ _ _ ___ ___|_ _| _ \| _ \| __ )
* / _ \ | '_ \| | | / __|/ _ \| || |_) | | | | _ \
* / ___ \| |_) | |_| \__ \ __/| || __/| |_| | |_) |
* /_/ \_\_.__/ \__,_|___/\___|___|_| |____/|____/
* _ ___ ___ ___ ___
* __ _| |__ _ _ ___ ___|_ _| _ \ \| _ )
* / _` | '_ \ || (_-</ -_)| || _/ |) | _ \
* \__,_|_.__/\_,_/__/\___|___|_| |___/|___/
*
* This file is part of Kristuff\AbsuseIPDB.
* This file is part of Kristuff\AbuseIPDB.
*
* (c) Kristuff <contact@kristuff.fr>
* (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 0.9.5
* @copyright 2020-2021 Kristuff
* @version 1.1
* @copyright 2020-2022 Kristuff
*/
namespace Kristuff\AbuseIPDB;
/**
* Custom Exception for non redable file
* 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());
}
}
}