Skip to content

Commit

Permalink
wip - add cookie baking and update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhilton committed Aug 5, 2024
1 parent 00828b5 commit 15e5c74
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 30 deletions.
15 changes: 3 additions & 12 deletions bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,25 +76,16 @@
$url = $path.'/auth/outage/info.php';
$outageinfo = strpos($_SERVER['REQUEST_URI'], $url) === 0 ? true : false;
}

$allowed = !file_exists($CFG->dataroot.'/climaintenance.php') // Not in maintenance mode.
|| (defined('ABORT_AFTER_CONFIG') && ABORT_AFTER_CONFIG) // Only config requested.
|| (defined('CLI_SCRIPT') && CLI_SCRIPT) // Allow CLI scripts.
|| $outageinfo // Allow outage info requests.
|| (defined('NO_AUTH_OUTAGE') && NO_AUTH_OUTAGE); // Allow any page should not be blocked by maintenance mode.
if (!$allowed) {
// Call the climaintenance.php which will check for allowed IPs.
// Call the climaintenance.php which will check for the conditions
// that have been baked into it from the frontend (ip, accesskey, etc...).
$CFG->dirroot = dirname(dirname(dirname(__FILE__))); // It is not defined yet but the script below needs it.

// TODO bake these values into the climaintenance page on save.
// $sessionoptions = [
// 'lifetime' => 0,
// 'path' => $CFG->sessioncookiepath,
// 'domain' => $CFG->sessioncookiedomain,
// 'secure' => $cookiesecure,
// 'httponly' => $CFG->cookiehttponly,
// ];
setcookie('auth_outage_accesskey', '12345', time() + 60, '', '', true, true);
global $_COOKIE;
require($CFG->dataroot.'/climaintenance.php'); // This call may terminate the script here or not.
}

