diff --git a/bin/module.php b/bin/module.php new file mode 100755 index 0000000000000000000000000000000000000000..f69cafbdf80cb24dd58a8d714de16d6b14c9896a --- /dev/null +++ b/bin/module.php @@ -0,0 +1,77 @@ +#!/usr/bin/env php +<?php + +/* This is the base directory of the simpleSAMLphp installation. */ +$baseDir = dirname(dirname(__FILE__)); + +/* Add library autoloader. */ +require_once($baseDir . '/lib/_autoload.php'); + +if (count($argv) < 1) { + echo "Wrong number of parameters. Run: " . $argv[0] . " [install,show] url [branch]\n"; exit; +} + +// Needed in order to make session_start to be called before output is printed. +$session = SimpleSAML_Session::getInstance(); +$config = SimpleSAML_Configuration::getConfig('config.php'); + + +$action = $argv[1]; + + +function getModinfo() { + global $argv; + if (count($argv) < 2) + throw new Exception('Missing second parameter: URL/ID'); + return sspmod_core_ModuleDefinition::load($argv[2]); +} + +function getBranch() { + global $argv; + if (isset($argv[3])) return $argv[3]; + return NULL; +} + +switch($action) { + case 'install': + $mod = getModinfo(); + $installer = new sspmod_core_ModuleInstaller($mod); + $installer->install(getBranch()); + break; + + case 'remove': + $mod = getModinfo(); + $installer = new sspmod_core_ModuleInstaller($mod); + $installer->remove(getBranch()); + break; + + case 'upgrade': + $mod = getModinfo(); + $installer = new sspmod_core_ModuleInstaller($mod); + $installer->upgrade(getBranch()); + break; + + case 'upgrade-all' : + $mdir = scandir($config->getBaseDir() . 'modules/'); + foreach($mdir AS $md) { + if (!sspmod_core_ModuleDefinition::validId($md)) continue; + if (!sspmod_core_ModuleDefinition::isDefined($md)) continue; + $moduledef = sspmod_core_ModuleDefinition::load($md, 'remote'); + $installer = new sspmod_core_ModuleInstaller($moduledef); + + if ($moduledef->updateExists() || $moduledef->alwaysUpdate()) { + echo "Upgrading [" . $md . "]\n"; + $installer->upgrade(); + } else { + echo "No updates available for [" . $md . "]\n"; + } + } + break; + + default: + throw new Exception('Unknown action [' . $action . ']'); +} + + + + diff --git a/modules/core/lib/ModuleDefinition.php b/modules/core/lib/ModuleDefinition.php new file mode 100644 index 0000000000000000000000000000000000000000..9499ed408f2a59cb2e20fe51605d2dac1eb1f1c5 --- /dev/null +++ b/modules/core/lib/ModuleDefinition.php @@ -0,0 +1,122 @@ +<?php + +class sspmod_core_ModuleDefinition { + + public $def; + private static $cache; + + private function __construct($def) { + $this->def = $def; + $this->requireValidIdentifier(); + } + + public static function validId($id) { + return preg_match('|^[a-zA-Z_]+$|', $id); + } + + public static function isDefined($id) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + $filename = $basedir . 'modules/' . $id . '/definition.json'; + return (file_exists($filename)); + } + + public static function load($id, $force = NULL) { + + if (isset($cache[$id])) return $cache[$id]; + + if (self::validId($id)) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + $filename = $basedir . 'modules/' . $id . '/definition.json'; + if (!file_exists($filename)) + throw new Exception('Could not read definition file for module [' . $id . '] : ' . $filename); + $defraw = file_get_contents($filename); + $def = json_decode($defraw, TRUE); + + } elseif(preg_match('|^http(s)?://.*$|', $id)) { + $defraw = file_get_contents($id); + $def = json_decode($defraw, TRUE); + } else { + throw new Exception('Could not resolve [' . $id . '] as URL nor module identifier.'); + } + $cache[$id] = new sspmod_core_ModuleDefinition($def); + + + + if (isset($force)) { + if ($force === 'local') { + if(preg_match('|^http(s)?://.*$|', $id)) { + return self::load($def['id']); + } + } elseif($force === 'remote') { + if (self::validId($id)) { + if (!isset($def['definition'])) + throw new Exception('Could not load remote definition file for module [' . $id . ']'); + return self::load($def['definition']); + } + } + } + + return $cache[$id]; + } + + private function requireValidIdentifier() { + if (!isset($this->def['id'])) + throw new Exception('Missing [id] value in module definition'); + if (!preg_match('|^[a-zA-Z_]+$|', $this->def['id'])) + throw new Exception('Illegal characters in [id] in module definition'); + } + + public function getVersion($branch = NULL) { + if (!isset($this->def['access'])) throw new Exception('Missing [access] statement in module definition'); + if (!isset($this->def['branch'])) throw new Exception('Missing [branch] statement in module definition'); + + if (is_null($branch)) $branch = $this->def['branch']; + + if (!isset($this->def['access'][$branch])) throw new Exception('Missing [access] information for branch [' . var_export($branch, TRUE) . ']'); + if (!isset($this->def['access'][$branch]['version'])) throw new Exception('Missing version information in [access] in branch [' . var_export($branch, TRUE) . ']'); + + return $this->def['access'][$branch]['version']; + } + + public function alwaysUpdate($branch = NULL) { + $access = $this->getAccess($branch); + if ($access['type'] === 'svn') return TRUE; + return FALSE; + } + + public function getBranch($branch = NULL) { + if (!isset($this->def['branch'])) throw new Exception('Missing [branch] statement in module definition'); + if (is_null($branch)) $branch = $this->def['branch']; + return $branch; + } + + public function updateExists($branch = NULL) { + $branch = $this->getBranch($branch); + + $localDef = self::load($this->def['id'], 'local'); + $thisVersion = $localDef->getVersion($branch); + + $remoteDef = self::load($this->def['definition'], 'remote'); + $remoteVersion = $remoteDef->getVersion($branch); + + #echo ' Comparing versions local [' . $thisVersion . '] and remote [' . $remoteVersion . ']' . "\n"; + + return version_compare($remoteVersion, $thisVersion, '>'); + } + + + public function getAccess($branch = NULL) { + if (!isset($this->def['access'])) throw new Exception('Missing [access] statement in module definition'); + if (!isset($this->def['branch'])) throw new Exception('Missing [branch] statement in module definition'); + + if (is_null($branch)) $branch = $this->def['branch']; + + if (!isset($this->def['access'][$branch])) throw new Exception('Missing [access] information for branch [' . var_export($branch, TRUE) . ']'); + + return $this->def['access'][$branch]; + } + + +} \ No newline at end of file diff --git a/modules/core/lib/ModuleInstaller.php b/modules/core/lib/ModuleInstaller.php new file mode 100644 index 0000000000000000000000000000000000000000..d18f59b88456a815c333ec8899495a6c1b9d292a --- /dev/null +++ b/modules/core/lib/ModuleInstaller.php @@ -0,0 +1,188 @@ +<?php + +class sspmod_core_ModuleInstaller { + + public $module; + + public function __construct(sspmod_core_ModuleDefinition $module) { + $this->module = $module; + + } + + public function remove($branch = NULL) { + $access = $this->module->getAccess($branch); + + switch($access['type']) { + // case 'svn' : + // $this->requireInstalled(); + // $this->remove($access); + // break; + + default: + $this->requireInstalled(); + $this->removeModuleDir($access); + break; + + } + } + + public function install($branch = NULL) { + + $access = $this->module->getAccess($branch); + + switch($access['type']) { + case 'svn' : + $this->requireNotInstalled(); + $this->svnCheckout($access); + $this->enable(); + $this->prepareConfig(); + break; + + case 'zip' : + $this->requireNotInstalled(); + $this->zipLoad($access); + $this->enable(); + $this->prepareConfig(); + break; + + + default: + throw new Exception('Unknown access method type. Not one of [zip,tgz,svn]'); + + } + + } + + public static function exec($cmd) { + echo ' $ ' . $cmd . "\n"; + $output = shell_exec(escapeshellcmd($cmd)); + + if (empty($output)) return; + + $oa = explode("\n", $output); + + foreach($oa AS $ol) { + echo ' > ' . $ol . "\n"; + } + + } + + public function upgrade($branch = NULL) { + + $access = $this->module->getAccess($branch); + + switch($access['type']) { + case 'svn' : + $this->requireInstalled(); + $this->svnUpdate($access); + $this->enable(); + $this->prepareConfig(); + break; + + case 'zip' : + $this->requireInstalled(); + $this->zipLoad($access); + $this->enable(); + $this->prepareConfig(); + break; + + default: + throw new Exception('Unknown access method type. Not one of [zip,tgz,svn]'); + + } + + } + + public function dirExists() { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + + $dir = $basedir . 'modules/' . $this->module->def['id']; + + return (file_exists($dir) && is_dir($dir)); + } + + public function requireValidURL($url) { + if (!preg_match('|http(s)?://[a-zA-Z0-9_-/.]|', $url)) + throw new Exception('Invalid URL [' . $url . ']'); + } + + public function requireNotInstalled() { + if ($this->dirExists()) + throw new Exception('The module [' . $this->module->def['id'] . '] is already installed.'); + } + + public function requireInstalled() { + if (!$this->dirExists()) + throw new Exception('The module [' . $this->module->def['id'] . '] is not installed.'); + } + + public function svnCheckout($access) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + $cmd = "svn co " . escapeshellarg($access['url']) . " " . $basedir . "modules/" . $this->module->def['id']; + self::exec($cmd); + } + + public function svnUpdate($access) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + $cmd = "svn up " . $basedir . "modules/" . $this->module->def['id']; + self::exec($cmd); + } + + public function removeModuleDir($access) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + $cmd = "rm -rf " . $basedir . "modules/" . $this->module->def['id']; + self::exec($cmd); + } + + public function enable() { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + + $this->requireInstalled(); + + $cmd = "touch " . $basedir . "modules/" . $this->module->def['id'] . '/enable'; + self::exec($cmd); + } + + public function prepareConfig() { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + + $this->requireInstalled(); + + $dir = $basedir . "modules/" . $this->module->def['id'] . '/config-templates'; + if (!file_exists($dir)) return; + + $files = scandir($dir); + foreach($files AS $file) { + if(!preg_match('|^.*\.php|', $file)) continue; + + if (file_exists($basedir . 'config/' . $file)) { + echo "Configuration file [" . $file . "] already exists. Will not overwrite existing file.\n"; + continue; + } + + $cmd = 'cp ' . $dir . '/' . $file . ' ' . $basedir . 'config/'; + self::exec($cmd); + } + } + + public function zipLoad($access) { + $config = SimpleSAML_Configuration::getConfig('config.php'); + $basedir = $config->getBaseDir(); + + $zipfile = $access['url']; + $localfile = tempnam(sys_get_temp_dir(), 'ssp-module-'); + $filecontents = file_get_contents($zipfile); + file_put_contents($localfile, $filecontents); + + $cmd = "unzip -qo " . escapeshellarg($localfile) . " -d " . $basedir . "modules/"; + self::exec($cmd); + + } + +} \ No newline at end of file diff --git a/modules/modinfo/dictionaries/modinfo.definition.json b/modules/modinfo/dictionaries/modinfo.definition.json index 2391a52a4af3c9a1ba20a016680e07d81beb681b..1c20ab7ae9b210649c9b9fd8e3cb4685e00a730b 100644 --- a/modules/modinfo/dictionaries/modinfo.definition.json +++ b/modules/modinfo/dictionaries/modinfo.definition.json @@ -13,5 +13,15 @@ }, "modlist_disabled": { "en": "Disabled" + }, + "latest_version" : { + "en" : "Updated" + }, + "update_exists" : { + "en" : "Update available" + }, + "version" : { + "en" : "Version" } + } \ No newline at end of file diff --git a/modules/modinfo/templates/modlist.php b/modules/modinfo/templates/modlist.php index d20df10891589d4c15545e3a3ae67295dcb0c03e..253adc7fe5da93aa895476dee1ecb844b1cb76df 100644 --- a/modules/modinfo/templates/modlist.php +++ b/modules/modinfo/templates/modlist.php @@ -10,15 +10,29 @@ $this->includeAtTemplateBase('includes/header.php'); <h2><?php echo($this->data['header']); ?></h2> -<table> +<table class="modules" style="width: 100%"> <tr> -<th><?php echo($this->t('{modinfo:modinfo:modlist_name}')); ?></th> -<th><?php echo($this->t('{modinfo:modinfo:modlist_status}')); ?></th> +<th colspan="2"><?php echo($this->t('{modinfo:modinfo:modlist_name}')); ?></th> +<th ><?php echo($this->t('{modinfo:modinfo:modlist_status}')); ?></th> +<th colspan="2"><?php echo($this->t('{modinfo:modinfo:version}')); ?></th> </tr> <?php + +$i = 0; foreach($this->data['modules'] as $id => $info) { - echo('<tr>'); - echo('<td>' . htmlspecialchars($id) . '</td>'); + echo('<tr class="' . ($i++ % 2 == 0 ? 'odd' : 'even') . '">'); + + + if (isset($info['def'])) { + echo('<td><a href="http://simplesamlphp.org/modules/' . htmlspecialchars($id) . '">' . htmlspecialchars($info['def']->def['name']) . '</a></td>'); + } else { + echo('<td> </td>'); + } + + + echo('<td><tt>' . htmlspecialchars($id) . '</tt></td>'); + + if($info['enabled']) { echo('<td><img src="/' . $this->data['baseurlpath'] . 'resources/icons/silk/accept.png" alt="' . htmlspecialchars($this->t('{modinfo:modinfo:modlist_enabled}')) . '" /></td>'); @@ -26,6 +40,22 @@ foreach($this->data['modules'] as $id => $info) { echo('<td><img src="/' . $this->data['baseurlpath'] . 'resources/icons/silk/delete.png" alt="' . htmlspecialchars($this->t('{modinfo:modinfo:modlist_disabled}')) . '" /></td>'); } + + if (isset($info['def'])) { + echo('<td>' . htmlspecialchars($info['def']->getVersion()) . ' (' .htmlspecialchars($info['def']->getBranch()) . ')</td>'); + if ($info['def']->updateExists()) { + echo('<td><img style="display: inline" src="/' . $this->data['baseurlpath'] . 'resources/icons/silk/delete.png" alt="' . + htmlspecialchars($this->t('{modinfo:modinfo:update_exists}')) . '" /> ' . + htmlspecialchars($this->t('{modinfo:modinfo:update_exists}')) . '</td>'); + } else { + echo('<td><img style="display: inline" src="/' . $this->data['baseurlpath'] . 'resources/icons/silk/accept.png" alt="' . + htmlspecialchars($this->t('{modinfo:modinfo:latest_version}')) . '" /> ' . + htmlspecialchars($this->t('{modinfo:modinfo:latest_version}')) . '</td>'); + } + } else { + echo('<td colspan="2"> </td>'); + } + echo('</tr>'); } ?> diff --git a/modules/modinfo/www/index.php b/modules/modinfo/www/index.php index 74897f025f654b710bb8558bd987ad13a8e1c942..d8b4547fcef0ea2e203aa21b8dd09e31bdd8cc4c 100644 --- a/modules/modinfo/www/index.php +++ b/modules/modinfo/www/index.php @@ -9,7 +9,19 @@ foreach($modules as $m) { $modinfo[$m] = array( 'enabled' => SimpleSAML_Module::isModuleEnabled($m), ); + if (sspmod_core_ModuleDefinition::isDefined($m)) { + $modinfo[$m]['def'] = sspmod_core_ModuleDefinition::load($m); + } + +} + +function cmpa($a, $b) { + + if (isset($a['def']) && !isset($b['def'])) return -1; + if (isset($b['def']) && !isset($a['def'])) return 1; + return 0; } +uasort($modinfo, 'cmpa'); $config = SimpleSAML_Configuration::getInstance(); $t = new SimpleSAML_XHTML_Template($config, 'modinfo:modlist.php'); diff --git a/www/resources/default.css b/www/resources/default.css index b3e66f69d459ba7f9bc6f65076faaa76ff6689f0..984304fed3ddf28a0f800ea40822cd20440a1b65 100644 --- a/www/resources/default.css +++ b/www/resources/default.css @@ -276,6 +276,15 @@ div.preferredidp { padding: 2px 2em 2px 2em; } +table.modules { + border-collapse: collapse; +} +table.modules tr td { + border-bottom: 1px solid #ddd; +} +table.modules tr.even td { + background: #f0f0f0; +} /* Attribute presentation in example page */ table.attributes {