Passed
Push — master ( e8f6e3...bf4671 )
by Antonio Carlos
21:13 queued 14:51
created

DataRepository::all()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 3
nop 0
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
1
<?php
2
3
namespace PragmaRX\Firewall\Repositories;
4
5
/*
6
 * Part of the Firewall package.
7
 *
8
 * NOTICE OF LICENSE
9
 *
10
 * Licensed under the 3-clause BSD License.
11
 *
12
 * This source file is subject to the 3-clause BSD License that is
13
 * bundled with this package in the LICENSE file.  It is also available at
14
 * the following URL: http://www.opensource.org/licenses/BSD-3-Clause
15
 *
16
 * @package    Firewall
17
 * @author     Antonio Carlos Ribeiro @ PragmaRX
18
 * @license    BSD License (3-clause)
19
 * @copyright  (c) 2013, PragmaRX
20
 * @link       http://pragmarx.com
21
 */
22
23
use ReflectionClass;
24
use PragmaRX\Support\Config;
25
use PragmaRX\Support\IpAddress;
26
use PragmaRX\Support\Filesystem;
27
use PragmaRX\Support\CacheManager;
28
use Illuminate\Database\Eloquent\Collection;
29
use PragmaRX\Firewall\Vendor\Laravel\Models\Firewall;
30
31
class DataRepository implements DataRepositoryInterface
32
{
33
    const CACHE_BASE_NAME = 'firewall.';
34
35
    const IP_ADDRESS_LIST_CACHE_NAME = 'firewall.ip_address_list';
36
37
    /**
38
     * @var object
39
     */
40
    public $firewall;
41
42
    /**
43
     * @var object
44
     */
45
    public $countries;
46
47
    /**
48
     * @var object
49
     */
50
    private $model;
51
52
    /**
53
     * @var Cache|CacheManager
54
     */
55
    private $cache;
56
57
    /**
58
     * @var Config
59
     */
60
    private $config;
61
62
    /**
63
     * @var Filesystem
64
     */
65
    private $fileSystem;
66
67
    /**
68
     * @var Message
69
     */
70
    private $messageRepository;
71
72
    /**
73
     * Create instance of DataRepository.
74
     *
75
     * @param Firewall $model
76
     * @param Config $config
77
     * @param CacheManager $cache
78
     * @param Filesystem $fileSystem
79
     * @param Countries $countries
80
     */
81
    public function __construct(
82
        Firewall $model,
83
        Config $config,
84
        CacheManager $cache,
85
        Filesystem $fileSystem,
86
        Countries $countries,
87
        Message $messageRepository
88
    ) {
89
        $this->model = $model;
0 ignored issues
show
Documentation Bug introduced by
It seems like $model of type PragmaRX\Firewall\Vendor\Laravel\Models\Firewall is incompatible with the declared type object of property $model.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
90
91
        $this->config = $config;
92
93
        $this->fileSystem = $fileSystem;
94
95
        $this->cache = $cache;
96
97
        $this->countries = $countries;
0 ignored issues
show
Documentation Bug introduced by
It seems like $countries of type PragmaRX\Firewall\Repositories\Countries is incompatible with the declared type object of property $countries.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
98
99
        $this->messageRepository = $messageRepository;
100
    }
101
102
    /**
103
     * Add ip or range to array list.
104
     *
105
     * @param $whitelist
106
     * @param $ip
107
     */
108
    private function addToArrayList($whitelist, $ip)
109
    {
110
        $data = $this->config->get($list = $whitelist ? 'whitelist' : 'blacklist');
111
112
        $data[] = $ip;
113
114
        $this->config->set($list, $data);
115
    }
116
117
    /**
118
     * Add ip or range to database.
119
     *
120
     * @param $whitelist
121
     * @param $ip
122
     *
123
     * @return mixed
124
     */
125
    private function addToDatabaseList($whitelist, $ip)
126
    {
127
        $this->model->unguard();
128
129
        $model = $this->model->create([
130
            'ip_address'  => $ip,
131
            'whitelisted' => $whitelist,
132
        ]);
133
134
        $this->cacheRemember($model);
135
136
        return $model;
137
    }
138
139
    /**
140
     * @param $whitelist
141
     * @param $ip
142
     *
143
     * @return object
144
     */
145
    private function createModel($whitelist, $ip)
0 ignored issues
show
Unused Code introduced by
The method createModel() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
146
    {
147
        $class = new ReflectionClass(get_class($this->model));
148
149
        $model = $class->newInstanceArgs([
150
            [
151
                'ip_address'  => $ip,
152
                'whitelisted' => $whitelist,
153
            ],
154
        ]);
155
156
        return $model;
157
    }
158
159
    /**
160
     * Find an IP address in the data source.
161
     *
162
     * @param string $ip
163
     *
164
     * @return object|null
165
     */
166
    public function find($ip)
167
    {
168
        if ($this->cacheHas($ip)) {
169
            return $this->cacheGet($ip);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->cacheGet($ip) returns the type Illuminate\Contracts\Cache\Repository which is incompatible with the documented return type object|null.
Loading history...
170
        }
171
172
        if ($model = $this->findIp($ip)) {
173
            $this->cacheRemember($model);
174
        }
175
176
        return $model;
177
    }
178
179
    /**
180
     * Find an IP address by country.
181
     *
182
     * @param $country
183
     *
184
     * @return bool|null|object
185
     */
186
    public function findByCountry($country)
187
    {
188
        if ($this->config->get('enable_country_search') && !is_null($country = $this->makeCountryFromString($country))) {
189
            return $this->find($country);
190
        }
191
192
        return false;
193
    }
194
195
    /**
196
     * Make a country info from a string.
197
     *
198
     * @param $country
199
     * @return bool|string
200
     */
201
    public function makeCountryFromString($country)
202
    {
203
        if ($ips = IpAddress::isCidr($country)) {
0 ignored issues
show
Bug introduced by
The method isCidr() does not exist on PragmaRX\Support\IpAddress. ( Ignorable by Annotation )

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

203
        if ($ips = IpAddress::/** @scrutinizer ignore-call */ isCidr($country)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
204
            $country = $ips[0];
205
        }
206
207
        if ($this->validCountry($country)) {
208
            return $country;
209
        }
210
211
        if ($this->ipIsValid($country)) {
212
            $country = $this->countries->getCountryFromIp($country);
213
        }
214
215
        return "country:{$country}";
216
    }
217
218
    /**
219
     * Get country code from an IP address.
220
     *
221
     * @param $ip_address
222
     *
223
     * @return bool|string
224
     */
225
    public function getCountryFromIp($ip_address)
226
    {
227
        return $this->countries->getCountryFromIp($ip_address);
228
    }
229
230
    /**
231
     * Check if IP address is valid.
232
     *
233
     * @param $ip
234
     *
235
     * @return bool
236
     */
237
    public function ipIsValid($ip)
238
    {
239
        $ip = $this->hostToIp($ip);
240
241
        try {
242
            return IpAddress::ipV4Valid($ip) || $this->validCountry($ip);
243
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type PragmaRX\Firewall\Repositories\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
244
            return false;
245
        }
246
    }
247
248
    /**
249
     * Find a Ip in the data source.
250
     *
251
     * @param string $ip
252
     *
253
     * @return object|null
254
     */
255
    public function addToProperList($whitelist, $ip)
256
    {
257
        if ($this->config->get('use_database')) {
258
            return $this->addToDatabaseList($whitelist, $ip);
259
        }
260
261
        return $this->addToArrayList($whitelist, $ip);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->addToArrayList($whitelist, $ip) targeting PragmaRX\Firewall\Reposi...itory::addToArrayList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
262
    }
263
264
    public function delete($ipAddress)
265
    {
266
        if ($this->config->get('use_database')) {
267
            return $this->removeFromDatabaseList($ipAddress);
268
        }
269
270
        return $this->removeFromArrayList($ipAddress);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->removeFromArrayList($ipAddress) targeting PragmaRX\Firewall\Reposi...::removeFromArrayList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
271
    }
272
273
    public function cacheKey($ip)
274
    {
275
        return static::CACHE_BASE_NAME."ip_address.$ip";
276
    }
277
278
    public function cacheHas($ip)
279
    {
280
        if ($this->config->get('cache_expire_time')) {
281
            return $this->cache->has($this->cacheKey($ip));
282
        }
283
284
        return false;
285
    }
286
287
    public function cacheGet($ip)
288
    {
289
        return $this->cache->get($this->cacheKey($ip));
290
    }
291
292
    public function cacheForget($ip)
293
    {
294
        $this->cache->forget($this->cacheKey($ip));
295
    }
296
297
    public function cacheRemember($model)
298
    {
299
        if ($timeout = $this->config->get('cache_expire_time')) {
300
            $this->cache->put($this->cacheKey($model->ip_address), $model, $timeout);
301
        }
302
    }
303
304
    public function all()
305
    {
306
        $cacheTime = $this->config->get('ip_list_cache_expire_time');
307
308
        if ($cacheTime && $list = $this->cache->get(static::IP_ADDRESS_LIST_CACHE_NAME)) {
309
            return $list;
310
        }
311
312
        $list = $this->mergeLists(
313
            $this->getAllFromDatabase(),
314
            $this->toModels($this->getNonDatabaseIps())
315
        );
316
317
        if ($cacheTime) {
318
            $this->cache->put(
319
                static::IP_ADDRESS_LIST_CACHE_NAME,
320
                $list,
321
                $this->config->get('ip_list_cache_expire_time')
322
            );
323
        }
324
325
        return $list;
326
    }
327
328
    /**
329
     * Get all IP addresses by country.
330
     *
331
     * @param $country
332
     * @return static
333
     */
334
    public function allByCountry($country)
335
    {
336
        $country = $this->makeCountryFromString($country);
337
338
        return $this->all()->filter(function($item) use ($country) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->all()->fil...ion(...) { /* ... */ }) returns the type Illuminate\Support\Colle...tracts\Cache\Repository which is incompatible with the documented return type PragmaRX\Firewall\Repositories\DataRepository.
Loading history...
339
            return $item['ip_address'] == $country ||
340
                $this->makeCountryFromString($this->getCountryFromIp($item['ip_address'])) == $country;
341
        });
342
    }
