diff --git a/public/admin/tool/mfa/classes/manager.php b/public/admin/tool/mfa/classes/manager.php index 74e9ca7216a..ab557b6f796 100644 --- a/public/admin/tool/mfa/classes/manager.php +++ b/public/admin/tool/mfa/classes/manager.php @@ -51,8 +51,49 @@ class manager { 'tool_mfa' => [ 'guidance', ], + 'theme_*' => [ + 'loginbackgroundimage', + ], ]; + /** + * Returns allowed fileareas for a component. + * + * Supports exact component matches and wildcard suffix matches such as `theme_*`. + * + * @param string $component the component to check. + * @return array|null the allowed fileareas, or null if the component is not allowed. + */ + protected static function get_allowed_fileareas_for_component(string $component): ?array { + if (array_key_exists($component, static::ALLOWED_COMPONENTS)) { + return static::ALLOWED_COMPONENTS[$component]; + } + + foreach (static::ALLOWED_COMPONENTS as $pattern => $fileareas) { + if (!str_ends_with($pattern, '*')) { + continue; + } + + $prefix = substr($pattern, 0, -1); + if ($prefix !== '' && str_starts_with($component, $prefix)) { + return $fileareas; + } + } + + return null; + } + + /** + * Checks whether a filearea is allowed. + * + * @param string $filearea the filearea to check. + * @param array $allowedfileareas the allowed fileareas for the component, where '*' allows all fileareas. + * @return bool + */ + protected static function is_allowed_filearea(string $filearea, array $allowedfileareas): bool { + return in_array('*', $allowedfileareas) || in_array($filearea, $allowedfileareas); + } + /** * Displays a debug table with current factor information. * @@ -469,13 +510,15 @@ class manager { $component = clean_param(array_shift($args), PARAM_COMPONENT); $filearea = clean_param(array_shift($args), PARAM_AREA); + $allowedfileareas = static::get_allowed_fileareas_for_component($component); + // Check allowed components. - if (!array_key_exists($component, static::ALLOWED_COMPONENTS)) { + if ($allowedfileareas === null) { return self::REDIRECT; } // Check allowed fileareas. - if (!in_array($filearea, static::ALLOWED_COMPONENTS[$component])) { + if (!static::is_allowed_filearea($filearea, $allowedfileareas)) { return self::REDIRECT; } diff --git a/public/admin/tool/mfa/tests/manager_test.php b/public/admin/tool/mfa/tests/manager_test.php index 16e97565198..35f856e3f9a 100644 --- a/public/admin/tool/mfa/tests/manager_test.php +++ b/public/admin/tool/mfa/tests/manager_test.php @@ -277,6 +277,14 @@ final class manager_test extends \advanced_testcase { $guideurl = new \moodle_url('/pluginfile.php/1/tool_mfa/guidance/0/capybara.png'); $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($guideurl, false)); + // Access the allowed theme pluginfile area via wildcard component rules. + $themeurl = new \moodle_url('/pluginfile.php/1/theme_boost/loginbackgroundimage/0/background.jpg'); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($themeurl, false)); + + // Access a different theme pluginfile area which is not explicitly allowed. + $themeurl = new \moodle_url('/pluginfile.php/1/theme_classic/customfield/0/example.txt'); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($themeurl, false)); + // Access private area. $user3 = $this->getDataGenerator()->create_user(); $privateurl = new \moodle_url("/pluginfile.php/{$user3->id}/user/private/privatefile.png");