Passed
Push — master ( eff0ac...d17b5e )
by Kris
01:35
created

ApiManager::validateCategories()   B

Complexity

Conditions 8
Paths 28

Size

Total Lines 48
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 48
rs 8.4444
cc 8
nc 28
nop 1
1
<?php
2
3
/**
4
 *     _    _                    ___ ____  ____  ____
5
 *    / \  | |__  _   _ ___  ___|_ _|  _ \|  _ \| __ )
6
 *   / _ \ | '_ \| | | / __|/ _ \| || |_) | | | |  _ \
7
 *  / ___ \| |_) | |_| \__ \  __/| ||  __/| |_| | |_) |
8
 * /_/   \_\_.__/ \__,_|___/\___|___|_|   |____/|____/
9
 *
10
 * This file is part of Kristuff\AbsuseIPDB.
11
 *
12
 * (c) Kristuff <[email protected]>
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 *
17
 * @version    0.1.0
18
 * @copyright  2020 Kristuff
19
 */
20
21
namespace Kristuff\AbuseIPDB;
22
23
/**
24
 * Class ApiManager
25
 * 
26
 * The main class to work with the AbuseIPDB API v2 
27
 */
28
class ApiManager extends ApiDefintion
29
{
30
    /**
31
     * AbuseIPDB API key
32
     *  
33
     * @access protected
34
     * @var string $aipdbApiKey  
35
     */
36
    protected $aipdbApiKey = null; 
37
38
    /**
39
     * AbuseIPDB user id 
40
     * 
41
     * @access protected
42
     * @var string $aipdbUserId  
43
     */
44
    protected $aipdbUserId = null; 
45
46
    /**
47
     * The ips to remove from message
48
     * Generally you will add to this list yours ipv4 and ipv6, and the hostname
49
     * 
50
     * @access protected
51
     * @var array $selfIps  
52
     */
53
    protected $selfIps = []; 
54
55
    /**
56
     * Constructor
57
     * 
58
     * @access public
59
     * @param string  $apiKey     The AbuseIPDB api key
60
     * @param string  $userId     The AbuseIPDB user's id
61
     * @param array   $myIps      The Ips you dont want to report
62
     * 
63
     */
64
    public function __construct(string $apiKey, string $userId, array $myIps = [])
65
    {
66
        $this->aipdbApiKey = $apiKey;
67
        $this->aipdbUserId = $userId;
68
        $this->selfIps = $myIps;
69
    }
70
71
    /**
72
     * Get the current configuration in a indexed array
73
     * 
74
     * @access public 
75
     * @return array
76
     */
77
    public function getConfig()
78
    {
79
        return array(
80
            'userId'  => $this->aipdbUserId,
81
            'apiKey'  => $this->aipdbApiKey,
82
83
          // TODO 'selfIps' => $this->selfIps,
84
          // TODO  default report cat 
85
        );
86
    }
87
88
    /**
89
     * Get a new instance of ApiManager with config stored in a Json file
90
     * 
91
     * @access public 
92
     * @static
93
     * @param string    $configPath     The configuration file path
94
     * 
95
     * @return \Kristuff\AbuseIPDB\ApiManager
96
     * @throws \InvalidArgumentException                        If the given file does not exist
97
     * @throws \Kristuff\AbuseIPDB\InvalidPermissionException   If the given file is not readable 
98
     */
99
    public static function fromConfigFile(string $configPath)
100
    {
101
102
        // check file exists
103
        if (!file_exists($configPath) || !is_file($configPath)){
104
            throw new \InvalidArgumentException('The file [' . $configPath . '] does not exist.');
105
        }
106
107
        // check file is redable
108
        if (!is_readable($configPath)){
109
            throw new InvalidPermissionException('The file [' . $configPath . '] is not readable.');
110
        }
111
112
        //todo check file exist
113
        $config = self::loadJsonFile($configPath);
114
115
        // TODO     $config->self_ips
116
        // TODO     other options
117
        return new ApiManager($config->api_key, $config->user_id);
0 ignored issues
show
Bug introduced by
The property api_key does not exist on string.
Loading history...
Bug introduced by
The property user_id does not exist on string.
Loading history...
118
    }
119
120
    /**
121
     * Get the list of report categories
122
     * 
123
     * @access public 
124
     * @return array
125
     */
126
    public function getCategories()
127
    {
128
        return $this->aipdbApiCategories;
129
    }
130
131
    /**
132
     * Performs a 'report' api request
133
     * 
134
     * Result, in json format will be something like this:
135
     *  {
136
     *       "data": {
137
     *         "ipAddress": "127.0.0.1",
138
     *         "abuseConfidenceScore": 52
139
     *       }
140
     *  }
141
     * 
142
     * @access public
143
     * @param string    $ip             The ip to report
144
     * @param string    $categories     The report categories
145
     * @param string    $message        The report message
146
     * @param bool     [$returnArray]   True to return an indexed array instead of an object. Default is false. 
0 ignored issues
show
Documentation Bug introduced by
The doc comment [$returnArray] at position 0 could not be parsed: Unknown type name '[' at position 0 in [$returnArray].
Loading history...
147
     *
148
     * @return object|array
149
     * @throws \InvalidArgumentException
150
     */
151
    public function report(string $ip = '', string $categories = '', string $message = '', bool $returnArray = false)
152
    {
153
         // ip must be set
154
        if (empty($ip)){
155
            throw new \InvalidArgumentException('Ip was empty');
156
        }
157
158
        // categories must be set
159
        if (empty($categories)){
160
            throw new \InvalidArgumentException('categories list was empty');
161
        }
162
163
        // message must be set
164
          if (empty($message)){
165
            throw new \InvalidArgumentException('report message was empty');
166
        }
167
168
        // TODO clean message ? selfips list 
169
        $cats = $this->validateCategories($categories);
170
171
        // report AbuseIPDB request
172
        return $this->apiRequest('report', [
173
            'ip' => $ip,
174
            'categories' => $cats,
175
            'comment' => $message
176
            ],
177
            'POST', $returnArray
178
        );
179
    }
180
181
    /**
182
     * Check if the category(ies) given is/are valid
183
     * Check for shortname or id, and categories that can't be used alone 
184
     * 
185
     * @access protected
186
     * @param array $categories       The report categories list
187
     *
188
     * @return string               Formatted string id list ('18,2,3...')
189
     * @throws \InvalidArgumentException
190
     */
191
    protected function validateCategories(string $categories)
192
    {
193
        // the return categories string
194
        $catsString = ''; 
195
196
        // used when cat that can't be used alone
197
        $needAnother = null;
198
199
        // parse given categories
200
        $cats = explode(',', $categories);
201
202
        foreach ($cats as $cat) {
203
204
            // get index on our array of categories
205
            $catIndex    = is_numeric($cat) ? $this->getCategoryIndex($cat, 1) : $this->getCategoryIndex($cat, 0);
206
207
            // check if found
208
            if ($catIndex === false ){
209
                throw new \InvalidArgumentException('Invalid report category was given : ['. $cat .  ']');
210
            }
211
212
            // get Id
213
            $catId = $this->aipdbApiCategories[$catIndex][1];
214
215
            // need another ?
216
            if ($needAnother !== false){
217
218
                // is a standalone cat ?
219
                if ($this->aipdbApiCategories[$catIndex][3] === false) {
220
                    $needAnother = true;
221
222
                } else {
223
                    // ok, continue with other at least one given
224
                    // no need to reperform this check
225
                    $needAnother = false;
226
                }
227
            }
228
229
            // set or add to cats list 
230
            $catsString = ($catsString === '') ? $catId : $catsString .','.$catId;
231
        }
232
233
        if ($needAnother !== false){
0 ignored issues
show
introduced by
The condition $needAnother !== false is always true.
Loading history...
234
            throw new \InvalidArgumentException('Invalid report category paremeter given: some categories can\'t be used alone');
235
        }
236
237
        // if here that ok
238
        return $catsString;
239
    }
240
241
    /**
242
     * Perform a 'check' api request
243
     * 
244
     * @access public
245
     * @param string    $ip             The ip to check
246
     * @param string    $maxAge         Max age in days
247
     * @param bool      [$verbose]      True to get the full response. Default is false
0 ignored issues
show
Documentation Bug introduced by
The doc comment [$verbose] at position 0 could not be parsed: Unknown type name '[' at position 0 in [$verbose].
Loading history...
248
     * @param bool     [$returnArray]   True to return an indexed array instead of an object. Default is false. 
249
     * 
250
     * @return object|array
251
     * @throws \InvalidArgumentException    When maxAge is not a numeric value, when maxAge is less than 1 or 
252
     *                                      greater than 365, or when ip value was not set. 
253
     */
254
    public function check(string $ip = null, string $maxAge = '30', bool $verbose = false, bool $returnArray = false)
255
    {
256
        
257
        if (!is_numeric($maxAge)){
258
            throw new \InvalidArgumentException('maxAge must be a numeric value (' . $maxAge . ' was given)');
259
        }
260
        $maxAge = intval($maxAge);
261
262
        // max age must less or equal to 365
263
        if ($maxAge > 365 || $maxAge < 1){
264
            throw new \InvalidArgumentException('maxAge must be at least 1 and less than 365 (' . $maxAge . ' was given)');
265
        }
266
267
        //ip must be set
268
        if (empty($ip)){
269
            throw new \InvalidArgumentException('ip argument must be set (null given)');
270
        }
271
272
        // minimal data
273
        $data = [
274
            'ipAddress'     => $ip, 
275
            'maxAgeInDays'  => $maxAge,  
276
        ];
277
278
        // option
279
        if ($verbose){
280
           $data['verbose'] = true;
281
        }
282
283
        // check AbuseIPDB request
284
        return $this->apiRequest('check', $data, 'GET', $returnArray) ;
285
    }
286
287
    /**
288
     * Perform a cURL request       
289
     * 
290
     * @access protected
291
     * @param string    $path           The api end path 
292
     * @param array     $data           The request data 
293
     * @param string   [$method]        The request method. Default is 'GET' 
0 ignored issues
show
Documentation Bug introduced by
The doc comment [$method] at position 0 could not be parsed: Unknown type name '[' at position 0 in [$method].
Loading history...
294
     * @param bool     [$returnArray]   True to return an indexed array instead of an object. Default is false. 
295
     * 
296
     * @return object|array
297
     */
298
    protected function apiRequest(string $path, array $data, string $method = 'GET', bool $returnArray = false) 
299
    {
300
        // set api url
301
        $url = $this->aipdbApiEndpoint . $path; 
302
303
        // open curl connection
304
        $ch = curl_init(); 
305
  
306
        // set the method and data to send
307
        if ($method == 'POST') {
308
            curl_setopt($ch, CURLOPT_POST, true);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

308
            curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_POST, true);
Loading history...
309
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
310
        } else {
311
            $url .= '?' . http_build_query($data);
312
        }
313
         
314
        // set the url to call
315
        curl_setopt($ch, CURLOPT_URL, $url);
316
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
317
      
318
        // set the AbuseIPDB API Key as a header
319
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
320
            'Accept: application/json;',
321
            'Key: ' . $this->aipdbApiKey,
322
        ]);