343
344
    /**
345
     * Clear all items from all lists.
346
     *
347
     * @return int
348
     */
349
    public function clear()
350
    {
351
        /**
352
         * Deletes one by one to also remove them from cache.
353
         */
354
        $deleted = 0;
355
356
        foreach ($this->all() as $ip) {
357
            if ($this->delete($ip['ip_address'])) {
358
                $deleted++;
359
            }
360
        }
361
362
        return $deleted;
363
    }
364
365
    private function findIp($ip)
366
    {
367
        if ($model = $this->nonDatabaseFind($ip)) {
368
            return $model;
369
        }
370
371
        if ($this->config->get('use_database')) {
372
            return $this->model->where('ip_address', $ip)->first();
373
        }
374
    }
375
376
    private function nonDatabaseFind($ip)
377
    {
378
        $ips = $this->getNonDatabaseIps();
379
380
        if ($ip = $this->ipArraySearch($ip, $ips)) {
381
            return $this->makeModel($ip);
382
        }
383
    }
384
385
    private function getNonDatabaseIps()
386
    {
387
        return array_merge_recursive(
388
            array_map(function ($ip) {
389
                $ip['whitelisted'] = true;
390
391
                return $ip;
392
            }, $this->formatIpArray($this->config->get('whitelist'))),
393
394
            array_map(function ($ip) {
395
                $ip['whitelisted'] = false;
396
397
                return $ip;
398
            }, $this->formatIpArray($this->config->get('blacklist')))
399
        );
400
    }
