From a3e012d754fc7e045a0d13f654ceb804fe389d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 12 Dec 2023 22:22:52 +0100 Subject: [PATCH] Added an event listener for console commands which shows a warning if the console is called as root or as wrong user The idea is to prevent permission issues, by accidential calling the console wrong. --- .../ConsoleEnsureWebserverUserListener.php | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/EventListener/ConsoleEnsureWebserverUserListener.php diff --git a/src/EventListener/ConsoleEnsureWebserverUserListener.php b/src/EventListener/ConsoleEnsureWebserverUserListener.php new file mode 100644 index 00000000..8cb84967 --- /dev/null +++ b/src/EventListener/ConsoleEnsureWebserverUserListener.php @@ -0,0 +1,150 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * This event listener is called before any console command is executed and should ensure that the webserver + * user is used for all operations (and show a warning if not). This ensures that all files are created with the + * correct permissions. + * If the console is in non-interactive mode, a warning is shown, but the command is still executed. + */ +#[AsEventListener(ConsoleEvents::COMMAND)] +class ConsoleEnsureWebserverUserListener +{ + public function __construct( + #[Autowire('%kernel.project_dir%')] + private readonly string $project_root) + { + } + + public function __invoke(ConsoleCommandEvent $event): void + { + $input = $event->getInput(); + $io = new SymfonyStyle($event->getInput(), $event->getOutput()); + + //Check if we are (not) running as the webserver user + $webserver_user = $this->getWebserverUser(); + $running_user = $this->getRunningUser(); + + //Check if we are trying to run as root + if ($this->isRunningAsRoot()) { + $io->warning('You are running this command as root. This is not recommended, as it can cause permission problems. Please run this command as the webserver user "'. ($webserver_user ?? '??') . '" instead.'); + $io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.'); + if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) { + $event->disableCommand(); + } + + return; + } + + if ($webserver_user !== null && $running_user !== null && $webserver_user !== $running_user) { + $io->warning('You are running this command as the user "' . $running_user . '". This is not recommended, as it can cause permission problems. Please run this command as the webserver user "' . $webserver_user . '" instead.'); + $io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.'); + if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) { + $event->disableCommand(); + } + + return; + } + } + + private function isRunningAsRoot(): bool + { + //If we are on windows, we can't run as root + if (PHP_OS_FAMILY === 'Windows') { + return false; + } + + //Try to use the posix extension if available (Linux) + if (function_exists('posix_geteuid')) { + //Check if the current user is root + return posix_geteuid() === 0; + } + + //Otherwise we can't determine the username + return false; + } + + /** + * Determines the username of the user who started the current script if possible. + * Returns null if the username could not be determined. + * @return string|null + */ + private function getRunningUser(): ?string + { + //Try to use the posix extension if available (Linux) + if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) { + $id = posix_geteuid(); + + $user = posix_getpwuid($id); + //Try to get the username from the posix extension or return the id + return $user['name'] ?? ("ID: " . $id); + } + + //Otherwise we can't determine the username + return $_SERVER['USERNAME'] ?? $_SERVER['USER'] ?? null; + } + + private function getWebserverUser(): ?string + { + //Determine the webserver user, by checking who owns the uploads/ directory + $path_to_check = $this->project_root . '/uploads/'; + + //Determine the owner of this directory + if (!is_dir($path_to_check)) { + return null; + } + + //If we are on windows we need some special logic + if (PHP_OS_FAMILY === 'Windows') { + //If we have the COM extension available, we can use it to determine the owner + if (extension_loaded('com_dotnet')) { + $su = new \COM("ADsSecurityUtility"); // Call interface + $securityInfo = $su->GetSecurityDescriptor($path_to_check, 1, 1); // Call method + return $securityInfo->owner; // Get file owner + } + + //Otherwise we can't determine the owner + return null; + } + + //When we are on a POSIX system, we can use the fileowner function + $owner = fileowner($path_to_check); + + if (function_exists('posix_getpwuid')) { + $user = posix_getpwuid($owner); + //Try to get the username from the posix extension or return the id + return $user['name'] ?? ("ID: " . $owner); + } + + return null; + } + +} \ No newline at end of file