Skip to content

Commit 666e403

Browse files
test(geolocation): Add unit test for geolocation
1 parent ba85708 commit 666e403

File tree

9 files changed

+219
-24
lines changed

9 files changed

+219
-24
lines changed

.github/workflows/markdown.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
on:
2+
push:
3+
workflow_dispatch:
4+
5+
name: Markdown files test and update
6+
jobs:
7+
markdown-test-and-update:
8+
name: Markdown files test and update
9+
runs-on: ubuntu-latest
10+
steps:
11+
12+
- name: Clone sources
13+
uses: actions/checkout@v2
14+
with:
15+
path: extension
16+
17+
- name: Launch localhost server
18+
run: |
19+
sudo npm install --global http-server
20+
http-server ./extension &
21+
22+
- name: Set up Ruby 2.6
23+
uses: ruby/setup-ruby@v1
24+
with:
25+
ruby-version: 2.6
26+
27+
- name: Check links in Markdown files
28+
run: |
29+
gem install awesome_bot
30+
cd extension
31+
awesome_bot --files README.md,docs/ddev.md --allow-dupe --allow 401 --skip-save-results --white-list ddev.site,crowdsec --base-url http://localhost:8080/
32+
33+
- name: Generate table of contents
34+
uses: technote-space/toc-generator@v4
35+
with:
36+
MAX_HEADER_LEVEL: 5
37+
COMMIT_NAME: CrowdSec Dev Bot
38+
TARGET_PATHS: 'docs/ddev.md'
39+
CHECK_ONLY_DEFAULT_BRANCH: true

.github/workflows/test-suite.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,24 @@ jobs:
6767
run: |
6868
ddev composer update --working-dir ./my-own-modules/crowdsec-php-lib
6969
70+
- name: Prepare PHP UNIT tests
71+
run: |
72+
ddev create-watcher PhpUnitTestMachine PhpUnitTestMachinePassword
73+
ddev maxmind-download DEFAULT GeoLite2-City /var/www/html/my-own-modules/crowdsec-php-lib/tests
74+
ddev maxmind-download DEFAULT GeoLite2-Country /var/www/html/my-own-modules/crowdsec-php-lib/tests
75+
cd my-own-modules/crowdsec-php-lib/tests
76+
sha256sum -c GeoLite2-Country.tar.gz.sha256.txt
77+
sha256sum -c GeoLite2-City.tar.gz.sha256.txt
78+
tar -xf GeoLite2-Country.tar.gz
79+
tar -xf GeoLite2-City.tar.gz
80+
rm GeoLite2-Country.tar.gz GeoLite2-Country.tar.gz.sha256.txt GeoLite2-City.tar.gz GeoLite2-City.tar.gz.sha256.txt
81+
7082
7183
- name: Run PHP UNIT tests
84+
# ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 MEMCACHED_DSN=memcached://memcached:11211 REDIS_DSN=redis://redis:6379 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/IpVerificationTest.php
7285
run: |
73-
ddev create-watcher PhpUnitTestMachine PhpUnitTestMachinePassword
74-
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 MEMCACHED_DSN=memcached://memcached:11211 REDIS_DSN=redis://redis:6379 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/IpVerificationTest.php
86+
ddev exec -s crowdsec crowdsec -version
87+
ddev exec BOUNCER_KEY=${{ env.BOUNCER_KEY }} LAPI_URL=http://crowdsec:8080 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/GeolocationTest.php
7588
7689
- name: Prepare END TO END tests
7790
run: |

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ super-linter.log
1818
# Auto prepend demo
1919
examples/auto-prepend/settings.php
2020
examples/auto-prepend/.logs
21-
examples/auto-prepend/.cache
21+
examples/auto-prepend/.cache
22+
23+
# MaxMind databases
24+
*.mmdb

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
<p align="center">
2-
<img src="https://raw.githubusercontent.com/crowdsecurity/crowdsec-docs/main/docs/assets/images/crowdsec_logo.png" alt="CrowdSec" title="CrowdSec" width="200" height="120"/>
3-
</p>
1+
![CrowdSec Logo](docs/images/logo_crowdsec.png)
42