401
402
    private function removeFromArrayList($ipAddress)
403
    {
404
        $this->removeFromArrayListType('whitelist', $ipAddress);
405
406
        $this->removeFromArrayListType('blacklist', $ipAddress);
407
    }
408
409
    private function removeFromArrayListType($type, $ipAddress)
410
    {
411
        $data = $this->config->get($type);
412
413
        if (($key = array_search($ipAddress, $data)) !== false) {
414
            unset($data[$key]);
415
        }
416
417
        $this->config->set($type, $data);
418
    }
419
420
    private function removeFromDatabaseList($ipAddress)
421
    {
422
        if ($ip = $this->find($ipAddress)) {
423
            $ip->delete();
424
425
            $this->cacheForget($ipAddress);
426
427
            return true;
428
        }
429
430
        return false;
431
    }
432
433
    /**
434
     * Transform a list of ips to a list of models.
435
     *
436
     * @param $ipList
437
     * @return array
438
     */
439
    private function toModels($ipList)
440
    {
441
        $ips = [];
442
443
        foreach ($ipList as $ip) {
444
            $ips[] = $this->makeModel($ip);
445
        }
446
447
        return $ips;
448
    }
449
450
    /**
451
     * @param $ip
452
     *
453
     * @return mixed
454
     */
455
    private function makeModel($ip)
