diff --git a/installation/forms/setup.xml b/installation/forms/setup.xml index 61c3587613914..92a1a7a4fbf0d 100644 --- a/installation/forms/setup.xml +++ b/installation/forms/setup.xml @@ -122,5 +122,65 @@ id="db_old" default="backup" /> + + + + + + + + + + + + + + + + + + + + diff --git a/installation/language/en-GB/joomla.ini b/installation/language/en-GB/joomla.ini index 583bf5eb3ae28..9d22f48ea9a81 100644 --- a/installation/language/en-GB/joomla.ini +++ b/installation/language/en-GB/joomla.ini @@ -51,6 +51,20 @@ INSTL_ZLIB_COMPRESSION_SUPPORT="Zlib Compression Support" ; Database view INSTL_DATABASE="Database Configuration" +INSTL_DATABASE_ENCRYPTION_CA_LABEL="Path to CA File" +INSTL_DATABASE_ENCRYPTION_CERT_LABEL="Path to Certificate File" +INSTL_DATABASE_ENCRYPTION_CIPHER_LABEL="Supported Cipher Suite (optional)" +INSTL_DATABASE_ENCRYPTION_KEY_LABEL="Path to Private Key File" +INSTL_DATABASE_ENCRYPTION_MODE_LABEL="Connection Encryption" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_NONE="Default (server controlled)" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_ONE_WAY="One-way authentication" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_TWO_WAY="Two-way authentication" +INSTL_DATABASE_ENCRYPTION_MSG_CONN_NOT_ENCRYPT="You have selected database connection enryption to be used, and a connection could be established, but it was not encrypted. The reason might be that the database server is configured to fall back to an unencrypted connection in case of bad encryption parameters. Either check and correct the database encryption parameters, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD="The file entered in field \"%s\" does not exist or is not accessible." +INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY="Field \"%s\" is empty or doesn't contain a valid path." +INSTL_DATABASE_ENCRYPTION_MSG_LOCALHOST="You have entered \"localhost\" as host name. Connecting to the database with connection encryption might fail with this. Either change \"localhost\" to \"127.0.0.1\" or \"::1\" or a different host name, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_MSG_SRV_NOT_SUPPORTS="The database server doesn't support connection encryption. Either enable TLS (often called SSL in docs) support on your database server, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_VERIFY_SERVER_CERT_LABEL="Verify Server Certificate" INSTL_DATABASE_ERROR_POSTGRESQL_QUERY="PostgreSQL database query failed." INSTL_DATABASE_HOST_DESC="Enter the host name, usually \"localhost\" or a name provided by your host." INSTL_DATABASE_HOST_LABEL="Host Name" diff --git a/installation/language/en-US/joomla.ini b/installation/language/en-US/joomla.ini index 0723565039e72..1225b16f56326 100644 --- a/installation/language/en-US/joomla.ini +++ b/installation/language/en-US/joomla.ini @@ -51,6 +51,20 @@ INSTL_ZLIB_COMPRESSION_SUPPORT="Zlib Compression Support" ; Database view INSTL_DATABASE="Database Configuration" +INSTL_DATABASE_ENCRYPTION_CA_LABEL="Path to CA File" +INSTL_DATABASE_ENCRYPTION_CERT_LABEL="Path to Certificate File" +INSTL_DATABASE_ENCRYPTION_CIPHER_LABEL="Supported Cipher Suite (optional)" +INSTL_DATABASE_ENCRYPTION_KEY_LABEL="Path to Private Key File" +INSTL_DATABASE_ENCRYPTION_MODE_LABEL="Connection Encryption" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_NONE="Default (server controlled)" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_ONE_WAY="One-way authentication" +INSTL_DATABASE_ENCRYPTION_MODE_VALUE_TWO_WAY="Two-way authentication" +INSTL_DATABASE_ENCRYPTION_MSG_CONN_NOT_ENCRYPT="You have selected database connection enryption to be used, and a connection could be established, but it was not encrypted. The reason might be that the database server is configured to fall back to an unencrypted connection in case of bad encryption parameters. Either check and correct the database encryption parameters, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD="The file entered in field \"%s\" does not exist or is not accessible." +INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY="Field \"%s\" is empty or doesn't contain a valid path." +INSTL_DATABASE_ENCRYPTION_MSG_LOCALHOST="You have entered \"localhost\" as host name. Connecting to the database with connection encryption might fail with this. Either change \"localhost\" to \"127.0.0.1\" or \"::1\" or a different host name, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_MSG_SRV_NOT_SUPPORTS="The database server doesn't support connection encryption. Either enable TLS (often called SSL in docs) support on your database server, or change field \"Connection Encryption\" back to \"Default (server controlled)\"." +INSTL_DATABASE_ENCRYPTION_VERIFY_SERVER_CERT_LABEL="Verify Server Certificate" INSTL_DATABASE_ERROR_POSTGRESQL_QUERY="PostgreSQL database query failed." INSTL_DATABASE_HOST_DESC="Enter the host name, usually \"localhost\" or a name provided by your host." INSTL_DATABASE_HOST_LABEL="Host Name" diff --git a/installation/src/Helper/DatabaseHelper.php b/installation/src/Helper/DatabaseHelper.php index d0b4bd49f3aae..7cd02578aa713 100644 --- a/installation/src/Helper/DatabaseHelper.php +++ b/installation/src/Helper/DatabaseHelper.php @@ -30,12 +30,13 @@ abstract class DatabaseHelper * @param string $database The database to use. * @param string $prefix The table prefix to use. * @param boolean $select True if the database should be selected. + * @param array $ssl Database TLS connection options. * * @return DatabaseInterface * * @since 1.6 */ - public static function getDbo($driver, $host, $user, $password, $database, $prefix, $select = true) + public static function getDbo($driver, $host, $user, $password, $database, $prefix, $select = true, array $ssl = []) { static $db; @@ -52,6 +53,24 @@ public static function getDbo($driver, $host, $user, $password, $database, $pref 'select' => $select, ]; + if (!empty($ssl['dbencryption'])) + { + $options['ssl'] = [ + 'enable' => true, + 'verify_server_cert' => (bool) $ssl['dbsslverifyservercert'], + ]; + + foreach (['cipher', 'ca', 'key', 'cert'] as $value) + { + $confVal = trim($ssl['dbssl' . $value]); + + if ($confVal !== '') + { + $options['ssl'][$value] = $confVal; + } + } + } + // Enable utf8mb4 connections for mysql adapters if (strtolower($driver) === 'mysqli') { @@ -69,4 +88,25 @@ public static function getDbo($driver, $host, $user, $password, $database, $pref return $db; } + + /** + * Convert encryption options to array. + * + * @param \stdClass $options The session options + * + * @return array The encryption settings + * + * @since __DEPLOY_VERSION__ + */ + public static function getEncryptionSettings($options) + { + return [ + 'dbencryption' => $options->db_encryption, + 'dbsslverifyservercert' => $options->db_sslverifyservercert, + 'dbsslkey' => $options->db_sslkey, + 'dbsslcert' => $options->db_sslcert, + 'dbsslca' => $options->db_sslca, + 'dbsslcipher' => $options->db_sslcipher, + ]; + } } diff --git a/installation/src/Model/ConfigurationModel.php b/installation/src/Model/ConfigurationModel.php index 8e8f5efd5f24a..0d34735147e3e 100644 --- a/installation/src/Model/ConfigurationModel.php +++ b/installation/src/Model/ConfigurationModel.php @@ -133,12 +133,12 @@ public function createConfiguration($options) $registry->set('password', $options->db_pass_plain); $registry->set('db', $options->db_name); $registry->set('dbprefix', $options->db_prefix); - $registry->set('dbencryption', 0); - $registry->set('dbsslverifyservercert', false); - $registry->set('dbsslkey', ''); - $registry->set('dbsslcert', ''); - $registry->set('dbsslca', ''); - $registry->set('dbsslcipher', ''); + $registry->set('dbencryption', $options->db_encryption); + $registry->set('dbsslverifyservercert', $options->db_sslverifyservercert); + $registry->set('dbsslkey', $options->db_sslkey); + $registry->set('dbsslcert', $options->db_sslcert); + $registry->set('dbsslca', $options->db_sslca); + $registry->set('dbsslcipher', $options->db_sslcipher); // Server settings. $registry->set('live_site', ''); @@ -274,7 +274,9 @@ private function createRootUser($options) $options->db_user, $options->db_pass_plain, $options->db_name, - $options->db_prefix + $options->db_prefix, + true, + DatabaseHelper::getEncryptionSettings($options) ); } catch (\RuntimeException $e) diff --git a/installation/src/Model/DatabaseModel.php b/installation/src/Model/DatabaseModel.php index d8c53a3d6242b..d01e72ee53753 100644 --- a/installation/src/Model/DatabaseModel.php +++ b/installation/src/Model/DatabaseModel.php @@ -328,7 +328,8 @@ public function initialise() $options->db_pass_plain, $options->db_name, $options->db_prefix, - isset($options->db_select) ? $options->db_select : false + isset($options->db_select) ? $options->db_select : false, + DatabaseHelper::getEncryptionSettings($options) ); } catch (\RuntimeException $e) @@ -401,6 +402,7 @@ public function createDatabase($options) 'password' => $options->db_pass_plain, 'prefix' => $options->db_prefix, 'select' => $options->db_select, + DatabaseHelper::getEncryptionSettings($options), ); $altDB = DatabaseDriver::getInstance($altDBoptions); diff --git a/installation/src/Model/SetupModel.php b/installation/src/Model/SetupModel.php index 7dd2faceefde6..9c392445b552a 100644 --- a/installation/src/Model/SetupModel.php +++ b/installation/src/Model/SetupModel.php @@ -12,6 +12,9 @@ defined('_JEXEC') or die; use Joomla\CMS\Factory; +use Joomla\CMS\Filesystem\File; +use Joomla\CMS\Filesystem\Folder; +use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Form\Form; use Joomla\CMS\Installation\Helper\DatabaseHelper; use Joomla\CMS\Language\LanguageHelper; @@ -269,6 +272,8 @@ public function validateDbConnection() $lang = Factory::getLanguage(); $currentLang = $lang->getTag(); + $optionsChanged = false; + // Load the selected language if (LanguageHelper::exists($currentLang, JPATH_ADMINISTRATOR)) { @@ -283,7 +288,7 @@ public function validateDbConnection() // Ensure a database type was selected. if (empty($options->db_type)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_INVALID_TYPE'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_INVALID_TYPE'), 'error'); return false; } @@ -291,7 +296,7 @@ public function validateDbConnection() // Ensure that a hostname and user name were input. if (empty($options->db_host) || empty($options->db_user)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_INVALID_DB_DETAILS'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_INVALID_DB_DETAILS'), 'error'); return false; } @@ -299,7 +304,7 @@ public function validateDbConnection() // Ensure that a database name was input. if (empty($options->db_name)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_EMPTY_NAME'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_EMPTY_NAME'), 'error'); return false; } @@ -307,7 +312,7 @@ public function validateDbConnection() // Validate database table prefix. if (!preg_match('#^[a-zA-Z]+[a-zA-Z0-9_]*$#', $options->db_prefix)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_PREFIX_MSG'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_PREFIX_MSG'), 'error'); return false; } @@ -315,7 +320,7 @@ public function validateDbConnection() // Validate length of database table prefix. if (strlen($options->db_prefix) > 15) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_FIX_TOO_LONG'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_FIX_TOO_LONG'), 'error'); return false; } @@ -323,7 +328,7 @@ public function validateDbConnection() // Validate length of database name. if (strlen($options->db_name) > 64) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_TOO_LONG'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_TOO_LONG'), 'error'); return false; } @@ -331,14 +336,14 @@ public function validateDbConnection() // Validate database name. if (in_array($options->db_type, ['pgsql', 'postgresql']) && !preg_match('#^[a-zA-Z_][0-9a-zA-Z_$]*$#', $options->db_name)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_MSG_POSTGRESQL'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_MSG_POSTGRESQL'), 'error'); return false; } if (in_array($options->db_type, ['mysql', 'mysqli']) && preg_match('#[\\\\\/\.]#', $options->db_name)) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_MSG_MYSQL'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_NAME_MSG_MYSQL'), 'error'); return false; } @@ -348,12 +353,157 @@ public function validateDbConnection() { if (strtolower($options->db_prefix) != $options->db_prefix) { - Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_FIX_LOWERCASE'), 'warning'); + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_FIX_LOWERCASE'), 'error'); return false; } } + // Validate database connection encryption options + if ($options->db_encryption === 0) + { + // Reset unused options + if (!empty($options->db_sslkey)) + { + $options->db_sslkey = ''; + $optionsChanged = true; + } + + if (!empty($options->db_sslcert)) + { + $options->db_sslcert = ''; + $optionsChanged = true; + } + + if ($options->db_sslverifyservercert) + { + $options->db_sslverifyservercert = false; + $optionsChanged = true; + } + + if (!empty($options->db_sslca)) + { + $options->db_sslca = ''; + $optionsChanged = true; + } + + if (!empty($options->db_sslcipher)) + { + $options->db_sslcipher = ''; + $optionsChanged = true; + } + } + else + { + // Check localhost + if (strtolower($options->db_host) === 'localhost') + { + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_ENCRYPTION_MSG_LOCALHOST'), 'error'); + + return false; + } + + // Check CA file and folder depending on database type if server certificate verification + if ($options->db_sslverifyservercert) + { + if (empty($options->db_sslca)) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_CA_LABEL')), + 'error' + ); + + return false; + } + + if (!File::exists(Path::clean($options->db_sslca))) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_CA_LABEL')), + 'error' + ); + + return false; + } + } + else + { + // Reset unused option + if (!empty($options->db_sslca)) + { + $options->db_sslca = ''; + $optionsChanged = true; + } + } + + // Check key and certificate if two-way encryption + if ($options->db_encryption === 2) + { + if (empty($options->db_sslkey)) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_KEY_LABEL')), + 'error' + ); + + return false; + } + + if (!File::exists(Path::clean($options->db_sslkey))) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_KEY_LABEL')), + 'error' + ); + + return false; + } + + if (empty($options->db_sslcert)) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_CERT_LABEL')), + 'error' + ); + + return false; + } + + if (!File::exists(Path::clean($options->db_sslcert))) + { + Factory::getApplication()->enqueueMessage( + Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_CERT_LABEL')), + 'error' + ); + + return false; + } + } + else + { + // Reset unused options + if (!empty($options->db_sslkey)) + { + $options->db_sslkey = ''; + $optionsChanged = true; + } + + if (!empty($options->db_sslcert)) + { + $options->db_sslcert = ''; + $optionsChanged = true; + } + } + } + + // Save options to session data if changed + if ($optionsChanged) + { + $session = Factory::getSession(); + $optsArr = ArrayHelper::fromObject($options); + $session->set('setup.options', $optsArr); + } + // Get a database object. try { @@ -364,12 +514,11 @@ public function validateDbConnection() $options->db_pass_plain, $options->db_name, $options->db_prefix, - isset($options->db_select) ? $options->db_select : false + isset($options->db_select) ? $options->db_select : false, + DatabaseHelper::getEncryptionSettings($options) ); $db->connect(); - - return true; } catch (\RuntimeException $e) { @@ -377,5 +526,23 @@ public function validateDbConnection() return false; } + + if ($options->db_encryption !== 0 && empty($db->getConnectionEncryption())) + { + if ($db->isConnectionEncryptionSupported()) + { + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_ENCRYPTION_MSG_CONN_NOT_ENCRYPT'), 'error'); + } + else + { + Factory::getApplication()->enqueueMessage(Text::_('INSTL_DATABASE_ENCRYPTION_MSG_SRV_NOT_SUPPORTS'), 'error'); + } + + $db->disconnect(); + + return false; + } + + return true; } } diff --git a/installation/template/js/template.js b/installation/template/js/template.js index 524d804222f6a..15574bb86dd16 100644 --- a/installation/template/js/template.js +++ b/installation/template/js/template.js @@ -14,7 +14,7 @@ var name = elements[i].name; var value = elements[i].value; if(name) { - if ((elements[i].type === 'checkbox' && elements[i].checked === true) || (elements[i].type !== 'checkbox')) { + if (((elements[i].type === 'checkbox' || elements[i].type === 'radio') && elements[i].checked === true) || (elements[i].type !== 'checkbox' && elements[i].type !== 'radio')) { obj.push(name.replace('[', '%5B').replace(']', '%5D') + '=' + encodeURIComponent(value)); } } diff --git a/installation/tmpl/setup/default.php b/installation/tmpl/setup/default.php index 4d496164d48a7..32d9441833c0c 100644 --- a/installation/tmpl/setup/default.php +++ b/installation/tmpl/setup/default.php @@ -102,6 +102,12 @@ form->getLabel('db_prefix'); ?> form->getInput('db_prefix'); ?> + form->getField('db_encryption')->renderField(); ?> + form->getField('db_sslverifyservercert')->renderField(); ?> + form->getField('db_sslkey')->renderField(); ?> + form->getField('db_sslcert')->renderField(); ?> + form->getField('db_sslca')->renderField(); ?> + form->getField('db_sslcipher')->renderField(); ?>
form->getLabel('db_old'); ?> form->getInput('db_old'); ?>