Compare commits

...
Author SHA1 Message Date
Rossco Hellmans 7caa987abf Merge pull request #423 from catalyst/test-fix-t20
Revert "Fix URL is blocked message when not behind a proxy/load balan…
2026-09-29 10:36:38 +10:00
Matthew Hilton 802dd7da81 T20: remove Moodle CI 2026-09-29 10:33:13 +10:00
Matthew Hilton 4de2837b30 Revert "Fix URL is blocked message when not behind a proxy/load balancer"
This reverts commit 51db933862.
2026-09-29 10:21:54 +10:00
Rossco Hellmans cb70c1e0f8 TOTARA 20: update admin_externalpage_setup with new classname 2026-08-31 13:00:04 +10:00
Rossco Hellmans 42853b27a9 TOTARA 20: update settings page for for T20 2026-08-31 12:59:51 +10:00
Vlad Kidanov 76f76dc634 Merge pull request #421 from catalyst/general-fixes-t19
Bug fixes and improvements
2026-08-05 10:06:02 +01:00
vlad.kidanov a4ee55c83e Bug fixes and improvements
WR492688: Issue #32

Fix: the 'static' flag can no longer be set from a raw client value. It
is now only true if the request presents a 'statickey' matching an
HMAC-SHA256 of the outage id keyed with a per-site secret
(infopage::statickey(), lazily generated via set_config()/get_config()).
maintenance_static_page::create_from_outage() now sends that computed
statickey instead of static=1 when it internally fetches info.php, so
the legitimate static-generation path keeps working while external
forgery of the flag is no longer possible.

WR492688: Issue #33

WR492688: Version bump

fix
2026-07-20 14:58:28 +01:00
10 changed files with 67 additions and 69 deletions

No files matched your search

