Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 381 additions & 0 deletions installation/src/Helper/DatabaseHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

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\Language\Text;
use Joomla\CMS\User\UserHelper;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseInterface;

Expand Down Expand Up @@ -177,4 +183,379 @@ public static function getMinimumServerVersion($db, $options)

return $minDbVersionRequired;
}

/**
* Validate and clean up database connection parameters.
*
* @param \stdClass $options The session options
*
* @return string|boolean A string with the translated error message if
* validation error, otherwise false.
*
* @since 4.0.0
*/
public static function validateConnectionParameters($options)
{
// Ensure a database type was selected.
if (empty($options->db_type))
{
return Text::_('INSTL_DATABASE_INVALID_TYPE');
}

// Ensure that a hostname and user name were input.
if (empty($options->db_host) || empty($options->db_user))
{
return Text::_('INSTL_DATABASE_INVALID_DB_DETAILS');
}

// Ensure that a database name is given.
if (empty($options->db_name))
{
return Text::_('INSTL_DATABASE_EMPTY_NAME');
}

// Validate length of database name.
if (strlen($options->db_name) > 64)
{
return Text::_('INSTL_DATABASE_NAME_TOO_LONG');
}

// Validate database table prefix.
if (empty($options->db_prefix) || !preg_match('#^[a-zA-Z]+[a-zA-Z0-9_]*$#', $options->db_prefix))
{
return Text::_('INSTL_DATABASE_PREFIX_MSG');
}

// Validate length of database table prefix.
if (strlen($options->db_prefix) > 15)
{
return Text::_('INSTL_DATABASE_FIX_TOO_LONG');
}

// Validate database name.
if (in_array($options->db_type, ['pgsql', 'postgresql']) && !preg_match('#^[a-zA-Z_][0-9a-zA-Z_$]*$#', $options->db_name))
{
return Text::_('INSTL_DATABASE_NAME_MSG_POSTGRESQL');
}

if (in_array($options->db_type, ['mysql', 'mysqli']) && preg_match('#[\\\\\/\.]#', $options->db_name))
{
return Text::_('INSTL_DATABASE_NAME_MSG_MYSQL');
}

// Workaround for UPPERCASE table prefix for postgresql
if (in_array($options->db_type, ['pgsql', 'postgresql']))
{
if (strtolower($options->db_prefix) != $options->db_prefix)
{
return Text::_('INSTL_DATABASE_FIX_LOWERCASE');
}
}

// Validate and clean up database connection encryption options
$optionsChanged = false;

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')
{
return Text::_('INSTL_DATABASE_ENCRYPTION_MSG_LOCALHOST');
}

// Check CA file and folder depending on database type if server certificate verification
if ($options->db_sslverifyservercert)
{
if (empty($options->db_sslca))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_CA_LABEL'));
}

if (!File::exists(Path::clean($options->db_sslca)))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_CA_LABEL'));
}
}
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))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_KEY_LABEL'));
}

if (!File::exists(Path::clean($options->db_sslkey)))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_KEY_LABEL'));
}

if (empty($options->db_sslcert))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_EMPTY', Text::_('INSTL_DATABASE_ENCRYPTION_CERT_LABEL'));
}

if (!File::exists(Path::clean($options->db_sslcert)))
{
return Text::sprintf('INSTL_DATABASE_ENCRYPTION_MSG_FILE_FIELD_BAD', Text::_('INSTL_DATABASE_ENCRYPTION_CERT_LABEL'));
}
}
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)
{
$optsArr = ArrayHelper::fromObject($options);
Factory::getSession()->set('setup.options', $optsArr);
}

return false;
}

