From 51387d7f93c2f7ed1615681b9e45c7ecfc400dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Anne?= Date: Wed, 22 Jan 2025 10:42:14 +0100 Subject: [PATCH] Fix routing of plugins public PHP scripts --- phpunit/functional/Glpi/Http/FirewallTest.php | 5 ++ src/Glpi/Http/Firewall.php | 3 ++ src/Glpi/Http/LegacyRouterTrait.php | 14 ++++++ .../Http/Listener/LegacyRouterListener.php | 47 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/phpunit/functional/Glpi/Http/FirewallTest.php b/phpunit/functional/Glpi/Http/FirewallTest.php index 87cf271eed6..407ca55b8e2 100644 --- a/phpunit/functional/Glpi/Http/FirewallTest.php +++ b/phpunit/functional/Glpi/Http/FirewallTest.php @@ -92,6 +92,9 @@ public function testComputeFallbackStrategy(): void ], 'foo.php' => '', ], + 'public' => [ + 'css.php' => '', + ], 'index.php' => '', ] ], @@ -129,6 +132,8 @@ public function testComputeFallbackStrategy(): void '/marketplace/myplugin/ajax/foo.php' => $default_for_plugins_legacy, '/marketplace/myplugin/front/dir/bar.php' => $default_for_plugins_legacy, '/marketplace/myplugin/front/foo.php' => $default_for_plugins_legacy, + '/marketplace/myplugin/public/css.php' => $default_for_plugins_legacy, // /public/css.php file accessed with its legacy path + '/marketplace/myplugin/css.php' => $default_for_plugins_legacy, // /public/css.php file accessed with the expected path '/marketplace/myplugin/index.php' => $default_for_plugins_legacy, '/marketplace/myplugin/PluginRoute' => $default_for_symfony_routes, diff --git a/src/Glpi/Http/Firewall.php b/src/Glpi/Http/Firewall.php index 2bc871b9a5f..d5e56432c1b 100644 --- a/src/Glpi/Http/Firewall.php +++ b/src/Glpi/Http/Firewall.php @@ -241,6 +241,9 @@ private function computeFallbackStrategyForPlugin(string $plugin_key, string $pl foreach ($this->plugins_dirs as $plugin_dir) { $expected_filenames = [ $plugin_dir . '/' . $plugin_key . $plugin_resource, + + // A PHP script located in the `/public` directory of a plugin will not have the `/public` prefix in its URL + $plugin_dir . '/' . $plugin_key . '/public' . $plugin_resource, ]; $resource_matches = []; if (\preg_match('#^(?.+\.php)(/.*)$#', $plugin_resource, $resource_matches)) { diff --git a/src/Glpi/Http/LegacyRouterTrait.php b/src/Glpi/Http/LegacyRouterTrait.php index 56de07b3ac7..7bc96970973 100644 --- a/src/Glpi/Http/LegacyRouterTrait.php +++ b/src/Glpi/Http/LegacyRouterTrait.php @@ -165,6 +165,20 @@ protected function extractPathAndPrefix(Request $request): array $path = $filepath; break; } + + // All plugins public resources should now be accessed without explicitely using the `/public` path, + // e.g. `/plugins/myplugin/public/css.php` -> `/plugins/myplugin/css.php`. + $path_matches = []; + if (preg_match('#^/plugins/(?[^\/]+)/public(?/.+)$#', $filepath, $path_matches) === 1) { + $new_path = sprintf('/plugins/%s%s', $path_matches['plugin_key'], $path_matches['plugin_resource']); + if ($this->getTargetFile($new_path) !== null) { + // To not break URLs than can be found in the wild (in e-mail, forums, external apps configuration, ...), + // please do not remove this behaviour before, at least, 2030 (about 5 years after GLPI 11.0.0 release). + Toolbox::deprecated('Plugins URLs containing the `/public` path are deprecated. You should remove the `/public` prefix from the URL.'); + $path = $new_path; + break; + } + } } if ($path === '') { diff --git a/tests/functional/Glpi/Http/Listener/LegacyRouterListener.php b/tests/functional/Glpi/Http/Listener/LegacyRouterListener.php index 5dbdf085a78..f3a0de719db 100644 --- a/tests/functional/Glpi/Http/Listener/LegacyRouterListener.php +++ b/tests/functional/Glpi/Http/Listener/LegacyRouterListener.php @@ -67,6 +67,9 @@ protected function fileProvider(): iterable 'file.test.php' => ' [ + 'css.php' => ' [ @@ -198,6 +201,14 @@ protected function fileProvider(): iterable 'target_pathinfo' => null, 'included' => true, ]; + + // Path to a PHP script located in the `/public` dir of a plugin + yield '/plugins/myplugin/front/some.dir/file.test.php/path/to/item' => [ + 'path' => '/plugins/myplugin/css.php', + 'target_path' => '/marketplace/myplugin/public/css.php', + 'target_pathinfo' => null, + 'included' => true, + ]; } /** @@ -607,6 +618,42 @@ function () use ($event) { $this->string($event->getRequest()->get('_glpi_file_to_load'))->isEqualTo(vfsStream::url('glpi/marketplace/myplugin/front/test.php')); } + public function testRunLegacyRouterFromDeprecatedPublicPath(): void + { + $structure = [ + 'plugins' => [ + 'myplugin' => [ + 'public' => [ + 'test.php' => 'newTestedInstance( + vfsStream::url('glpi'), + [vfsStream::url('glpi/marketplace'), vfsStream::url('glpi/plugins')] + ); + + $event = $this->getRequestEvent('/plugins/myplugin/public/test.php'); + + $this->when( + function () use ($event) { + $reporting_level = \error_reporting(E_ALL); // be sure to report deprecations + $this->testedInstance->onKernelRequest($event); + \error_reporting($reporting_level); // restore previous level + } + )->error + ->withMessage('Plugins URLs containing the `/public` path are deprecated. You should remove the `/public` prefix from the URL.') + ->withType(E_USER_DEPRECATED) + ->exists(); + + $this->string($event->getRequest()->attributes->get('_controller'))->isEqualTo(LegacyFileLoadController::class); + $this->string($event->getRequest()->get('_glpi_file_to_load'))->isEqualTo(vfsStream::url('glpi/plugins/myplugin/public/test.php')); + } + public function testRunLegacyRouterFromPluginInMultipleDirectories(): void { $structure = [