hotfix: Added aliases, implemented new runners
This commit is contained in:
41
src/Hotfix/AliasManager.php
Normal file
41
src/Hotfix/AliasManager.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use NoccyLabs\Hotfix\Exception\ConfigurationException;
|
||||
|
||||
class AliasManager
|
||||
{
|
||||
private static $aliases = [];
|
||||
|
||||
public static function registerFromConfig($file)
|
||||
{
|
||||
$config = parse_ini_file($file, true);
|
||||
if (!array_key_exists('alias', $config)) {
|
||||
return ;
|
||||
}
|
||||
foreach ($config['alias'] as $prefix=>$url) {
|
||||
self::registerAlias($prefix, $url);
|
||||
}
|
||||
}
|
||||
|
||||
public static function registerAlias($prefix, $url)
|
||||
{
|
||||
if (strpos($url,'{fix}')===false) {
|
||||
throw new ConfigurationException("The alias URL for {$prefix} does not contain the magic '{fix}' placeholder");
|
||||
}
|
||||
self::$aliases[$prefix] = $url;
|
||||
}
|
||||
|
||||
public static function getRegisteredAlises()
|
||||
{
|
||||
return self::$aliases;
|
||||
}
|
||||
|
||||
public static function getAlias($prefix)
|
||||
{
|
||||
if (!array_key_exists($prefix,self::$aliases)) return null;
|
||||
return self::$aliases[$prefix];
|
||||
}
|
||||
|
||||
}
|
83
src/Hotfix/Header.php
Normal file
83
src/Hotfix/Header.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use NoccyLabs\Hotfix\Exception\HotfixException;
|
||||
|
||||
class Header
|
||||
{
|
||||
/**
|
||||
* @var string The name of the hotfix
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string Information about this hotfix
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
/**
|
||||
* @var string The author of the hotfix
|
||||
*/
|
||||
protected $author;
|
||||
|
||||
/**
|
||||
* @var string The language this hotfix is written in, should match a runner.
|
||||
*/
|
||||
protected $language;
|
||||
|
||||
/**
|
||||
* @var array A list of expressions to verify that the system is compatible
|
||||
* with this fix
|
||||
*/
|
||||
protected $requirements;
|
||||
|
||||
public function __construct($header)
|
||||
{
|
||||
$header = Yaml::parse($header);
|
||||
|
||||
if (!(
|
||||
array_key_exists('hotfix', $header) &&
|
||||
array_key_exists('author', $header) &&
|
||||
array_key_exists('info', $header) &&
|
||||
array_key_exists('lang', $header)
|
||||
)) {
|
||||
throw new HotfixException("Invalid hotfix, header is missing hotfix, author, info or lang fields.");
|
||||
}
|
||||
|
||||
$this->name = $header['hotfix'];
|
||||
$this->author = $header['author'];
|
||||
$this->info = $header['info'];
|
||||
$this->language = $header['lang'];
|
||||
|
||||
if (array_key_exists('for', $header)) {
|
||||
$this->requirements = $header['for'];
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function getInfo()
|
||||
{
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function getRequirements()
|
||||
{
|
||||
return $this->requirements;
|
||||
}
|
||||
}
|
@ -3,23 +3,45 @@
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use NoccyLabs\Hotfix\Runner\RunnerFactory;
|
||||
|
||||
class Hotfix
|
||||
{
|
||||
|
||||
protected $signer;
|
||||
|
||||
protected $keyId;
|
||||
|
||||
protected $header;
|
||||
|
||||
protected $body;
|
||||
|
||||
public function __construct($hotfix, $signer)
|
||||
protected $signature;
|
||||
|
||||
protected $origin;
|
||||
|
||||
public function __construct($body, Header $header, Signature $signature, $origin)
|
||||
{
|
||||
$this->load($hotfix);
|
||||
$this->signer = $signer[0];
|
||||
$this->keyId = $signer[1];
|
||||
$this->header = $header;
|
||||
$this->body = $body;
|
||||
$this->signature = $signature;
|
||||
$this->origin = $origin;
|
||||
}
|
||||
|
||||
public function getHeader()
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function getHash()
|
||||
{
|
||||
return sha1($this->body);
|
||||
}
|
||||
|
||||
public function getSignature()
|
||||
{
|
||||
return $this->signature;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
protected function load($hotfix)
|
||||
@ -34,88 +56,11 @@ class Hotfix
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function apply()
|
||||
{
|
||||
if (!array_key_exists('lang', $this->header)) {
|
||||
$lang = 'bash';
|
||||
} else {
|
||||
$lang = strtolower($this->header['lang']);
|
||||
}
|
||||
|
||||
$script = null;
|
||||
$head = null;
|
||||
$foot = null;
|
||||
switch ($lang) {
|
||||
case 'bash':
|
||||
$exec = "/bin/bash";
|
||||
$head = file_get_contents(__DIR__."/../stubs/bash.stub");
|
||||
break;
|
||||
case 'php':
|
||||
$exec = "/usr/bin/env php";
|
||||
$head = "<?php require_once \"".__DIR__."/../../vendor/autoload.php\"; ".file_get_contents(__DIR__."/../stubs/php.stub");
|
||||
break;
|
||||
default:
|
||||
fprintf(STDERR, "Error: Unsupported language %s\n", $lang);
|
||||
return;
|
||||
}
|
||||
$tmpfile = sys_get_temp_dir()."/hotfix_".sha1($this->header['hotfix']);
|
||||
|
||||
file_put_contents($tmpfile, $head."\n".$this->body."\n".$foot);
|
||||
passthru($exec." ".$tmpfile);
|
||||
unlink($tmpfile);
|
||||
}
|
||||
|
||||
public function getRequirements()
|
||||
{
|
||||
if (!array_key_exists('for',$this->header)) {
|
||||
return [];
|
||||
}
|
||||
return $this->header['for'];
|
||||
}
|
||||
|
||||
public function getSignedBy()
|
||||
{
|
||||
if (!$this->signer) {
|
||||
return null;
|
||||
}
|
||||
return sprintf("%s <%s>",
|
||||
$this->signer['uids'][0]['name'],
|
||||
$this->signer['uids'][0]['email']
|
||||
);
|
||||
}
|
||||
|
||||
public function getKeyId()
|
||||
{
|
||||
return $this->keyId;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!array_key_exists('hotfix', $this->header)) {
|
||||
return "Untitled hotfix";
|
||||
}
|
||||
return $this->header['hotfix'];
|
||||
}
|
||||
|
||||
public function getInfo()
|
||||
{
|
||||
if (!array_key_exists('info', $this->header)) {
|
||||
return "No additional information";
|
||||
}
|
||||
return $this->header['info'];
|
||||
}
|
||||
|
||||
public function getAuthor()
|
||||
{
|
||||
if (!array_key_exists('author', $this->header)) {
|
||||
return "Unknown author";
|
||||
}
|
||||
return $this->header['author'];
|
||||
$runner = RunnerFactory::createRunner($this);
|
||||
$runner->apply();
|
||||
}
|
||||
|
||||
}
|
||||
|
75
src/Hotfix/HotfixLoader.php
Normal file
75
src/Hotfix/HotfixLoader.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use NoccyLabs\Hotfix\Exception\HotfixException;
|
||||
use NoccyLabs\Hotfix\Service\ServiceManager;
|
||||
use NoccyLabs\Hotfix\Hotfix\AliasManager;
|
||||
|
||||
class HotfixLoader
|
||||
{
|
||||
|
||||
const PGP_HEADER_TOKEN = '-----BEGIN PGP SIGNATURE-----';
|
||||
|
||||
public static function load($uri)
|
||||
{
|
||||
// First, check if this is a valid URL or filename
|
||||
if ((strpos($uri,"://")!==false) || (file_exists($uri))) {
|
||||
return self::loadFromUri($uri);
|
||||
}
|
||||
|
||||
if (strpos($uri,":")!==false) {
|
||||
list($prefix,$id) = explode(":",$uri,2);
|
||||
} else {
|
||||
throw new HotfixException("Invalid hotfix specified {$uri}");
|
||||
}
|
||||
|
||||
// Check services first, as aliases should for security reasons not
|
||||
// be allowed to overload services.
|
||||
if (($service = ServiceManager::getService($prefix))) {
|
||||
return self::loadFromService($service,$id);
|
||||
}
|
||||
|
||||
// Check aliases
|
||||
if (($alias = AliasManager::getAlias($prefix))) {
|
||||
$url = str_replace('{fix}', $id, $alias);
|
||||
return self::loadFromUri($url);
|
||||
}
|
||||
|
||||
throw new HotfixException("Could not read hotfix {$uri}");
|
||||
}
|
||||
|
||||
private static function loadFromUri($url)
|
||||
{
|
||||
$source = file_get_contents($url);
|
||||
return self::loadHotfix($source,$url);
|
||||
}
|
||||
|
||||
private static function loadFromService(ReaderInterface $service, $id)
|
||||
{
|
||||
$source = $service->read($id);
|
||||
return self::loadHotfix($source,$service->getName());
|
||||
}
|
||||
|
||||
private static function loadHotfix($source,$origin)
|
||||
{
|
||||
|
||||
// Check for a signature header
|
||||
if (false === strpos($source, self::PGP_HEADER_TOKEN)) {
|
||||
$body = $source;
|
||||
$signature = new Signature($body,null);
|
||||
} else {
|
||||
list ($body, $sig) = explode(self::PGP_HEADER_TOKEN, $source);
|
||||
$sig = self::PGP_HEADER_TOKEN.$sig;
|
||||
$signature = new Signature($body,$sig);
|
||||
}
|
||||
|
||||
// Extract header next
|
||||
list ($header,$body) = explode("\n---\n", $body, 2);
|
||||
$header = new Header($header);
|
||||
|
||||
$hotfix = new Hotfix($body, $header, $signature, $origin);
|
||||
return $hotfix;
|
||||
}
|
||||
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
class Loader
|
||||
{
|
||||
|
||||
protected $signedBy;
|
||||
|
||||
protected $loaders = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addLoader(new Loader\FileLoader());
|
||||
$this->addLoader(new Loader\HttpLoader());
|
||||
$this->addLoader(new Loader\GistLoader());
|
||||
$this->addLoader(new Loader\PastebinLoader());
|
||||
}
|
||||
|
||||
public function addLoader(Loader\LoaderInterface $loader)
|
||||
{
|
||||
$this->loaders[] = $loader;
|
||||
}
|
||||
|
||||
public function getLoaders()
|
||||
{
|
||||
return $this->loaders;
|
||||
}
|
||||
|
||||
public function load($fix, $insecure=false)
|
||||
{
|
||||
foreach ($this->loaders as $loader) {
|
||||
$hotfix = $loader->load($fix);
|
||||
if ($hotfix === false) {
|
||||
continue;
|
||||
}
|
||||
$sigHeader = '-----BEGIN PGP SIGNATURE-----';
|
||||
if (false === strpos($hotfix, $sigHeader)) {
|
||||
if (!$insecure) {
|
||||
throw new \Exception("Hotfix is not signed");
|
||||
}
|
||||
$body = $hotfix;
|
||||
$signer = null;
|
||||
} else {
|
||||
list ($body, $signature) = explode($sigHeader, $hotfix);
|
||||
$signature = $sigHeader.$signature;
|
||||
if (!$insecure) {
|
||||
$signer = $this->verifySignature($body, $signature);
|
||||
} else {
|
||||
$signer = null;
|
||||
}
|
||||
}
|
||||
return new Hotfix($body, $signer);
|
||||
}
|
||||
fprintf(STDERR, "Error: Couldn't load '%s'\n", $fix);
|
||||
}
|
||||
|
||||
protected function verifySignature($body, $signature)
|
||||
{
|
||||
$gpg = gnupg_init();
|
||||
|
||||
$sigInfo = gnupg_verify($gpg, $body, $signature);
|
||||
|
||||
if ($sigInfo === false) {
|
||||
throw new \Exception("Hotfix signature is not valid!");
|
||||
}
|
||||
|
||||
$fingerprint = $sigInfo[0]['fingerprint'];
|
||||
$keyInfo = gnupg_keyinfo($gpg, $fingerprint);
|
||||
|
||||
if (empty($keyInfo)) {
|
||||
throw new \Exception("Unknown signer (key id {$sigInfo[0]['fingerprint']})");
|
||||
}
|
||||
|
||||
$subKeys = $keyInfo[0]['subkeys'];
|
||||
$keyId = null;
|
||||
foreach ($subKeys as $subKey) {
|
||||
if ($subKey['fingerprint'] == $fingerprint) {
|
||||
$keyId = $subKey['keyid'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [ $keyInfo[0], $keyId ];
|
||||
}
|
||||
}
|
98
src/Hotfix/Signature.php
Normal file
98
src/Hotfix/Signature.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\Hotfix\Hotfix;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Signature
|
||||
{
|
||||
/** @var bool Whether the signature is valid */
|
||||
protected $valid;
|
||||
|
||||
/** @var string|null The name of the key used to sign */
|
||||
protected $signer;
|
||||
|
||||
/** @var string|null The ID of the key used to sign */
|
||||
protected $keyId;
|
||||
|
||||
protected $error;
|
||||
|
||||
protected $body;
|
||||
|
||||
protected $signature;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param bool $valid Whether the signature is valid
|
||||
* @param string $signer For a valid signature, the name of the signer
|
||||
* @param string $keyId For a valid signature, the key ID
|
||||
*/
|
||||
public function __construct($body, $signature)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->signature = $signature;
|
||||
$this->verify();
|
||||
}
|
||||
|
||||
public function verify()
|
||||
{
|
||||
if (!$this->signature) {
|
||||
$this->error = "Hotfix is not signed!";
|
||||
return;
|
||||
}
|
||||
|
||||
$gpg = gnupg_init();
|
||||
|
||||
$sigInfo = gnupg_verify($gpg, $this->body, $this->signature);
|
||||
|
||||
if ($sigInfo === false) {
|
||||
$this->error = "Invalid signature";
|
||||
return;
|
||||
}
|
||||
|
||||
$fingerprint = $sigInfo[0]['fingerprint'];
|
||||
$keyInfo = gnupg_keyinfo($gpg, $fingerprint);
|
||||
|
||||
if (empty($keyInfo)) {
|
||||
$this->error = "Unknown signer (key id {$sigInfo[0]['fingerprint']})";
|
||||
return;
|
||||
}
|
||||
|
||||
$subKeys = $keyInfo[0]['subkeys'];
|
||||
$keyId = null;
|
||||
foreach ($subKeys as $subKey) {
|
||||
if ($subKey['fingerprint'] == $fingerprint) {
|
||||
$keyId = $subKey['keyid'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$signerInfo = sprintf("%s (%s)", $keyInfo[0]['uids'][0]['name'], $keyInfo[0]['uids'][0]['email']);
|
||||
|
||||
$this->valid = true;
|
||||
$this->signer = $signerInfo;
|
||||
$this->keyId = $keyId;
|
||||
}
|
||||
|
||||
public function isValid()
|
||||
{
|
||||
return ($this->valid === true);
|
||||
}
|
||||
|
||||
public function getSigner()
|
||||
{
|
||||
return $this->signer;
|
||||
}
|
||||
|
||||
public function getKeyId()
|
||||
{
|
||||
return $this->keyId;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user