Skip to content

Commit cb37b17

Browse files
feat(geolocation): Implement geolocation feature
1 parent 8338134 commit cb37b17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+926
-471
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
}
2020
},
2121
"require": {
22-
"crowdsec/bouncer": "0.22.1",
22+
"crowdsec/bouncer": "0.24.0",
2323
"symfony/polyfill-mbstring": "1.20.0",
2424
"symfony/service-contracts": "2.4.1"
2525
},

composer.lock

Lines changed: 26 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

geolocation/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.mmdb

inc/Bounce.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,20 @@
1818
* @copyright Copyright (c) 2020+ CrowdSec
1919
* @license MIT License
2020
*/
21-
class Bounce extends AbstractBounce implements IBounce
21+
class Bounce extends AbstractBounce
2222
{
2323
public function init(array $crowdSecConfig, array $forcedConfigs = []): Bouncer
2424
{
25-
$this->settings = $crowdSecConfig;
26-
$crowdsecRandomLogFolder = $this->settings['crowdsec_random_log_folder'];
25+
$finalConfigs = array_merge($crowdSecConfig, $forcedConfigs);
26+
$crowdsecRandomLogFolder = $finalConfigs['crowdsec_random_log_folder'];
2727
crowdsecDefineConstants($crowdsecRandomLogFolder);
28-
$this->setDebug($crowdSecConfig['crowdsec_debug_mode']??false);
29-
$this->setDisplayErrors($crowdSecConfig['crowdsec_display_errors'] ?? false);
28+
$this->setDebug($finalConfigs['crowdsec_debug_mode']??false);
29+
$this->setDisplayErrors($finalConfigs['crowdsec_display_errors'] ?? false);
3030
$this->initLogger();
3131

32-
return $this->getBouncerInstance($this->settings);
32+
33+
34+
return $this->getBouncerInstance($finalConfigs);
3335
}
3436

3537
protected function escape(string $value)
@@ -49,8 +51,8 @@ public function getBouncerInstance(array $settings, bool $forceReload = false):
4951
{
5052
$crowdSecLogPath = CROWDSEC_LOG_PATH;
5153
$crowdSecDebugLogPath = CROWDSEC_DEBUG_LOG_PATH;
52-
5354
$this->logger = getStandaloneCrowdSecLoggerInstance($crowdSecLogPath, $this->debug, $crowdSecDebugLogPath);
55+
$this->settings = $settings;
5456

5557
$configs = [
5658
// LAPI connection
@@ -62,6 +64,7 @@ public function getBouncerInstance(array $settings, bool $forceReload = false):
6264
'debug_mode' => $this->getBoolSettings('crowdsec_debug_mode'),
6365
'log_directory_path' => CROWDSEC_LOG_BASE_PATH,
6466
'forced_test_ip' => $this->getStringSettings('crowdsec_forced_test_ip'),
67+
'forced_test_forwarded_ip' => $this->getStringSettings('crowdsec_forced_test_forwarded_ip'),
6568
'display_errors' => $this->getBoolSettings('crowdsec_display_errors'),
6669
// Bouncer
6770
'bouncing_level' => $this->getStringSettings('crowdsec_bouncing_level'),
@@ -80,10 +83,17 @@ public function getBouncerInstance(array $settings, bool $forceReload = false):
8083
'geolocation_cache_duration' => $this->getIntegerSettings('crowdsec_geolocation_cache_duration')
8184
?:Constants::CACHE_EXPIRATION_FOR_GEO,
8285
// Geolocation
83-
'geolocation' => []
86+
'geolocation' => [
87+
'enabled' => $this->getBoolSettings('crowdsec_geolocation_enabled'),
88+
'type' => $this->getStringSettings('crowdsec_geolocation_type')?:Constants::GEOLOCATION_TYPE_MAXMIND,
89+
'save_result' => $this->getBoolSettings('crowdsec_geolocation_save_result'),
90+
'maxmind' => [
91+
'database_type' => $this->getStringSettings('crowdsec_geolocation_maxmind_database_type')?:Constants::MAXMIND_COUNTRY,
92+
'database_path' => CROWDSEC_BOUNCER_GEOLOCATION_DIR. '/'.ltrim((string) esc_attr(get_option('crowdsec_geolocation_maxmind_database_path')), '/'),
93+
]
94+
]
8495
];
8596

86-
8797
$this->bouncer = getBouncerInstanceStandalone($configs, $forceReload);
8898

8999
return $this->bouncer;

inc/admin/advanced-settings.php

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,24 @@ function adminAdvancedSettings()
196196
return (int) $input > 0 ? (int) $input : Constants::CACHE_EXPIRATION_FOR_CAPTCHA ;
197197
}, ' seconds. <p>The lifetime of cached captcha flow for some IP. <br>If a user has to interact with a captcha wall, we store in cache some values in order to know if he has to resolve or not the captcha again.<br>Minimum 1 second. Default: '.Constants::CACHE_EXPIRATION_FOR_CAPTCHA.'.', Constants::CACHE_EXPIRATION_FOR_CAPTCHA, 'width: 115px;', 'number');
198198