Expand Down
37 changes: 27 additions & 10 deletions classes/local/outagelib.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,13 @@ private static function injection_allowed() {
* @param int $starttime Outage start time.
* @param int $stoptime Outage stop time.
* @param string $allowedips List of IPs allowed.
* @param string|null $accesskey access key, or null if no access key set.
*
* @return string
* @throws invalid_parameter_exception
*/
public static function create_climaintenancephp_code($starttime, $stoptime, $allowedips) {
public static function create_climaintenancephp_code($starttime, $stoptime, $allowedips, $accesskey = null) {
global $CFG;
if (!is_int($starttime) || !is_int($stoptime)) {
throw new invalid_parameter_exception('Make sure $startime and $stoptime are integers.');
}
Expand All @@ -305,6 +307,9 @@ public static function create_climaintenancephp_code($starttime, $stoptime, $all
// single-quotes (and double for the sake of it) are present otherwise it would break the code.
$allowedips = addslashes($allowedips);

$cookiesecure = is_moodle_cookie_secure();
$cookiehttponly = (bool) $CFG->cookiehttponly;

$code = <<<'EOT'
<?php
if ((time() >= {{STARTTIME}}) && (time() < {{STOPTIME}})) {
Expand All @@ -319,14 +324,16 @@ public static function create_climaintenancephp_code($starttime, $stoptime, $all
$urlaccesskey = optional_param('accesskey', null, PARAM_TEXT);
if (!empty($urlaccesskey)) {
// TODO bake httpsecure, etc... in via the actual values.
setcookie('auth_outage_accesskey', $urlaccesskey, time() + 86400, '/', '', true, true);
setcookie('auth_outage_accesskey', $urlaccesskey, time() + 86400, '/', '', {{COOKIESECURE}}, {{COOKIEHTTPONLY}});
}
$accesskey = $urlaccesskey ?: $_COOKIE['auth_outage_accesskey'];
// Use url access key if given, else the cookie, else null.
$useraccesskey = $urlaccesskey ?: $_COOKIE['auth_outage_accesskey'] ?? null;
// TODO put actual access key val here.
if (!remoteip_in_list('{{ALLOWEDIPS}}') && $accesskey != '12345') {
$ipblocked = {{USEALLOWEDIPS}} && !remoteip_in_list('{{ALLOWEDIPS}}');
$accesskeyblocked = {{USEACCESSKEY}} && $useraccesskey != '{{ACCESSKEY}}';
if ($ipblocked || $accesskeyblocked) {
header($_SERVER['SERVER_PROTOCOL'] . ' 503 Moodle under maintenance');
header('Status: 503 Moodle under maintenance');
header('Retry-After: 300');
Expand All @@ -342,7 +349,15 @@ public static function create_climaintenancephp_code($starttime, $stoptime, $all
if ((defined('AJAX_SCRIPT') && AJAX_SCRIPT) || (defined('WS_SERVER') && WS_SERVER)) {
exit(0);
}
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
if ($ipblocked) {
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
}
if ($accesskeyblocked) {
echo '<!-- Blocked by missing access key, acces key given: '. $useraccesskey .' -->';
}
if (file_exists($CFG->dataroot.'/climaintenance.template.html')) {
require($CFG->dataroot.'/climaintenance.template.html');
exit(0);
Expand All @@ -352,8 +367,10 @@ public static function create_climaintenancephp_code($starttime, $stoptime, $all
}
}
EOT;
$search = ['{{STARTTIME}}', '{{STOPTIME}}', '{{ALLOWEDIPS}}', '{{YOURIP}}'];
$replace = [$starttime, $stoptime, $allowedips, getremoteaddr('n/a')];
$search = ['{{STARTTIME}}', '{{STOPTIME}}', '{{USEALLOWEDIPS}}', '{{ALLOWEDIPS}}', '{{USEACCESSKEY}}', '{{ACCESSKEY}}', '{{YOURIP}}', '{{COOKIESECURE}}', '{{COOKIEHTTPONLY}}'];
// Note that var_export is required because (string) false == '', not 'false'.
$replace = [$starttime, $stoptime, var_export(!empty($allowedips), true), $allowedips, var_export(!empty($accesskey), true),
$accesskey, getremoteaddr('n/a'), var_export($cookiesecure, true), var_export($cookiehttponly, true)];
return str_replace($search, $replace, $code);
}

Expand Down Expand Up @@ -381,7 +398,7 @@ public static function update_climaintenance_code($outage) {
unlink($file);
}
} else {
$code = self::create_climaintenancephp_code($outage->starttime, $outage->stoptime, $allowedips);
$code = self::create_climaintenancephp_code($outage->starttime, $outage->stoptime, $allowedips, $outage->accesskey);

$dir = dirname($file);
if (!file_exists($dir) || !is_dir($dir)) {
Expand Down
62 changes: 54 additions & 8 deletions tests/local/outagelib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,29 @@ public function test_createmaintenancephpcode() {
$expected = <<<'EOT'
<?php
if ((time() >= 123) && (time() < 456)) {
define('MOODLE_INTERNAL', true);
if (!defined('MOODLE_INTERNAL')) {
define('MOODLE_INTERNAL', true);
}
require_once($CFG->dirroot.'/lib/moodlelib.php');
if (file_exists($CFG->dirroot.'/lib/classes/ip_utils.php')) {
require_once($CFG->dirroot.'/lib/classes/ip_utils.php');
}
if (!remoteip_in_list('hey\'\"you
// Put access key as a cookie if given. This stops the need to put it as a url param on every request.
$urlaccesskey = optional_param('accesskey', null, PARAM_TEXT);
if (!empty($urlaccesskey)) {
setcookie('auth_outage_accesskey', $urlaccesskey, time() + 86400, '/', '', true, false);
}
// Use url access key if given, else the cookie, else null.
$useraccesskey = $urlaccesskey ?: $_COOKIE['auth_outage_accesskey'] ?? null;
$ipblocked = true && !remoteip_in_list('hey\'\"you
a.b.c.d
e.e.e.e/20')) {
e.e.e.e/20');
$accesskeyblocked = true && $useraccesskey != '12345';
if ($ipblocked || $accesskeyblocked) {
header($_SERVER['SERVER_PROTOCOL'] . ' 503 Moodle under maintenance');
header('Status: 503 Moodle under maintenance');
header('Retry-After: 300');
Expand All @@ -343,7 +358,15 @@ public function test_createmaintenancephpcode() {
if ((defined('AJAX_SCRIPT') && AJAX_SCRIPT) || (defined('WS_SERVER') && WS_SERVER)) {
exit(0);
}
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
if ($ipblocked) {
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
}
if ($accesskeyblocked) {
echo '<!-- Blocked by missing access key, acces key given: '. $useraccesskey .' -->';
}
if (file_exists($CFG->dataroot.'/climaintenance.template.html')) {
require($CFG->dataroot.'/climaintenance.template.html');
exit(0);
Expand All @@ -353,7 +376,7 @@ public function test_createmaintenancephpcode() {
}
}
EOT;
$found = outagelib::create_climaintenancephp_code(123, 456, "hey'\"you\na.b.c.d\ne.e.e.e/20");
$found = outagelib::create_climaintenancephp_code(123, 456, "hey'\"you\na.b.c.d\ne.e.e.e/20", '12345');
self::assertSame($expected, $found);
}

Expand All @@ -370,12 +393,27 @@ public function test_createmaintenancephpcode_withoutage($configkey) {
$expected = <<<'EOT'
<?php
if ((time() >= 123) && (time() < 456)) {
define('MOODLE_INTERNAL', true);
if (!defined('MOODLE_INTERNAL')) {
define('MOODLE_INTERNAL', true);
}
require_once($CFG->dirroot.'/lib/moodlelib.php');
if (file_exists($CFG->dirroot.'/lib/classes/ip_utils.php')) {
require_once($CFG->dirroot.'/lib/classes/ip_utils.php');
}
if (!remoteip_in_list('127.0.0.1')) {
// Put access key as a cookie if given. This stops the need to put it as a url param on every request.
$urlaccesskey = optional_param('accesskey', null, PARAM_TEXT);
if (!empty($urlaccesskey)) {
setcookie('auth_outage_accesskey', $urlaccesskey, time() + 86400, '/', '', true, false);
}
// Use url access key if given, else the cookie, else null.
$useraccesskey = $urlaccesskey ?: $_COOKIE['auth_outage_accesskey'] ?? null;
$ipblocked = true && !remoteip_in_list('127.0.0.1');
$accesskeyblocked = false && $useraccesskey != '';
if ($ipblocked || $accesskeyblocked) {
header($_SERVER['SERVER_PROTOCOL'] . ' 503 Moodle under maintenance');
header('Status: 503 Moodle under maintenance');
header('Retry-After: 300');
Expand All @@ -391,7 +429,15 @@ public function test_createmaintenancephpcode_withoutage($configkey) {
if ((defined('AJAX_SCRIPT') && AJAX_SCRIPT) || (defined('WS_SERVER') && WS_SERVER)) {
exit(0);
}
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
if ($ipblocked) {
echo '<!-- Blocked by ip, your ip: '.getremoteaddr('n/a').' -->';
}
if ($accesskeyblocked) {
echo '<!-- Blocked by missing access key, acces key given: '. $useraccesskey .' -->';
}
if (file_exists($CFG->dataroot.'/climaintenance.template.html')) {
require($CFG->dataroot.'/climaintenance.template.html');
exit(0);
Expand Down

0 comments on commit 15e5c74

Please sign in to comment.