323
  
324
      // execute curl call
325
      $result = curl_exec($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

325
      $result = curl_exec(/** @scrutinizer ignore-type */ $ch);
Loading history...
326
  
327
      // close connection
328
      curl_close($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

328
      curl_close(/** @scrutinizer ignore-type */ $ch);
Loading history...
329
  
330
      // return response as object / array
331
      return json_decode($result, $returnArray);
332
    }
333
334
    /** 
335
     * Load and returns decoded Json from given file  
336
     *
337
     * @access public
338
     * @static
339
	 * @param string    $filePath       The file's full path
340
	 * @param bool     [$trowError]     Throw error on true or silent process. Default is true
0 ignored issues
show
Documentation Bug introduced by
The doc comment [$trowError] at position 0 could not be parsed: Unknown type name '[' at position 0 in [$trowError].
Loading history...
341
     *  
342
	 * @return string|null 
343
     * @throws \Exception
344
     * @throws \LogicException
345
     */
346
    protected static function loadJsonFile(string $filePath, bool $throwError = true)
347
    {
348
        // check file exists
349
        if (!file_exists($filePath) || !is_file($filePath)){
350
           if ($throwError) {
351
                throw new \Exception('Config file not found');
352
           }
353
           return null;  
354
        }
355
356
        // get and parse content
357
        $content = file_get_contents($filePath);
358
        $json    = json_decode(utf8_encode($content));
359
360
        // check for errors
361
        if ($json == null && json_last_error() != JSON_ERROR_NONE){
362
            if ($throwError) {
363
                throw new \LogicException(sprintf("Failed to parse config file Error: '%s'", json_last_error_msg()));
364
            }
365
        }
366
367
        return $json;        
368
    }
369
}