456
    {
457
        return $this->model->newInstance($ip);
458
    }
459
460
    private function readFile($file)
461
    {
462
        if ($this->fileSystem->exists($file)) {
463
            $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
464
465
            return $this->makeArrayOfIps($lines);
466
        }
467
468
        return [];
469
    }
470
471
    private function toCollection($array)
472
    {
473
        return new Collection($array);
474
    }
475
476
    private function formatIpArray($list)
477
    {
478
        return array_map(function ($ip) {
479
            return ['ip_address' => $ip];
480
        }, $this->makeArrayOfIps($list));
481
    }
482
483
    private function makeArrayOfIps($list)
484
    {
485
        $list = $list ?: [];
486
487
        $ips = [];
488
489
        foreach ($list as $item) {
490
            $ips = array_merge($ips, $this->getIpsFromAnything($item));
491
        }
492
493
        return $ips;
494
    }
495
496
    private function getIpsFromAnything($item)
497
    {
498
        if (starts_with($item, 'country:')) {
499
            return [$item];
500
        }
501
502
        $item = $this->hostToIp($item);
503
504
        if (IpAddress::ipV4Valid($item)) {
505
            return [$item];
506
        }
507
508
        return $this->readFile($item);
509
    }
510
511
    private function ipArraySearch($ip, $ips)
512
    {
513
        foreach ($ips as $key => $value) {
514
            if (
515
                (isset($value['ip_address']) && $value['ip_address'] == $ip) ||
516
                (strval($key) == $ip) ||
517
                ($value == $ip)
518
            ) {
519
                return $value;
520
            }
521
        }
522
523
        return false;
524
    }
525
526
    /**
527
     * Get all IPs from database.
528
     *
529
     * @return array
530
     */
531
    private function getAllFromDatabase()
