Skip to content
Snippets Groups Projects
Commit ec1c1e6a authored by omkar podey's avatar omkar podey Committed by Ted Bowman
Browse files

Issue #3299094 by omkar.podey, Wim Leers, tedbow, AjitS: Prevent staging areas...

Issue #3299094 by omkar.podey, Wim Leers, tedbow, AjitS: Prevent staging areas that nested in the active Composer project directory
parent 955794b1
No related branches found
No related tags found
No related merge requests found
......@@ -218,6 +218,12 @@ services:
class: Drupal\package_manager\Validator\SupportedReleaseValidator
tags:
- { name: event_subscriber }
package_manager.validator.stage_not_in_active:
class: Drupal\package_manager\Validator\StageNotInActiveValidator
arguments:
- '@package_manager.path_locator'
tags:
- { name: event_subscriber }
package_manager.validator.xdebug:
class: Drupal\package_manager\Validator\XdebugValidator
tags:
......
This diff is collapsed.
<?php
declare(strict_types = 1);
namespace Drupal\package_manager\Validator;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\PathLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates staging root is not a subdirectory of active.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
class StageNotInActiveValidator implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* The path locator service.
*
* @var \Drupal\package_manager\PathLocator
*/
protected $pathLocator;
/**
* Constructs a new StageNotInActiveValidator object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
*/
public function __construct(PathLocator $path_locator) {
$this->pathLocator = $path_locator;
}
/**
* Check if staging root is a subdirectory of active.
*/
public function checkNotInActive(PreOperationStageEvent $event) {
$project_root = $this->pathLocator->getProjectRoot();
$staging_root = $this->pathLocator->getStagingRoot();
if (str_starts_with($staging_root, $project_root)) {
$message = $this->t("Stage directory is a subdirectory of the active directory.");
$event->addError([$message]);
$event->stopPropagation();
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
PreCreateEvent::class => 'checkNotInActive',
StatusCheckEvent::class => 'checkNotInActive',
];
}
}
......@@ -6,6 +6,7 @@ namespace Drupal\package_manager_bypass;
use Drupal\Core\State\StateInterface;
use Drupal\package_manager\PathLocator as BasePathLocator;
use Drupal\package_manager\Path;
/**
* Overrides the path locator to return pre-set values for testing purposes.
......@@ -83,10 +84,16 @@ class PathLocator extends BasePathLocator {
* parent class.
*/
public function setPaths(?string $project_root, ?string $vendor_dir, ?string $web_root, ?string $staging_root): void {
$this->state->set(static::class . ' root', $project_root);
$this->state->set(static::class . ' vendor', $vendor_dir);
$this->state->set(static::class . ' web', $web_root);
$this->state->set(static::class . ' stage', $staging_root);
foreach ([$project_root, $staging_root] as $path) {
// @todo Remove the VFS check in https://www.drupal.org/i/3328234.
if (!empty($path) && !str_starts_with($path, 'vfs://') && !str_starts_with($path, 'sites/') && !Path::isAbsolute($path)) {
throw new \InvalidArgumentException('project_root and staging_root need to be absolute paths.');
}
}
$this->state->set(static::class . ' root', is_null($project_root) ? NULL : Path::canonicalize($project_root));
$this->state->set(static::class . ' vendor', is_null($vendor_dir) ? NULL : Path::canonicalize($vendor_dir));
$this->state->set(static::class . ' web', is_null($web_root) ? NULL : Path::canonicalize($web_root));
$this->state->set(static::class . ' stage', is_null($staging_root) ? NULL : Path::canonicalize($staging_root));
}
}
......@@ -4,7 +4,9 @@ declare(strict_types = 1);
namespace Drupal\Tests\package_manager\Traits;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\ValidationResult;
use Drupal\Tests\UnitTestCase;
/**
* Contains helpful methods for testing stage validators.
......@@ -44,8 +46,16 @@ trait ValidationTestTrait {
* - summary: (string|null) A summary string if there is one or NULL if not.
*/
protected function getValidationResultsAsArray(array $results): array {
return array_values(array_map(static function (ValidationResult $result) {
$messages = array_map(static function ($message): string {
$string_translation_stub = NULL;
if (is_a(get_called_class(), UnitTestCase::class, TRUE)) {
$string_translation_stub = $this->getStringTranslationStub();
}
return array_values(array_map(static function (ValidationResult $result) use ($string_translation_stub) {
$messages = array_map(static function ($message) use ($string_translation_stub): string {
// Support data providers in unit tests using TranslatableMarkup.
if ($message instanceof TranslatableMarkup && is_a(get_called_class(), UnitTestCase::class, TRUE)) {
$message = new TranslatableMarkup($message->getUntranslatedString(), $message->getArguments(), $message->getOptions(), $string_translation_stub);
}
return (string) $message;
}, $result->getMessages());
......
<?php
declare(strict_types = 1);
namespace Drupal\Tests\package_manager\Unit;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Path;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\Stage;
use Drupal\package_manager\ValidationResult;
use Drupal\package_manager\Validator\StageNotInActiveValidator;
use Drupal\Tests\package_manager\Traits\ValidationTestTrait;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\package_manager\Validator\StageNotInActiveValidator
* @group package_manager
* @internal
*/
class StageNotInActiveValidatorTest extends UnitTestCase {
use ValidationTestTrait;
/**
* @covers ::checkNotInActive
*
* @param \Drupal\package_manager\ValidationResult[] $expected
* The expected result.
* @param string $project_root
* The project root.
* @param string $staging_root
* The staging root.
*
* @dataProvider providerTestCheckNotInActive
*/
public function testCheckNotInActive(array $expected, string $project_root, string $staging_root) {
$path_locator_prophecy = $this->prophesize(PathLocator::class);
$path_locator_prophecy->getProjectRoot()->willReturn(Path::canonicalize($project_root));
$path_locator_prophecy->getStagingRoot()->willReturn(Path::canonicalize($staging_root));
$path_locator = $path_locator_prophecy->reveal();
$stage = $this->prophesize(Stage::class)->reveal();
$stage_not_in_active_validator = new StageNotInActiveValidator($path_locator);
$stage_not_in_active_validator->setStringTranslation($this->getStringTranslationStub());
$event = new PreCreateEvent($stage, ['some/path']);
$stage_not_in_active_validator->checkNotInActive($event);
$this->assertValidationResultsEqual($expected, $event->getResults());
}
/**
* Data provider for testCheckNotInActive().
*
* @return mixed[]
* The test cases.
*/
public function providerTestCheckNotInActive(): array {
$expected_symlink_validation_error = ValidationResult::createError([
t('Stage directory is a subdirectory of the active directory.'),
]);
return [
"Absolute paths which don't satisfy" => [
[$expected_symlink_validation_error],
"/var/root",
"/var/root/xyz",
],
"Absolute paths which satisfy" => [
[],
"/var/root",
"/home/var/root",
],
// @todo Replace `vfs://` with `/var/ in https://www.drupal.org/i/3328234.
'Stage with .. segments, outside active' => [
[],
"vfs://root/active",
"vfs://root/active/../stage",
],
'Stage without .. segments, outside active' => [
[],
"vfs://root/active",
"vfs://root/stage",
],
'Stage with .. segments, inside active' => [
[$expected_symlink_validation_error],
"vfs://root/active",
"vfs://root/active/../active/stage",
],
'Stage without .. segments, inside active' => [
[$expected_symlink_validation_error],
"vfs://root/active",
"vfs://root/active/stage",
],
'Stage with .. segments, outside active, active with .. segments' => [
[],
"vfs://root/active",
"vfs://root/active/../stage",
],
'Stage without .. segments, outside active, active with .. segments' => [
[],
"vfs://root/random/../active",
"vfs://root/stage",
],
'Stage with .. segments, inside active, active with .. segments' => [
[$expected_symlink_validation_error],
"vfs://root/random/../active",
"vfs://root/active/../active/stage",
],
'Stage without .. segments, inside active, active with .. segments' => [
[$expected_symlink_validation_error],
"vfs://root/random/../active",
"vfs://root/active/stage",
],
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment