Test Failed
Push — master ( 4b7b93...a5f8f7 )
by Antonio Carlos
03:28
created

IpList::removeFromDatabaseList()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 5
nc 1
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 54
    public function __construct(FirewallModel $model)
25
    {
26 54
        $this->model = $model;
27 54
    }
28
29
    /**
30
     * Get a list of non database ip addresses.
31
     *
32
     * @return array
33
     */
34 50
    private function getNonDatabaseIps()
35
    {
36 50
        return array_merge_recursive(
37 50
            array_map(function ($ip) {
38 7
                $ip['whitelisted'] = true;
39
40 7
                return $ip;
41 50
            }, $this->formatIpArray($this->config()->get('whitelist'))),
42
43 50
            array_map(function ($ip) {
44 17
                $ip['whitelisted'] = false;
45
46 17
                return $ip;
47 50
            }, $this->formatIpArray($this->config()->get('blacklist')))
48
        );
49
    }
50
51
    /**
52
     * Remove ip from all array lists.
53
     *
54
     * @param $ip
55
     *
56
     * @return bool
57
     */
58 4
    private function removeFromArrayList($ip)
59
    {
60 4
        return $this->removeFromArrayListType('whitelist', $ip) ||
61 4
            $this->removeFromArrayListType('blacklist', $ip);
62
    }
63
64
    /**
65
     * Remove the ip address from an array list.
66
     *
67
     * @param $type
68
     * @param $ip
69
     *
70
     * @return bool
71
     */
72 4
    private function removeFromArrayListType($type, $ip)
73
    {
74 4
        if (($key = array_search($ip, $data = $this->config()->get($type))) !== false) {
75 4
            unset($data[$key]);
76
77 4
            $this->cache()->forget($ip);
78
79 4
            $this->config()->set($type, $data);
80
81 4
            $this->messages()->addMessage(sprintf('%s removed from %s', $ip, $type));
82
83 4
            return true;
84
        }
85
86 2
        return false;
87
    }
88
89
    /**
90
     * Remove ip from database.
91
     *
92
     * @param \Illuminate\Database\Eloquent\Model $ip
93
     *
94
     * @return bool
95
     */
96 4
    private function removeFromDatabaseList($ip)
97
    {
98 4
        $ip = $this->find($ip);
99
100 4
        $ip->delete();
0 ignored issues
show
Bug introduced by
The call to delete() misses a required argument $ip.

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...
101
102 4
        $this->cache()->forget($ip->ip_address);
103
104 4
        $this->messages()->addMessage(sprintf('%s removed from %s', $ip, $ip->whitelisted ? 'whitelist' : 'blacklist'));
105 4
    }
106
107
    /**
108
     * Transform a list of ips to a list of models.
109
     *
110
     * @param $ipList
111
     *
112
     * @return \Illuminate\Support\Collection
113
     */
114 50
    private function toModels($ipList)
115
    {
116 50
        $ips = [];
117
118 50
        foreach ($ipList as $ip) {
119 8
            $ips[] = $this->makeModel($ip);
120
        }
121
122 50
        return collect($ips);
123
    }
124
125
    /**
126
     * Make a model instance.
127
     *
128
     * @param $ip
129
     *
130
     * @return \Illuminate\Database\Eloquent\Model
131
     */
132 22
    private function makeModel($ip)
133
    {
134 22
        return $this->model->newInstance($ip);
135
    }
136
137
    /**
138
     * Read a file contents.
139
     *
140
     * @param $file
141
     *
142
     * @return array
143
     */
144 1
    private function readFile($file)
145
    {
146 1
        if ($this->fileSystem()->exists($file)) {
147 1
            $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
148
149 1
            return $this->makeArrayOfIps($lines);
150
        }
151
152 1
        return [];
153
    }
154
155
    /**
156
     * Format all ips in an array.
157
     *
158
     * @param $list
159
     *
160
     * @return array
161
     */
162
    private function formatIpArray($list)
163
    {
164 50
        return array_map(function ($ip) {
165 22
            return ['ip_address' => $ip];
166 50
        }, $this->makeArrayOfIps($list));
167
    }
168
169
    /**
170
     * Make a list of arrays from all sort of things.
171
     *
172
     * @param $list
173
     *
174
     * @return array
175
     */
176 50
    private function makeArrayOfIps($list)
177
    {
178 50
        $list = (array) $list ?: [];
179
180 50
        $ips = [];
181
182 50
        foreach ($list as $item) {
183 22
            $ips = array_merge($ips, $this->getIpsFromAnything($item));
184
        }
185
186 50
        return $ips;
187
    }