/**
* Security check for remote db hosts
*
* @param \stdClass $options The session options
*
* @return boolean True if passed, otherwise false.
*
* @since 4.0.0
*/
public static function checkRemoteDbHost($options)
{
// Security check for remote db hosts: Check env var if disabled
$shouldCheckLocalhost = getenv('JOOMLA_INSTALLATION_DISABLE_LOCALHOST_CHECK') !== '1';

// Per Default allowed DB Hosts
$localhost = array(
'localhost',
'127.0.0.1',
'::1',
);

// Check the security file if the db_host is not localhost / 127.0.0.1 / ::1
if ($shouldCheckLocalhost && !in_array($options->db_host, $localhost))
{
$remoteDbFileTestsPassed = Factory::getSession()->get('remoteDbFileTestsPassed', false);

// When all checks have been passed we don't need to do this here again.
if ($remoteDbFileTestsPassed === false)
{
$generalRemoteDatabaseMessage = Text::sprintf(
'INSTL_DATABASE_HOST_IS_NOT_LOCALHOST_GENERAL_MESSAGE',
'https://docs.joomla.org/Special:MyLanguage/J3.x:Secured_procedure_for_installing_Joomla_with_a_remote_database'
);

$remoteDbFile = Factory::getSession()->get('remoteDbFile', false);

if ($remoteDbFile === false)
{
// Add the general message
Factory::getApplication()->enqueueMessage($generalRemoteDatabaseMessage, 'warning');

// This is the file you need to remove if you want to use a remote database
$remoteDbFile = '_Joomla' . UserHelper::genRandomPassword(21) . '.txt';
Factory::getSession()->set('remoteDbFile', $remoteDbFile);

// Get the path
$remoteDbPath = JPATH_INSTALLATION . '/' . $remoteDbFile;

// When the path is not writable the user needs to create the file manually
if (!File::write($remoteDbPath, ''))
{
// Request to create the file manually
Factory::getApplication()->enqueueMessage(
Text::sprintf(
'INSTL_DATABASE_HOST_IS_NOT_LOCALHOST_CREATE_FILE',
$remoteDbFile,
'installation',
Text::_('INSTL_INSTALL_JOOMLA')
),
'notice'
);

Factory::getSession()->set('remoteDbFileUnwritable', true);

return false;
}

// Save the file name to the session
Factory::getSession()->set('remoteDbFileWrittenByJoomla', true);

// Request to delete that file
Factory::getApplication()->enqueueMessage(
Text::sprintf(
'INSTL_DATABASE_HOST_IS_NOT_LOCALHOST_DELETE_FILE',
$remoteDbFile,
'installation',
Text::_('INSTL_INSTALL_JOOMLA')
),
'notice'
);

return false;
}

if (Factory::getSession()->get('remoteDbFileWrittenByJoomla', false) === true
&& File::exists(JPATH_INSTALLATION . '/' . $remoteDbFile))
{
// Add the general message
Factory::getApplication()->enqueueMessage($generalRemoteDatabaseMessage, 'warning');

// Request to delete the file
Factory::getApplication()->enqueueMessage(
Text::sprintf(
'INSTL_DATABASE_HOST_IS_NOT_LOCALHOST_DELETE_FILE',
$remoteDbFile,
'installation',
Text::_('INSTL_INSTALL_JOOMLA')
),
'notice'
);

return false;
}

if (Factory::getSession()->get('remoteDbFileUnwritable', false) === true && !File::exists(JPATH_INSTALLATION . '/' . $remoteDbFile))
{
// Add the general message
Factory::getApplication()->enqueueMessage($generalRemoteDatabaseMessage, 'warning');

// Request to create the file manually
Factory::getApplication()->enqueueMessage(
Text::sprintf(
'INSTL_DATABASE_HOST_IS_NOT_LOCALHOST_CREATE_FILE',
$remoteDbFile,
'installation',
Text::_('INSTL_INSTALL_JOOMLA')
),
'notice'
);

return false;
}

// All tests for this session passed set it to the session
Factory::getSession()->set('remoteDbFileTestsPassed', true);
}
}

return true;
}

/**
* Check database server parameters after connection
*
* @param DatabaseDriver $db Database object
* @param \stdClass $options The session options
*
* @return string|boolean A string with the translated error message if
* some server parameter is not ok, otherwise false.
*
* @since 4.0.0
*/
public static function checkDbServerParameters($db, $options)
{
$dbVersion = $db->getVersion();

// Get required database version
$minDbVersionRequired = self::getMinimumServerVersion($db, $options);

// Check minimum database version
if (version_compare($dbVersion, $minDbVersionRequired) < 0)
{
if (in_array($options->db_type, ['mysql', 'mysqli']) && $db->isMariaDb())
{
$errorMessage = Text::sprintf(
'INSTL_DATABASE_INVALID_MARIADB_VERSION',
$minDbVersionRequired,
$dbVersion
);
}
else
{
$errorMessage = Text::sprintf(
'INSTL_DATABASE_INVALID_' . strtoupper($options->db_type) . '_VERSION',
$minDbVersionRequired,
$dbVersion
);
}

return $errorMessage;
}

// Check database connection encryption
if ($options->db_encryption !== 0 && empty($db->getConnectionEncryption()))
{
if ($db->isConnectionEncryptionSupported())
{
$errorMessage = Text::_('INSTL_DATABASE_ENCRYPTION_MSG_CONN_NOT_ENCRYPT');
}
else
{
$errorMessage = Text::_('INSTL_DATABASE_ENCRYPTION_MSG_SRV_NOT_SUPPORTS');
}

return $errorMessage;
}

return false;
}
}
Loading