diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 837fa1161d60711a78b75277cdd6405679b6e4cf..63edf67623edaf4423a5d86fba58188dc30b3d12 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -297,6 +297,14 @@ encrypt_ils_password = false ; filled in with a random string value when encrypt_ils_passwords is set to true. ils_encryption_key = false +; This is the algorithm used to encrypt and decrypt catalog passwords. +; A symmetrical encryption algorithm must be used. +; You can use mcrypt_list_algorithms() to see available options on your system. +; Common choices: blowfish (default), aes +; If you want to convert from one algorithm to another, run this from $VUFIND_HOME: +; php public/index.php util switch_db_hash oldhash:oldkey (or none) newhash:newkey +;ils_encryption_algo = "blowfish" + ; This setting may optionally be uncommented to restrict the email domain(s) from ; which users are allowed to register when using the Database method. ;domain_whitelist[] = "myuniversity.edu" diff --git a/module/VuFind/src/VuFind/Db/Row/User.php b/module/VuFind/src/VuFind/Db/Row/User.php index 9acbcadcb23ccd5908fc3cf36a3b09ff8619d5f7..5519daff5549f9e91798cc4a37454df419b1985e 100644 --- a/module/VuFind/src/VuFind/Db/Row/User.php +++ b/module/VuFind/src/VuFind/Db/Row/User.php @@ -191,7 +191,10 @@ class User extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterface, } // Perform encryption: - $cipher = new BlockCipher(new Mcrypt(['algorithm' => 'blowfish'])); + $algo = isset($this->config->Authentication->ils_encryption_algo) + ? $this->config->Authentication->ils_encryption_algo + : 'blowfish'; + $cipher = new BlockCipher(new Mcrypt(['algorithm' => $algo])); $cipher->setKey($this->encryptionKey); return $encrypt ? $cipher->encrypt($text) : $cipher->decrypt($text); } diff --git a/module/VuFindConsole/Module.php b/module/VuFindConsole/Module.php index 1043410a50a446ca8404ec98d56f2d0ddb2995ab..8b21d20f506d6d81170362eff5b1d0539b57883d 100644 --- a/module/VuFindConsole/Module.php +++ b/module/VuFindConsole/Module.php @@ -101,6 +101,9 @@ class Module implements \Zend\ModuleManager\Feature\ConsoleUsageProviderInterfac 'util index_reserves' => 'Solr reserves indexer', 'util optimize' => 'Solr optimize tool', 'util sitemap' => 'XML sitemap generator', + 'util switch_db_hash' => 'Switch the hashing algorithm in the database ' + . 'and config. Expects new algorithm and (optional) new key as' + . ' parameters.', 'util suppressed' => 'Remove ILS-suppressed records from Solr', ]; } diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php b/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php index 38256c4865f083b62c083c4f96058f5249b76973..022e32a2d7b0b56df7cbd21398c155c1f079066c 100644 --- a/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php +++ b/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php @@ -112,6 +112,18 @@ class AbstractBase extends AbstractActionController return $this->getResponse()->setErrorLevel(0); } + /** + * Get a VuFind configuration. + * + * @param string $id Configuration identifier (default = main VuFind config) + * + * @return \Zend\Config\Config + */ + public function getConfig($id = 'config') + { + return $this->getServiceLocator()->get('VuFind\Config')->get($id); + } + /** * Get the ILS connection. * diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php index 2f6b954ee576891d9a4acb99fa9240262cc6c642..29d013db4dde6e026bfa3e8392d8b136ac5051f9 100644 --- a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php +++ b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php @@ -27,9 +27,13 @@ */ namespace VuFindConsole\Controller; use File_MARC, File_MARCXML, VuFind\Sitemap\Generator as Sitemap; +use VuFind\Config\Locator as ConfigLocator; +use VuFind\Config\Writer as ConfigWriter; use VuFindSearch\Backend\Solr\Document\UpdateDocument; use VuFindSearch\Backend\Solr\Record\SerializableRecord; use Zend\Console\Console; +use Zend\Crypt\Symmetric\Mcrypt, + Zend\Crypt\BlockCipher as BlockCipher; /** * This controller handles various command-line tools @@ -690,4 +694,106 @@ class UtilController extends AbstractBase Console::writeLine(str_replace('%%count%%', $count, $successString)); return $this->getSuccessResponse(); } + + /** + * Convert hash algorithms + * Expected parameters: oldmethod:oldkey (or none) newmethod:newkey + * + * @return \Zend\Console\Response + */ + public function switchdbhashAction() + { + // Validate command line arguments: + $argv = $this->consoleOpts->getRemainingArgs(); + if (count($argv) < 1) { + Console::writeLine( + 'Expected parameters: newmethod [newkey]' + ); + return $this->getFailureResponse(); + } + + // Pull existing encryption settings from the configuration: + $config = $this->getConfig(); + if (!isset($config->Authentication->encrypt_ils_password) + || !isset($config->Authentication->ils_encryption_key) + || !$config->Authentication->encrypt_ils_password + ) { + $oldhash = 'none'; + $oldkey = null; + } else { + $oldhash = isset($config->Authentication->ils_encryption_algo) + ? $config->Authentication->ils_encryption_algo : 'blowfish'; + $oldkey = $config->Authentication->ils_encryption_key; + } + + // Pull new encryption settings from arguments: + $newhash = $argv[0]; + $newkey = isset($argv[1]) ? $argv[1] : $oldkey; + + // No key specified AND no key on file = fatal error: + if ($newkey === null) { + Console::writeLine('Please specify a key as the second parameter.'); + return $this->getFailureResponse(); + } + + // If no changes were requested, abort early: + if ($oldkey == $newkey && $oldhash == $newhash) { + Console::writeLine('No changes requested -- no action needed.'); + return $this->getSuccessResponse(); + } + + // Initialize Mcrypt first, so we can catch any illegal algorithms before + // making any changes: + try { + if ($oldhash != 'none') { + $oldCrypt = new Mcrypt(['algorithm' => $oldhash]); + } + $newCrypt = new Mcrypt(['algorithm' => $newhash]); + } catch (\Exception $e) { + Console::writeLine($e->getMessage()); + return $this->getFailureResponse(); + } + + // Next update the config file, so if we are unable to write the file, + // we don't go ahead and make unwanted changes to the database: + $configPath = ConfigLocator::getLocalConfigPath('config.ini', null, true); + Console::writeLine("\tUpdating $configPath..."); + $writer = new ConfigWriter($configPath); + $writer->set('Authentication', 'encrypt_ils_password', true); + $writer->set('Authentication', 'ils_encryption_algo', $newhash); + $writer->set('Authentication', 'ils_encryption_key', $newkey); + if (!$writer->save()) { + Console::writeLine("\tWrite failed!"); + return $this->getFailureResponse(); + } + + // Now do the database rewrite: + $userTable = $this->getServiceLocator()->get('VuFind\DbTablePluginManager') + ->get('User'); + $users = $userTable->select( + function ($select) { + $select->where->isNotNull('cat_username'); + } + ); + Console::writeLine("\tConverting hashes for " . count($users) . ' user(s).'); + foreach ($users as $row) { + $pass = null; + if ($oldhash != 'none' && isset($row['cat_pass_enc'])) { + $oldcipher = new BlockCipher($oldCrypt); + $oldcipher->setKey($oldkey); + $pass = $oldcipher->decrypt($row['cat_pass_enc']); + } else { + $pass = $row['cat_password']; + } + $newcipher = new BlockCipher($newCrypt); + $newcipher->setKey($newkey); + $row['cat_password'] = null; + $row['cat_pass_enc'] = $newcipher->encrypt($pass); + $row->save(); + } + + // If we got this far, all went well! + Console::writeLine("\tFinished."); + return $this->getSuccessResponse(); + } }