53
# PHP Bouncer Library
64

docs/ddev.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ Finally, run
134134
ddev exec BOUNCER_KEY=your-bouncer-key LAPI_URL=http://crowdsec:8080 MEMCACHED_DSN=memcached://memcached:11211 REDIS_DSN=redis://redis:6379 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/IpVerificationTest.php
135135
```
136136

137+
For geolocation Unit Test, you should first put 2 free MaxMind databases in the `tests` folder : `GeoLite2-City.mmdb`
138+
and`GeoLite2-Country.mmdb`. You can download these databases by creating a maxmind account and browse to [the download page](https://www.maxmind.com/en/accounts/current/geoip/downloads).
139+
140+
141+
Then, you can run:
142+
143+
```
144+
ddev exec BOUNCER_KEY=your-bouncer-key LAPI_URL=http://crowdsec:8080 /usr/bin/php ./my-own-modules/crowdsec-php-lib/vendor/bin/phpunit --testdox --colors --exclude-group ignore ./my-own-modules/crowdsec-php-lib/tests/GeolocationTest.php
145+
146+
```
147+
148+
137149
### Use a `check-ip` php script for test
138150

139151

docs/images/logo_crowdsec.png

7.55 KB
Loading

tests/GeolocationTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
require __DIR__ . '/TestHelpers.php';
5+
require __DIR__ . '/WatcherClient.php';
6+
7+
use CrowdSecBouncer\ApiCache;
8+
use CrowdSecBouncer\ApiClient;
9+
use CrowdSecBouncer\Bouncer;
10+
use PHPUnit\Framework\MockObject\MockObject;
11+
use PHPUnit\Framework\TestCase;
12+
use Psr\Log\LoggerInterface;
13+
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
14+
15+
final class GeolocationTest extends TestCase
16+
{
17+
/** @var WatcherClient */
18+
private $watcherClient;
19+
20+
/** @var LoggerInterface */
21+
private $logger;
22+
23+
protected function setUp(): void
24+
{
25+
$this->logger = TestHelpers::createLogger();
26+
27+
$this->watcherClient = new WatcherClient($this->logger);
28+
$this->watcherClient->configure();
29+
}
30+
31+
public function maxmindConfigProvider(): array
32+
{
33+
return TestHelpers::maxmindConfigProvider();
34+
}
35+
36+
/**
37+
* @group integration
38+
* @covers \Bouncer
39+
* @dataProvider maxmindConfigProvider
40+
* @group ignore_
41+
* @throws \Symfony\Component\Cache\Exception\CacheException
42+
*/
43+
public function testCanVerifyIpAndCountryWithMaxmindInLiveMode(array $maxmindConfig):
44+
void
45+
{
46+
// Check if MaxMind database exist
47+
if (!file_exists($maxmindConfig['database_path'])) {
48+
$this->fail('There must be a MaxMind Database here: '.$maxmindConfig['database_path']);
49+
$this->stopFlag = true; // Stop further processing if this occurs
50+
}
51+
// Init context
52+
$cacheAdapter = new PhpFilesAdapter('php_array_adapter_backup_cache', 0,
53+
TestHelpers::PHP_FILES_CACHE_ADAPTER_DIR);
54+
$this->watcherClient->setInitialState();
55+
$cacheAdapter->clear();
56+
57+
$geolocationConfig = [
58+
'save_in_session' => false,
59+
'enabled' => true,
60+
'type' => 'maxmind',
61+
'maxmind' => [
62+
'database_type' => $maxmindConfig['database_type'],
63+
'database_path' => $maxmindConfig['database_path']
64+
]
65+
];
66+
// Init bouncer
67+
/** @var ApiClient */
68+
$apiClientMock = $this->getMockBuilder(ApiClient::class)
69+
->setConstructorArgs([$this->logger])
70+
->enableProxyingToOriginalMethods()
71+
->getMock();
72+
$apiCache = new ApiCache($this->logger, $apiClientMock, $cacheAdapter);
73+
$bouncerConfig = [
74+
'api_key' => TestHelpers::getBouncerKey(),
75+
'api_url' => TestHelpers::getLapiUrl(),
76+
'geolocation' => $geolocationConfig
77+
];
78+
$bouncer = new Bouncer(null, $this->logger, $apiCache);
79+
$bouncer->configure($bouncerConfig);
80+
81+
$this->assertEquals(
82+
'captcha',
83+
$bouncer->getRemediationForIp(TestHelpers::IP_JAPAN),
84+
'Get decisions for a clean IP but bad country'
85+
);
86+
87+
88+
// Disable Geolocation feature
89+
$geolocationConfig['enabled'] = false;
90+
$bouncerConfig['geolocation'] = $geolocationConfig;
91+
$bouncer = new Bouncer(null, $this->logger, $apiCache);
92+
$bouncer->configure($bouncerConfig);
93+
$cacheAdapter->clear();
94+
95+
$this->assertEquals(
96+
'bypass',
97+
$bouncer->getRemediationForIp(TestHelpers::IP_JAPAN),
98+
'Get decisions for a clean IP and bad country but with geolocation disabled'
99+
);
100+
101+
102+
103+
104+
105+
106+
}
107+
}

tests/TestHelpers.php

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
use Bramus\Monolog\Formatter\ColoredLineFormatter;
66
use Monolog\Handler\StreamHandler;
77
use Monolog\Logger;
8-
use Predis\ClientInterface;
98
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
109
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
1110
use Symfony\Component\Cache\Adapter\RedisAdapter;
11+
use Symfony\Component\Cache\Exception\CacheException;
1212

1313
class TestHelpers
1414
{
@@ -19,14 +19,16 @@ class TestHelpers
1919
public const LARGE_IPV4_RANGE = '23';
2020
public const BAD_IPV6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
2121
public const IPV6_RANGE = '64';
22+
public const JAPAN = 'JP';
23+
public const IP_JAPAN = '210.249.74.42';
24+
public const FRANCE = 'FR';
25+
public const IP_FRANCE = '78.119.253.85';
26+
2227

23-
public const FS_CACHE_ADAPTER_DIR = __DIR__.'/../var/fs.cache';
24-
public const PHP_FILES_CACHE_ADAPTER_DIR = __DIR__.'/../var/phpFiles.cache';
2528

26-
public const WATCHER_LOGIN = 'PhpUnitTestMachine';
27-
public const WATCHER_PASSWORD = 'PhpUnitTestMachinePassword';
29+
public const PHP_FILES_CACHE_ADAPTER_DIR = __DIR__.'/../var/phpFiles.cache';
2830

29-
public const LOG_LEVEL = Logger::WARNING; // set to Logger::DEBUG to get high verbosity
31+
public const LOG_LEVEL = Logger::DEBUG; // set to Logger::DEBUG to get high verbosity
3032

3133
public static function createLogger(): Logger
3234
{
@@ -38,15 +40,17 @@ public static function createLogger(): Logger
3840
return $log;
3941
}
4042

43+
/**
44+
* @return array
45+
* @throws ErrorException
46+
* @throws CacheException
47+
*/
4148
public static function cacheAdapterProvider(): array
4249
{
4350
// Init all adapters
44-
4551
$phpFilesAdapter = new PhpFilesAdapter('php_array_adapter_backup_cache', 0, self::PHP_FILES_CACHE_ADAPTER_DIR);
46-
4752
/** @var string */
4853
$redisCacheAdapterDsn = getenv('REDIS_DSN');
49-
/** @var ClientInterface */
5054
$redisClient = RedisAdapter::createConnection($redisCacheAdapterDsn);
5155
$redisAdapter = new RedisAdapter($redisClient);
5256

@@ -68,6 +72,21 @@ public static function cacheAdapterProvider(): array
6872
];
6973
}
7074

75+
76+
public static function maxmindConfigProvider(): array
77+
{
78+
return [
79+
'country database' => [[
80+
'database_type' => 'country',
81+
'database_path' => __DIR__.'/GeoLite2-Country.mmdb'
82+
]],
83+
'city database' => [[
84+
'database_type' => 'city',
85+
'database_path' => __DIR__.'/GeoLite2-City.mmdb'
86+
]],
87+
];
88+
}
89+
7190
public static function getLapiUrl(): string
7291
{
7392
return getenv('LAPI_URL');
@@ -82,8 +101,7 @@ public static function getBouncerKey(): string
82101
if (false === $path) {
83102
throw new RuntimeException("'.bouncer-key' file was not found.");
84103
}
85-
$apiKey = file_get_contents($path);
86104

87-
return $apiKey;
105+
return file_get_contents($path);
88106
}
89107
}

tests/WatcherClient.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function setInitialState(): void
5454
$now = new DateTime();
5555
$this->addDecision($now, '12h', '+12 hours', TestHelpers::BAD_IP, 'captcha');
5656
$this->addDecision($now, '24h', '+24 hours', TestHelpers::BAD_IP.'/'.TestHelpers::IP_RANGE, 'ban');
57+
$this->addDecision($now, '24h', '+24 hours', TestHelpers::JAPAN, 'captcha', 'country');
5758
}
5859

5960
/** Set the initial watcher state */
@@ -100,9 +101,13 @@ public function deleteAllDecisions(): void
100101
$this->request('/v1/decisions', null, null, 'DELETE');
101102
}
102103

103-
public function addDecision(DateTime $now, string $durationString, string $dateTimeDurationString, string $ipOrRange, string $type)
104+
protected function getFinalScope($scope, $value){
105+
return ($scope === 'Ip' && 2 === count(explode('/', $value))) ? 'Range' : $scope;
106+
}
107+
108+
public function addDecision(DateTime $now, string $durationString, string $dateTimeDurationString, string
109+
$value, string $type, string $scope = 'Ip')
104110
{
105-
$isRange = (2 === count(explode('/', $ipOrRange)));
106111
$stopAt = (clone $now)->modify($dateTimeDurationString)->format('Y-m-d\TH:i:s.000\Z');
107112
$startAt = $now->format('Y-m-d\TH:i:s.000\Z');
108113

@@ -112,10 +117,10 @@ public function addDecision(DateTime $now, string $durationString, string $dateT
112117
[
113118
'duration' => $durationString,
114119
'origin' => 'cscli',
115-
'scenario' => 'captcha for '.$ipOrRange.' for '.$durationString.' for PHPUnit tests',
116-
'scope' => $isRange ? 'Range' : 'Ip',
120+
'scenario' => $type.' for scope/value ('.$scope.'/'.$value.') for '.$durationString.' for PHPUnit tests',
121+
'scope' => $this->getFinalScope($scope, $value),
117122
'type' => $type,
118-
'value' => $ipOrRange,
123+
'value' => $value,
119124
],
120125
],
121126
'events' => [
@@ -129,8 +134,8 @@ public function addDecision(DateTime $now, string $durationString, string $dateT
129134
'scenario_version' => '',
130135
'simulated' => false,
131136
'source' => [
132-
'scope' => $isRange ? 'Range' : 'Ip',
133-
'value' => $ipOrRange,
137+
'scope' => $this->getFinalScope($scope, $value),
138+
'value' => $value,
134139
],
135140
'start_at' => $startAt,
136141
'stop_at' => $stopAt,

0 commit comments

Comments
 (0)