199+
// Field "crowdsec_geolocation_cache_duration"
200+
addFieldString('crowdsec_geolocation_cache_duration', 'Geolocation cache duration', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_cache', function ($input) {
201+
if ( (int) $input <= 0) {
202+
add_settings_error('Geolocation cache duration', 'crowdsec_error', 'Geolocation cache duration: Minimum is 1 second.');
203+
204+
return Constants::CACHE_EXPIRATION_FOR_GEO;
205+
}
206+
207+
return (int) $input > 0 ? (int) $input : Constants::CACHE_EXPIRATION_FOR_CAPTCHA ;
208+
}, ' seconds. <p>The lifetime of cached country geolocation result for some IP.<br>Minimum 1 second. Default: '.Constants::CACHE_EXPIRATION_FOR_GEO.'.', Constants::CACHE_EXPIRATION_FOR_GEO, 'width: 115px;', 'number');
209+
199210

200211
/***************************
201212
** Section "Remediation" **
202213
**************************/
203214

204215
add_settings_section('crowdsec_admin_advanced_remediations', 'Remediations', function () {
205-
echo 'Configuration some details about remediations.';
216+
echo 'Configure some details about remediations.';
206217
}, 'crowdsec_advanced_settings');
207218

208219
// Field "crowdsec_fallback_remediation"
@@ -271,9 +282,51 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra
271282
'<br><strong>Comma (,)</strong> separated ips or ips ranges. Example: 1.2.3.4/24, 2.3.4.5, 3.4.5.6/27.<br><br>Some common CDN IP list: <a href="https://www.cloudflare.com/fr-fr/ips/" target="_blank">Cloudflare</a>, <a href="https://api.fastly.com/public-ip-list" target="_blank">Fastly</a>',
272283
'fill the IPs or IPs ranges here...', '');
273284

