From faf567b526eb96f0683c0a97d537b3cdac2d3378 Mon Sep 17 00:00:00 2001 From: Steven Ngesera Date: Fri, 10 Nov 2023 14:11:37 +0200 Subject: [PATCH] Allow database authentication using .env file --- .env.dist | 6 +++ composer.json | 3 +- composer.lock | 73 ++++++++++++++++++++++++++++++++++- lib/db.php | 51 ++++++++++++------------ lib/environment.php | 67 ++++++++++++++++++++++++++++++++ lib/framework.php | 1 + tests/phpunit/environment.php | 50 ++++++++++++++++++++++++ tests/phpunit/phpunit.xml | 3 ++ 8 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 .env.dist create mode 100644 lib/environment.php create mode 100644 tests/phpunit/environment.php diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000000..a3e4c937c2 --- /dev/null +++ b/.env.dist @@ -0,0 +1,6 @@ +DB_DRIVER=mysql +DB_PORT=3306 +DB_HOST=localhost +DB_NAME=cypht_db +DB_USER=root +DB_PASS=rootroot \ No newline at end of file diff --git a/composer.json b/composer.json index f58cde8917..53dce9994c 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,8 @@ "webklex/composer-info": "^0.0.1", "composer" : "^2.0.0", "zbateson/mail-mime-parser": "^2.4", - "league/commonmark": "^2.4" + "league/commonmark": "^2.4", + "symfony/dotenv": "^4.3 || 5.4" }, "require-dev": { "phpunit/phpunit": "^9.3.0" diff --git a/composer.lock b/composer.lock index ab30c980a3..2121885dbf 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a79c47279566fbf037e39c48cc66b210", + "content-hash": "27c38366440192e46c8346d1886f2d00", "packages": [ { "name": "bacon/bacon-qr-code", @@ -1234,6 +1234,77 @@ ], "time": "2022-01-02T09:53:40+00:00" }, + { + "name": "symfony/dotenv", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/9bd173ff68fa90d39c59d91a42ae42b7f11713a0", + "reference": "9bd173ff68fa90d39c59d91a42ae42b7f11713a0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T10:19:22+00:00" + }, { "name": "symfony/polyfill-iconv", "version": "v1.28.0", diff --git a/lib/db.php b/lib/db.php index 863716a724..bc35e6d265 100644 --- a/lib/db.php +++ b/lib/db.php @@ -26,15 +26,20 @@ class Hm_DB { * @return void */ static private function parse_config($site_config) { - self::$config = array('db_driver' => $site_config->get('db_driver', false), - 'db_host' => $site_config->get('db_host', false), - 'db_name' => $site_config->get('db_name', false), - 'db_user' => $site_config->get('db_user', false), - 'db_pass' => $site_config->get('db_pass', false), - 'db_socket' => $site_config->get('db_socket', false), - 'db_conn_type' => $site_config->get('db_connection_type', 'host'), - 'db_port' => $site_config->get('db_port', false), + $environment = Hm_Environment::getInstance(); + $environment->load();//load env files + + self::$config = array( + 'db_driver' => $environment->get('DB_DRIVER', $site_config->get('db_driver', false)), + 'db_host' => $environment->get('DB_HOST', $site_config->get('db_host', false)), + 'db_name' => $environment->get('DB_NAME', $site_config->get('db_name', false)), + 'db_user' => $environment->get('DB_USER', $site_config->get('db_user', false)), + 'db_pass' => $environment->get('DB_PASS', $site_config->get('db_pass', false)), + 'db_socket' => $environment->get('DB_SOCKET', $site_config->get('db_socket', false)), + 'db_conn_type' => $environment->get('DB_CONNECTION_TYPE', $site_config->get('db_connection_type', 'host')), + 'db_port' => $environment->get('DB_PORT', $site_config->get('db_port', false)), ); + foreach (self::$required_config as $v) { if (!self::$config[$v]) { Hm_Debug::add(sprintf('Missing configuration setting for %s', $v)); @@ -47,14 +52,15 @@ static private function parse_config($site_config) { * @return string md5 of the DB settings */ static private function db_key() { - return md5(self::$config['db_driver']. - self::$config['db_host']. - self::$config['db_port']. - self::$config['db_name']. - self::$config['db_user']. - self::$config['db_pass']. - self::$config['db_conn_type']. - self::$config['db_socket'] + return md5( + self::$config['db_driver'] . + self::$config['db_host'] . + self::$config['db_port'] . + self::$config['db_name'] . + self::$config['db_user'] . + self::$config['db_pass'] . + self::$config['db_conn_type'] . + self::$config['db_socket'] ); } @@ -68,12 +74,10 @@ static public function build_dsn() { } if (self::$config['db_conn_type'] == 'socket') { return sprintf('%s:unix_socket=%s;dbname=%s', self::$config['db_driver'], self::$config['db_socket'], self::$config['db_name']); - } - else { + } else { if (self::$config['db_port']) { return sprintf('%s:host=%s;port=%s;dbname=%s', self::$config['db_driver'], self::$config['db_host'], self::$config['db_port'], self::$config['db_name']); - } - else { + } else { return sprintf('%s:host=%s;dbname=%s', self::$config['db_driver'], self::$config['db_host'], self::$config['db_name']); } } @@ -87,7 +91,7 @@ static public function build_dsn() { * @param bool $all optional flag to return multiple rows * @return boolean|integer|array */ - static public function execute($dbh, $sql, $args, $type=false, $all=false) { + static public function execute($dbh, $sql, $args, $type = false, $all = false) { if (!$dbh) { return false; } @@ -112,7 +116,7 @@ static public function execute($dbh, $sql, $args, $type=false, $all=false) { * @return string */ static private function execute_type($sql) { - switch(substr($sql, 0, 1)) { + switch (substr($sql, 0, 1)) { case 'd': case 'u': case 'i': @@ -141,8 +145,7 @@ static public function connect($site_config) { self::$dbh[$key]->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); Hm_Debug::add(sprintf('Connecting to dsn: %s', $dsn)); return self::$dbh[$key]; - } - catch (Exception $oops) { + } catch (Exception $oops) { Hm_Debug::add($oops->getMessage()); self::$dbh[$key] = false; return false; diff --git a/lib/environment.php b/lib/environment.php new file mode 100644 index 0000000000..876145776e --- /dev/null +++ b/lib/environment.php @@ -0,0 +1,67 @@ +set_required_environment_variables(); + + $dotenvLoader = new Dotenv(); + if (method_exists($dotenvLoader, 'usePutenv')) { + $dotenvLoader->usePutenv(true); + } + $envDistFile = APP_PATH. '.env.dist'; + if (!file_exists($envDistFile)) { + Hm_Msgs::add('ERR.env.dist file not found at: "' . $envDistFile . '"'); + return; + } + + $envFile = static::get('TM_DOTENV'); + $dotenvLoader->load($envDistFile); + if ($envFile) { + $dotenvLoader->loadEnv($envFile); + } + } + + public static function get($key, $defaultValue = null) + { + $variables = self::getInstance()->get_environment_variables(); + + return array_key_exists($key, $variables) ? $variables[$key] : $defaultValue; + } + + /** + * Sets required environment variables that are used within .env files + */ + private function set_required_environment_variables() + { + $_ENV['TM_DOTENV'] = APP_PATH . '.env'; + } + + /** + * Get a merge of environment variables $_ENV and $_SERVER. + * + * @return array + */ + protected function get_environment_variables() + { + return array_merge($_ENV, $_SERVER); + } +} diff --git a/lib/framework.php b/lib/framework.php index 26a854e3f5..df8886cce6 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -28,6 +28,7 @@ require APP_PATH.'lib/crypt.php'; require APP_PATH.'lib/crypt_sodium.php'; require APP_PATH.'lib/sodium_compat.php'; +require APP_PATH.'lib/environment.php'; require APP_PATH.'lib/db.php'; require APP_PATH.'lib/servers.php'; require APP_PATH.'lib/api.php'; diff --git a/tests/phpunit/environment.php b/tests/phpunit/environment.php new file mode 100644 index 0000000000..e107f81d83 --- /dev/null +++ b/tests/phpunit/environment.php @@ -0,0 +1,50 @@ +load(); + $tm_dotenv = $environment->get('TM_DOTENV'); + $this->assertStringEndsWith(".env", $tm_dotenv); + } + + /** + * @preserveGlobalState disabled + * @runInSeparateProcess + */ + public function test_get_default_value() { + $environment = Hm_Environment::getInstance(); + $environment->load(); + $undifined_env_data = $environment::get('APP_VERSION', "DEFAUL_VALUE"); + $this->assertEquals('DEFAUL_VALUE', $undifined_env_data); + } + + /** + * @preserveGlobalState disabled + * @runInSeparateProcess + */ + public function test_get_environment_variables() { + $environment = Hm_Environment::getInstance(); + $reflection = new ReflectionClass($environment); + $method = $reflection->getMethod('get_environment_variables'); + $method->setAccessible(true); + $env_vars = $method->invoke($environment); + $expected = array_merge($_ENV, $_SERVER); + $this->assertEquals($expected, $env_vars); + } +} +?> diff --git a/tests/phpunit/phpunit.xml b/tests/phpunit/phpunit.xml index 50a45a35ee..345cdb94db 100644 --- a/tests/phpunit/phpunit.xml +++ b/tests/phpunit/phpunit.xml @@ -23,6 +23,9 @@ ./config.php + + ./environment.php + ./db.php