From 9282e770f4db5fa670a5d1f9ca16c38aa7e31119 Mon Sep 17 00:00:00 2001 From: Matt Lowe Date: Sat, 16 Nov 2013 16:36:46 +0000 Subject: [PATCH] Added first version of the IonCube encoder extension task This allows you to encrypt a project prior to pushing it to a server. --- Mage/Task/BuiltIn/Ioncube/EncryptTask.php | 669 ++++++++++++++++++++++ 1 file changed, 669 insertions(+) create mode 100644 Mage/Task/BuiltIn/Ioncube/EncryptTask.php diff --git a/Mage/Task/BuiltIn/Ioncube/EncryptTask.php b/Mage/Task/BuiltIn/Ioncube/EncryptTask.php new file mode 100644 index 0000000..1b6779b --- /dev/null +++ b/Mage/Task/BuiltIn/Ioncube/EncryptTask.php @@ -0,0 +1,669 @@ + + * + */ +namespace Mage\Task\BuiltIn\Ioncube; + +use Mage\Task\AbstractTask; +use Mage\Console; +use Mage\Task\ErrorWithMessageException; +use Mage\Task\ErrorWithMessageException; +use Mage\Task\ErrorWithMessageException; +use Mage\Task\ErrorWithMessageException; +use Mage\Task\ErrorWithMessageException; + +class EncryptTask extends AbstractTask { + /** + * Name of the task + * + * @var string + */ + private $name = 'IonCube Encoder'; + + /** + * Array of default Ioncube + * options + * + * @var array + */ + private $default = array (); + + /** + * Array of YAML Ioncube + * options + * + * @var array + */ + private $yaml = array (); + + /** + * Array of file Ioncube + * options (taken from additional + * external config file if supplied) + * + * @var array + */ + private $file = array (); + + /** + * Source directory as used by + * main scripts + * + * @var string + */ + private $source = ''; + + /** + * Name of tempory folder + * for source code to be moved + * to. + * + * @var string + */ + private $ionSource = ''; + + /** + * How the default/yaml/project + * files interact with each other + * + * @var string + */ + private $ionOverRide = ''; + + /** + * Config options from the + * enviroment config file + * + * @var array + */ + private $mageConfig = array (); + + /** + * Final version of the IonCube + * options, after merging all + * sources together + * + * @var array + */ + private $ionCubeConfig = array (); + + /** + * Default encoder version to use + * for the ioncube encoder + * + * @var string + */ + private $encoder = 'ioncube_encoder54'; + + /** + * Name of tempory IonCube Project + * file, used when running encoder + * + * @var string + */ + private $projectFile = ''; + + /** + * (non-PHPdoc) + * + * @see \Mage\Task\AbstractTask::getName() + */ + public function getName() { + return $this->name; + } + + /** + * (non-PHPdoc) + * + * @see \Mage\Task\AbstractTask::init() + */ + public function init() { + // Get any options specfic to this task + $this->mageConfig = $this->getConfig ()->environmentConfig( 'ioncube' ); + /* + * Get all our IonCube config options + */ + $this->_getAllIonCubeConfigs(); + /* + * get the source code location + */ + $this->source = $this->getConfig ()->deployment ( 'from' ); + /* + * remove trailing slash if present + */ + if (substr ( $this->source, - 1 ) == DIRECTORY_SEPARATOR) { + $this->source = substr ( $this->source, 0, - 1 ); + } + /* + * Set the name of the folder that the un-encrypted + * files will be moved into + */ + $this->ionSource = $this->source . '.raw'; + /* + * set the filename for the ioncube project build file + */ + $this->projectFile = $this->source . '.prj'; + /* + * Check if we have been given an encoder script + * If not then we will just use the default + */ + if (isset ( $this->mageConfig ['encoder'] )) { + $this->encoder = $this->mageConfig ['encoder']; + } + /* + * Check if a differant merge type has been + * supplied, this defines how the 3 differant + * config files will be merged together. + */ + if (isset ( $this->mageConfig ['override'] )) { + $this->ionOverRide = $this->mageConfig ['override']; + } + /* + * now merge all the config options together + */ + $this->ionCubeConfig = $this->mergeConfigFiles (); + } + + /** + * This gets all the Ioncube configs + * Basicly it gets the default options contianed within this script + * It reads any project options from the enviroment.yaml config file + * It reads any additional options from external project file if set + * + * @return void + */ + private function _getAllIonCubeConfigs() + { + + /* + * Get a set of default IonCube options + */ + $this->default = $this->getOptionsDefault (); + /* + * Check if there is a 'project' section, + * if so then get the options from there + */ + if (isset ( $this->mageConfig ['project'] )) { + $this->yaml = $this->getOptionsFromYaml ( $this->mageConfig ['project'] ); + } else { + $this->yaml = array ( + 's' => array (), + 'p' => array () + ); + } + /* + * Check if a seperate projectfile has been specified, and if so + * then read the options from there. + */ + if (isset ( $this->mageConfig ['projectfile'] )) { + $this->file = $this->getOptionsFromFile ( $this->mageConfig ['projectfile'] ); + } else { + $this->file = array ( + 's' => array (), + 'p' => array () + ); + } + } + + + /** + * Encrypt the project + * Steps are as follows : + * Switch our current source dir to the ioncube srouce dir and create new empty dir to encrypt into + * Write the IonCube project file (this is the file that controls IonCube encoder) + * Run IonCube encoder + * Delete the tempory files that we created (so long as we hadn't set 'keeptemp') + * Return the result of the IonCube encoder + * + * @see \Mage\Task\AbstractTask::run() + * + * @return bool + */ + public function run() { + $this->switchSrcToTmp (); + $this->writeProjectFile (); + $result = $this->runIonCube (); + $this->deleteTmpFiles (); + return $result; + } + + /** + * Deletes tempory folder and project file + * if 'keeptemp' is set then skips delete + * process + * + * @throws ErrorWithMessageException If there was a problem with deleting the tempory files + * + * @return void + */ + private function deleteTmpFiles() { + if (isset ( $this->mageConfig ['keeptemp'] )) { + return; + } + $ret1 = $this->runCommandLocal ( 'rm -Rf ' . $this->ionSource, $out1 ); + $ret2 = $this->runCommandLocal ( 'rm ' . $this->projectFile, $out2 ); + if ($ret1 && $ret2) { + return; + } + throw new ErrorWithMessageException ( 'Error deleting temp files :' . $out1 . ' : ' . $out2, 40 ); + } + + /** + * Builds the ioncube command + * and runs it, returning the result + * + * @return bool + */ + private function runIonCube() { + $cli = $this->encoder . ' --project-file ' . $this->projectFile . ' ' . $this->ionSource . DIRECTORY_SEPARATOR.'*'; + $ret = $this->runCommandLocal ( $cli, $out ); + return $ret; + } + + /** + * Write the config options into + * a project file ready for use + * with ioncube cli + * + * @throws ErrorWithMessageException If it cant write the project file + * + * @return void + */ + private function writeProjectFile() { + // array used to build config file into + $out = array (); + // set the project destination + $out [] = '--into ' . $this->source . PHP_EOL; + // output the switches + foreach ( $this->ionCubeConfig ['s'] as $key => $value ) { + if ($value) { + // switch was set to true, so output it + $out [] = '--' . $key . PHP_EOL; + } + } + // output the options + foreach ( $this->ionCubeConfig ['p'] as $key => $value ) { + // check if we have an array of values + if (is_array ( $value )) { + foreach ( $value as $entry ) { + $out [] = '--' . $key . ' "' . $entry . '"' . PHP_EOL; + } + } else { + // ok just a normal single option + if (strlen ( $value ) > 0) { + $out [] = '--' . $key . ' "' . $value . '"' . PHP_EOL; + } + } + } + $ret = file_put_contents ( $this->projectFile, $out ); + if (! $ret) { + // something went wrong + $this->deleteTmpFiles (); + throw new ErrorWithMessageException ( 'Unable to create project file.', 20 ); + } + } + + /** + * This merges the 3 config arrays + * depending on the 'override' option + * + * @return array Final config array + */ + private function mergeConfigFiles() { + /* + * Options are the order the arrays are in + * F - Project File + * Y - YAML config options (enviroment file) + * D - Default options as stored in script + * + * more options could be added to make this a bit more flexable + * @todo I'm sure this could be combined into a loop to make it easier and shorter + * + */ + $s = array (); + $p = array (); + switch (strtolower ( $this->ionOverRide )) { + case 'fyd' : + // FILE / YAML / DEFAULT + $s = array_merge ( $this->file ['s'], $this->yaml ['s'], $this->default ['s'] ); + $p = array_merge ( $this->file ['p'], $this->yaml ['p'], $this->default ['p'] ); + break; + + case 'yfd' : + // YAML / FILE / DEFAULT + $s = array_merge ( $this->yaml ['s'], $this->file ['s'], $this->default ['s'] ); + $p = array_merge ( $this->yaml ['p'], $this->file ['p'], $this->default ['p'] ); + break; + case 'dyf' : + // DEFAULT / YAML / FILE + $s = array_merge ( $this->default ['s'], $this->yaml ['s'], $this->file ['s'] ); + $p = array_merge ( $this->default ['p'], $this->yaml ['p'], $this->file ['p'] ); + break; + case 'd' : + default : + // Use defaults only + $s = $this->default ['s']; + $p = $this->default ['p']; + break; + } + return array ( + 's' => $s, + 'p' => $p + ); + } + + /** + * Switches the original source + * code dir to tempory name + * and recreates orginal dir + * allows encryption to be done + * into source dir, so other functions + * work without changing + * + * @throws ErrorWithMessageException If source dir can't be renamed + * @throws ErrorWithMessageException If original source dir cant be created + * + * @return bool + */ + private function switchSrcToTmp() { + echo "Switching :" . $this->source . " -> "; + echo "To :" . $this->ionSource . "\n"; + $ret = $this->runCommandLocal ( 'mv ' . $this->source . ' ' . $this->ionSource, $out ); + if (! $ret) { + throw new ErrorWithMessageException ( 'Cant create tmp dir :' . $out, $ret ); + } + $ret = $this->runCommandLocal ( 'mkdir -p ' . $this->source, $out ); + if (! $ret) { + throw new ErrorWithMessageException ( 'Cant re-create dir :' . $out, $ret ); + } + return true; + } + + /** + * Reads a set of options taken from the YAML config + * Compares keys against the default ioncube settings + * if a key doesnt appear in the default options, it + * is ignored + * + * return array + */ + private function getOptionsFromYaml($options) { + $s = array (); + $p = array (); + foreach ( $options as $key => $value ) { + if (array_key_exists ( $key, $this->default ['s'] )) { + $s [$key] = true; + } + if (array_key_exists ( $key, $this->default ['p'] )) { + $p [$key] = $value; + } + } + return array ( + 's' => $s, + 'p' => $p + ); + } + + /** + * reads an existing ioncube project + * file. + * + * @return array + */ + private function getOptionsFromFile($fileName) { + $s = array (); + $p = array (); + $fileContents = file_get_contents ( $fileName ); + /* + * split the config file on every occurance of '--' at start of a line + * Adds a PHP_EOL at the start, so we can catch the first '--' + */ + $entrys = explode ( PHP_EOL . '--', PHP_EOL . $fileContents ); + foreach ( $entrys as $line ) { + $line = trim ( $line ); + if ($line != '') { + /* + * get position of first space + * so we can split the options out + */ + $str = strpos ( $line, ' ' ); + if ($str === false) { + /* + * Ok, no spaces found, so take this as a single line + */ + $str = strlen ( $line ); + } + $key = substr ( $line, $str ); + $value = substr ( $line, $str + 1 ); + if ((array_key_exists ( $key, $this->default ['s'] ))) { + /* + * ok this key appears in the switch config + * so store it as a switch + */ + $s [$key] = true; + } + if ((array_key_exists ( $key, $this->default ['p'] ))) { + /* + * Ok this key exists in the parameter section, + * So store it allong with its value + */ + $p [$key] = $this->splitParam ( $value ); + } + } + } + return array ( + 's' => $s, + 'p' => $p + ); + } + + /** + * Takes supplied line and splits it if required + * into an array + * returns ether the array, or a plain + * string. + * Allows options to be spread accross several lines + * + * @param $string String to split + * + * @return mixed + */ + private function splitParam($string) { + $split = explode ( PHP_EOL, $string ); + if ($split === false) { + // nothing found, so return a blank string + return ''; + } + if (count ( $split ) == 1) { + return $split [0]; + } else { + return $split; + } + } + + /** + * returns an array of default ioncube options + * This is also used as a 'filter' for the YAML + * and Config files, if an option hasnt got an + * entry in this list, then it can not be set + * via the VAML or Config files + * + * @return array + */ + private function getOptionsDefault() { + $s = array (); + $p = array (); + // Set the switches + $s ['allow-encoding-into-source'] = false; + + $s ['ascii'] = false; + $s ['binary'] = true; + + $s ['replace-target'] = true; + $s ['merge-target'] = false; + $s ['rename-target'] = false; + $s ['update-target'] = false; + + $s ['only-include-encoded-files'] = false; + + $s ['use-hard-links'] = false; + + $s ['without-keeping-file-perms'] = false; + $s ['without-keeping-file-times'] = false; + $s ['without-keeping-file-owner'] = false; + + $s ['no-short-open-tags'] = false; + + $s ['ignore-strict-warnings'] = false; + $s ['ignore-deprecated-warnings'] = false; + + $s ['without-runtime-loader-support'] = false; + $s ['without-loader-check'] = false; + + $s ['disable-auto-prepend-append'] = true; + + $s ['no-doc-comments'] = true; + + // Now set the params + $p ['encrypt'] [] = '*.tpl.html'; + $p ['encode'] = array (); + $p ['copy'] = array (); + $p ['ignore'] = array ( + '.git', + '.svn', + '.mage', + '.gitignore', + '.gitkeep', + 'nohup.out' + ); + $p ['keep'] = array (); + $p ['obfuscate'] = ''; + $p ['obfuscation-key'] = ''; + $p ['obfuscation-exclusion-file'] = ''; + $p ['expire-in'] = '7d'; + $p ['expire-on'] = ''; + $p ['allowed-server'] = ''; + $p ['with-license'] = 'license.txt'; + $p ['passphrase'] = ''; + $p ['license-check'] = ''; + $p ['apply-file-user'] = ''; + $p ['apply-file-group'] = ''; + $p ['register-autoglobal'] = array (); + $p ['message-if-no-loader'] = ''; + $p ['action-if-no-loader'] = ''; + $p ['loader-path'] = ''; + $p ['preamble-file'] = ''; + $p ['add-comment'] = array (); + $p ['add-comments'] = ''; + $p ['loader-event'] = array (); + $p ['callback-file'] = ''; + $p ['property'] = ''; + $p ['propertys'] = ''; + $p ['include-if-property'] = array (); + $p ['optimise'] = 'max'; + $p ['shell-script-line'] = ''; + $p ['min-loader-version'] = ''; + + return array ( + 's' => $s, + 'p' => $p + ); + } +} +/** + * + * Example evirmonment YAML file : + * +#master +deployment: + user: marl + from: ./ + to: /var/www/test1 + source: + type: git + repository: git@bitbucket.org:myuser/myproject.git + from: master + ioncube: test + +releases: + enabled: true + symlink: current + directory: releases +hosts: + - localhost +tasks: + pre-deploy: + - ioncube/encrypt + on-deply: + post-deploy: + + ioncube: + override: dyf + keeptemp: + encoder: ioncube_encoder54 + projfile: project.prj + project: + replace-target: + binary: + ignore-deprecated-warnings: + ignore-strict-warnings: + ignore: + - _* + - templates_c/* + - *~ + - database.md + - specs/ + - composer.json + - README.md + - .git/ + - .project + - .settings/ + - .buildpath + message-if-no-loader: "System error No Loader" + passphrase: "My really secure passphrase" + encrypt: + - templates/* + add-comment: + - 'Comment 1' + - 'Comment 2' + - "(c) ACTweb 2013" + - "Draft Version" + + loader-event: + - corrupt-file=Corupted files + - expired-file=System needs updated + - no-permissions=Not allowed on this server + - clock-skew=Time incorect + - license-not-found=License not installed + - license-corrupt=Something wrong with your license + - license-expired=Out of time + - license-property-invalid=Invalid license data + - license-header-invalid=Files corupted + - license-server-invalid=Server problem + - unauth-including-file=Sorry these files can only be used within defined software + - unauth-included-file=Crtical Software Error + - unauth-append-prepend-file=System can not be used with PHP Prepend/Append set + + */