Skip to content

Commit 58c7cf7

Browse files
committed
redesign captcha behaviour with cache
1 parent 59f7f4e commit 58c7cf7

File tree

1 file changed

+73
-59
lines changed

1 file changed

+73
-59
lines changed

inc/bounce-current-ip.php

Lines changed: 73 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,115 +3,130 @@
33
use CrowdSecBouncer\Bouncer;
44
use CrowdSecBouncer\Constants;
55

6-
// Captcha repeat delay in seconds
7-
define('CROWDSEC_CAPTCHA_REPEAT_MIN_DELAY', 15 * 60); // TODO P3 Dynamize this value
8-
96

107
function bounceCurrentIp()
118
{
129
$ip = $_SERVER["REMOTE_ADDR"];
1310

14-
function displayCaptchaPage($ip, $error = false)
11+
function displayCaptchaWall()
1512
{
16-
if (!isset($_SESSION['phrase'])) {
17-
$captchaCouple = Bouncer::buildCaptchaCouple();
18-
$_SESSION['phrase'] = $captchaCouple['phrase'];
19-
$_SESSION['img'] = $captchaCouple['inlineImage'];
20-
}
21-
$captchaImageSrc = $_SESSION['img'];
22-
$captchaResolutionFormUrl = '';
23-
echo Bouncer::getCaptchaHtmlTemplate($error, $captchaImageSrc, $captchaResolutionFormUrl);
13+
header('HTTP/1.0 401 Unauthorized');
14+
echo Bouncer::getCaptchaHtmlTemplate($_SESSION["crowdsec_captcha_resolution_failed"], $_SESSION['crowdsec_captcha_inline_image'], '');
2415
die();
2516
}
2617

27-
function handleBanRemediation(Bouncer $bouncer, $ip)
18+
function handleBanRemediation()
2819
{
29-
// TODO P3 make a function instead of this
3020
header('HTTP/1.0 403 Forbidden');
3121
echo Bouncer::getAccessForbiddenHtmlTemplate();
3222
die();
3323
}
3424

35-
function checkCaptcha(string $ip)
25+
function storeNewCaptchaCoupleInSession()
3626
{
37-
//error_log("crowdsec-wp: " . $ip . " is in captcha mode"); TODO P2 check how
27+
$captchaCouple = Bouncer::buildCaptchaCouple();
28+
$_SESSION['crowdsec_captcha_phrase_to_guess'] = $captchaCouple['phrase'];
29+
$_SESSION['crowdsec_captcha_inline_image'] = $captchaCouple['inlineImage'];
30+
}
3831

39-
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['crowdsec_captcha'])) {
32+
function clearCaptchaSessionContext()
33+
{
34+
unset($_SESSION['crowdsec_captcha_has_to_be_resolved']);
35+
unset($_SESSION['crowdsec_captcha_phrase_to_guess']);
36+
unset($_SESSION['crowdsec_captcha_inline_image']);
37+
unset($_SESSION['crowdsec_captcha_resolution_failed']);
38+
}
4039

41-
// Handle image refresh.
42-
$refreshImage = (isset($_POST['refresh']) && (bool)(int)$_POST['refresh']);
40+
function handleCaptchaResolutionForm(string $ip)
41+
{
42+
// Early return if no captcha has to be resolved.
43+
if (!isset($_SESSION["crowdsec_captcha_has_to_be_resolved"])) {
44+
return;
45+
}
4346

44-
if ($refreshImage) {
45-
// generate new image
46-
$captchaCouple = Bouncer::buildCaptchaCouple();
47-
$_SESSION['phrase'] = $captchaCouple['phrase'];
48-
$_SESSION['img'] = $captchaCouple['inlineImage'];
47+
// Captcha already resolved.
48+
if (!$_SESSION["crowdsec_captcha_has_to_be_resolved"]) {
49+
return;
50+
}
4951

50-
// display captcha page
51-
$_SESSION["captchaResolved"] = false;
52-
displayCaptchaPage($ip, true);
53-
}
52+
// Early return if no form captcha form has been filled.
53+
if (($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['crowdsec_captcha']))) {
54+
return;
55+
}
5456

57+
// Handle image refresh.
58+
if (isset($_POST['refresh']) && (bool)(int)$_POST['refresh']) {
59+
// Generate new captcha image for the user
60+
storeNewCaptchaCoupleInSession();
61+
$_SESSION["crowdsec_captcha_resolution_failed"] = false;
62+
return;
63+
}
5564

56-
// Handle captcha resolve.
65+
// Handle a captcha resolution try
66+
if (isset($_POST['phrase'])) {
5767
$bouncer = getBouncerInstance();
58-
$captchaCorrectlyFilled = (isset($_POST['phrase']) && $bouncer->checkCaptcha($_SESSION['phrase'], $_POST['phrase'], $ip));
59-
if ($captchaCorrectlyFilled) {
60-
$_SESSION["captchaResolved"] = true;
61-
$_SESSION["captchaResolvedAt"] = time();
62-
unset($_SESSION['phrase']);
63-
return;
68+
if ($bouncer->checkCaptcha($_SESSION['crowdsec_captcha_phrase_to_guess'], $_POST['phrase'], $ip)) {
69+
70+
// User has correctly fill the captcha
71+
72+
$_SESSION["crowdsec_captcha_has_to_be_resolved"] = false;
73+
unset($_SESSION['crowdsec_captcha_phrase_to_guess']);
74+
unset($_SESSION['crowdsec_captcha_inline_image']);
75+
unset($_SESSION['crowdsec_captcha_resolution_failed']);
76+
} else {
77+
78+
// The user failed to resolve the captcha.
79+
80+
$_SESSION["crowdsec_captcha_resolution_failed"] = true;
6481
}
6582
}
66-
$_SESSION["captchaResolved"] = false;
67-
displayCaptchaPage($ip, true);
6883
}
6984

70-
function handleCaptchaRemediation(string $ip)
85+
function handleCaptchaRemediation($ip)
7186
{
72-
// Never displayed to user.
73-
if (!isset($_SESSION["captchaResolved"])) {
74-
displayCaptchaPage($ip);
75-
}
76-
// User was unable to resolve.
77-
if (!$_SESSION["captchaResolved"]) {
78-
displayCaptchaPage($ip);
79-
}
8087

88+
// Check captcha resolution form
89+
handleCaptchaResolutionForm($ip);
8190

82-
// User resolved too long ago.
83-
$resolvedTooLongAgo = ((time() - $_SESSION["captchaResolvedAt"]) > CROWDSEC_CAPTCHA_REPEAT_MIN_DELAY);
84-
if ($resolvedTooLongAgo) {
85-
displayCaptchaPage($ip);
91+
if (!isset($_SESSION["crowdsec_captcha_has_to_be_resolved"])) {
92+
93+
// Setup the first captcha remediation.
94+
95+
storeNewCaptchaCoupleInSession();
96+
$_SESSION["crowdsec_captcha_has_to_be_resolved"] = true;
97+
$_SESSION["crowdsec_captcha_resolution_failed"] = false;
98+
}
99+
100+
// Display captcha page if this is required.
101+
if ($_SESSION["crowdsec_captcha_has_to_be_resolved"]) {
102+
displayCaptchaWall();
86103
}
87104
}
88105

89-
function handleRemediation(string $remediation, string $ip, Bouncer $bouncer)
106+
function handleRemediation(string $remediation, string $ip)
90107
{
108+
if ($remediation !== Constants::REMEDIATION_CAPTCHA && isset($_SESSION["crowdsec_captcha_has_to_be_resolved"])) {
109+
clearCaptchaSessionContext();
110+
}
91111
switch ($remediation) {
92112
case Constants::REMEDIATION_BYPASS:
93113
return;
94114
case Constants::REMEDIATION_CAPTCHA:
95115
handleCaptchaRemediation($ip);
96116
break;
97117
case Constants::REMEDIATION_BAN:
98-
handleBanRemediation($bouncer, $ip);
118+
handleBanRemediation();
99119
}
100120
}
101121

102-
// Control Captcha
103-
if (isset($_SESSION['phrase'])) {
104-
checkCaptcha($ip);
105-
}
106-
107122
$bouncingLevel = esc_attr(get_option("crowdsec_bouncing_level"));
108123
$shouldBounce = ($bouncingLevel !== CROWDSEC_BOUNCING_LEVEL_DISABLED);
109124

110125
if ($shouldBounce) {
111126
try {
112127
$bouncer = getBouncerInstance();
113128
$remediation = $bouncer->getRemediationForIp($ip);
114-
handleRemediation($remediation, $ip, $bouncer);
129+
handleRemediation($remediation, $ip);
115130
} catch (WordpressCrowdSecBouncerException $e) {
116131
// TODO log error for debug mode only.
117132
}
@@ -138,7 +153,6 @@ function safelyBounceCurrentIp()
138153
bounceCurrentIp();
139154
}
140155
restore_error_handler();
141-
142156
} catch (\Exception $e) {
143157
getCrowdSecLoggerInstance()->error(null, [
144158
'type' => 'WP_EXCEPTION_WHILE_BOUNCING',

0 commit comments

Comments
 (0)