-12
View File
@@ -1,12 +0,0 @@
# .github/workflows/ci.yml
name: ci
on: [push, pull_request]
jobs:
ci:
uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main
secrets:
moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }}
with:
disable_behat: true
+23 -2
View File
@@ -53,10 +53,14 @@ class infopage {
$CFG->svgicons = true;
if (is_null($params)) {
$id = optional_param('id', null, PARAM_INT);
$params = [
'id' => optional_param('id', null, PARAM_INT),
'id' => $id,
'outage' => null,
'static' => optional_param('static', false, PARAM_BOOL),
'static' => !is_null($id) && hash_equals(
self::statickey($id),
optional_param('statickey', '', PARAM_ALPHANUM)
),
];
} else {
$defaults = [
@@ -145,4 +149,21 @@ class infopage {
$this->outage = $params['outage'];
$this->static = $params['static'];
}
/**
* Computes the secret token that proves a request to view an outage's static
* rendering came from this plugin's own static-page generator, not an external
* client forging the request. Used to gate the 'static' flag (see constructor).
*
* @param int $outageid
* @return string
*/
public static function statickey($outageid) {
$secret = get_config('auth_outage', 'staticsecret');
if (empty($secret)) {
$secret = random_string(64);
set_config('staticsecret', $secret, 'auth_outage');
}
return hash_hmac('sha256', (string)$outageid, $secret);
}
}
@@ -50,7 +50,8 @@ class maintenance_static_page {
$html = '<html></html>';
} else {
$data = maintenance_static_page_io::file_get_data(
$CFG->wwwroot . '/auth/outage/info.php?auth_outage_hide_warning=1&static=1&id=' . $outage->id
$CFG->wwwroot . '/auth/outage/info.php?auth_outage_hide_warning=1&id=' . $outage->id
. '&statickey=' . infopage::statickey($outage->id)
);
$html = $data['contents'];
}
+1 -1
View File
@@ -56,7 +56,7 @@ class outagelib {
global $CFG;
require_once($CFG->libdir . '/filelib.php');
$curl = new curl(['ignoresecurity' => true]);
$curl = new curl();
$contents = $curl->get($file);
$info = $curl->get_info();
if (!empty($info['content_type'])) {
+1 -1
View File
@@ -30,7 +30,7 @@ use auth_outage\local\outagelib;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('auth_outage_manage');
core\setting\page\externalpage::setup(null, 'auth_outage_manage');
$PAGE->set_url(new moodle_url('/auth/outage/manage.php'));
$output = $PAGE->get_renderer('auth_outage');
+4
View File
@@ -30,7 +30,11 @@ use auth_outage\local\controllers\maintenance_static_page;
// @codingStandardsIgnoreStart
require_once(__DIR__.'/../../config.php');
require_once($CFG->libdir . '/adminlib.php');
// @codingStandardsIgnoreEnd
admin_externalpage_setup('auth_outage_manage');
$id = optional_param('id', null, PARAM_INT);
$outage = is_null($id) ? outagedb::get_next_starting() : outagedb::get_by_id($id);
if (is_null($outage)) {
+25 -16
View File
@@ -23,12 +23,21 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @var stdClass $CFG
* @var admin_settingpage $settings
* @var core\setting\part\page $settings
* @var bootstrap_renderer $OUTPUT
* @var admin_root $ADMIN
* @var core\setting\root $ADMIN
* @var moodle_page $PAGE
*/
use auth_outage\local\outagelib;
use core\setting\heading;
use core\setting\part\category;
use core\setting\page\externalpage;
use core\setting\type\checkbox;
use core\setting\type\duration;
use core\setting\type\list_ipaddresses;
use core\setting\type\text;
use core\setting\type\textarea;
defined('MOODLE_INTERNAL') || die;
@@ -37,48 +46,48 @@ if ($hassiteconfig) {
$settings->visiblename = get_string('menusettings', 'auth_outage');
$description = outagelib::generate_plugin_configuration_warning();
$settings->add(new admin_setting_heading(
$settings->add(new heading(
'defaults',
get_string('settingssectiondefaults', 'auth_outage'),
get_string('settingssectiondefaultsdescription', 'auth_outage') . $description
));
$settings->add(new admin_setting_configcheckbox(
$settings->add(new checkbox(
'auth_outage/default_autostart',
get_string('defaultoutageautostart', 'auth_outage'),
get_string('defaultoutageautostartdescription', 'auth_outage'),
$defaults['default_autostart']
));
$settings->add(new admin_setting_configduration(
$settings->add(new duration(
'auth_outage/default_warning_duration',
get_string('defaultwarningduration', 'auth_outage'),
get_string('defaultwarningdurationdescription', 'auth_outage'),
$defaults['default_warning_duration'],
60
));
$settings->add(new admin_setting_configduration(
$settings->add(new duration(
'auth_outage/default_duration',
get_string('defaultoutageduration', 'auth_outage'),
get_string('defaultoutagedurationdescription', 'auth_outage'),
$defaults['default_duration'],
60
));
$settings->add(new admin_setting_configtext(
$settings->add(new text(
'auth_outage/default_time',
get_string('defaulttime', 'auth_outage'),
get_string('defaulttimedescription', 'auth_outage'),
'',
PARAM_TEXT
));
$settings->add(new admin_setting_configtext(
$settings->add(new text(
'auth_outage/default_title',
get_string('defaulttitle', 'auth_outage'),
get_string('defaulttitledescription', 'auth_outage'),
$defaults['default_title'],
PARAM_TEXT
));
$settings->add(new admin_setting_configtextarea(
$settings->add(new textarea(
'auth_outage/default_description',
get_string('defaultdescription', 'auth_outage'),
get_string('defaultdescriptiondescription', 'auth_outage'),
@@ -86,13 +95,13 @@ if ($hassiteconfig) {
PARAM_RAW
));
$settings->add(new admin_setting_heading(
$settings->add(new heading(
'plugin',
get_string('settingssectionplugin', 'auth_outage'),
get_string('settingssectionplugindescription', 'auth_outage')
));
$settings->add(new admin_setting_configtextarea(
$settings->add(new textarea(
'auth_outage/css',
get_string('defaultlayoutcss', 'auth_outage'),
get_string('defaultlayoutcssdescription', 'auth_outage'),
@@ -119,7 +128,7 @@ if ($hassiteconfig) {
$description .= '<p>' . get_string('ipblockersyntax', 'admin') . '</p>';
$description .= '<p>' . get_string('ips_combine', 'auth_outage') . '</p>';
$iplist = new admin_setting_configiplist(
$iplist = new list_ipaddresses(
'auth_outage/allowedips',
get_string('allowediplist', 'admin'),
$description,
@@ -128,7 +137,7 @@ if ($hassiteconfig) {
$iplist->set_updatedcallback('auth_outage_outagelib_prepare_next_outage');
$settings->add($iplist);
$iplist = new admin_setting_configiplist(
$iplist = new list_ipaddresses(
'auth_outage/allowedips_forced',
get_string('builtinallowediplist', 'auth_outage'),
get_string('builtinallowediplist_desc', 'auth_outage'),
@@ -137,7 +146,7 @@ if ($hassiteconfig) {
$settings->add($iplist);
// Create 'Static Page - Elements to Remove' settings.
$toremove = new admin_setting_configtextarea(
$toremove = new textarea(
'auth_outage/remove_selectors',
get_string('removeselectors', 'auth_outage'),
get_string('removeselectorsdescription', 'auth_outage'),
@@ -147,7 +156,7 @@ if ($hassiteconfig) {
$settings->add($toremove);
// Create category for Outage.
$ADMIN->add('authsettings', new admin_category('auth_outage', get_string('pluginname', 'auth_outage')));
$ADMIN->add('authsettings', new category('auth_outage', get_string('pluginname', 'auth_outage')));
// Add settings page toconfigure defaults.
$ADMIN->add('auth_outage', $settings);
// Clear '$settings' to prevent adding again outsite category.
@@ -155,7 +164,7 @@ if ($hassiteconfig) {
// Add options.
$ADMIN->add(
'auth_outage',
new admin_externalpage(
new externalpage(
'auth_outage_manage',
get_string('menumanage', 'auth_outage'),
new moodle_url($CFG->wwwroot . '/auth/outage/manage.php')
@@ -429,39 +429,6 @@ final class controllers_maintenance_static_page_test extends base_testcase {
maintenance_static_page_io::file_get_data(200);
}
/**
* Test file_get_data with curlsecurityblockedhosts.
* We will use an external URL to test passing ignoresecurity inside of file_get_data works,
* ideally in real code we should only be calling file_get_data with internal URLs.
*/
public function test_file_get_data_curlsecurityblockedhosts(): void {
global $CFG, $USER;
$testhtml = $this->getExternalTestFileUrl('/test.html');
$url = new \moodle_url($testhtml);
$host = $url->get_host();
set_config('curlsecurityblockedhosts', $host); // Blocks $host.
// Test a regular curl with the default security enabled does in fact get blocked.
$curl = new \curl();
$contents = $curl->get($testhtml);
$expected = $curl->get_security()->get_blocked_url_string();
self::assertSame($expected, $contents);
self::assertSame(0, $curl->get_errno());
if ($CFG->branch >= 403) {
self::assertDebuggingCalled(
"Blocked $testhtml: The URL is blocked. [user {$USER->id}]",
DEBUG_NONE
);
}
// Test file_get_data does return the page and isn't blocked by security.
$found = maintenance_static_page_io::file_get_data($url->out());
$expected = '47250a973d1b88d9445f94db4ef2c97a';
self::assertSame($expected, md5($found['contents']));
self::assertSame('text/html', $found['mime']);
}
/**
* Test remove css selector.
*/
+2 -2
View File
@@ -28,8 +28,8 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = "auth_outage";
$plugin->version = 2024081901; // The current plugin version (Date: YYYYMMDDXX).
$plugin->release = 2024081901; // Human-readable release information.
$plugin->version = 2024081902; // The current plugin version (Date: YYYYMMDDXX).
$plugin->release = 2024081902; // Human-readable release information.
$plugin->requires = 2017111309; // 2017111309 = T13, but this really requires 3.9 and higher.
$plugin->maturity = MATURITY_STABLE; // Suitable for PRODUCTION environments!
$plugin->supported = [39, 405]; // A range of branch numbers of supported moodle versions.
+9 -1
View File
@@ -39,7 +39,15 @@ defined('MOODLE_INTERNAL') || die();
<b><?php echo get_string('infountil', 'auth_outage'); ?></b>
<?php echo userdate($viewbag['outage']->stoptime, get_string('datetimeformat', 'auth_outage')); ?>
</div>
<div class="auth_outage_info_description"><?php echo $viewbag['outage']->get_description(); ?></div>
<div class="auth_outage_info_description">
<?php
echo format_text(
$viewbag['outage']->get_description(),
FORMAT_HTML,
['context' => context_system::instance()]
);
?>
</div>
<?php if ($viewbag['admin']) : ?>
<?php