532
    {
533
        if ($this->config->get('use_database')) {
534
            return $this->model->all();
535
        } else {
536
            return $this->toCollection([]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->toCollection(array()) returns the type Illuminate\Database\Eloquent\Collection which is incompatible with the documented return type array.
Loading history...
537
        }
538
    }
539
540
    private function mergeLists($database_ips, $config_ips)
541
    {
542
        return collect($database_ips)
543
            ->merge(collect($config_ips));
544
    }
545
546
    public function hostToIp($ip)
547
    {
548
        if (is_string($ip) && starts_with($ip, $string = 'host:')) {
549
            return gethostbyname(str_replace($string, '', $ip));
550
        }
551
552
        return $ip;
553
    }
554
555
    /**
556
     * Check if an IP address is in a secondary (black/white) list.
557
     *
558
     * @param $ip_address
559
     *
560
     * @return bool
561
     */
562
    public function checkSecondaryLists($ip_address)
563
    {
564
        foreach ($this->all() as $range) {
565
            if ($this->hostToIp($range) == $ip_address || $this->ipIsInValidRange($ip_address, $range)) {
566
                return $range;
567
            }
568
        }
569
570
        return false;
571
    }
572
573
    /**
574
     * Check if IP is in a valid range.
575
     *
576
     * @param $ip_address
577
     * @param $range
578
     *
579
     * @return bool
580
     */
581
    private function ipIsInValidRange($ip_address, $range)
582
    {
583
        return $this->config->get('enable_range_search') &&
584
            IpAddress::ipV4Valid($range->ip_address) &&
585
            ipv4_in_range($ip_address, $range->ip_address);
0 ignored issues
show
Bug introduced by
The function ipv4_in_range was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

585
            /** @scrutinizer ignore-call */ ipv4_in_range($ip_address, $range->ip_address);
Loading history...
586
    }
587
588
    /**
589
     * Check if a string is a valid country info.
590
     *
591
     * @param $country
592
     *
593
     * @return bool
594
     */
595
    public function validCountry($country)
596
    {
597
        $country = strtolower($country);
598
599
        if ($this->config->get('enable_country_search')) {
600
            if (starts_with($country, 'country:') && $this->countries->isValid($country)) {
601
                return true;
602
            }
603
        }
604
605
        return false;
606
    }
607
608
    /**
609
     * Add an IP to black or whitelist.
610
     *
611
     * @param $whitelist
612
     * @param $ip
613
     * @param $force
614
     *
615
     * @return bool
616
     */
617
    public function addToList($whitelist, $ip, $force)
618
    {
619
        $list = $whitelist
620
            ? 'whitelist'
621
            : 'blacklist';
622
623
        if (!$this->ipIsValid($ip)) {
624
            $this->messageRepository->addMessage(sprintf('%s is not a valid IP address', $ip));
625
626
            return false;
627
        }
628
629
        $listed = $this->whichList($ip);
630
631
        if ($listed == $list) {
632
            $this->messageRepository->addMessage(sprintf('%s is already %s', $ip, $list.'ed'));
633
634
            return false;
635
        } else {
636
            if (!$listed || $force) {
637
                if ($listed) {
638
                    $this->remove($ip);
639
                }
640
641
                $this->addToProperList($whitelist, $ip);
642
643
                $this->messageRepository->addMessage(sprintf('%s is now %s', $ip, $list.'ed'));
644
645
                return true;
646
            }
647
        }
648
649
        $this->messageRepository->addMessage(sprintf('%s is currently %sed', $ip, $listed));
650
651
        return false;
652
    }
653
654
    /**
655
     * Tell in which list (black/white) an IP address is.
656
     *
657
     * @param $ip_address
658
     *
659
     * @return bool|string
660
     */
661
    public function whichList($ip_address)
662
    {
663
        $ip_address = $ip_address
664
            ?: $this->getIp();
0 ignored issues
show
Bug introduced by
The method getIp() does not exist on PragmaRX\Firewall\Repositories\DataRepository. Did you maybe mean getGeoIp()? ( Ignorable by Annotation )

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

664
            ?: $this->/** @scrutinizer ignore-call */ getIp();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
665
666
        if (!$ip_found = $this->find($ip_address)) {
667
            if (!$ip_found = $this->findByCountry($ip_address)) {
668
                if (!$ip_found = $this->checkSecondaryLists($ip_address)) {
669
                    return false;
670
                }
671
            }
672
        }
673
674
        if ($ip_found) {
675
            return $ip_found['whitelisted']
676
                ? 'whitelist'
677
                : 'blacklist';
678
        }
679
680
        return false;
681
    }
682
683
    /**
684
     * Remove IP from all lists.
685
     *
686
     * @param $ip
687
     *
688
     * @return bool
689
     */
690
    public function remove($ip)
691
    {
692
        $listed = $this->whichList($ip);
693
694
        if ($listed) {
695
            $this->delete($ip);
696
697
            $this->messageRepository->addMessage(sprintf('%s removed from %s', $ip, $listed));
0 ignored issues
show
Bug introduced by
It seems like $listed can also be of type true; however, parameter $args of sprintf() does only seem to accept string, 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

697
            $this->messageRepository->addMessage(sprintf('%s removed from %s', $ip, /** @scrutinizer ignore-type */ $listed));
Loading history...
698
699
            return true;
700
        }
701
702
        $this->messageRepository->addMessage(sprintf('%s is not listed', $ip));
703
704
        return false;
705
    }
706
707
    /**
708
     * Get the GeoIP instance.
709
     *
710
     * @return object
711
     */
712
    public function getGeoIp()
713
    {
714
        return $this->countries->getGeoIp();
715
    }
716
}
717