Skip to content
Snippets Groups Projects

Issue #3266092: Make sure staging root is unique for each Drupal site

Files
7
@@ -5,6 +5,7 @@ namespace Drupal\package_manager;
@@ -5,6 +5,7 @@ namespace Drupal\package_manager;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Crypt;
 
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\TempStore\SharedTempStoreFactory;
use Drupal\Core\TempStore\SharedTempStoreFactory;
@@ -39,6 +40,16 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -39,6 +40,16 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
* operations on the staging area, and the stage must be "claimed" by its owner
* operations on the staging area, and the stage must be "claimed" by its owner
* before any such operations are done. A stage is claimed by presenting a
* before any such operations are done. A stage is claimed by presenting a
* unique token that is generated when the stage is created.
* unique token that is generated when the stage is created.
 
*
 
* Although a site can only have one staging area, it is possible for privileged
 
* users to destroy a stage created by another user. To prevent such actions
 
* from putting the file system into an uncertain state (for example, if a stage
 
* is destroyed by another user while it is still being created), the staging
 
* directory has a randomly generated name. For additional cleanliness, all
 
* staging directories created by a specific site live in a single directory,
 
* called the "staging root" and identified by the UUID of the current site
 
* (e.g. `/tmp/.package_managerSITE_UUID`), which is deleted when any stage
 
* created by that site is destroyed.
*/
*/
class Stage {
class Stage {
@@ -66,6 +77,13 @@ class Stage {
@@ -66,6 +77,13 @@ class Stage {
*/
*/
private const TEMPSTORE_APPLY_TIME_KEY = 'apply_time';
private const TEMPSTORE_APPLY_TIME_KEY = 'apply_time';
 
/**
 
* The config factory service.
 
*
 
* @var \Drupal\Core\Config\ConfigFactoryInterface
 
*/
 
protected $configFactory;
 
/**
/**
* The path locator service.
* The path locator service.
*
*
@@ -134,6 +152,8 @@ class Stage {
@@ -134,6 +152,8 @@ class Stage {
/**
/**
* Constructs a new Stage object.
* Constructs a new Stage object.
*
*
 
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 
* The config factory service.
* @param \Drupal\package_manager\PathLocator $path_locator
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* The path locator service.
* @param \PhpTuf\ComposerStager\Domain\BeginnerInterface $beginner
* @param \PhpTuf\ComposerStager\Domain\BeginnerInterface $beginner
@@ -151,7 +171,8 @@ class Stage {
@@ -151,7 +171,8 @@ class Stage {
* @param \Drupal\Component\Datetime\TimeInterface $time
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* The time service.
*/
*/
public function __construct(PathLocator $path_locator, BeginnerInterface $beginner, StagerInterface $stager, CommitterInterface $committer, FileSystemInterface $file_system, EventDispatcherInterface $event_dispatcher, SharedTempStoreFactory $shared_tempstore, TimeInterface $time) {
public function __construct(ConfigFactoryInterface $config_factory, PathLocator $path_locator, BeginnerInterface $beginner, StagerInterface $stager, CommitterInterface $committer, FileSystemInterface $file_system, EventDispatcherInterface $event_dispatcher, SharedTempStoreFactory $shared_tempstore, TimeInterface $time) {
 
$this->configFactory = $config_factory;
$this->pathLocator = $path_locator;
$this->pathLocator = $path_locator;
$this->beginner = $beginner;
$this->beginner = $beginner;
$this->stager = $stager;
$this->stager = $stager;
@@ -337,20 +358,17 @@ class Stage {
@@ -337,20 +358,17 @@ class Stage {
}
}
$this->dispatch(new PreDestroyEvent($this));
$this->dispatch(new PreDestroyEvent($this));
// Delete all directories in parent staging directory.
// Delete the staging root and everything in it.
$parent_stage_dir = static::getStagingRoot();
try {
if (is_dir($parent_stage_dir)) {
$this->fileSystem->deleteRecursive($this->getStagingRoot(), function (string $path): void {
try {
$this->fileSystem->chmod($path, 0777);
$this->fileSystem->deleteRecursive($parent_stage_dir, function (string $path): void {
});
$this->fileSystem->chmod($path, 0777);
}
});
catch (FileException $e) {
}
// Deliberately swallow the exception so that the stage will be marked
catch (FileException $e) {
// as available and the post-destroy event will be fired, even if the
// Deliberately swallow the exception so that the stage will be marked
// staging area can't actually be deleted. The file system service logs
// as available and the post-destroy event will be fired, even if the
// the exception, so we don't need to do anything else here.
// staging area can't actually be deleted. The file system service logs
// the exception, so we don't need to do anything else here.
}
}
}
$this->markAsAvailable();
$this->markAsAvailable();
$this->dispatch(new PostDestroyEvent($this));
$this->dispatch(new PostDestroyEvent($this));
@@ -498,14 +516,12 @@ class Stage {
@@ -498,14 +516,12 @@ class Stage {
*
*
* @throws \LogicException
* @throws \LogicException
* If this method is called before the stage has been created or claimed.
* If this method is called before the stage has been created or claimed.
*
* @todo Make this method public in https://www.drupal.org/i/3251972.
*/
*/
public function getStageDirectory(): string {
public function getStageDirectory(): string {
if (!$this->lock) {
if (!$this->lock) {
throw new \LogicException(__METHOD__ . '() cannot be called because the stage has not been created or claimed.');
throw new \LogicException(__METHOD__ . '() cannot be called because the stage has not been created or claimed.');
}
}
return static::getStagingRoot() . DIRECTORY_SEPARATOR . $this->lock[0];
return $this->getStagingRoot() . DIRECTORY_SEPARATOR . $this->lock[0];
}
}
/**
/**
@@ -515,8 +531,9 @@ class Stage {
@@ -515,8 +531,9 @@ class Stage {
* The absolute path of the directory containing the staging areas managed
* The absolute path of the directory containing the staging areas managed
* by this class.
* by this class.
*/
*/
protected static function getStagingRoot(): string {
protected function getStagingRoot(): string {
return FileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . '.package_manager';
$site_id = $this->configFactory->get('system.site')->get('uuid');
 
return FileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . '.package_manager' . $site_id;
}
}
}
}
Loading