Passed
Branch master (a88228)
by Antonio Carlos
03:04
created

IpList::nonDatabaseFind()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
namespace PragmaRX\Firewall\Repositories;
4
5
use PragmaRX\Firewall\Support\ServiceInstances;
6
use PragmaRX\Firewall\Vendor\Laravel\Models\Firewall as FirewallModel;
7
8
class IpList
9
{
10
    use ServiceInstances;
11
12
    const IP_ADDRESS_LIST_CACHE_NAME = 'firewall.ip_address_list';
13
14
    /**
15
     * @var \PragmaRX\Firewall\Vendor\Laravel\Models\Firewall
16
     */
17
    private $model;
18
19
    /**
20
     * Create instance of DataRepository.
21
     *
22
     * @param \PragmaRX\Firewall\Vendor\Laravel\Models\Firewall $model
23
     */
24 26
    public function __construct(FirewallModel $model)
25
    {
26 26
        $this->model = $model;
27 26
    }
28
29
    /**
30
     * Get a list of non database ip addresses.
31
     *
32
     * @return array
33
     */
34 24
    private function getNonDatabaseIps()
35
    {
36 24
        return array_merge_recursive(
37 24
            array_map(function ($ip) {
38 6
                $ip['whitelisted'] = true;
39
40 6
                return $ip;
41 24
            }, $this->formatIpArray($this->config()->get('whitelist'))),
42
43 24
            array_map(function ($ip) {
44 8
                $ip['whitelisted'] = false;
45
46 8
                return $ip;
47 24
            }, $this->formatIpArray($this->config()->get('blacklist')))
48
        );
49
    }
50
51
    /**
52
     * Remove ip from all array lists.
53
     *
54
     * @param $ipAddress
55
     *
56
     * @return bool
57
     */
58 2
    private function removeFromArrayList($ipAddress)
59
    {
60 2
        return $this->removeFromArrayListType('whitelist', $ipAddress) ||
61 2
            $this->removeFromArrayListType('blacklist', $ipAddress);
62
    }
63
64
    /**
65
     * Remove the ip address from an array list.
66
     *
67
     * @param $type
68
     * @param $ipAddress
69
     *
70
     * @return bool
71
     */
72 2
    private function removeFromArrayListType($type, $ipAddress)
73
    {
74 2
        if (($key = array_search($ipAddress, $data = $this->config()->get($type))) !== false) {
75 2
            unset($data[$key]);
76
77 2
            $this->config()->set($type, $data);
78
79 2
            return true;
80
        }
81
82
        return false;
83
    }
84
85
    /**
86
     * Remove ip from database.
87
     *
88
     * @param \Illuminate\Database\Eloquent\Model $ipAddress
89
     *
90
     * @return bool
91
     */
92 2
    private function removeFromDatabaseList($ipAddress)
93
    {
94 2
        if ($ip = $this->find($ipAddress)) {
95 2
            $ip->delete();
0 ignored issues
show
Bug introduced by
The call to delete() misses a required argument $ipAddress.

This check looks for function calls that miss required arguments.

Loading history...
Bug introduced by
The method delete does only exist in Illuminate\Database\Eloq...all\Repositories\IpList, but not in Illuminate\Contracts\Cache\Repository.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
96
97 2
            $this->cache()->forget($ipAddress);
98
99 2
            return true;
100
        }
101
102
        return false;
103
    }
104
105
    /**
106
     * Transform a list of ips to a list of models.
107
     *
108
     * @param $ipList
109
     *
110
     * @return \Illuminate\Support\Collection
111
     */
112 24
    private function toModels($ipList)
113
    {
114 24
        $ips = [];
115
116 24
        foreach ($ipList as $ip) {
117 7
            $ips[] = $this->makeModel($ip);
118
        }
119
120 24
        return collect($ips);
121
    }
122
123
    /**
124
     * Make a model instance.
125
     *
126
     * @param $ip
127
     *
128
     * @return \Illuminate\Database\Eloquent\Model
129
     */
130 12
    private function makeModel($ip)
131
    {
132 12
        return $this->model->newInstance($ip);
133
    }
134
135
    /**
136
     * Read a file contents.
137
     *
138
     * @param $file
139
     *
140
     * @return array
141
     */
142 1
    private function readFile($file)
143
    {
144 1
        if ($this->fileSystem()->exists($file)) {
145 1
            $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
146
147 1
            return $this->makeArrayOfIps($lines);
148
        }
149
150 1
        return [];
151
    }
152
153
    /**
154
     * Format all ips in an array.
155
     *
156
     * @param $list
157
     *
158
     * @return array
159
     */
160
    private function formatIpArray($list)
161
    {
162 24
        return array_map(function ($ip) {
163 12
            return ['ip_address' => $ip];
164 24
        }, $this->makeArrayOfIps($list));
165
    }
166
167
    /**
168
     * Make a list of arrays from all sort of things.
169
     *
170
     * @param $list
171
     *
172
     * @return array
173
     */
174 24
    private function makeArrayOfIps($list)
175
    {
176 24
        $list = (array) $list ?: [];
177
178 24
        $ips = [];
179
180 24
        foreach ($list as $item) {
181 12
            $ips = array_merge($ips, $this->getIpsFromAnything($item));
182
        }
183
184 24
        return $ips;
185
    }
186
187
    /**
188
     * Get a list of ips from anything.
189
     *
190
     * @param $item
191
     *
192
     * @return array
193
     */
194 12
    private function getIpsFromAnything($item)
195
    {
196 12
        if (starts_with($item, 'country:')) {
197 1
            return [$item];
198
        }
199
200 11
        $item = $this->ipAddress()->hostToIp($item);
201
202 11
        if ($this->ipAddress()->ipV4Valid($item)) {
203 11
            return [$item];
204
        }
205
206 1
        return $this->readFile($item);
207
    }
208
209
    /**
210
     * Search for an ip in alist of ips.
211
     *
212
     * @param $ip
213
     * @param $ips
214
     *
215
     * @return null|\Illuminate\Database\Eloquent\Model
216
     */
217 23
    private function ipArraySearch($ip, $ips)
218
    {
219 23
        foreach ($ips as $key => $value) {
220
            if (
221 12
                (isset($value['ip_address']) && $value['ip_address'] == $ip) ||
222 9
                (strval($key) == $ip) ||
223 9
                ($value == $ip)
224
            ) {
225 8
                return $value;
226
            }
227
        }
228 23
    }
229
230
    /**
231
     * Get all IPs from database.
232
     *
233
     * @return \Illuminate\Support\Collection
234
     */
235 24
    private function getAllFromDatabase()
236
    {
237 24
        if ($this->config()->get('use_database')) {
238 10
            return $this->model->all();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->model->all(); of type Illuminate\Database\Eloq...base\Eloquent\Builder[] adds the type Illuminate\Database\Eloquent\Builder[] to the return on line 238 which is incompatible with the return type documented by PragmaRX\Firewall\Reposi...ist::getAllFromDatabase of type Illuminate\Support\Collection.
Loading history...
239
        } else {
240 14
            return collect([]);
241
        }
242
    }
243
244
    /**
245
     * Merge IP lists.
246
     *
247
     * @param $database_ips
248
     * @param $config_ips
249
     *
250
     * @return \Illuminate\Support\Collection
251
     */
252 24
    private function mergeLists($database_ips, $config_ips)
253
    {
254 24
        return collect($database_ips)
255 24
            ->merge(collect($config_ips));
256
    }
257
258
    /**
259
     * Check if an IP address is in a secondary (black/white) list.
260
     *
261
     * @param $ip_address
262
     *
263
     * @return bool|array
264
     */
265 23
    public function checkSecondaryLists($ip_address)
266
    {
267 23
        foreach ($this->all() as $range) {
268 12
            if ($this->ipAddress()->hostToIp($range) == $ip_address || $this->ipAddress()->validRange($ip_address, $range)) {
269 3
                return $range;
270
            }
271
        }
272
273 22
        return false;
274
    }
275
276
    /**
277
     * Add an IP to black or whitelist.
278
     *
279
     * @param $whitelist
280
     * @param $ip
281
     * @param bool $force
282
     *
283
     * @return bool
284
     */
285 23
    public function addToList($whitelist, $ip, $force = false)
286
    {
287 23
        $list = $whitelist
288 14
            ? 'whitelist'
289 23
            : 'blacklist';
290
291 23
        if (!$this->ipAddress()->isValid($ip)) {
292 3
            $this->messages()->addMessage(sprintf('%s is not a valid IP address', $ip));
293
294 3
            return false;
295
        }
296
297 20
        $listed = $this->whichList($ip);
298
299 20
        if ($listed == $list) {
300
            $this->messages()->addMessage(sprintf('%s is already %s', $ip, $list.'ed'));
301
302
            return false;
303
        } else {
304 20
            if (empty($listed) || $force) {
305 20
                if (!empty($listed)) {
306 2
                    $this->remove($ip);
307
                }
308
309 20
                $this->addToProperList($whitelist, $ip);
310
311 20
                $this->messages()->addMessage(sprintf('%s is now %s', $ip, $list.'ed'));
312
313 20
                return true;
314
            }
315
        }
316
317 2
        $this->messages()->addMessage(sprintf('%s is currently %sed', $ip, $listed));
318
319 2
        return false;
320
    }
321
322
    /**
323
     * Tell in which list (black/white) an IP address is.
324
     *
325
     * @param $ip_address
326
     *
327
     * @return null|string
328
     */
329 23
    public function whichList($ip_address)
330
    {
331 23
        if (!$ip_found = $this->find($ip_address)) {
332 23
            if (!$ip_found = $this->findByCountry($ip_address)) {
333 23
                if (!$ip_found = $this->checkSecondaryLists($ip_address)) {
334 22
                    return;
335
                }
336
            }
337
        }
338
339 16
        return !is_null($ip_found)
340 16
            ? ($ip_found['whitelisted'] ? 'whitelist' : 'blacklist')
341 16
            : null;
342
    }
343
344
    /**
345
     * Remove IP from all lists.
346
     *
347
     * @param $ip
348
     *
349
     * @return bool
350
     */
351 4
    public function remove($ip)
352
    {
353 4
        $listed = $this->whichList($ip);
354
355 4
        if (!empty($listed)) {
356 4
            $this->delete($ip);
357
358 4
            $this->messages()->addMessage(sprintf('%s removed from %s', $ip, $listed));
359
360 4
            return true;
361
        }
362
363
        $this->messages()->addMessage(sprintf('%s is not listed', $ip));
364
365
        return false;
366
    }
367
368
    /**
369
     * Add ip or range to array list.
370
     *
371
     * @param $whitelist
372
     * @param $ip
373
     *
374
     * @return array|mixed
375
     */
376 11
    private function addToArrayList($whitelist, $ip)
377
    {
378 11
        $data = $this->config()->get($list = $whitelist ? 'whitelist' : 'blacklist');
379
380 11
        $data[] = $ip;
381
382 11
        $this->config()->set($list, $data);
383
384 11
        return $data;
385
    }
386
387
    /**
388
     * Find an IP address in the data source.
389
     *
390
     * @param string $ip
391
     *
392
     * @return mixed
393
     */
394 23
    public function find($ip)
395
    {
396 23
        if ($this->cache()->has($ip)) {
397
            return $this->cache()->get($ip);
398
        }
399
400 23
        if ($model = $this->findIp($ip)) {
401 15
            $this->cache()->remember($model);
402
        }
403
404 23
        return $model;
405
    }
406
407
    /**
408
     * Find an IP address by country.
409
     *
410
     * @param $country
411
     *
412
     * @return mixed
413
     */
414 23
    public function findByCountry($country)
415
    {
416 23
        if ($this->config()->get('enable_country_search') && !is_null($country = $this->countries()->makeCountryFromString($country))) {
417 3
            return $this->find($country);
418
        }
419 20
    }
420
421
    /**
422
     * Find a Ip in the data source.
423
     *
424
     * @param string $ip
425
     *
426
     * @return void
427
     */
428 20
    public function addToProperList($whitelist, $ip)
429
    {
430 20
        $this->config()->get('use_database') ?
431 9
            $this->addToDatabaseList($whitelist, $ip) :
432 11
            $this->addToArrayList($whitelist, $ip);
433 20
    }
434
435
    /**
436
     * Delete ip address.
437
     *
438
     * @param $ipAddress
439
     *
440
     * @return bool|void
441
     */
442 4
    public function delete($ipAddress)
443
    {
444 4
        $this->config()->get('use_database') ?
445 2
            $this->removeFromDatabaseList($ipAddress) :
446 2
            $this->removeFromArrayList($ipAddress);
447 4
    }
448
449
    /**
450
     * Get all IP addresses.
451
     *
452
     * @return \Illuminate\Support\Collection
453
     */
454 24
    public function all()
455
    {
456 24
        $cacheTime = $this->config()->get('ip_list_cache_expire_time');
457
458 24
        if ($cacheTime > 0 && $list = $this->cache()->get(static::IP_ADDRESS_LIST_CACHE_NAME)) {
459
            return $list;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $list; (Illuminate\Contracts\Cache\Repository) is incompatible with the return type documented by PragmaRX\Firewall\Repositories\IpList::all of type Illuminate\Support\Collection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
460
        }
461
462 24
        $list = $this->mergeLists(
463 24
            $this->getAllFromDatabase(),
464 24
            $this->toModels($this->getNonDatabaseIps())
465
        );
466
467 24
        if ($cacheTime > 0) {
468
            $this->cache()->put(
469
                static::IP_ADDRESS_LIST_CACHE_NAME,
470
                $list,
471
                $cacheTime
472
            );
473
        }
474
475 24
        return $list;
476
    }
477
478
    /**
479
     * Find ip address in all lists.
480
     *
481
     * @param $ip
482
     *
483
     * @return \Illuminate\Database\Eloquent\Model|null|static
484
     */
485 23
    private function findIp($ip)
486
    {
487 23
        if ($model = $this->nonDatabaseFind($ip)) {
488 8
            return $model;
489
        }
490
491 23
        if ($this->config()->get('use_database')) {
492 10
            return $this->model->where('ip_address', $ip)->first();
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<PragmaRX\Firewall...aravel\Models\Firewall>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
493
        }
494 13
    }
495
496
    /**
497
     * Find ip in non database lists.
498
     *
499
     * @param $ip
500
     *
501
     * @return \Illuminate\Database\Eloquent\Model
502
     */
503 23
    private function nonDatabaseFind($ip)
504
    {
505 23
        $ips = $this->getNonDatabaseIps();
506
507 23
        if ($ip = $this->ipArraySearch($ip, $ips)) {
508 8
            return $this->makeModel($ip);
509
        }
510 23
    }
511
512
    /**
513
     * Add ip or range to database.
514
     *
515
     * @param $whitelist
516
     * @param $ip
517
     *
518
     * @return \Illuminate\Database\Eloquent\Model
519
     */
520 9
    private function addToDatabaseList($whitelist, $ip)
521
    {
522 9
        $this->model->unguard();
523
524 9
        $model = $this->model->create([
0 ignored issues
show
Bug introduced by
The method create() does not exist on PragmaRX\Firewall\Vendor\Laravel\Models\Firewall. Did you maybe mean created()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
525 9
            'ip_address'  => $ip,
526 9
            'whitelisted' => $whitelist,
527
        ]);
528
529 9
        $this->cache()->remember($model);
530
531 9
        return $model;
532
    }
533
}
534