274-
// Field "crowdsec_hide_mentions"
275-
addFieldCheckbox('crowdsec_hide_mentions', 'Hide CrowdSec mentions', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_remediations', function () {}, function () {}, '
276-
<p>Enable if you want to hide CrowdSec mentions on the Ban and Captcha pages</p>');
285+
286+
287+
/***************************
288+
** Section "Geolocation" **
289+
**************************/
290+
291+
add_settings_section('crowdsec_admin_advanced_geolocation', 'Geolocation', function () {
292+
echo 'Configure some details about geolocation.';
293+
}, 'crowdsec_advanced_settings');
294+
295+
// Field "Geolocation enabled"
296+
addFieldCheckbox('crowdsec_geolocation_enabled', 'Enable geolocation feature', 'crowdsec_plugin_advanced_settings',
297+
'crowdsec_advanced_settings', 'crowdsec_admin_advanced_geolocation', function () {}, function () {}, '
298+
<p>Enable if you want to use also CrowdSec country scoped decisions.<br>If enabled, bounced IP will be geolocalized and the final remediation will take into account any country related decision.</p>');
299+
300+
$geolocationTypes = [Constants::GEOLOCATION_TYPE_MAXMIND => 'MaxMind database' ];
301+
addFieldSelect('crowdsec_geolocation_type', 'Geolocation type', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings',
302+
'crowdsec_admin_advanced_geolocation', function ($input) {
303+
if ($input !== Constants::GEOLOCATION_TYPE_MAXMIND) {
304+
$input = Constants::GEOLOCATION_TYPE_MAXMIND;
305+
add_settings_error('Geolocation type', 'crowdsec_error', 'Geolocation type: Incorrect geolocation type selected.');
306+
}
307+
308+
return $input;
309+
}, '<p>For now, only Maxmind database type is allowed</p>', $geolocationTypes);
310+
311+
$maxmindDatabaseTypes = [Constants::MAXMIND_COUNTRY => 'Country', Constants::MAXMIND_CITY => 'City'];
312+
addFieldSelect('crowdsec_geolocation_maxmind_database_type', 'MaxMind database type', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings',
313+
'crowdsec_admin_advanced_geolocation', function ($input) {
314+
if (!in_array($input, [Constants::MAXMIND_COUNTRY, Constants::MAXMIND_CITY])) {
315+
$input = Constants::MAXMIND_COUNTRY;
316+
add_settings_error('Geolocation MaxMind database type', 'crowdsec_error', 'MaxMind database type: Incorrect type selected.');
317+
}
318+
319+
return $input;
320+
}, '<p></p>', $maxmindDatabaseTypes);
321+
322+
addFieldString('crowdsec_geolocation_maxmind_database_path', 'Path to the MaxMind database', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_geolocation', function ($input) {
323+
return $input;
324+
}, '<p>Relative path from <i>wp-content/plugins/cs-wordpress-bouncer/geolocation</i> folder</p>', 'GeoLite2-Country.mmdb', '');
325+
326+
addFieldCheckbox('crowdsec_geolocation_save_result', 'Save geolocalized country in cache', 'crowdsec_plugin_advanced_settings',
327+
'crowdsec_advanced_settings', 'crowdsec_admin_advanced_geolocation', function () {}, function () {}, '
328+
<p>Enabling this will avoid multiple call to the geolocation system (e.g. MaxMind database)</p> If enabled, the geolocalized country associated to the IP will be saved in cache.<br>See the <i>Geolocation cache duration</i> setting above to set the lifetime of this result.');
329+
277330

278331
/*******************************
279332
** Section "Debug mode" **
@@ -298,4 +351,25 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra
298351
// Field "crowdsec_display_errors"
299352
addFieldCheckbox('crowdsec_display_errors', 'Enable errors display', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_display_errors', function () {}, function () {}, '
300353
<p>Do not use in production. When this mode is enabled, you will see every unexpected bouncing errors in the browser.</p>');
354+
355+
/*******************************
356+
** Section "Test mode" **
357+
******************************/
358+
359+
add_settings_section('crowdsec_admin_advanced_test', 'Test settings', function () {
360+
echo 'Configure some test parameters.';
361+
}, 'crowdsec_advanced_settings');
362+
363+
// Field "test ip"
364+
addFieldString('crowdsec_forced_test_ip', 'Forced test IP', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_test', function ($input) {
365+
return $input;
366+
}, '<p>This Ip will be used instead of the current detected browser IP: '.$_SERVER['REMOTE_ADDR'].'.<br><strong>Must be empty in production.</strong></p>',
367+
'1.2.3.4', '');
368+
369+
addFieldString('crowdsec_forced_test_forwarded_ip', 'Forced test X-Forwrded-For IP', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_test', function ($input) {
370+
return $input;
371+
}, '<p>This Ip will be used instead of the current X-Forwarded-For Ip if any.<br><strong>Must be empty in production.</strong></p>',
372+
'1.2.3.4', '');
373+
374+
301375
}

inc/bouncer-instance.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function getDatabaseSettings(): array
4949
'debug_mode' => !empty(get_option('crowdsec_debug_mode')),
5050
'log_directory_path' => CROWDSEC_LOG_BASE_PATH,
5151
'forced_test_ip' => esc_attr(get_option('crowdsec_forced_test_ip')),
52+
'forced_test_forwarded_ip' => esc_attr(get_option('crowdsec_forced_test_forwarded_ip')),
5253
'display_errors' => !empty(get_option('crowdsec_display_errors')),
5354
// Bouncer
5455
'bouncing_level' => esc_attr(get_option('crowdsec_bouncing_level')),
@@ -66,8 +67,18 @@ function getDatabaseSettings(): array
6667
Constants::CACHE_EXPIRATION_FOR_BAD_IP,
6768
'captcha_cache_duration' => (int)get_option('crowdsec_captcha_cache_duration') ?:
6869
Constants::CACHE_EXPIRATION_FOR_CAPTCHA,
70+
'geolocation_cache_duration' => (int)get_option('crowdsec_geolocation_cache_duration') ?:
71+
Constants::CACHE_EXPIRATION_FOR_CAPTCHA,
6972
// Geolocation
70-
'geolocation' => []
73+
'geolocation' => [
74+
'enabled' => !empty(get_option('crowdsec_geolocation_enabled')),
75+
'type' => esc_attr(get_option('crowdsec_geolocation_type')),
76+
'save_result' => !empty(get_option('crowdsec_geolocation_save_result')),
77+
'maxmind' => [
78+
'database_type' => esc_attr(get_option('crowdsec_geolocation_maxmind_database_type')),
79+
'database_path' => CROWDSEC_BOUNCER_GEOLOCATION_DIR. '/'.ltrim(esc_attr(get_option('crowdsec_geolocation_maxmind_database_path')), '/'),
80+
]
81+
]
7182
];
7283
}
7384

0 commit comments

Comments
 (0)