188
189
    /**
190
     * Get a list of ips from anything.
191
     *
192
     * @param $item
193
     *
194
     * @return array
195
     */
196 22
    private function getIpsFromAnything($item)
197
    {
198 22
        if (starts_with($item, 'country:')) {
199 2
            return [$item];
200
        }
201
202 20
        $item = $this->ipAddress()->hostToIp($item);
203
204 20
        if ($this->ipAddress()->ipV4Valid($item)) {
205 20
            return [$item];
206
        }
207
208 1
        return $this->readFile($item);
209
    }
210
211
    /**
212
     * Search for an ip in alist of ips.
213
     *
214
     * @param $ip
215
     * @param $ips
216
     *
217
     * @return null|\Illuminate\Database\Eloquent\Model
218
     */
219 49
    private function ipArraySearch($ip, $ips)
220
    {
221 49
        foreach ($ips as $key => $value) {
222
            if (
223 22
                (isset($value['ip_address']) && $value['ip_address'] == $ip) ||
224 10
                (strval($key) == $ip) ||
225 10
                ($value == $ip)
226
            ) {
227 18
                return $value;
228
            }
229
        }
230 49
    }
231
232
    /**
233
     * Get all IPs from database.
234
     *
235
     * @return \Illuminate\Support\Collection
236
     */
237 50
    private function getAllFromDatabase()
238
    {
239 50
        if ($this->config()->get('use_database')) {
240 17
            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 240 which is incompatible with the return type documented by PragmaRX\Firewall\Reposi...ist::getAllFromDatabase of type Illuminate\Support\Collection.
Loading history...
241
        } else {
242 33
            return collect([]);
243
        }
244
    }
245
246
    /**
247
     * Merge IP lists.
248
     *
249
     * @param $database_ips
250
     * @param $config_ips
251
     *
252
     * @return \Illuminate\Support\Collection
253
     */
254 50
    private function mergeLists($database_ips, $config_ips)
255
    {
256 50
        return collect($database_ips)
257 50
            ->merge(collect($config_ips));
258
    }
259
260
    /**
261
     * Check if an IP address is in a secondary (black/white) list.
262
     *
263
     * @param $ip_address
264
     *
265
     * @return bool|array
266
     */
267 49
    public function checkSecondaryLists($ip_address)
268
    {
269 49
        foreach ($this->all() as $range) {
270 14
            if ($this->ipAddress()->hostToIp($range->ip_address) == $ip_address || $this->ipAddress()->validRange($ip_address, $range)) {
271 4
                return $range;
272
            }
273
        }
274
275 48
        return false;
276
    }
277
278
    /**
279
     * Add an IP to black or whitelist.
280
     *
281
     * @param $whitelist
282
     * @param $ip
283
     * @param bool $force
284
     *
285
     * @return bool
286
     */
287 41
    public function addToList($whitelist, $ip, $force = false)
288
    {
289 41
        $list = $whitelist
290 17
            ? 'whitelist'
291 41
            : 'blacklist';
292
293 41
        if (!$this->ipAddress()->isValid($ip)) {
294 5
            return false;
295
        }
296
297 36
        $listed = $this->whichList($ip);
298
299 36
        if ($listed == $list) {
300 2
            $this->messages()->addMessage(sprintf('%s is already %s', $ip, $list.'ed'));
301
302 2
            return false;
303
        } else {
304 36
            if (empty($listed) || $force) {
305 36
                if (!empty($listed)) {
306 2
                    $this->remove($ip);
307
                }
308
309 36
                $this->addToProperList($whitelist, $ip);
310
311 36
                $this->messages()->addMessage(sprintf('%s is now %s', $ip, $list.'ed'));
312
313 36
                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 49
    public function whichList($ip_address)
330
    {
331 49
        if (!$ip_found = $this->find($ip_address)) {
332 49
            if (!$ip_found = $this->findByCountry($ip_address)) {
333 49
                if (!$ip_found = $this->checkSecondaryLists($ip_address)) {
334 48
                    return;
335
                }
336
            }
337
        }
338
339 31
        return !is_null($ip_found)
340 31
            ? ($ip_found['whitelisted'] ? 'whitelist' : 'blacklist')
341 31
            : null;
342
    }
343
344
    /**
345
     * Remove IP from all lists.
346
     *
347
     * @param $ip
348
     *
349
     * @return bool
350
     */
351 8
    public function remove($ip)
352
    {
353 8
        $listed = $this->whichList($ip);
354
355 8
        if (!empty($listed)) {
356 8
            $this->delete($ip);
357
358 8
            return true;
359
        }
360
361 2
        $this->messages()->addMessage(sprintf('%s is not listed', $ip));
362
363 2
        return false;
364
    }
365
366
    /**
367
     * Add ip or range to array list.
368
     *
369
     * @param $whitelist
370
     * @param $ip
371
     *
372
     * @return array|mixed
373
     */
374 21
    private function addToArrayList($whitelist, $ip)
375
    {
376 21
        $data = $this->config()->get($list = $whitelist ? 'whitelist' : 'blacklist');
377
378 21
        $data[] = $ip;
379
380 21
        $this->config()->set($list, $data);
381
382 21
        return $data;
383
    }
384
385
    /**
386
     * Find an IP address in the data source.
387
     *
388
     * @param string $ip
389
     *
390
     * @return mixed
391
     */
392 49
    public function find($ip)
393
    {
394 49
        if ($this->cache()->has($ip)) {
395 18
            return $this->cache()->get($ip);
396
        }
397
398 49
        if ($model = $this->findIp($ip)) {
399 18
            $this->cache()->remember($model);
400
        }
401
402 49
        return $model;
403
    }
404
405
    /**
406
     * Find an IP address by country.
407
     *
408
     * @param $country
409
     *
410
     * @return mixed
411
     */
412 49
    public function findByCountry($country)
413
    {
414 49
        if ($this->config()->get('enable_country_search') && !is_null($country = $this->countries()->makeCountryFromString($country))) {
415 23
            return $this->find($country);
416
        }
417 26
    }
418
419
    /**
420
     * Find a Ip in the data source.
421
     *
422
     * @param string $ip
423
     *
424
     * @return void
425
     */
426 36
    public function addToProperList($whitelist, $ip)
427
    {
428 36
        $this->config()->get('use_database') ?
429 15
            $this->addToDatabaseList($whitelist, $ip) :
430 21
            $this->addToArrayList($whitelist, $ip);
431 36
    }
432
433
    /**
434
     * Delete ip address.
435
     *
436
     * @param $ip
437
     *
438
     * @return bool|void
439
     */
440 8
    public function delete($ip)
441
    {
442 8
        $this->config()->get('use_database') ?
443 4
            $this->removeFromDatabaseList($ip) :
444 4
            $this->removeFromArrayList($ip);
445 8
    }
446
447
    /**
448
     * Get all IP addresses.
449
     *
450
     * @return \Illuminate\Support\Collection
451
     */
452 50
    public function all()
453
    {
454 50
        $cacheTime = $this->config()->get('ip_list_cache_expire_time');
455
456 50
        if ($cacheTime > 0 && $list = $this->cache()->get(static::IP_ADDRESS_LIST_CACHE_NAME)) {
457 1
            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...
458
        }
459
460 50
        $list = $this->mergeLists(
461 50
            $this->getAllFromDatabase(),
462 50
            $this->toModels($this->getNonDatabaseIps())
463
        );
464
465 50
        if ($cacheTime > 0) {
466 1
            $this->cache()->put(static::IP_ADDRESS_LIST_CACHE_NAME, $list, $cacheTime);
467
        }
468
469 50
        return $list;
470
    }
471
472
    /**
473
     * Find ip address in all lists.
474
     *
475
     * @param $ip
476
     *
477
     * @return \Illuminate\Database\Eloquent\Model|null|static
478
     */
479 49
    private function findIp($ip)
480
    {
481 49
        if ($model = $this->nonDatabaseFind($ip)) {
482 18
            return $model;
483
        }
484
485 49
        if ($this->config()->get('use_database')) {
486 16
            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...
487
        }
488 33
    }
489
490
    /**
491
     * Find ip in non database lists.
492
     *
493
     * @param $ip
494
     *
495
     * @return \Illuminate\Database\Eloquent\Model
496
     */
497 49
    private function nonDatabaseFind($ip)
498
    {
499 49
        $ips = $this->getNonDatabaseIps();
500
501 49
        if ($ip = $this->ipArraySearch($ip, $ips)) {
502 18
            return $this->makeModel($ip);
503
        }
504 49
    }
505
506
    /**
507
     * Add ip or range to database.
508
     *
509
     * @param $whitelist
510
     * @param $ip
511
     *
512
     * @return \Illuminate\Database\Eloquent\Model
513
     */
514 15
    private function addToDatabaseList($whitelist, $ip)
515
    {
516 15
        $this->model->unguard();
517
518 15
        $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...
519 15
            'ip_address'  => $ip,
520 15
            'whitelisted' => $whitelist,
521
        ]);
522
523 15
        $this->cache()->remember($model);
524
525 15
        return $model;
526
    }
527
}
528