Merge branch '2.3'

pull/10933/head
Jordi Boggiano 2 years ago
commit 70b78137a9
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC

@ -1,3 +1,10 @@
### [2.3.9] 2022-07-05
* Fixed non-interactive behavior of allow-plugins to throw instead of continue with a warning to avoid broken installs (#10920)
* Fixed allow-plugins BC mode to ensure old lock files created pre-2.2 can be installed with only a warning but plugins fully loaded (#10920)
* Fixed deprecation notice (#10921)
* Fixed type errors (#10924)
### [2.3.8] 2022-07-01
* Fixed support for `cache-read-only` where the filesystem is not writable (#10906)
@ -104,6 +111,12 @@
* Fixed symlink creation in linux VM guest filesystems to be recognized by Windows (#10592)
* Performance improvement in pool optimization step (#10585)
### [2.2.16] 2022-07-05
* Fixed non-interactive behavior of allow-plugins to throw instead of continue with a warning to avoid broken installs (#10920)
* Fixed allow-plugins BC mode to ensure old lock files created pre-2.2 can be installed with only a warning but plugins fully loaded (#10920)
* Fixed deprecation notice (#10921)
### [2.2.15] 2022-07-01
* Fixed support for `cache-read-only` where the filesystem is not writable (#10906)
@ -1561,6 +1574,7 @@
* Initial release
[2.3.9]: https://github.com/composer/composer/compare/2.3.8...2.3.9
[2.3.8]: https://github.com/composer/composer/compare/2.3.7...2.3.8
[2.3.7]: https://github.com/composer/composer/compare/2.3.6...2.3.7
[2.3.6]: https://github.com/composer/composer/compare/2.3.5...2.3.6
@ -1572,6 +1586,7 @@
[2.3.0]: https://github.com/composer/composer/compare/2.3.0-RC2...2.3.0
[2.3.0-RC2]: https://github.com/composer/composer/compare/2.3.0-RC1...2.3.0-RC2
[2.3.0-RC1]: https://github.com/composer/composer/compare/2.2.9...2.3.0-RC1
[2.2.16]: https://github.com/composer/composer/compare/2.2.15...2.2.16
[2.2.15]: https://github.com/composer/composer/compare/2.2.14...2.2.15
[2.2.14]: https://github.com/composer/composer/compare/2.2.13...2.2.14
[2.2.13]: https://github.com/composer/composer/compare/2.2.12...2.2.13

@ -52,7 +52,15 @@ and **false** to disallow while suppressing further warnings and prompts.
}
```
You can also set the config option itself to `false` to disallow all plugins, or `true` to allow all plugins to run (NOT recommended).
You can also set the config option itself to `false` to disallow all plugins, or `true` to allow all plugins to run (NOT recommended). For example:
```json
{
"config": {
"allow-plugins": false
}
}
```
## use-include-path

@ -1129,7 +1129,12 @@ EOT
foreach ($arrayTree as $package) {
$io->write(sprintf('<info>%s</info>', $package['name']), false);
$io->write(' ' . $package['version'], false);
$io->write(' ' . strtok($package['description'], "\r\n"));
if (isset($package['description'])) {
$io->write(' ' . strtok($package['description'], "\r\n"));
} else {
// output newline
$io->write('');
}
if (isset($package['requires'])) {
$requires = $package['requires'];

@ -406,6 +406,20 @@ class Factory
// add installers to the manager (must happen after download manager is created since they read it out of $composer)
$this->createDefaultInstallers($im, $composer, $io, $process);
// init locker if possible
if ($composer instanceof Composer && isset($composerFile)) {
$lockFile = self::getLockFile($composerFile);
if (!$config->get('lock') && file_exists($lockFile)) {
$io->writeError('<warning>'.$lockFile.' is present but ignored as the "lock" config option is disabled.</warning>');
}
$locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process);
$composer->setLocker($locker);
} elseif ($composer instanceof Composer) {
$locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process);
$composer->setLocker($locker);
}
if ($composer instanceof Composer) {
$globalComposer = null;
if (realpath($config->get('home')) !== $cwd) {
@ -418,17 +432,6 @@ class Factory
$pm->loadInstalledPlugins();
}
// init locker if possible
if ($composer instanceof Composer && isset($composerFile)) {
$lockFile = self::getLockFile($composerFile);
if (!$config->get('lock') && file_exists($lockFile)) {
$io->writeError('<warning>'.$lockFile.' is present but ignored as the "lock" config option is disabled.</warning>');
}
$locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process);
$composer->setLocker($locker);
}
if ($fullLoad) {
$initEvent = new Event(PluginEvents::INIT);
$composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent);

@ -43,6 +43,19 @@ class PluginInstaller extends LibraryInstaller
return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
}
/**
* @inheritDoc
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
// fail install process early if it going to fail due to a plugin not being allowed
if ($type === 'install' || $type === 'update') {
$this->getPluginManager()->isPluginAllowed($package->getName(), false);
}
return parent::prepare($type, $package, $prevPackage);
}
/**
* @inheritDoc
*/

@ -318,6 +318,16 @@ class Locker
return $lockData['aliases'] ?? array();
}
/**
* @return string
*/
public function getPluginApi()
{
$lockData = $this->getLockData();
return isset($lockData['plugin-api-version']) ? $lockData['plugin-api-version'] : '1.1.0';
}
/**
* @return array<string, mixed>
*/

@ -18,6 +18,7 @@ use Composer\Installer\InstallerInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\Locker;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\PartialComposer;
@ -75,8 +76,7 @@ class PluginManager
$this->globalComposer = $globalComposer;
$this->versionParser = new VersionParser();
$this->disablePlugins = $disablePlugins;
$this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'));
$this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker());
$this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false);
}
@ -648,12 +648,12 @@ class PluginManager
}
/**
* @param array<string, bool>|bool|null $allowPluginsConfig
* @param array<string, bool>|bool $allowPluginsConfig
* @return array<non-empty-string, bool>|null
*/
private function parseAllowedPlugins($allowPluginsConfig): ?array
private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array
{
if (null === $allowPluginsConfig) {
if (array() === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) {
return null;
}
@ -674,22 +674,28 @@ class PluginManager
}
/**
* @internal
*
* @param string $package
* @param bool $isGlobalPlugin
* @return bool
*/
private function isPluginAllowed(string $package, bool $isGlobalPlugin): bool
public function isPluginAllowed(string $package, bool $isGlobalPlugin): bool
{
static $warned = array();
$rules = $isGlobalPlugin ? $this->allowGlobalPluginRules : $this->allowPluginRules;
if ($isGlobalPlugin) {
$rules = &$this->allowGlobalPluginRules;
} else {
$rules = &$this->allowPluginRules;
}
// This is a BC mode for lock files created pre-Composer-2.2 where the expectation of
// an allow-plugins config being present cannot be made.
if ($rules === null) {
if (!$this->io->isInteractive()) {
if (!isset($warned['all'])) {
$this->io->writeError('<warning>For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins</warning>');
$this->io->writeError('<warning>You have until July 2022 to add the setting. Composer will then switch the default behavior to disallow all plugins.</warning>');
$warned['all'] = true;
}
$this->io->writeError('<warning>For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins</warning>');
$this->io->writeError('<warning>This warning will become an exception once you run composer update!</warning>');
$rules = array('{}' => true);
// if no config is defined we allow all plugins for BC
return true;
@ -709,59 +715,54 @@ class PluginManager
return false;
}
if (!isset($warned[$package])) {
if ($this->io->isInteractive()) {
$composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer;
$this->io->writeError('<warning>'.$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>');
$attempts = 0;
while (true) {
// do not allow more than 5 prints of the help message, at some point assume the
// input is not interactive and bail defaulting to a disabled plugin
$default = '?';
if ($attempts > 5) {
$default = 'd';
}
switch ($answer = $this->io->ask('Do you trust "<fg=green;options=bold>'.$package.'</>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [<comment>y,n,d,?</comment>] ', $default)) {
case 'y':
case 'n':
case 'd':
$allow = $answer === 'y';
// persist answer in current rules to avoid prompting again if the package gets reloaded
if ($isGlobalPlugin) {
$this->allowGlobalPluginRules[BasePackage::packageNameToRegexp($package)] = $allow;
} else {
$this->allowPluginRules[BasePackage::packageNameToRegexp($package)] = $allow;
}
// persist answer in composer.json if it wasn't simply discarded
if ($answer === 'y' || $answer === 'n') {
$composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins.'.$package, $allow);
}
return $allow;
case '?':
default:
$attempts++;
$this->io->writeError(array(
'y - add package to allow-plugins in composer.json and let it run immediately',
'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts',
'd - discard this, do not change composer.json and do not allow the plugin to run',
'? - print help',
));
break;
}
if ($this->io->isInteractive()) {
$composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer;
$this->io->writeError('<warning>'.$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>');
$attempts = 0;
while (true) {
// do not allow more than 5 prints of the help message, at some point assume the
// input is not interactive and bail defaulting to a disabled plugin
$default = '?';
if ($attempts > 5) {
$this->io->writeError('Too many failed prompts, aborting.');
break;
}
switch ($answer = $this->io->ask('Do you trust "<fg=green;options=bold>'.$package.'</>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [<comment>y,n,d,?</comment>] ', $default)) {
case 'y':
case 'n':
case 'd':
$allow = $answer === 'y';
// persist answer in current rules to avoid prompting again if the package gets reloaded
$rules[BasePackage::packageNameToRegexp($package)] = $allow;
// persist answer in composer.json if it wasn't simply discarded
if ($answer === 'y' || $answer === 'n') {
$composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins.'.$package, $allow);
}
return $allow;
case '?':
default:
$attempts++;
$this->io->writeError(array(
'y - add package to allow-plugins in composer.json and let it run immediately',
'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts',
'd - discard this, do not change composer.json and do not allow the plugin to run',
'? - print help',
));
break;
}
} else {
$this->io->writeError('<warning>'.$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe. See https://getcomposer.org/allow-plugins</warning>');
$this->io->writeError('<warning>You can run "composer '.($isGlobalPlugin ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or keep it disabled and suppress this warning (false)</warning>');
}
$warned[$package] = true;
}
return false;
throw new \UnexpectedValueException(
$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL.
'You can run "composer '.($isGlobalPlugin ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL.
'See https://getcomposer.org/allow-plugins'
);
}
}

@ -101,9 +101,9 @@ class GitHub
$this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));
$this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth');
$token = trim($this->io->askAndHideAnswer('Token (hidden): '));
$token = trim((string) $this->io->askAndHideAnswer('Token (hidden): '));
if (!$token) {
if ($token === '') {
$this->io->writeError('<warning>No token given, aborting.</warning>');
$this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

@ -15,10 +15,12 @@ namespace Composer\Test\Plugin;
use Composer\Composer;
use Composer\Config;
use Composer\Installer\PluginInstaller;
use Composer\Json\JsonFile;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\Loader\JsonLoader;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Locker;
use Composer\Package\RootPackage;
use Composer\Plugin\PluginManager;
use Composer\IO\BufferIO;
@ -26,6 +28,7 @@ use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Test\TestCase;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
class PluginInstallerTest extends TestCase
{
@ -123,6 +126,7 @@ class PluginInstallerTest extends TestCase
$this->composer->setAutoloadGenerator($this->autoloadGenerator);
$this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io));
$this->composer->setPackage(new RootPackage('dummy/root', '1.0.0.0', '1.0.0'));
$this->composer->setLocker(new Locker($this->io, new JsonFile(Platform::getDevNull()), $im, '{}'));
$config->merge(array(
'config' => array(

Loading…
Cancel
Save