diff --git a/config-dist.php b/config-dist.php index 214f36ff1b2..9bc9b7256ca 100644 --- a/config-dist.php +++ b/config-dist.php @@ -164,6 +164,12 @@ $CFG->dboptions = array( $CFG->wwwroot = 'http://example.com/moodle'; +// Generally it is not advisable to use a wwwroot that ends in 'public'. +// This is because the 'public' directory is used to serve web-accessible content. +// Moodle looks for any URL which ends in 'public' and assumes that it is a misconfiguration. +// In the event that there is a need to have a wwwroot that ends in 'public', the +// following setting can be used to override this check. +$CFG->wwwrootendsinpublic = false; //========================================================================= // 3. DATA FILES LOCATION diff --git a/public/lang/en/error.php b/public/lang/en/error.php index 39e2def2638..a0f3f5b9ee6 100644 --- a/public/lang/en/error.php +++ b/public/lang/en/error.php @@ -648,6 +648,7 @@ $string['wrongusernamepassword'] = 'Wrong user/password'; $string['wrongzipfilename'] = 'Wrong ZIP file name'; $string['wscouldnotcreateecoursenopermission'] = 'WS - Could not create course - No permission'; $string['wwwrootmismatch'] = 'Incorrect access detected, this server may be accessed only through "{$a}" address, sorry.
Please notify server administrator.'; +$string['wwwrootpublic'] = 'Detected incorrect $CFG->wwwroot in config.php, it should not end in /public. See MDL-85816 for further information.'; $string['wwwrootslash'] = 'Detected incorrect $CFG->wwwroot in config.php, it must not contain trailing slash.
Please notify server administrator.'; $string['xmldberror'] = 'XMLDB error!'; $string['alreadyloggedin'] = 'You are already logged in as {$a}, you need to log out before logging in as different user.'; diff --git a/public/lib/classes/setup.php b/public/lib/classes/setup.php new file mode 100644 index 00000000000..72cab6d40dc --- /dev/null +++ b/public/lib/classes/setup.php @@ -0,0 +1,83 @@ +. + +namespace core; + +use core\exception\moodle_exception; + +/** + * Core setup functionality for Moodle. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class setup { + /** + * Check the validity of the wwwroot, throwing a Moodle exception if invalid. + * + * @throws moodle_exception + * @return true + */ + public function validate_wwwroot(): bool { + if ($this->does_wwwroot_end_in_slash()) { + // The wwwroot should not end in a slash as this may suggest a misconfiguration. + throw new moodle_exception('wwwrootslash', 'error'); + } + + if (!$this->can_wwwroot_end_in_public() && $this->does_wwwroot_end_in_public()) { + // The wwwroot should not end in /public as this may suggest a misconfiguration. + // There may be legitimate sites out there that currently do this but it is not recommended. + // Where a site _does_ need to do this, then they can set the $CFG->wwwrootendsinpublic var to true. + throw new moodle_exception('wwwrootpublic', 'error'); + } + + return true; + } + + /** + * Whether the wwwroot is allowed to end in public. + * + * @return bool + */ + protected function can_wwwroot_end_in_public(): bool { + global $CFG; + + return property_exists($CFG, 'wwwrootendsinpublic') && $CFG->wwwrootendsinpublic; + } + + /** + * Detect whether the wwwroot ends in /public. + * + * @return bool + */ + protected function does_wwwroot_end_in_public(): bool { + global $CFG; + + return substr($CFG->wwwroot, -7) == '/public'; + } + + /** + * Detect whether the wwwroot ends in /. + * + * @return bool + */ + protected function does_wwwroot_end_in_slash(): bool { + global $CFG; + + return str_ends_with($CFG->wwwroot, '/'); + } +} diff --git a/public/lib/setuplib.php b/public/lib/setuplib.php index 9ebddda6b9d..3f191ebbe4d 100644 --- a/public/lib/setuplib.php +++ b/public/lib/setuplib.php @@ -634,10 +634,10 @@ function hash_local_config_cache() { function initialise_fullme() { global $CFG, $FULLME, $ME, $SCRIPT, $FULLSCRIPT; - // Detect common config error. - if (substr($CFG->wwwroot, -1) == '/') { - throw new \moodle_exception('wwwrootslash', 'error'); - } + $setuphelper = \core\di::get(\core\setup::class); + + // Detect common config errors in the wwwroot. + $setuphelper->validate_wwwroot(); if (CLI_SCRIPT) { initialise_fullme_cli(); diff --git a/public/lib/tests/setup_test.php b/public/lib/tests/setup_test.php new file mode 100644 index 00000000000..ea6786b37a8 --- /dev/null +++ b/public/lib/tests/setup_test.php @@ -0,0 +1,110 @@ +. + +namespace core; + +/** + * Tests for the \core\setup class. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[\PHPUnit\Framework\Attributes\CoversClass(\core\setup::class)] +final class setup_test extends \advanced_testcase { + #[\PHPUnit\Framework\Attributes\DataProvider('wwwroot_validity_provider')] + public function test_wwwroot_ends_in_slash( + string $wwwroot, + bool $mayendinpublic, + bool $valid, + ?string $exceptionstring = null, + ): void { + global $CFG; + + $this->resetAfterTest(true); + $CFG->wwwroot = $wwwroot; + $CFG->wwwrootendsinpublic = $mayendinpublic; + + if (!$valid) { + $this->expectException(\core\exception\moodle_exception::class); + $this->expectExceptionMessage($exceptionstring); + } + + $this->assertTrue(di::get(setup::class)->validate_wwwroot()); + } + + /** + * Data provider for test_wwwroot_ends_in_slash. + * + * @return \Generator + */ + public static function wwwroot_validity_provider(): \Generator { + foreach (['https', 'http'] as $protocol) { + yield "Valid {$protocol} wwwroot on root domain" => [ + "{$protocol}://example.com", + false, + true, + ]; + + yield "Valid {$protocol} wwwroot on sub domain" => [ + "{$protocol}://moodle.example.com", + false, + true, + ]; + + yield "Valid {$protocol} wwwroot on directory" => [ + "{$protocol}://example.com/moodle", + false, + true, + ]; + } + + yield "Root domain ends in slash" => [ + "https://example.com/", + false, + false, + get_string('wwwrootslash', 'error'), + ]; + + yield "Sub domain ends in slash" => [ + "https://moodle.example.com/", + false, + false, + get_string('wwwrootslash', 'error'), + ]; + + yield "Directory ends in slash" => [ + "https://example.com/moodle/", + false, + false, + get_string('wwwrootslash', 'error'), + ]; + + yield "Directory ends in public and should not" => [ + "https://example.com/public", + false, + false, + get_string('wwwrootpublic', 'error'), + ]; + + yield "Directory ends in public and is allowed to" => [ + "https://example.com/public", + true, + true, + ]; + } +}