diff --git a/doc/06-config.md b/doc/06-config.md
index 47e1a6f5e..70caf4432 100644
--- a/doc/06-config.md
+++ b/doc/06-config.md
@@ -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
diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php
index 93253b0b3..9fb0be111 100644
--- a/src/Composer/Factory.php
+++ b/src/Composer/Factory.php
@@ -404,6 +404,17 @@ 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(''.$lockFile.' is present but ignored as the "lock" config option is disabled.');
+ }
+
+ $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 ($composer instanceof Composer) {
$globalComposer = null;
if (realpath($config->get('home')) !== $cwd) {
@@ -416,17 +427,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(''.$lockFile.' is present but ignored as the "lock" config option is disabled.');
- }
-
- $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);
diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php
index bb87d6beb..751130e7f 100644
--- a/src/Composer/Installer/PluginInstaller.php
+++ b/src/Composer/Installer/PluginInstaller.php
@@ -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->composer->getPluginManager()->isPluginAllowed($package->getName(), false);
+ }
+
+ return parent::prepare($type, $package, $prevPackage);
+ }
+
/**
* @inheritDoc
*/
diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php
index 1763f42b4..12101b643 100644
--- a/src/Composer/Package/Locker.php
+++ b/src/Composer/Package/Locker.php
@@ -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
*/
diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php
index b93164168..f83353829 100644
--- a/src/Composer/Plugin/PluginManager.php
+++ b/src/Composer/Plugin/PluginManager.php
@@ -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,9 +76,8 @@ class PluginManager
$this->globalComposer = $globalComposer;
$this->versionParser = new VersionParser();
$this->disablePlugins = $disablePlugins;
-
- $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'));
- $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false);
+ $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker());
+ $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false, $globalComposer !== null ? $globalComposer->getLocker() : null);
}
/**
@@ -648,12 +648,12 @@ class PluginManager
}
/**
- * @param array|bool|null $allowPluginsConfig
+ * @param array|bool $allowPluginsConfig
* @return array|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('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');
- $this->io->writeError('You have until July 2022 to add the setting. Composer will then switch the default behavior to disallow all plugins.');
- $warned['all'] = true;
- }
+ $this->io->writeError('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');
+ $this->io->writeError('This warning will become an exception once you run composer update!');
+
+ $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;
+ if ($this->io->isInteractive()) {
+ $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer;
- $this->io->writeError(''.$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins');
- $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 "'.$package.'>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $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;
- }
+ $this->io->writeError(''.$package.($isGlobalPlugin ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins');
+ $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 "'.$package.'>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $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(''.$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');
- $this->io->writeError('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)');
}
- $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'
+ );
}
}
diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php
index 5d22400e8..cd717fa24 100644
--- a/src/Composer/Util/GitHub.php
+++ b/src/Composer/Util/GitHub.php
@@ -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('No token given, aborting.');
$this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "');