fix unit-tests with new language-class; fix language access in standard_error/standard_success; add MysqlServer API command and possibility to allow/disallow customers available mysql-servers

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2022-04-29 08:55:07 +02:00
parent 4f4c71d79b
commit fe747b321c
12 changed files with 414 additions and 56 deletions

View File

@@ -270,6 +270,9 @@ class Customers extends ApiCommand implements ResourceEntity
* @param int $hosting_plan_id
* optional, specify a hosting-plan to set certain resource-values from the plan
* instead of specifying them
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
*
* @access admin
* @return string json-encoded array
@@ -349,6 +352,13 @@ class Customers extends ApiCommand implements ResourceEntity
$logviewenabled = $this->getBoolParam('logviewenabled', true, 0);
}
if ($mysqls == -1 || $mysqls > 0) {
$p_allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, [0]);
} else {
// mysql not allowed, so no mysql available for customer
$p_allowed_mysqlserver = [];
}
// validation
$name = Validate::validate($name, 'name', '', '', [], true);
$firstname = Validate::validate($firstname, 'first name', '', '', [], true);
@@ -389,6 +399,15 @@ class Customers extends ApiCommand implements ResourceEntity
}
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
$allowed_mysqlserver = array();
if (! empty($p_allowed_mysqlserver) && is_array($p_allowed_mysqlserver)) {
foreach ($p_allowed_mysqlserver as $allowed_ms) {
$allowed_ms = intval($allowed_ms);
$allowed_mysqlserver[] = $allowed_ms;
}
}
$allowed_mysqlserver = array_map('intval', $allowed_mysqlserver);
$diskspace = $diskspace * 1024;
$traffic = $traffic * 1024 * 1024;
@@ -496,7 +515,8 @@ class Customers extends ApiCommand implements ResourceEntity
'logviewenabled' => $logviewenabled,
'theme' => $_theme,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show
'custom_notes_show' => $custom_notes_show,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$ins_stmt = Database::prepare("
@@ -538,7 +558,8 @@ class Customers extends ApiCommand implements ResourceEntity
`logviewenabled` = :logviewenabled,
`theme` = :theme,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show
`custom_notes_show` = :custom_notes_show,
`allowed_mysqlserver`= :allowed_mysqlserver
");
Database::pexecute($ins_stmt, $ins_data, true, true);
@@ -717,8 +738,8 @@ class Customers extends ApiCommand implements ResourceEntity
'USERNAME' => $loginname,
'PASSWORD' => $password,
'SERVER_HOSTNAME' => $srv_hostname,
'SERVER_IP' => isset($srv_ip['ip']) ? $srv_ip['ip'] : '',
'SERVER_PORT' => isset($srv_ip['port']) ? $srv_ip['port'] : '',
'SERVER_IP' => $srv_ip['ip'] ?? '',
'SERVER_PORT' => $srv_ip['port'] ?? '',
'DOMAINNAME' => $_stdsubdomain
];
@@ -726,12 +747,12 @@ class Customers extends ApiCommand implements ResourceEntity
$mail_subject = $this->getMailTemplate([
'adminid' => $this->getUserDetail('adminid'),
'def_language' => $def_language
], 'mails', 'createcustomer_subject', $replace_arr, $this->lng['mails']['createcustomer']['subject']);
], 'mails', 'createcustomer_subject', $replace_arr, lng('mails.createcustomer.subject'));
// get template for mail body
$mail_body = $this->getMailTemplate([
'adminid' => $this->getUserDetail('adminid'),
'def_language' => $def_language
], 'mails', 'createcustomer_mailbody', $replace_arr, $this->lng['mails']['createcustomer']['mailbody']);
], 'mails', 'createcustomer_mailbody', $replace_arr, lng('mails.createcustomer.mailbody'));
$_mailerror = false;
$mailerr_msg = "";
@@ -984,6 +1005,9 @@ class Customers extends ApiCommand implements ResourceEntity
* optional, whether to allow access to webserver access/error-logs, default 0 (false)
* @param string $theme
* optional, change theme
* @param array $allowed_mysqlserver
* optional, array of IDs of defined mysql-servers the customer is allowed to use,
* default is to allow the default dbserver (id=0)
*
* @access admin, customer
* @return string json-encoded array
@@ -1044,6 +1068,7 @@ class Customers extends ApiCommand implements ResourceEntity
$logviewenabled = $this->getBoolParam('logviewenabled', true, $result['logviewenabled']);
$deactivated = $this->getBoolParam('deactivated', true, $result['deactivated']);
$theme = $this->getParam('theme', true, $result['theme']);
$allowed_mysqlserver = $this->getParam('allowed_mysqlserver', true, json_decode($result['allowed_mysqlserver'], true));
} else {
// allowed parameters
$def_language = $this->getParam('def_language', true, $result['def_language']);
@@ -1068,6 +1093,9 @@ class Customers extends ApiCommand implements ResourceEntity
if (!empty($allowed_phpconfigs)) {
$allowed_phpconfigs = array_map('intval', $allowed_phpconfigs);
}
if (! empty($allowed_mysqlserver)) {
$allowed_mysqlserver = array_map('intval', $allowed_mysqlserver);
}
}
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
$theme = Validate::validate($theme, 'theme', '', '', [], true);
@@ -1088,6 +1116,9 @@ class Customers extends ApiCommand implements ResourceEntity
Response::standardError('youcantallocatemorethanyouhave', '', true);
}
// validate allowed_mysqls whether the customer has databases on a removed, now disallowed db-server and abort if true
// @todo
if ($email == '') {
Response::standardError([
'stringisempty',
@@ -1305,7 +1336,8 @@ class Customers extends ApiCommand implements ResourceEntity
'logviewenabled' => $logviewenabled,
'custom_notes' => $custom_notes,
'custom_notes_show' => $custom_notes_show,
'api_allowed' => $api_allowed
'api_allowed' => $api_allowed,
'allowed_mysqlserver' => empty($allowed_mysqlserver) ? "" : json_encode($allowed_mysqlserver)
];
$upd_data = $upd_data + $admin_upd_data;
}
@@ -1347,7 +1379,8 @@ class Customers extends ApiCommand implements ResourceEntity
`logviewenabled` = :logviewenabled,
`custom_notes` = :custom_notes,
`custom_notes_show` = :custom_notes_show,
`api_allowed` = :api_allowed";
`api_allowed` = :api_allowed,
`allowed_mysqlserver` = :allowed_mysqlserver";
$upd_query .= $admin_upd_query;
}
$upd_query .= " WHERE `customerid` = :customerid";

View File

@@ -0,0 +1,312 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Api\Commands;
use Exception;
use PDO;
use PDOException;
use Froxlor\Froxlor;
use Froxlor\Api\ApiCommand;
use Froxlor\Api\ResourceEntity;
use Froxlor\Validate\Validate;
class MysqlServer extends ApiCommand implements ResourceEntity
{
/**
* check whether the user is allowed
*
* @throws Exception
*/
private function validateAccess()
{
if ($this->isAdmin() == false || ($this->isAdmin() && $this->getUserDetail('change_serversettings') == 0)) {
throw new Exception("You cannot access this resource", 405);
}
}
/**
* add a new mysql-server
*
* @param string $mysql_host
* ip/hostname of mysql-server
* @param string $mysql_port
* optional, port to connect to
* @param string $mysql_ca
* optional, path to certificate file
* @param string $mysql_verifycert
* optional, verify server certificate
* @param string $privileged_user
* privileged user on the mysql-server (must have GRANT privileges)
* @param string $privileged_password
* password of privileged user
* @param string $description
* optional, description for server
* @param bool $allow_all_customers
* optional add this configuration to the list of every existing customer's allowed-fpm-config list, default is false (no)
*
* @access admin
* @throws Exception
* @return string json-encoded array
*/
public function add()
{
$this->validateAccess();
$mysql_host = $this->getParam('mysql_host');
$mysql_port = $this->getParam('mysql_port', true, 3306);
$mysql_ca = $this->getParam('mysql_ca', true, '');
$mysql_verifycert = $this->getBoolParam('mysql_verifycert', true, 0);
$privileged_user = $this->getParam('privileged_user');
$privileged_password = $this->getParam('privileged_password');
$description = $this->getParam('description', true, '');
// validation
$mysql_host = Validate::validate_ip2($mysql_host, true, 'invalidip', true, true, false);
if ($mysql_host === false) {
$mysql_host = Validate::validateLocalHostname($mysql_host);
if ($mysql_host === false) {
$mysql_host = Validate::validateDomain($mysql_host);
if ($mysql_host === false) {
throw new Exception("Invalid mysql server ip/hostname", 406);
}
}
}
$mysql_port = Validate::validate($mysql_port, 'port', Validate::REGEX_PORT, '', [3306], true);
$privileged_password = Validate::validate($privileged_password, 'password', '', '', [], true);
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);
// testing connection with given credentials
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET names utf8'
);
if (!empty($mysql_ca)) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $mysql_ca;
$options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (bool) $mysql_verifycert;
}
$dsn = "mysql:host=" . $mysql_host . ";port=" . $mysql_port . ";";
try {
$db_test = new \PDO($dsn, $privileged_user, $privileged_password, $options);
unset($db_test);
} catch (PDOException $e) {
throw new Exception("Connection to given mysql database could not be established. Error-message: " . $e->getMessage(), $e->getCode());
}
// get all data from lib/userdata
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
// le format
if (isset($sql['root_user']) && isset($sql['root_password']) && (!isset($sql_root) || !is_array($sql_root))) {
$sql_root = array(
0 => array(
'caption' => 'Default',
'host' => $sql['host'],
'socket' => (isset($sql['socket']) ? $sql['socket'] : null),
'user' => $sql['root_user'],
'password' => $sql['root_password']
)
);
unset($sql['root_user']);
unset($sql['root_password']);
}
// add new values to sql_root array
$sql_root[] = [
'caption' => $description,
'host' => $mysql_host,
'port' => $mysql_port,
'user' => $privileged_user,
'password' => $privileged_password,
'ssl' => [
'caFile' => $mysql_ca ?? "",
'verifyServerCertificate' => $mysql_verifycert ?? false
]
];
$this->generateNewUserData($sql, $sql_root);
return $this->response(['true']);
}
/**
* remove a mysql-server
*
* @param int $dbserver number of the mysql server
*
* @access admin
* @throws Exception
* @return string json-encoded array
*/
public function delete()
{
$this->validateAccess();
$dbserver = (int) $this->getParam('dbserver');
if ($dbserver == 0) {
throw new Exception('Cannot delete first/default mysql-server');
}
// @todo check whether the server is in use by any customer
// get all data from lib/userdata
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
if (!isset($sql_root[$dbserver])) {
throw new Exception('Mysql server not found', 404);
}
unset($sql_root[$dbserver]);
$this->generateNewUserData($sql, $sql_root);
return $this->response(['true']);
}
/**
* list available mysql-server
*
* @access admin, customer
* @return string json-encoded array
*/
public function listing()
{
// get all data from lib/userdata
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
// limit customer to its allowed servers
$allowed_mysqls = [];
if ($this->isAdmin() == false) {
$allowed_mysqls = json_decode($this->getUserDetail('allowed_mysqlserver'), true);
}
$result = [];
foreach ($sql_root as $index => $sqlrootdata) {
if ($this->isAdmin() == false) {
if ($allowed_mysqls === false || empty($allowed_mysqls)) {
break;
} elseif (!in_array($index, $allowed_mysqls)) {
continue;
}
}
// passwords will not be returned in any case for security reasons
unset($sqlrootdata['password']);
$result[$index] = $sqlrootdata;
}
return $this->response(['list' => $result, 'count' => count($result)]);
}
/**
* returns the total number of mysql servers
*
* @access admin, customer
* @return string json-encoded array
*/
public function listingCount()
{
if ($this->isAdmin() == false) {
$allowed_mysqls = json_decode($this->getUserDetail('allowed_mysqlserver'), true);
if ($allowed_mysqls) {
return $this->response(count($allowed_mysqls));
}
return $this->response(0);
}
// get all data from lib/userdata
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
return $this->response(count($sql_root));
}
/**
* Return info about a specific mysql-server
*
* @param int $dbserver
* index of the mysql-server
*
* @access admin
* @throws Exception
* @return string json-encoded array
*/
public function get()
{
$this->validateAccess();
$dbserver = (int) $this->getParam('dbserver');
// get all data from lib/userdata
require Froxlor::getInstallDir() . "/lib/userdata.inc.php";
if (!isset($sql_root[$dbserver])) {
throw new Exception('Mysql server not found', 404);
}
unset($sql_root[$dbserver]['password']);
return $this->response($sql_root[$dbserver]);
}
/**
* @TODO implement me
*/
public function update()
{
throw new Exception('@TODO Later', 303);
}
private function generateNewUserData(array $sql, array $sql_root)
{
$content = '<?php' . PHP_EOL;
$content .= '//automatically generated userdata.inc.php for Froxlor' . PHP_EOL;
$content .= '$sql[\'host\']=\'' . $sql['host'] . '\';' . PHP_EOL;
$content .= '$sql[\'user\']=\'' . $sql['user'] . '\';' . PHP_EOL;
$content .= '$sql[\'password\']=\'' . $sql['password'] . '\';' . PHP_EOL;
$content .= '$sql[\'db\']=\'' . $sql['db'] . '\';' . PHP_EOL;
foreach ($sql_root as $index => $sqlroot_data) {
$content .= '// database server #' . ($index + 1) . PHP_EOL;
foreach ($sqlroot_data as $field => $value) {
// ssl-fields
if (is_array($value)) {
foreach ($value as $vfield => $vvalue) {
if ($vfield == 'verifyServerCertificate') {
$content .= '$sql_root[' . (int)$index . '][\'' . $field . '\'][\'' . $vfield . '\'] = ' . ($vvalue ? 'true' : 'false') . ';' . PHP_EOL;
} else {
$content .= '$sql_root[' . (int)$index . '][\'' . $field . '\'][\'' . $vfield . '\'] = \'' . $vvalue . '\';' . PHP_EOL;
}
}
} else {
if ($field == 'password') {
$content .= '$sql_root[' . (int)$index . '][\'' . $field . '\'] = <<<EOP
' . $value . '
EOP;' . PHP_EOL;
} else {
$content .= '$sql_root[' . (int)$index . '][\'' . $field . '\'] = \'' . $value . '\';' . PHP_EOL;
}
}
}
}
$content .= '$sql[\'debug\']=' . ($sql['debug'] ? 'true' : 'false') . ';' . PHP_EOL;
$content .= '?>' . PHP_EOL;
file_put_contents(Froxlor::getInstallDir() . "/lib/userdata.inc.php", $content);
}
}

View File

@@ -82,7 +82,7 @@ class DbManagerMySQL
if (!$update) {
// create user
if ($p_encrypted) {
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.0', '<')) {
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.0', '<') || version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
$stmt = Database::prepare("
CREATE USER '" . $username . "'@'" . $access_host . "' IDENTIFIED BY PASSWORD :password
");
@@ -109,7 +109,7 @@ class DbManagerMySQL
]);
} else {
// set password
if (version_compare(Database::getAttribute(PDO::ATTR_SERVER_VERSION), '5.7.6', '<')) {
if (version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '5.7.6', '<') || version_compare(Database::getAttribute(\PDO::ATTR_SERVER_VERSION), '10.0.0', '>=')) {
if ($p_encrypted) {
$stmt = Database::prepare("SET PASSWORD FOR :username@:host = :password");
} else {

View File

@@ -64,7 +64,7 @@ class Language
}
// load fallback from browser if nothing requested
$iso = trim(substr(strtok(strtok($_SERVER['HTTP_ACCEPT_LANGUAGE'], ','), ';'), 0, 5));
$iso = trim(substr(strtok(strtok(($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en'), ','), ';'), 0, 5));
if (!self::$requestedLanguage && strlen($iso) == 2 && $iso !== self::$defaultLanguage) {
self::$lng = array_merge(self::$lng, self::loadLanguage($iso));
}

View File

@@ -116,16 +116,7 @@ class Response
$error = '';
foreach ($errors as $single_error) {
if (isset($lng['error'][$single_error])) {
$single_error = $lng['error'][$single_error];
$single_error = strtr($single_error, [
'%s' => $replacer
]);
} else {
$error = 'Unknown Error (' . $single_error . '): ' . $replacer;
break;
}
$single_error = lng('error.'.$single_error, [htmlentities($replacer)]);
if (empty($error)) {
$error = $single_error;
} else {
@@ -181,11 +172,7 @@ class Response
{
global $lng;
if (isset($lng['success'][$success_message])) {
$success_message = strtr($lng['success'][$success_message], [
'%s' => htmlentities($replacer)
]);
}
$success_message = lng('success.'.$success_message, [htmlentities($replacer)]);
if ($throw_exception) {
throw new Exception(strip_tags($success_message), 200);

View File

@@ -32,17 +32,17 @@ return [
'type' => 'label',
'value' => $result['databasename']
],
'mysql_server' => [
'visible' => count($mysql_servers) > 1,
'label' => lng('mysql.mysql_server'),
'type' => 'label',
'value' => $mysql_servers[$result['dbserver']] ?? 'unknown db server'
],
'description' => [
'label' => lng('mysql.databasedescription'),
'type' => 'text',
'value' => $result['description']
],
'mysql_server' => [
'visible' => $count_mysql_servers > 1,
'label' => lng('mysql.mysql_server'),
'type' => 'label',
'value' => $sql_root['caption']
],
'mysql_password' => [
'label' => lng('changepassword.new_password_ifnotempty'),
'type' => 'password',